Files
Dune/tests/faction-allocation-controls.test.tsx

89 lines
4.5 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { createElement, isValidElement, type ReactElement, type ReactNode } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { FactionAllocation } from '../components/faction-allocation';
import type { Action, GameView } from '../game/engine';
type Element = ReactElement<{ children?: ReactNode; [key: string]: unknown }>;
const 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)];
};
const base = {
status: 'lobby', host: 'a', me: 'a', expansions: [], factionDraft: null,
players: [
{ id: 'a', name: 'Alice', faction: 'atreides', ready: true },
{ id: 'b', name: 'Basil', faction: 'harkonnen', ready: false },
{ id: 'c', name: 'Computer', faction: 'guild', bot: 'Medium', ready: true },
],
} as unknown as GameView;
const draft = {
event: 'draft-event-1',
order: [{ player: 'b', card: 9 }, { player: 'a', card: 6 }, { player: 'c', card: 2 }],
picks: [{ player: 'b', faction: 'atreides' }],
};
const send = async (_action: Action) => {};
const render = (game: GameView, busy = false) =>
renderToStaticMarkup(createElement(FactionAllocation, { game, send, busy }));
void test('host sees both allocation methods and readiness/commit semantics', () => {
const html = render(base);
assert.match(html, /Deal random factions/);
assert.match(html, /Draft factions by prediction card/);
assert.match(html, /only after everyone has picked/);
assert.match(html, /clears human readiness/);
assert.doesNotMatch(render({ ...base, me: 'b' }), /Deal random factions|Draft factions by prediction card/);
assert.doesNotMatch(render({ ...base, status: 'playing' }), /Faction allocation/);
assert.match(render({ ...base, players: base.players.slice(0, 1) }), /At least two faction seats/);
});
void test('draft shows public numbered order and only unpicked enabled factions to the active player', () => {
const active = { ...base, factionDraft: draft } as unknown as GameView;
const html = render(active);
assert.match(html, /Basil · prediction 9: Atreides/);
assert.match(html, /Alice · prediction 6: choosing now/);
assert.match(html, /Computer \(AI\) · prediction 2: waiting/);
assert.match(html, /Choose your faction/);
assert.match(html, />Harkonnen<\/button>/);
assert.doesNotMatch(html, />Atreides<\/button>|>Ixians<\/button>/);
const expanded = render({ ...active, expansions: ['ix'] });
assert.match(expanded, />Ixians<\/button>/);
const waiting = render({ ...active, me: 'b' });
assert.match(waiting, /Waiting for Alice to choose a faction/);
assert.doesNotMatch(waiting, /Choose your faction|Cancel faction draft/);
});
void test('allocation controls send the exact owner-initiated actions and draft event', () => {
const calls: Action[] = [];
const sendAction = async (action: Action) => { calls.push(action); };
const initial = elements(FactionAllocation({ game: base, send: sendAction, busy: false }));
const click = (nodes: Element[], text: string) => {
const button = nodes.find((node) => node.props.children === text && typeof node.props.onClick === 'function');
assert.ok(button, `Expected action ${text}`);
(button.props.onClick as () => void)();
};
click(initial, 'Deal random factions');
click(initial, 'Draft factions by prediction card');
const inDraft = elements(FactionAllocation({ game: { ...base, factionDraft: draft } as unknown as GameView, send: sendAction, busy: false }));
click(inDraft, 'Harkonnen');
click(inDraft, 'Cancel faction draft');
assert.deepEqual(calls, [
{ type: 'factionAllocation', mode: 'random' },
{ type: 'factionAllocation', mode: 'draft' },
{ type: 'factionDraftPick', event: 'draft-event-1', faction: 'harkonnen' },
{ type: 'factionDraftCancel', event: 'draft-event-1' },
]);
});
void test('busy draft disables selection and a bot turn explains automatic progress', () => {
const game = { ...base, factionDraft: { ...draft, picks: [
{ player: 'b', faction: 'atreides' }, { player: 'a', faction: 'emperor' },
] } } as unknown as GameView;
assert.match(render(game), /Waiting for Computer \(AI\).*AI will choose automatically/);
const nodes = elements(FactionAllocation({ game: { ...base, factionDraft: draft } as unknown as GameView, send, busy: true }));
for (const button of nodes.filter((node) => typeof node.props.onClick === 'function'))
assert.equal(button.props.disabled, true);
});