Files
Dune/tests/reinforcements-typed-choice-controls.test.tsx
T
Quintine 1cf90f1328
Verify and publish checkpoint container / container (push) Successful in 11m11s
Allow chosen Reinforcements reserve costs
2026-10-29 04:41:26 +11:00

62 lines
2.9 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { registerHooks } from 'node:module';
import { readFileSync } from 'node:fs';
import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { applyAction, viewGame, type Game, type GameView } from '../game/engine';
import { harassWithdrawGame, takeHarassCard } from './fixture-harass-withdraw';
const aliases = registerHooks({
resolve(specifier, context, next) {
return next(specifier === 'next/image' ? 'vinext/shims/image' : specifier === 'next/link' ? 'vinext/shims/link' : specifier, context);
},
load(url, context, next) {
if (!url.endsWith('.module.css')) return next(url, context);
const css = readFileSync(new URL(url), 'utf8');
const classes = Object.fromEntries([...css.matchAll(/\.([a-zA-Z_][\w-]*)/g)].map(match => [match[1], match[1]]));
return { format: 'module', source: `export default ${JSON.stringify(classes)}`, shortCircuit: true };
},
});
const { GameTable } = await import('../components/game-table');
aliases.deregister();
const card = 'ecaz-reinforcements';
function game(): Game {
const result = harassWithdrawGame({ advanced: true, factions: ['emperor', 'atreides', 'harkonnen'] });
takeHarassCard(result, 'a', card);
return result;
}
function markup(view: GameView): string {
return renderToStaticMarkup(createElement(GameTable, { game: view, send: async () => {}, onExit() {}, busy: false }));
}
void test('owner can see every available Reinforcements reserve mix in the battle plan', () => {
const state = game(), view = viewGame(state, 'a');
assert.deepEqual(view.battle?.reinforcements?.eliteOptions, [0, 1, 2, 3]);
// Staged projection forces the physical card into its slot, avoiding a
// browser hook driver; the actual sealed-plan test below uses engine actions.
view.battle!.ownCommitments = [{ source: 'native', beneficiary: 'd', target: 'a', field: 'weapon', value: card }];
const html = markup(view);
const select = html.match(/<label[^>]*>\s*<span>Reinforcements reserve fee<\/span>[\s\S]*?<\/label>/)?.[0];
assert.ok(select);
for (const elite of [0, 1, 2, 3]) {
assert.match(select, new RegExp(`value="${elite}"`));
assert.match(select, new RegExp(`${3 - elite} normal \\+ ${elite} special`));
}
const opponent = markup(viewGame(state, 'd'));
assert.doesNotMatch(opponent, /Reinforcements reserve fee/);
});
void test('sealed typed reserve choice is visible only to its owner', () => {
const state = applyAction(game(), 'a', {
type: 'battlePlan', dial: 1, support: 1, leader: 'emperor-0', weapon: card, defense: null,
reinforcementsElite: 2,
});
const owner = viewGame(state, 'a'), opponent = viewGame(state, 'd');
assert.equal(owner.battle?.plans.a.reinforcementsElite, 2);
assert.deepEqual(opponent.battle?.plans, {});
assert.match(markup(owner), /Sealed reserve fee:\s*1 normal \+ 2 special/);
assert.doesNotMatch(markup(opponent), /Sealed reserve fee:/);
});