Files
Dune/tests/choam-market-auto-waiver-controls.test.tsx
T
Quintine 825f903cce
Verify and publish checkpoint container / container (push) Successful in 10m42s
Add opt-in CHOAM market skip for the current turn
2026-10-29 07:08:53 +11:00

97 lines
4.5 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { Children, createElement, isValidElement, type ReactNode } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { ChoamMarket } from '../components/choam-market';
import { ChoamMarketAuto } from '../components/choam-market-auto';
import { createGame, joinGame, newPlayer, viewGame, type Action, type GameView } from '../game/engine';
// Staged projections isolate the controls; the engine suite exercises actual
// market eligibility, turn expiry and the owner-only view.
function projected(id = 'c'): GameView {
const game = createGame('MARKETCONTROLS', newPlayer('c', 'CHOAM', 'choam'), false, ['choam']);
joinGame(game, newPlayer('e', 'Emperor', 'emperor'));
game.status = 'playing';
game.phase = 4;
game.turn = 2;
game.choamMarket = { owner: 'c', resume: 'phase', blocked: [] };
game.decision = { kind: 'choamMarket', player: 'c' };
const view = viewGame(game, id);
view.choamMarketSkipAvailable = id === 'c';
view.choamMarketSkipActive = false;
return view;
}
type ButtonProps = { children?: ReactNode; disabled?: boolean; onClick?: () => void };
function findButton(node: ReactNode, label: string): ButtonProps | undefined {
for (const child of Children.toArray(node)) {
if (!isValidElement<ButtonProps>(child)) continue;
if (child.props.children === label && child.props.onClick) return child.props;
const nested = findButton(child.props.children, label);
if (nested) return nested;
}
return undefined;
}
void test('the market owner can finish this phase or explicitly waive later markets with informed consent', () => {
const game = projected();
const actions: Action[] = [];
const props = { game, busy: false, act(action: Action) { actions.push(action); } };
const html = renderToStaticMarkup(createElement(ChoamMarket, props));
assert.match(html, /Finish this phase/);
assert.match(html, /Finish and skip remaining markets this turn/);
assert.match(html, /other sales and trades and opportunities to receive gifts/);
assert.match(html, /enable it again only from a future market/);
const tree = ChoamMarket(props);
const finish = findButton(tree, 'Finish this phase');
const skip = findButton(tree, 'Finish and skip remaining markets this turn');
assert.ok(finish && skip);
assert.equal(skip.disabled, false);
finish.onClick!();
skip.onClick!();
assert.deepEqual(actions, [
{ type: 'decision', done: true },
{ type: 'decision', done: true, skipMarketsThisTurn: true },
]);
const busy = findButton(ChoamMarket({ ...props, busy: true }), 'Finish and skip remaining markets this turn');
assert.ok(busy?.disabled);
busy.onClick!();
assert.equal(actions.length, 2);
});
void test('the skip control is absent without the owner quote or in another market decision', () => {
for (const game of [projected('e'), { ...projected(), choamMarketSkipAvailable: false }]) {
assert.doesNotMatch(renderToStaticMarkup(createElement(ChoamMarket, { game, busy: false, act() {} })),
/Finish and skip remaining markets this turn/);
}
const reply = projected();
reply.decision = { kind: 'choamTradeReply', player: 'c' };
assert.doesNotMatch(renderToStaticMarkup(createElement(ChoamMarket, { game: reply, busy: false, act() {} })),
/Finish and skip remaining markets this turn/);
});
void test('the active preference is persistent owner-facing guidance with a busy-safe cancel action', () => {
const game = projected();
game.choamMarket = null;
game.decision = null;
game.choamMarketSkipAvailable = false;
game.choamMarketSkipActive = true;
const actions: Action[] = [];
const props = { game, busy: false, act(action: Action) { actions.push(action); } };
const html = renderToStaticMarkup(createElement(ChoamMarketAuto, props));
assert.match(html, /remaining markets this turn will be skipped/);
assert.match(html, /other sales and trades and opportunities to receive gifts/);
assert.match(html, /enable it again only from a future market/);
const cancel = findButton(ChoamMarketAuto(props), 'Cancel skipping remaining markets');
assert.ok(cancel);
assert.equal(cancel.disabled, false);
cancel.onClick!();
assert.deepEqual(actions, [{ type: 'cancelChoamMarketSkip' }]);
const busy = findButton(ChoamMarketAuto({ ...props, busy: true }), 'Cancel skipping remaining markets');
assert.ok(busy?.disabled);
busy.onClick!();
assert.equal(actions.length, 1);
game.choamMarketSkipActive = false;
assert.equal(renderToStaticMarkup(createElement(ChoamMarketAuto, props)), '');
});