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(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)), ''); });