71 lines
3.6 KiB
TypeScript
71 lines
3.6 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { createElement } from 'react';
|
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
import type { GameView } from '../game/engine';
|
|
import { HomeworldShipment } from '../components/homeworld-shipment';
|
|
import { homeworldShipmentChoice } from '../game/homeworld-shipment-options';
|
|
import { homeworldForceGroups, type HomeworldCustody } from '../game/homeworld-custody';
|
|
import { homeworldPopulations } from '../game/homeworld-population';
|
|
|
|
/** Personalized public view: the Atreides player has an actual physical
|
|
* foreign garrison. Native and foreign pools are projected using the same
|
|
* Homeworld helpers as the existing shipment-control fixture. */
|
|
function fixture(advanced: boolean): GameView {
|
|
const context = { advanced, players: [
|
|
{ id: 'a', faction: 'atreides' as const, reserves: 17, eliteReserves: 0, ally: null },
|
|
{ id: 'g', faction: 'guild' as const, reserves: 20, eliteReserves: 0, ally: null },
|
|
] };
|
|
const custody: HomeworldCustody = { salusa: null,
|
|
visitors: { 'homeworld:guild': { a: { normal: 3, elite: 0 } } } };
|
|
const populations = homeworldPopulations(context, custody);
|
|
return {
|
|
me: 'a', active: 'a', advanced, status: 'playing', phase: 5,
|
|
homeworldShipment: { event: 'shipment:1:a', blocked: null },
|
|
aid: { available: 0, pledged: 0 },
|
|
players: context.players.map(player => ({ ...player, name: player.faction,
|
|
spice: player.id === 'a' ? 10 : undefined, shipped: false,
|
|
elites: { reserves: player.eliteReserves } })),
|
|
homeworlds: { worlds: homeworldForceGroups(context, custody).map(world => ({
|
|
...world, ...populations.find(row => row.location === world.id)!,
|
|
})) },
|
|
} as unknown as GameView;
|
|
}
|
|
|
|
function markup(game: GameView): string {
|
|
return renderToStaticMarkup(createElement(HomeworldShipment, { game, busy: false, act() {} }));
|
|
}
|
|
function destinationOptions(html: string): string {
|
|
const options = /<select aria-label="Destination Homeworld"[^>]*>([\s\S]*?)<\/select>/.exec(html);
|
|
assert.ok(options, 'The native Homeworld shipment destination selector is present');
|
|
return options[1];
|
|
}
|
|
|
|
void test('Advanced personal shipment exposes the own Homeworld, source distinction and price', () => {
|
|
const game = fixture(true), before = structuredClone(game), html = markup(game);
|
|
const options = destinationOptions(html);
|
|
assert.match(options, /value="homeworld:atreides"[^>]*>Caladan · return to your Homeworld/);
|
|
assert.match(options, /value="homeworld:guild"/);
|
|
assert.match(html, /You may return a foreign garrison to your own Homeworld/);
|
|
assert.match(html, /Native reserves and foreign garrisons have separate source pools/);
|
|
assert.match(html, /Pay one spice per force/);
|
|
const quote = homeworldShipmentChoice(game, 'homeworld:atreides',
|
|
{ 'homeworld:guild': { normal: 2, elite: 0 } });
|
|
assert.equal(quote.blocked, null);
|
|
assert.equal(quote.cost, 2);
|
|
assert.equal(quote.action?.destination, 'homeworld:atreides');
|
|
assert.deepEqual(game, before);
|
|
});
|
|
|
|
void test('Basic personal shipment retains the explicit native destination restriction', () => {
|
|
const game = fixture(false), html = markup(game);
|
|
const options = destinationOptions(html);
|
|
assert.doesNotMatch(options, /value="homeworld:atreides"/);
|
|
assert.match(options, /value="homeworld:guild"/);
|
|
assert.match(html, /Your own Homeworld cannot be a destination/);
|
|
const quote = homeworldShipmentChoice(game, 'homeworld:atreides',
|
|
{ 'homeworld:guild': { normal: 2, elite: 0 } });
|
|
assert.equal(quote.action, null);
|
|
assert.match(quote.blocked ?? '', /Basic play.*own Homeworld/);
|
|
});
|