62 lines
2.9 KiB
TypeScript
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:/);
|
|
});
|