Files
Dune/tests/homeworld-occupation-modules-profile.test.ts

156 lines
11 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
homeworldOccupationModulesProfile as supported,
homeworldOccupationLeaderSkillsProfile as cleanSkills,
homeworldPhaseModulesProfile as clean,
type HomeworldPhaseModulesProfile,
} from '../game/homeworld-module-profile';
import { independentDiscoveryProfile, nexusCoreProfile } from '../game/faction-module-profile';
import {
classicNexusLeaderSkillsProfile,
homeworldLeaderSkillsProfile,
pairedNexusLeaderSkillsProfile,
} from '../game/leader-skill-profile';
import {
classicDiscoveryLeaderSkillsProfile,
classicDiscoveryNexusProfile,
homeworldDiscoveryProfile,
pairedDiscoveryNexusProfile,
} from '../game/discovery-module-profile';
import { mixedE3NexusProfile, standaloneE3NexusProfile } from '../game/nexus-module-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 }, ...extra };
}
const nexus = () => ({ cards: null, phase: null });
void test('original classic World Nexus accepts its initialized core reactions with and without Skills', () => {
const game = profile(['atreides', 'harkonnen', 'guild'], [], { advanced: true, nexusCards: nexus(),
techTokens: createTechTokens(), strongholdCards: createStrongholdCards(),
homeworldOccupationPreview: true, nexusIxianReplacementPreview: true,
nexusHarkonnenBetrayalPreview: true, guildBetrayalPreview: true });
assert.equal(nexusCoreProfile(game), true); assert.equal(supported(game), true);
const trained = { ...game, leaderSkills: { deck: [], assignments: [] }, spiceBankerIncomePreview: true };
assert.equal(homeworldLeaderSkillsProfile(trained), true); assert.equal(supported(trained), true);
assert.equal(clean(game), false); assert.equal(cleanSkills(trained), false);
});
void test('original classic Discovery retains separate untrained and all14 World admission', () => {
const game = profile(undefined, undefined, { discoveryEnabled: true, techTokens: createTechTokens() });
assert.equal(homeworldDiscoveryProfile(game), true); assert.equal(supported(game), true);
const trained = { ...game, leaderSkills: { deck: [], offers: {} }, spiceBankerIncomePreview: true };
assert.equal(classicDiscoveryLeaderSkillsProfile(trained), true);
assert.equal(homeworldLeaderSkillsProfile(trained), true); assert.equal(supported(trained), true);
assert.equal(supported({ ...game, discoveryEnabled: false, discoveries: {} }), false);
assert.equal(supported({ ...game, discoveryEnabled: 'true' }), false);
});
void test('classic World Nexus and Discovery use their original joint profiles rather than hiding either module', () => {
const game = profile(undefined, undefined, { advanced: true, nexusCards: nexus(), discoveryEnabled: true,
techTokens: createTechTokens(), strongholdCards: createStrongholdCards() });
assert.equal(classicDiscoveryNexusProfile(game), true); assert.equal(supported(game), true);
const trained = { ...game, leaderSkills: true, spiceBankerIncomePreview: true };
assert.equal(classicNexusLeaderSkillsProfile(trained), true);
assert.equal(homeworldLeaderSkillsProfile(trained), true); assert.equal(supported(trained), true);
assert.equal(nexusCoreProfile(game), false, 'The non-Discovery core does not manufacture this separate admission');
});
void test('original native Nexus includes bare paired E1 and existing standalone and mixed E3 World rosters', () => {
const paired = profile(['ixians', 'tleilaxu', 'emperor'], ['ix'], { nexusCards: nexus() });
assert.equal(pairedNexusLeaderSkillsProfile(paired), true, 'The original paired configuration needs no phase module');
assert.equal(supported(paired), true);
const discovery = { ...paired, discoveryEnabled: true };
assert.equal(pairedDiscoveryNexusProfile(discovery), true); assert.equal(supported(discovery), true);
const standalone = profile(['moritani', 'emperor', 'guild'], ['ecaz'], { advanced: true,
nexusCards: nexus(), moritaniAssassinatePreview: true });
assert.equal(standaloneE3NexusProfile(standalone), true); assert.equal(supported(standalone), true);
const mixed = profile(['ixians', 'choam', 'ecaz', 'moritani'], ['ix', 'choam', 'ecaz'], {
advanced: true, nexusCards: nexus(), techTokens: createTechTokens(), strongholdCards: createStrongholdCards(),
ecazOccupyPreview: true, moritaniAssassinatePreview: true, homeworldOccupationPreview: true,
homeworldTupilePreview: true, homeworldEcazVictoryPreview: true });
assert.equal(mixedE3NexusProfile(mixed), true); assert.equal(supported(mixed), true);
const trained = { ...mixed, leaderSkills: true, discoveryEnabled: true, spiceBankerIncomePreview: true };
assert.equal(homeworldLeaderSkillsProfile(trained), true); assert.equal(supported(trained), true);
});
void test('independent cards retain already-admitted selected and unused families beside Nexus or Discovery', () => {
const selected = profile(['ecaz', 'ixians', 'emperor'], ['ix', 'choam', 'ecaz'], {
nexusCards: nexus(), ecazTreachery: true, nexusIxianBetrayalPreview: true,
ecazOccupyPreview: true, homeworldOccupationPreview: true, homeworldEcazVictoryPreview: true });
assert.equal(nexusCoreProfile(selected), true); assert.equal(supported(selected), true);
const unused = profile(undefined, ['ix', 'choam', 'ecaz'], {
discoveryEnabled: true, ecazTreachery: true, techTokens: createTechTokens() });
assert.equal(independentDiscoveryProfile(unused), true); assert.equal(supported(unused), true);
const both = profile(['ecaz', 'moritani', 'choam'], ['ix', 'choam', 'ecaz'], {
advanced: true, ecazTreachery: true, nexusCards: nexus(), discoveryEnabled: true, leaderSkills: true,
techTokens: createTechTokens(), strongholdCards: createStrongholdCards(), spiceBankerIncomePreview: true,
nexusIxianReplacementPreview: true, moritaniAssassinatePreview: true, ecazOccupyPreview: true,
homeworldOccupationPreview: true, homeworldTupilePreview: true, homeworldEcazVictoryPreview: true });
assert.equal(independentDiscoveryProfile(both), true); assert.equal(homeworldLeaderSkillsProfile(both), true);
assert.equal(supported(both), true);
});
void test('occupation selection preserves original Advanced roles, family requirements and phase limits', () => {
const guarded = profile(['ecaz', 'harkonnen', 'guild'], ['ecaz'], {
advanced: true, nexusCards: nexus(), leaderSkills: true });
assert.equal(homeworldLeaderSkillsProfile(guarded), false); assert.equal(supported(guarded), false);
const discoveryGuard = profile(['moritani', 'harkonnen', 'guild'], ['ecaz'], {
advanced: true, discoveryEnabled: true });
assert.equal(homeworldDiscoveryProfile(discoveryGuard), false); assert.equal(supported(discoveryGuard), false);
for (const game of [
profile(['ixians', 'emperor'], [], { nexusCards: nexus() }),
profile(['ixians', 'emperor', 'guild'], ['ix', 'choam'], { nexusCards: nexus() }),
profile(['atreides', 'emperor'], [], { nexusCards: nexus(), techTokens: createTechTokens() }),
profile(undefined, undefined, { nexusCards: nexus(), strongholdCards: createStrongholdCards() }),
profile(['atreides', 'atreides'], [], { nexusCards: nexus() }),
profile(undefined, ['ix', 'ix'], { discoveryEnabled: true }),
]) assert.equal(supported(game), false);
assert.equal(supported(profile(['atreides', 'emperor'], [], {
advanced: true, nexusCards: nexus(), strongholdCards: createStrongholdCards() })), true);
});
void test('unrelated previews and mismatched native, parent, reaction or Banker stamps remain outside occupation permission', () => {
const game = profile(undefined, undefined, { nexusCards: nexus() });
for (const field of ['semutaPreview', 'advancedPreview', 'kullPreview', 'nexusKullPreview',
'richeseBetrayalPreview'] as const) assert.equal(supported({ ...game, [field]: true }), false, field);
for (const extra of [
{ ecazOccupyPreview: true }, { moritaniAssassinatePreview: true },
{ homeworldOccupationPreview: true, homeworldTupilePreview: true },
{ homeworldOccupationPreview: true, homeworldEcazVictoryPreview: true },
{ nexusIxianBetrayalPreview: true }, { nexusHarkonnenBetrayalPreview: true },
{ spiceBankerIncomePreview: true }, { ecazTreachery: 'true' },
]) assert.equal(supported({ ...game, ...extra }), false);
assert.equal(supported(profile(['atreides', 'emperor', 'harkonnen'], [], {
nexusCards: nexus(), guildBetrayalPreview: true })), false);
assert.equal(supported(profile(['ixians', 'emperor', 'guild'], ['ix'], {
nexusCards: nexus(), ecazTreachery: true, nexusIxianReplacementPreview: true })), false);
for (const field of ['homeworldOccupationPreview', 'homeworldTupilePreview', 'homeworldEcazVictoryPreview',
'ecazOccupyPreview', 'moritaniAssassinatePreview', 'spiceBankerIncomePreview', 'guildBetrayalPreview',
'nexusIxianReplacementPreview', 'nexusIxianBetrayalPreview', 'nexusHarkonnenBetrayalPreview'] as const)
assert.equal(supported({ ...game, [field]: 'forged' }), false, field);
});
void test('live native Discovery, active printed Sandtrout and occupied custody stay configuration-neutral in original module contexts', () => {
const custody = { visitors: { 'homeworld:ixians': { emperor: { normal: 1, elite: 1 } } } };
const discoveries = { turn: 2, tokens: [], encounters: [] };
const game = profile(['ixians', 'tleilaxu', 'emperor'], ['ix'], { nexusCards: nexus(), discoveryEnabled: true,
homeworlds: { custody, historyVersion: 1 }, homeworldOccupationPreview: true, sandtrout: true,
discoveries, discoveryStash: {}, greatMaker: { turn: 2 } });
assert.equal(pairedDiscoveryNexusProfile(game), true);
const before = structuredClone(game); Object.freeze(custody); Object.freeze(game.players); Object.freeze(game);
assert.equal(supported(game), true); assert.deepEqual(game, before);
assert.equal((game.homeworlds as { custody: unknown }).custody, custody); assert.equal(game.discoveries, discoveries);
const plain = profile(); const trained = { ...plain, leaderSkills: true };
assert.equal(clean(plain), true); assert.equal(cleanSkills(trained), true);
assert.equal(supported(plain), false); assert.equal(supported(trained), false);
assert.equal(clean({ ...plain, sandtrout: true }), true); assert.equal(cleanSkills({ ...trained, sandtrout: true }), true);
for (const sandtrout of [null, 0, 1, 'true', {}]) assert.equal(supported({ ...game, sandtrout }), false);
for (const value of [null, {}, { ...game, expansions: 'ix' }, { ...game, players: [{ faction: 'ixians' }, null] }])
assert.equal(supported(value as unknown as HomeworldPhaseModulesProfile), false);
});