Add random faction deal and prediction draft
This commit is contained in:
1 parent
a244aeb5f8
commit
4dc2df2513
16 files changed
+819
-12
No files matched your search
@@ -0,0 +1,88 @@
|
||||
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);
|
||||
});
|
||||
Reference in new issue
Block a user