125 lines
8.1 KiB
TypeScript
125 lines
8.1 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
homeworldOccupationLeaderSkillsProfile as supported,
|
|
homeworldPhaseModulesProfile as untrained,
|
|
type HomeworldPhaseModulesProfile,
|
|
} from '../game/homeworld-module-profile';
|
|
import { homeworldLeaderSkillsProfile as admitted } from '../game/leader-skill-profile';
|
|
import { createTechTokens } from '../game/tech-tokens';
|
|
import { createStrongholdCards } from '../game/stronghold-cards';
|
|
|
|
function profile(factions: readonly string[] = ['atreides', 'emperor', 'guild'], expansions: readonly string[] = [],
|
|
extra: Partial<HomeworldPhaseModulesProfile> = {}): HomeworldPhaseModulesProfile {
|
|
return { advanced: false, expansions, players: factions.map(faction => ({ faction })),
|
|
homeworlds: { custody: null }, leaderSkills: true, ...extra };
|
|
}
|
|
|
|
void test('original classic World/all14 admission gains clean occupation benefits with its optional phase modules', () => {
|
|
for (const game of [profile(), profile(undefined, undefined, { advanced: true }),
|
|
profile(undefined, undefined, { techTokens: createTechTokens(), ecazTreachery: true }),
|
|
profile(undefined, undefined, { advanced: true, techTokens: createTechTokens(), strongholdCards: createStrongholdCards() }),
|
|
]) {
|
|
assert.equal(admitted(game), true); assert.equal(supported(game), true);
|
|
assert.equal(untrained(game), false, 'The original untrained World predicate still rejects Skills');
|
|
}
|
|
});
|
|
|
|
void test('the unchanged selected-family all14 predicate remains the source of native roster and role admission', () => {
|
|
for (const game of [
|
|
profile(['ecaz', 'ixians', 'emperor'], ['ecaz', 'ix']),
|
|
profile(['ecaz', 'moritani', 'choam'], ['ecaz', 'choam'], { advanced: true }),
|
|
profile(['ixians', 'tleilaxu', 'choam', 'richese', 'ecaz', 'moritani'], ['ix', 'choam', 'ecaz'],
|
|
{ advanced: true, ecazTreachery: true, techTokens: createTechTokens(), strongholdCards: createStrongholdCards() }),
|
|
profile(['atreides', 'emperor', 'guild'], ['ix', 'choam', 'ecaz']),
|
|
]) {
|
|
assert.equal(admitted(game), true); assert.equal(supported(game), true);
|
|
}
|
|
for (const game of [
|
|
profile(['ecaz', 'harkonnen', 'guild'], ['ecaz'], { advanced: true }),
|
|
profile(['moritani', 'harkonnen', 'guild'], ['ecaz'], { advanced: true }),
|
|
]) {
|
|
assert.equal(admitted(game), false); assert.equal(supported(game), false);
|
|
}
|
|
});
|
|
|
|
void test('required modules, physical seat bounds and existing phase limits do not change with benefit initialization', () => {
|
|
assert.equal(supported(profile(undefined, undefined, { homeworlds: null })), false);
|
|
assert.equal(supported(profile(undefined, undefined, { leaderSkills: null })), false);
|
|
const two = profile(['atreides', 'emperor']);
|
|
assert.equal(supported(two), true);
|
|
assert.equal(supported({ ...two, techTokens: createTechTokens() }), false);
|
|
assert.equal(supported({ ...two, strongholdCards: createStrongholdCards() }), false);
|
|
assert.equal(supported({ ...two, advanced: true, strongholdCards: createStrongholdCards() }), true);
|
|
for (const game of [profile(['atreides']), profile(['atreides', 'atreides']),
|
|
profile(['ecaz', 'guild', 'emperor']), profile(undefined, ['ix', 'ix']),
|
|
profile(['atreides', 'emperor', 'guild', 'fremen', 'beneGesserit', 'harkonnen', 'ecaz'], ['ecaz']),
|
|
]) assert.equal(supported(game), false);
|
|
});
|
|
|
|
void test('occupation benefit context excludes Nexus, Discovery and unrelated reaction selectors without changing library admission', () => {
|
|
for (const field of ['nexusCards', 'discoveryEnabled', 'discoveries', 'discoveryStash', 'greatMaker',
|
|
'semutaPreview', 'advancedPreview', 'kullPreview', 'nexusKullPreview', 'guildBetrayalPreview',
|
|
'richeseBetrayalPreview', 'nexusIxianReplacementPreview', 'nexusIxianBetrayalPreview', 'nexusHarkonnenBetrayalPreview',
|
|
] as const) assert.equal(supported({ ...profile(), [field]: true }), false, field);
|
|
const nexus = profile(undefined, undefined, { nexusCards: { cards: null, phase: null } });
|
|
assert.equal(admitted(nexus), true, 'Existing Nexus/World/all14 library entry stays independently admitted');
|
|
assert.equal(supported(nexus), false, 'This clean benefit slice does not claim that separate combination');
|
|
});
|
|
|
|
void test('pending printed Sandtrout retains occupation and all14 roles under the same original World admission', () => {
|
|
const game = profile(['ixians', 'emperor', 'atreides'], ['ix'], { advanced: true,
|
|
homeworldOccupationPreview: true, spiceBankerIncomePreview: true,
|
|
techTokens: createTechTokens(), strongholdCards: createStrongholdCards(),
|
|
});
|
|
for (const sandtrout of [undefined, false, true]) {
|
|
const active = { ...game, sandtrout };
|
|
assert.equal(admitted(active), true);
|
|
assert.equal(supported(active), true);
|
|
}
|
|
for (const sandtrout of [null, 0, 1, 'true', {}]) assert.equal(supported({ ...game, sandtrout }), false);
|
|
});
|
|
|
|
void test('canonical Banker and native occupation stamps require their actual parent factions and retain the Advanced assassination band', () => {
|
|
const native = profile(['ecaz', 'moritani', 'choam'], ['ecaz', 'choam'], { advanced: true,
|
|
homeworldOccupationPreview: true, homeworldTupilePreview: true, homeworldEcazVictoryPreview: true,
|
|
ecazOccupyPreview: true, moritaniAssassinatePreview: true, spiceBankerIncomePreview: true });
|
|
assert.equal(admitted(native), true); assert.equal(supported(native), true);
|
|
assert.equal(supported(profile(undefined, undefined, { spiceBankerIncomePreview: true })), true);
|
|
for (const game of [
|
|
{ ...native, homeworldOccupationPreview: undefined }, { ...native, advanced: false },
|
|
profile(undefined, undefined, { ecazOccupyPreview: true }),
|
|
profile(undefined, undefined, { advanced: true, moritaniAssassinatePreview: true }),
|
|
profile(undefined, undefined, { homeworldOccupationPreview: true, homeworldTupilePreview: true }),
|
|
profile(undefined, undefined, { homeworldOccupationPreview: true, homeworldEcazVictoryPreview: true }),
|
|
]) assert.equal(supported(game), false);
|
|
for (const field of ['spiceBankerIncomePreview', 'ecazOccupyPreview', 'moritaniAssassinatePreview',
|
|
'homeworldOccupationPreview', 'homeworldTupilePreview', 'homeworldEcazVictoryPreview'] as const)
|
|
assert.equal(supported({ ...native, [field]: 'forged' }), false, field);
|
|
});
|
|
|
|
void test('fresh offers and live occupied posture remain configuration-neutral rather than a new deal or ownership predicate', () => {
|
|
const skills = { deck: ['sandmaster'], offers: {}, assignments: [
|
|
{ owner: 'seat-a', leader: 'disc-a', skill: 'spice-banker', faceUp: false },
|
|
] };
|
|
const custody = { visitors: { 'homeworld:atreides': { 'seat-e': { normal: 1, elite: 1 } } },
|
|
salusa: { normal: 0, elite: 4 } };
|
|
const game = profile(undefined, undefined, { advanced: true, leaderSkills: skills,
|
|
homeworlds: { custody, historyVersion: 1 }, homeworldOccupationPreview: true,
|
|
spiceBankerIncomePreview: true, techTokens: createTechTokens(), strongholdCards: createStrongholdCards() });
|
|
const before = structuredClone(game); Object.freeze(game.players); Object.freeze(game);
|
|
assert.equal(admitted(game), true); assert.equal(supported(game), true); assert.deepEqual(game, before);
|
|
assert.equal(game.leaderSkills, skills); assert.equal((game.homeworlds as { custody: unknown }).custody, custody);
|
|
});
|
|
|
|
void test('the previous untrained helper retains its behavior and malformed configuration never widens the all14 route', () => {
|
|
const plain = profile(undefined, undefined, { leaderSkills: undefined });
|
|
assert.equal(untrained(plain), true); assert.equal(supported(plain), false);
|
|
assert.equal(untrained(profile()), false); assert.equal(supported(profile()), true);
|
|
assert.equal(untrained(profile(undefined, undefined, { leaderSkills: undefined, spiceBankerIncomePreview: true })), false);
|
|
for (const game of [profile(undefined, undefined, { ecazTreachery: 'true' }),
|
|
profile(undefined, undefined, { advanced: 'advanced' as unknown as boolean })]) assert.equal(supported(game), false);
|
|
for (const value of [null, {}, { ...profile(), expansions: 'ix' }, { ...profile(), players: [{ faction: 'atreides' }, null] }])
|
|
assert.equal(supported(value as unknown as HomeworldPhaseModulesProfile), false);
|
|
});
|