Files
Dune/tests/richese-homeworld-marker-fresh-mixed-controls.test.tsx
Quintine 4d769af8c7
Verify and publish checkpoint container / container (push) Successful in 10m7s
Expand Semuta and Richese Homeworld shipments
2026-10-28 22:26:47 +11:00

81 lines
4.8 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { readFileSync } from 'node:fs';
import { runInNewContext } from 'node:vm';
import { createElement, isValidElement, type ReactElement, type ReactNode } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import * as jsxRuntime from 'react/jsx-runtime';
import ts from 'typescript';
import type { Action, GameView } from '../game/engine';
import { createRicheseNoField } from '../game/richese-no-field';
import { homeworldForceGroups, type HomeworldCustody } from '../game/homeworld-custody';
import { homeworldPopulations } from '../game/homeworld-population';
import * as combat from '../game/combat';
import * as options from '../game/homeworld-shipment-options';
import { HomeworldNoFieldShipment } from '../components/homeworld-shipment';
function fixture(): GameView {
const context = {advanced: false, players: [
{id: 'r', faction: 'richese' as const, reserves: 12, eliteReserves: 0, ally: null as string | null},
{id: 'a', faction: 'atreides' as const, reserves: 15, eliteReserves: 0, ally: null as string | null},
{id: 'g', faction: 'guild' as const, reserves: 20, eliteReserves: 0, ally: null as string | null},
]};
const custody: HomeworldCustody = {salusa: null, visitors: {}};
const populations = homeworldPopulations(context, custody);
return {me: 'r', active: 'r', advanced: false, status: 'playing', phase: 5,
homeworldShipment: {event: 'homeworld-event', blocked: null}, aid: {available: 0, pledged: 0},
players: context.players.map(p => ({...p, name: p.faction, spice: p.id === 'r' ? 5 : undefined,
shipped: false, elites: {reserves: 0}})),
homeworlds: {worlds: homeworldForceGroups(context, custody).map(world => ({...world,
...populations.find(population => population.location === world.id)!}))},
richeseNoField: {owner: 'r', event: 'marker-event', private: createRicheseNoField(['zero', 'three', 'five']),
canShip: true, shipBlock: null},
} as unknown as GameView;
}
type Element = ReactElement<{children?: ReactNode; [key: string]: unknown}>;
function elements(node: ReactNode): Element[] {
if (Array.isArray(node)) return node.flatMap(elements);
if (!isValidElement<{children?: ReactNode; [key: string]: unknown}>(node)) return [];
return [node, ...elements(node.props.children)];
}
void test('fresh marker controls expose native reserve quantity and submit one mixed shipment', () => {
const view = fixture(), sent: Action[] = [], states: unknown[] = [];
const markup = renderToStaticMarkup(createElement(HomeworldNoFieldShipment, {game: view, act() {}, busy: false}));
assert.match(markup, /your reserves/); assert.match(markup, /12.*available/);
let cursor = 0;
const exports: {HomeworldNoFieldShipment?: typeof HomeworldNoFieldShipment} = {};
runInNewContext(ts.transpileModule(readFileSync(new URL('../components/homeworld-shipment.tsx', import.meta.url), 'utf8'), {
compilerOptions: {module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, jsx: ts.JsxEmit.ReactJSX},
}).outputText, {exports, require(name: string) {
if (name === 'react/jsx-runtime') return jsxRuntime;
if (name === 'react') return {useState(initial: unknown) {
const index = cursor++; if (!(index in states)) states[index] = initial;
return [states[index], (next: unknown) => { states[index] = next; }];
}};
if (name === '@/game/combat') return combat;
if (name === '@/game/homeworld-shipment-options') return options;
if (name === './ui/button') return {Button: 'button'};
if (name === './ui/input') return {Input: 'input'};
throw Error(`Unexpected import ${name}`);
}});
assert.ok(exports.HomeworldNoFieldShipment);
const render = () => { cursor = 0; return elements(exports.HomeworldNoFieldShipment!({game: view, busy: false,
act: action => sent.push(action)})); };
for (const [label, value] of [['No-Field destination Homeworld', 'homeworld:atreides'],
['Private No-Field token', 'zero']] as const) {
const select = render().find(element => element.type === 'select' && element.props['aria-label'] === label);
assert.ok(select);
(select.props.onChange as (event: unknown) => void)({target: {value}});
}
const input = render().find(element => element.type === 'input'); assert.ok(input);
assert.equal(input.props.max, 12);
(input.props.onChange as (event: unknown) => void)({target: {value: '2'}});
const button = render().find(element => element.type === 'button'); assert.ok(button);
assert.equal(button.props.disabled, false);
(button.props.onClick as () => void)();
assert.deepEqual(JSON.parse(JSON.stringify(sent[0])), {type: 'homeworldShip', event: 'homeworld-event',
destination: 'homeworld:atreides', sources: {'homeworld:richese': {normal: 2, elite: 0}},
noField: 'zero', noFieldEvent: 'marker-event', allyPayment: 0});
});