Files
Dune/tests/coalition-unlimited-controls.test.tsx
T
Quintine 9cb65a21f5
Verify and publish checkpoint container / container (push) Canceled after 0s
Prototype unlimited alliance variant
2026-10-29 00:33:42 +11:00

65 lines
3.5 KiB
TypeScript

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 players = ['a', 'b', 'c', 'd', 'e', 'f'].map(id => ({id, name: id.toUpperCase()}));
function group(members: string[]): CoalitionGroup {
const edges: CoalitionGroup['edges'] = [];
for (let left = 0; left < members.length; left++)
for (let right = left + 1; right < members.length; right++)
edges.push({members: [members[left], members[right]], formedTurn: 1});
return {id: 'group', members, edges, formedTurn: 1};
}
function view(me: string, members: string[], unlimited = true): CoalitionNexusView {
return {me, groups: [group(members)], players, proposal: null, revision: 7,
...(unlimited ? {variant: 'unlimited' as const} : {})};
}
function render(value: CoalitionNexusView, disabled = false) {
return renderToStaticMarkup(createElement(CoalitionNexus, {view: value, disabled, onIntent() {}}));
}
void test('unlimited membership offers entry only to a member or the unallied entrant', () => {
const member = view('a', ['a', 'b', 'c']);
assert.deepEqual(coalitionNexusOptions(member).invitations.map(row => row.entrant), ['d', 'e', 'f']);
assert.match(render(member), /Invite D to A, B, C/);
const entrant = view('d', ['a', 'b', 'c']);
assert.deepEqual(coalitionNexusOptions(entrant).invitations.map(row => row.entrant), ['d']);
assert.match(render(entrant), /Ask to join A, B, C/);
assert.deepEqual(coalitionNexusOptions(view('missing', ['a', 'b', 'c'])).invitations, []);
assert.deepEqual(coalitionNexusOptions(view('a', ['a', 'b', 'c'], false)).invitations, [],
'An older three-member prototype must not admit a fourth player.');
});
void test('unlimited invitations allow a sixth player but never a seventh', () => {
const five = view('a', ['a', 'b', 'c', 'd', 'e']);
assert.deepEqual(coalitionNexusOptions(five).invitations.map(row => row.entrant), ['f']);
assert.match(render(five), /Invite F to A, B, C, D, E/);
assert.match(render(five), /Leaving keeps the other players allied/);
const six = view('a', ['a', 'b', 'c', 'd', 'e', 'f']);
assert.deepEqual(coalitionNexusOptions(six).invitations, []);
assert.match(render(six), /Leave A, B, C, D, E, F/);
assert.match(render(six, true), /disabled=""[^>]*>Leave A, B, C, D, E, F/);
});
void test('every existing member and the entrant can answer one pending group invitation', () => {
const original = group(['a', 'b', 'c']);
const value = view('d', original.members);
value.proposal = {event: 'event', nexus: 'nexus', turn: 2, groupId: original.id,
original, entrant: 'd', proposer: 'a', approvals: ['a', 'b']};
assert.equal(coalitionNexusOptions(value).mayAccept, true);
assert.match(render(value), /Every current member and the invited player must agree/);
assert.match(render(value), /Approved: A, B/);
assert.match(render(value), /Waiting for: C, D/);
assert.match(render(value), /Accept invitation/);
value.me = 'b';
assert.equal(coalitionNexusOptions(value).mayAccept, false);
assert.match(render(value), /Withdraw approval and cancel invitation/);
value.me = 'e';
assert.equal(coalitionNexusOptions(value).mayAccept, false);
assert.equal(coalitionNexusOptions(value).mayDecline, false);
assert.doesNotMatch(render(value), /Accept invitation|cancel invitation/);
});