import test from 'node:test'; import assert from 'node:assert/strict'; import { createElement } from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import { CoalitionNexus, coalitionNexusOptions, type CoalitionNexusView } from '../components/coalition-nexus'; import type { CoalitionGroup } from '../game/coalition-alliance'; const pair: CoalitionGroup = { id: 'pair', members: ['a', 'b'], formedTurn: 2, edges: [{ members: ['a', 'b'], formedTurn: 2 }] }; function view(me = 'a'): CoalitionNexusView { return { me, revision: 4, groups: [pair], proposal: null, players: [ { id: 'a', name: 'Atreides' }, { id: 'b', name: 'Emperor' }, { id: 'c', name: 'Fremen' }, { id: 'd', name: 'Guild' }, ] }; } const html = (value: CoalitionNexusView, disabled = false) => renderToStaticMarkup(createElement(CoalitionNexus, { view: value, disabled, onIntent() {} })); void test('only a pair member or the unallied entrant is offered an invitation', () => { const member = coalitionNexusOptions(view('a')); assert.deepEqual(member.invitations.map(({ entrant }) => entrant), ['c', 'd']); assert.equal(member.mayLeave, true); const entrant = coalitionNexusOptions(view('c')); assert.deepEqual(entrant.invitations.map(({ entrant }) => entrant), ['c']); assert.equal(entrant.mayLeave, false); assert.match(html(view('a')), /Invite Fremen to Atreides, Emperor/); assert.match(html(view('c')), /Ask to join Atreides, Emperor/); assert.deepEqual(coalitionNexusOptions(view('missing')).invitations, []); assert.doesNotMatch(html(view('missing')), /Invite Fremen|Ask to join|Leave Atreides/); }); void test('pending invitation presents approval and withdrawal only to its participants', () => { const v = view('c'); v.proposal = { event: 'offer', nexus: 'nexus', turn: 3, groupId: pair.id, original: pair, entrant: 'c', proposer: 'a', approvals: ['a'] }; let options = coalitionNexusOptions(v); assert.equal(options.mayAccept, true); assert.equal(options.mayDecline, true); assert.deepEqual(options.invitations, []); assert.equal(options.mayLeave, false); assert.match(html(v), /Approved: Atreides/); assert.match(html(v), /Waiting for: Emperor, Fremen/); assert.match(html(v), /Accept invitation/); assert.match(html(v), /Decline and cancel invitation/); v.me = 'a'; options = coalitionNexusOptions(v); assert.equal(options.mayAccept, false); assert.equal(options.mayDecline, true); assert.match(html(v), /Withdraw and cancel invitation/); assert.doesNotMatch(html(v), /Accept invitation/); v.me = 'b'; v.proposal.approvals.push('b'); assert.match(html(v), /Withdraw approval and cancel invitation/); assert.doesNotMatch(html(v), /Accept invitation/); v.me = 'd'; options = coalitionNexusOptions(v); assert.equal(options.mayAccept, false); assert.equal(options.mayDecline, false); assert.doesNotMatch(html(v), /Accept invitation|cancel invitation/); }); void test('three-member groups cannot receive another entrant and describe leaving policy', () => { const v = view('a'); v.groups = [{ ...pair, members: ['a', 'b', 'c'] }]; const options = coalitionNexusOptions(v); assert.deepEqual(options.invitations, []); assert.equal(options.mayLeave, true); assert.match(html(v), /Leaving keeps the other two players allied/); assert.match(html(v), /Leave Atreides, Emperor, Fremen/); assert.match(html(v, true), /disabled=""[^>]*>Leave Atreides, Emperor, Fremen/); });