90 lines
4.4 KiB
TypeScript
90 lines
4.4 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { Children, createElement, isValidElement, type ButtonHTMLAttributes, type MouseEvent, type ReactElement, type ReactNode } from 'react';
|
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
import { ShipmentPromises } from '../components/shipment-promises';
|
|
import { viewGame, type Action, type GameView } from '../game/engine';
|
|
import { deployRicheseNoField } from '../game/richese-no-field';
|
|
import type { TruthQuestion } from '../game/truthtrance';
|
|
import {
|
|
createTruthtranceRichesePromisesFixture as fixture,
|
|
actTruthtranceRichesePromises as act,
|
|
askTruthtranceRichesePromises as ask,
|
|
advanceTruthtranceRichesePromises as advance,
|
|
truthtranceRichesePromisesClean as clean,
|
|
truthtranceRichesePromisesPlayer as player,
|
|
} from './fixture-truthtrance-richese-promises';
|
|
|
|
const question: TruthQuestion = {
|
|
kind: 'shipment', target: 'richese',
|
|
claim: {kind: 'noField', territory: 'carthag', sector: 11},
|
|
};
|
|
|
|
/** Genuine public setup and Truthtrance action; the optional existing marker
|
|
* is staged in conserved custody to represent a previous turn. */
|
|
function promised(existing: boolean) {
|
|
const f = fixture();
|
|
const source = structuredClone(f.game);
|
|
if (existing) {
|
|
const owner = player(source), marker = owner.noField!.tokens.find(token => token.value === 3);
|
|
assert.ok(marker);
|
|
owner.noField = deployRicheseNoField(owner.noField!, {
|
|
tokenId: marker.id, controller: owner.id, location: {territory:'polar_sink', sector:0},
|
|
});
|
|
}
|
|
let game = ask(source, f, question);
|
|
game = act(game, f.target, {type:'truthAnswer', answer:'yes'});
|
|
game = advance(game, state => state.phase === 5 && state.active === f.target && clean(state));
|
|
return {f, game};
|
|
}
|
|
function button(game: GameView, actions: Action[], label: string) {
|
|
function find(node: ReactNode): ReactElement<ButtonHTMLAttributes<HTMLButtonElement>> | undefined {
|
|
if (!isValidElement<{children?: ReactNode; onClick?: unknown}>(node)) return;
|
|
if (node.props.children === label && typeof node.props.onClick === 'function')
|
|
return node as ReactElement<ButtonHTMLAttributes<HTMLButtonElement>>;
|
|
return Children.toArray(node.props.children).map(find).find(Boolean);
|
|
}
|
|
const found = find(ShipmentPromises({game, busy:false, act: action => actions.push(action)}));
|
|
assert.ok(found, label);
|
|
found.props.onClick!({} as MouseEvent<HTMLButtonElement>);
|
|
}
|
|
function html(game: GameView) {
|
|
return renderToStaticMarkup(createElement(ShipmentPromises, {game, busy:false, act() {}}));
|
|
}
|
|
|
|
void test('a real No-Field promise offers a correctly priced one-marker shipment', () => {
|
|
const {f, game} = promised(false), projected = viewGame(game, f.target);
|
|
const witness = projected.shipmentCompletion?.actions[0];
|
|
assert.equal(witness?.type, 'ship'); assert.ok(witness.noField);
|
|
const output = html(projected);
|
|
assert.match(output, /Ship your No-Field to Carthag, sector 11/);
|
|
assert.match(output, /Cost: 1 spice/);
|
|
assert.doesNotMatch(output, /NaN|Ship 1 forces|Play your preparation card/);
|
|
const sent: Action[] = [];
|
|
button(projected, sent, 'Ship your No-Field to Carthag, sector 11');
|
|
assert.deepEqual(sent, [witness]);
|
|
const done = advance(act(game, f.target, sent[0]), clean);
|
|
assert.equal(done.shipmentPromises![0].fulfilled, true);
|
|
assert.equal(player(done).noField?.deployed?.tokenId, witness.noField);
|
|
});
|
|
|
|
void test('an existing marker is labeled as a reveal and the next shipment uses its fresh event', () => {
|
|
const {f, game: original} = promised(true), projected = viewGame(original, f.target);
|
|
const first = projected.shipmentCompletion?.actions[0];
|
|
assert.equal(first?.type, 'revealNoField');
|
|
const output = html(projected);
|
|
assert.match(output, /Reveal your current No-Field/);
|
|
assert.doesNotMatch(output, /NaN|Play your preparation card|Cost:/);
|
|
const sent: Action[] = [];
|
|
button(projected, sent, 'Reveal your current No-Field');
|
|
assert.deepEqual(sent, [first]);
|
|
const revealed = act(original, f.target, sent[0]);
|
|
const next = viewGame(revealed, f.target).shipmentCompletion?.actions[0];
|
|
assert.equal(next?.type, 'ship');
|
|
assert.notEqual(next?.noField, first?.token);
|
|
assert.equal(next?.event, player(revealed).noFieldEvent);
|
|
const done = advance(act(revealed, f.target, next!), clean);
|
|
assert.equal(done.shipmentPromises![0].fulfilled, true);
|
|
assert.equal(player(done).noField?.deployed?.tokenId, next?.noField);
|
|
});
|