Files
Dune/tests/fremen-ordered-revival-controls.test.tsx
T
Quintine 220d78c779
Verify and publish checkpoint container / container (push) Canceled after 0s
Coordinate Fremen grants in ordered revival
2026-10-29 09:19:17 +11:00

92 lines
4.6 KiB
TypeScript

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 { FremenRevivalGrant } from '../components/fremen-revival-grant';
import { createGame, newPlayer, viewGame, type Action, type GameView } from '../game/engine';
/** Projection-only control fixtures; engine tests own the actual alliance and
* ordered Revival transitions. The ally's private revival inventory is not read. */
function projected(group = false): GameView {
const game = createGame('ORDEREDGRANTCONTROLS', newPlayer('f', 'Fremen', 'fremen'), true);
game.players.push(newPlayer('a', 'Ally', 'atreides'));
game.players.push(newPlayer('e', 'Emperor', 'emperor'));
Object.assign(game, { status: 'playing', phase: 4, turn: 3, order: ['f', 'e', 'a'] });
game.players[0].ally = 'a';
game.players[1].ally = 'f';
const view = viewGame(game, 'f');
view.players.find(player => player.id === 'a')!.autopilot = 'Easy';
view.players.find(player => player.id === 'e')!.bot = 'Medium';
view.revivalOrder = { version: 1, turn: 3, original: ['f', 'e', 'a'],
remaining: ['e', 'a'], completed: ['f'], started: false };
view.fremenRevivalGrant = { recipient: 'a', granted: false, blocked: null, waitReleased: false };
if (group) view.fremenGroupRevivalGrant = [
{ recipient: 'a', granted: false, blocked: null, waitReleased: false },
{ recipient: 'e', granted: false, blocked: null, waitReleased: false },
];
return view;
}
function markup(game: GameView, busy = false) {
return renderToStaticMarkup(createElement(FremenRevivalGrant, { game, busy, act() {} }));
}
function click(node: ReactNode, target: 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'] === `fremen-revival-wait-release-${target}` && child.props.onClick) {
child.props.onClick();
return true;
}
if (click(child.props.children, target)) return true;
}
return false;
}
function release(game: GameView, target: string, busy = false): Action[] {
const actions: Action[] = [];
click(FremenRevivalGrant({ game, busy, act(action) { actions.push(action); } }), target);
return actions;
}
void test('Fremen can release a later automated ally before its ordered Revival turn', () => {
const game = projected();
assert.match(markup(game), /Continue without waiting/);
assert.match(markup(game), /may still grant later while Revival remains open/);
assert.deepEqual(release(game, 'a'), [{type:'releaseFremenRevivalWait', target:'a'}]);
});
void test('release is disabled during a busy window or after release without hiding grant', () => {
const game = projected();
assert.deepEqual(release(game, 'a', true), []);
game.fremenRevivalGrant!.waitReleased = true;
assert.match(markup(game), /Ally can continue without waiting for your grant/);
assert.match(markup(game), /Grant ally free revival/);
assert.deepEqual(release(game, 'a'), []);
game.truthtrance = {} as NonNullable<GameView['truthtrance']>;
game.fremenRevivalGrant!.waitReleased = false;
assert.deepEqual(release(game, 'a'), []);
});
void test('group controls release each recipient independently without reading private forces', () => {
const game = projected(true);
for (const ally of game.players.filter(player => player.id !== 'f')) {
for (const field of ['spice', 'tanks', 'deadForces', 'revived', 'freeForcesRevived'])
Object.defineProperty(ally, field, {get() { throw new Error(`Read private ${field}`); }});
}
assert.deepEqual(release(game, 'a'), [{type:'releaseFremenRevivalWait', target:'a'}]);
assert.deepEqual(release(game, 'e'), [{type:'releaseFremenRevivalWait', target:'e'}]);
game.fremenGroupRevivalGrant![0].waitReleased = true;
assert.deepEqual(release(game, 'a'), []);
assert.deepEqual(release(game, 'e'), [{type:'releaseFremenRevivalWait', target:'e'}]);
});
void test('release is absent for human, finished, blocked or nonordered allies', () => {
for (const condition of ['human', 'finished', 'blocked', 'basic'] as const) {
const game = projected();
if (condition === 'human') game.players.find(player => player.id === 'a')!.autopilot = undefined;
if (condition === 'finished') game.revivalOrder!.remaining = ['e'];
if (condition === 'blocked') game.fremenRevivalGrant!.blocked = 'Grant prevented.';
if (condition === 'basic') game.advanced = false;
assert.doesNotMatch(markup(game), /Continue without waiting/, condition);
assert.deepEqual(release(game, 'a'), [], condition);
}
});