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(/]*>\s*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:/); });