88 lines
5.2 KiB
TypeScript
88 lines
5.2 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import { test } from 'node:test';
|
|
import {
|
|
bindCharityPromise, charityPromiseAnswers, CharityPromiseError,
|
|
guardCharityClaim, guardCharityReady, liveCharityPromises, reconcileCharityPromises, validateCharityPromises,
|
|
type CharityPromise,
|
|
} from '../game/charity-promises';
|
|
|
|
const ctx = { player: 'emperor', asker: 'atreides', turn: 3 };
|
|
const empty: CharityPromise[] = [];
|
|
const bind = (rows: readonly CharityPromise[], answer: boolean, canClaim = true) =>
|
|
bindCharityPromise(rows, ctx.player, ctx.asker, ctx.turn, answer, canClaim);
|
|
|
|
void test('Charity answer availability follows actual eligibility and incompatible earlier answers', () => {
|
|
assert.deepEqual(charityPromiseAnswers(empty, ctx.player, ctx.turn, true), ['yes', 'no']);
|
|
assert.deepEqual(charityPromiseAnswers(empty, ctx.player, ctx.turn, false), ['no']);
|
|
assert.deepEqual(charityPromiseAnswers(bind(empty, true), ctx.player, ctx.turn, true), ['yes']);
|
|
assert.deepEqual(charityPromiseAnswers(bind(empty, false), ctx.player, ctx.turn, true), ['no']);
|
|
assert.throws(() => bind(bind(empty, true), false), CharityPromiseError);
|
|
assert.throws(() => bind(bind(empty, false), true), CharityPromiseError);
|
|
assert.throws(() => bind(empty, true, false), CharityPromiseError);
|
|
assert.throws(() => bindCharityPromise(empty, ctx.player, ctx.player, ctx.turn, false, true), CharityPromiseError);
|
|
assert.deepEqual(empty, []);
|
|
});
|
|
|
|
void test('a Yes blocks ready until the actual claim and a No blocks the actual claim', () => {
|
|
const yes = bind(bind(empty, true), true);
|
|
assert.throws(() => guardCharityReady(yes, ctx.player, ctx.turn, true), CharityPromiseError);
|
|
assert.doesNotThrow(() => guardCharityReady(yes, ctx.player, ctx.turn, false));
|
|
const claimed = guardCharityClaim(yes, ctx.player, ctx.turn);
|
|
assert.equal(claimed.every(row => row.fulfilled && !row.released), true);
|
|
assert.equal(yes.every(row => !row.fulfilled), true, 'the original input is unchanged');
|
|
assert.doesNotThrow(() => guardCharityReady(claimed, ctx.player, ctx.turn, true));
|
|
assert.throws(() => guardCharityClaim(bind(empty, false), ctx.player, ctx.turn), CharityPromiseError);
|
|
});
|
|
|
|
void test('an external loss releases Yes, while a voluntary loss or phase exit rejects unchanged', () => {
|
|
const yes = bind(empty, true);
|
|
for (const phaseOpen of [true, false]) {
|
|
assert.throws(() => reconcileCharityPromises(yes, ctx.player, ctx.turn,
|
|
{ phaseOpen, canClaim: false, change: 'voluntary' }), CharityPromiseError);
|
|
assert.equal(yes[0].released, false);
|
|
const released = reconcileCharityPromises(yes, ctx.player, ctx.turn,
|
|
{ phaseOpen, canClaim: false, change: 'external' });
|
|
assert.equal(released[0].released, true);
|
|
assert.equal(released[0].fulfilled, false);
|
|
}
|
|
});
|
|
|
|
void test('No survives Basic ready while the phase is open and finishes at phase close', () => {
|
|
const no = bind(empty, false);
|
|
const ready = reconcileCharityPromises(no, ctx.player, ctx.turn,
|
|
{ phaseOpen: true, canClaim: true, change: 'voluntary' });
|
|
assert.deepEqual(liveCharityPromises(ready, ctx.player, ctx.turn), no);
|
|
assert.throws(() => guardCharityClaim(ready, ctx.player, ctx.turn), CharityPromiseError);
|
|
const closed = reconcileCharityPromises(ready, ctx.player, ctx.turn,
|
|
{ phaseOpen: false, canClaim: false, change: 'voluntary' });
|
|
assert.equal(closed[0].fulfilled, true);
|
|
assert.equal(closed[0].released, false);
|
|
assert.deepEqual(liveCharityPromises(closed, ctx.player, ctx.turn), []);
|
|
});
|
|
|
|
void test('promises for other players, turns and completed answers are unaffected', () => {
|
|
const yes = bind(empty, true);
|
|
const other = bindCharityPromise(yes, 'guild', 'atreides', 3, true, true);
|
|
const later = bindCharityPromise(other, 'emperor', 'atreides', 4, false, true);
|
|
const settled = guardCharityClaim(later, ctx.player, ctx.turn);
|
|
assert.equal(settled[0].fulfilled, true);
|
|
assert.deepEqual(settled.slice(1), later.slice(1));
|
|
assert.deepEqual(charityPromiseAnswers(settled, ctx.player, ctx.turn, true), ['yes', 'no']);
|
|
assert.throws(() => liveCharityPromises(settled, ctx.player, 0), CharityPromiseError);
|
|
});
|
|
|
|
void test('every answer has a stable unique receipt event through claim, release and closure', () => {
|
|
const yes = bindCharityPromise(empty, ctx.player, ctx.asker, ctx.turn, true, true, 'event-yes');
|
|
assert.equal(yes[0].event, 'event-yes');
|
|
assert.equal(guardCharityClaim(yes, ctx.player, ctx.turn)[0].event, 'event-yes');
|
|
assert.equal(reconcileCharityPromises(yes, ctx.player, ctx.turn,
|
|
{ phaseOpen: true, canClaim: false, change: 'external' })[0].event, 'event-yes');
|
|
const no = bindCharityPromise(yes, 'guild', ctx.asker, ctx.turn, false, true, 'event-no');
|
|
assert.equal(reconcileCharityPromises(no, 'guild', ctx.turn,
|
|
{ phaseOpen: false, canClaim: false, change: 'voluntary' })[1].event, 'event-no');
|
|
assert.throws(() => bindCharityPromise(yes, 'guild', ctx.asker, ctx.turn, true, true, 'event-yes'), CharityPromiseError);
|
|
assert.throws(() => validateCharityPromises([{ ...yes[0], event: '' }]), CharityPromiseError);
|
|
assert.throws(() => validateCharityPromises([{ ...yes[0], fulfilled: true, released: true }]), CharityPromiseError);
|
|
assert.ok(bind(empty, false)[0].event, 'normal binding assigns an event');
|
|
});
|