import assert from 'node:assert/strict'; import test from 'node:test'; import { createElement } from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import { CoalitionNexus, coalitionNexusOptions } from '../components/coalition-nexus'; import { botActions } from '../game/bots'; import { DIFFICULTIES } from '../game/bot-profiles'; import { coalitionOf, emptyCoalitionState } from '../game/coalition-alliance'; import { applyAction, viewGame, type Game } from '../game/engine'; import { createTruthAllianceFixture } from './fixture-truth-alliance'; /** Public Advanced Harkonnen/Atreides/Emperor setup, genuine first turn and * ordinary worm Nexus. The fixture selects only unopened original deals, * auction and Spice positions and scalar lotteries. The optional group's * empty unexposed ledger is inserted at the real Nexus, before any offer. */ function pair(variant: 'three-short' | 'unlimited') { const source = createTruthAllianceFixture({advanced:true, consent:'none'}); let game = structuredClone(source.game); assert.equal(game.advanced, true); assert.equal(game.nexus, true); assert.ok(game.players.every(player => !player.ally)); game.coalitionState = emptyCoalitionState(variant); game = applyAction(game, source.asker, {type:'alliance', target:source.opponent}); game = applyAction(game, source.opponent, {type:'alliance', target:source.asker}); return {game, source}; } function answer(game: Game, player: string): Game { const proposal = game.coalitionState!.proposal; assert.ok(proposal); return applyAction(game, player, {type:'coalitionAnswer', event:proposal.event, nexus:proposal.nexus, turn:proposal.turn, accept:true, expectedRevision:game.coalitionState!.revision}); } for (const variant of ['three-short', 'unlimited'] as const) { void test(`Advanced ${variant} genuine Nexus forms a three-player group with legal bot and human consent`, () => { const {game, source} = pair(variant); const group = coalitionOf(game.players, game.coalitionState, source.asker); assert.ok(group); assert.deepEqual(new Set(group.members), new Set([source.asker, source.opponent])); const controls = viewGame(game, source.target).coalitionNexus; assert.ok(controls); assert.equal(controls.variant, variant); assert.deepEqual(coalitionNexusOptions(controls).invitations.map(row => row.entrant), [source.target]); const html = renderToStaticMarkup(createElement(CoalitionNexus, {view:controls, onIntent(){}})); assert.match(html, /Ask to join/); for (const difficulty of DIFFICULTIES) { const view = viewGame(game, source.target); view.players.find(seat => seat.id === source.target)!.bot = difficulty; const propose = botActions(view).find(action => action.type === 'coalitionPropose'); assert.ok(propose, difficulty); let pending = applyAction(game, source.target, propose); assert.deepEqual(pending.coalitionState!.proposal!.approvals, [source.target]); const projected = viewGame(pending, source.opponent).coalitionNexus; assert.ok(projected); assert.equal(projected.proposal!.entrant, source.target); assert.equal(coalitionNexusOptions(projected).mayAccept, true); assert.match(renderToStaticMarkup(createElement(CoalitionNexus, {view:projected, onIntent(){}})), /Accept invitation/); pending = answer(pending, source.asker); assert.deepEqual(new Set(coalitionOf(pending.players, pending.coalitionState, source.asker)!.members), new Set([source.asker, source.opponent])); const approvalView = viewGame(pending, source.opponent); approvalView.players.find(seat => seat.id === source.opponent)!.bot = difficulty; const approval = botActions(approvalView).find(action => action.type === 'coalitionAnswer'); assert.ok(approval, difficulty); assert.equal(approval.accept, true); pending = applyAction(pending, source.opponent, approval); assert.equal(pending.coalitionState!.proposal, null); assert.deepEqual(new Set(coalitionOf(pending.players, pending.coalitionState, source.asker)!.members), new Set([source.asker, source.opponent, source.target])); assert.equal(viewGame(pending, source.target).coalitionNexus!.variant, variant); } }); }