Files
Dune/tests/tleilaxu-special-karama-controls.test.tsx
Quintine 1a505525a8 Open Tleilaxu special Karama throughout Revival
Permit native Advanced quiet any-player declarations alongside the original target-bound pending request. Preserve completed returns and charges. Adopt explicit provisional Ghola inclusion during the remaining Revival and phase expiry; keep printed Emperor special immunity and the existing low-Homeworld guard. Connect persistent controls and minimal legal bots through the shared quote.

Validation: 14 related files148/148, types/lint/build, six actual native control/engine/GameTable SSR journeys and four existing-profile continuations. Private-driver-only feedback reuses passed source-identical checks. Native111/113-action setup, paid shipment and battle sources earn originals. No saved-room/D1/restart/deployment operation; full modes and browser/deployed acceptance remain open.
2026-10-24 19:30:21 +11:00

187 lines
9.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict';
import test from 'node:test';
import { Children, createElement, isValidElement, type ReactNode } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { TleilaxuSpecialKarama } from '../components/tleilaxu-special-karama';
import { baseDeck } from '../game/cards';
import {
createGame,
newPlayer,
viewGame,
type Action,
type GameView,
} from '../game/engine';
// Client projections isolate eligibility controls. The runtime suite covers
// physical Karama spending, original pending returns and prevention duration.
function projected(): GameView {
const game = createGame('TLEILAXUSPECIALCONTROLS', newPlayer('t', 'Tleilaxu', 'tleilaxu'), true, ['ix']);
game.players.push(newPlayer('a', 'Ally', 'atreides'), newPlayer('e', 'Ready Emperor', 'emperor'), newPlayer('g', 'Earlier Guild', 'guild'));
Object.assign(game, { status: 'playing', phase: 4, turn: 3, order: ['t', 'a', 'e', 'g'] });
game.players[0].ally = 'a';
game.players[1].ally = 't';
game.players[2].ready = true;
game.players[3].revived = 1;
game.players[3].freeForcesRevived = 1;
const cards = baseDeck().filter((card) => card.effect === 'karama').slice(0, 2);
game.players[0].hand = cards;
game.deck = baseDeck().filter((card) => !cards.some((held) => held.id === card.id));
const view = viewGame(game, 't');
view.tleilaxuSpecialKarama = {
blocked: null, cards: cards.map((card) => card.id), pending: false,
targets: game.players.map((player) => ({ player: player.id, blocked: null })),
};
return view;
}
function markup(game: GameView, busy = false) {
return renderToStaticMarkup(createElement(TleilaxuSpecialKarama, { game, busy, act() {} }));
}
function click(node: ReactNode, selector: string): boolean {
for (const child of Children.toArray(node)) {
if (!isValidElement<{
children?: ReactNode;
onClick?: () => void;
'data-testid'?: string;
}>(child)) continue;
if (child.props['data-testid'] === selector && child.props.onClick) {
child.props.onClick();
return true;
}
if (click(child.props.children, selector)) return true;
}
return false;
}
function prevent(game: GameView, card: string, target = 'e', busy = false): Action[] {
const actions: Action[] = [];
click(TleilaxuSpecialKarama({ game, busy, act(action) { actions.push(action); } }),
`tleilaxu-special-karama-${card}-${target}`);
return actions;
}
void test('actual quiet-window buttons include self, ally, ready and prior-return targets using every projected card', () => {
const game = projected();
const html = markup(game);
assert.match(html, /aria-label="Tleilaxu special Karama revival prevention"/);
assert.match(html, /prevent revival for Tleilaxu/);
assert.match(html, /prevent revival for Ally/);
assert.match(html, /prevent revival for Ready Emperor/);
assert.match(html, /prevent revival for Earlier Guild/);
assert.equal((html.match(/data-testid="tleilaxu-special-karama-/g) ?? []).length, 8);
for (const card of game.tleilaxuSpecialKarama!.cards) {
for (const target of game.tleilaxuSpecialKarama!.targets) {
assert.deepEqual(prevent(game, card, target.player), [{ type: 'card', mode: 'special', card, target: target.player }]);
}
}
assert.deepEqual(prevent(game, 'stale-card'), []);
assert.deepEqual(prevent(game, game.tleilaxuSpecialKarama!.cards[0], 'not-a-target'), []);
});
void test('persistent guidance includes current-Revival Ghola, phase expiry and protected Emperor special without reversing completed returns', () => {
const game = projected();
for (let render = 0; render < 3; render++) {
const html = markup(game);
assert.match(html, /once-per-game special Karama/);
assert.match(html, /force and leader revivals for the rest of this Revival, including returns from Ghola Treachery/);
assert.match(html, /yourself or an ally/);
assert.match(html, /even if the chosen faction is ready or has already completed revivals/);
assert.match(html, /Completed returns and payments stay unchanged/);
assert.match(html, /does not remove returned pieces or refund spice/);
assert.match(html, /Provisional rule: Ghola Treachery is included during the affected Revival/);
assert.match(html, /prevention expires when the phase ends/);
assert.match(html, /does not stop Ghola outside Revival/);
assert.match(html, /Emperor’s native special Karama remains protected by the printed immunity of special Karama powers/);
}
});
void test('global and per-target projected block reasons remain readable and prevent only the affected actions', () => {
const game = projected();
const card = game.tleilaxuSpecialKarama!.cards[0];
game.tleilaxuSpecialKarama!.targets.find((target) => target.player === 'e')!.blocked = 'This Homeworld revival combination is not yet available.';
const html = markup(game);
assert.match(html, /Ready Emperor: This Homeworld revival combination is not yet available/);
assert.match(html, /aria-describedby="tleilaxu-special-karama-e-unavailable"/);
assert.deepEqual(prevent(game, card, 'e'), []);
assert.deepEqual(prevent(game, card, 'a'), [{ type: 'card', mode: 'special', card, target: 'a' }]);
game.tleilaxuSpecialKarama!.blocked = 'This once-per-game special Karama has already been used.';
assert.match(markup(game), /once-per-game special Karama has already been used/);
assert.deepEqual(prevent(game, card, 'a'), []);
});
void test('cardless and targetless projections show useful guidance without inventing actions', () => {
const game = projected();
const card = game.tleilaxuSpecialKarama!.cards[0];
game.tleilaxuSpecialKarama!.cards = [];
assert.match(markup(game), /No spendable Karama is currently available for this special power/);
assert.doesNotMatch(markup(game), /data-testid="tleilaxu-special-karama-/);
assert.deepEqual(prevent(game, card), []);
game.tleilaxuSpecialKarama!.targets = [];
assert.match(markup(game), /No faction is currently available for this revival-prevention power/);
});
void test('the owned pending revival-stop uses its original projected recipient without adding a decline or acknowledgement', () => {
const game = projected();
const card = game.tleilaxuSpecialKarama!.cards[0];
game.tleilaxuSpecialKarama!.pending = true;
game.tleilaxuSpecialKarama!.targets = [{ player: 'e', blocked: null }];
game.decision = { kind: 'revivalStop', player: 't', recipient: 'e', revival: 'forces' };
const html = markup(game);
assert.match(html, /Only the original recipient can be selected during this pending revival/);
assert.deepEqual(prevent(game, card), [{ type: 'card', mode: 'special', card, target: 'e' }]);
assert.deepEqual(prevent(game, card, 'a'), []);
assert.doesNotMatch(html, /Allow this revival|Decline|Acknowledge|Confirm prevention/);
game.decision = null;
assert.deepEqual(prevent(game, card), []);
});
void test('busy and other current priorities block actual callbacks while retaining the guide', () => {
const game = projected();
const card = game.tleilaxuSpecialKarama!.cards[0];
assert.deepEqual(prevent(game, card, 'e', true), []);
assert.equal((markup(game, true).match(/<button(?=[^>]*data-testid="tleilaxu-special-karama-)(?=[^>]*disabled="")[^>]*>/g) ?? []).length, 8);
for (const field of ['response', 'decision', 'phaseOpening', 'truthtrance', 'nexus'] as const) {
const changed = structuredClone(game);
if (field === 'response') changed.response = {} as NonNullable<GameView['response']>;
if (field === 'decision') changed.decision = { kind: 'revivalStop', player: 'e', recipient: 'e', revival: 'forces' };
if (field === 'phaseOpening') changed.phaseOpening = {} as NonNullable<GameView['phaseOpening']>;
if (field === 'truthtrance') changed.truthtrance = {} as NonNullable<GameView['truthtrance']>;
if (field === 'nexus') changed.nexusTraitors = {
offer: null, pending: {} as NonNullable<NonNullable<GameView['nexusTraitors']>['pending']>,
};
assert.deepEqual(prevent(changed, card), [], field);
assert.match(markup(changed), /Finish the current response, decision or phase opening/, field);
}
});
void test('only the native Advanced Tleilaxu owner receives these phase-four controls', () => {
for (const field of ['projection', 'basic', 'viewer', 'faction', 'phase', 'status'] as const) {
const game = projected();
const card = game.tleilaxuSpecialKarama!.cards[0];
if (field === 'projection') game.tleilaxuSpecialKarama = null;
if (field === 'basic') game.advanced = false;
if (field === 'viewer') game.me = 'a';
if (field === 'faction') game.players[0].faction = 'atreides';
if (field === 'phase') game.phase = 5;
if (field === 'status') game.status = 'lobby';
assert.equal(markup(game), '', field);
assert.deepEqual(prevent(game, card), [], field);
}
});
void test('eligibility controls never inspect private hands, Tanks, leader liveness, readiness or revival counters', () => {
const game = projected();
const card = game.tleilaxuSpecialKarama!.cards[0];
for (const player of game.players) {
for (const field of ['hand', 'spice', 'tanks', 'leaders', 'ready', 'revived', 'freeForcesRevived', 'leaderRevived']) {
Object.defineProperty(player, field, {
get() { throw new Error(`Revival-prevention UI read ${player.id} private ${field}`); },
});
}
}
assert.match(markup(game), /prevent revival for Ready Emperor/);
assert.deepEqual(prevent(game, card, 't'), [{ type: 'card', mode: 'special', card, target: 't' }]);
assert.deepEqual(prevent(game, card, 'g'), [{ type: 'card', mode: 'special', card, target: 'g' }]);
});