135 lines
4.6 KiB
TypeScript
135 lines
4.6 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { baseDeck, ixDeck, treacheryDeck, type Card } from '../game/cards';
|
|
import { cardPresentation } from '../game/card-presentation';
|
|
import { DEFENSE_KINDS, WEAPON_KINDS } from '../game/battle-cards';
|
|
import {
|
|
ECAZ_TREACHERY_DEFINITIONS,
|
|
ECAZ_TREACHERY_VARIANT,
|
|
ecazTreacheryCards,
|
|
ecazTreacheryDefinition,
|
|
} from '../game/ecaz-cards';
|
|
|
|
void test('the verified Ecaz variant has one physical special of each identity and does not silently activate', () => {
|
|
const cards = ecazTreacheryCards();
|
|
assert.deepEqual(cards, [
|
|
{
|
|
id: 'ecaz-recruits',
|
|
name: 'Recruits',
|
|
kind: 'special',
|
|
effect: 'recruits',
|
|
},
|
|
{
|
|
id: 'ecaz-reinforcements',
|
|
name: 'Reinforcements',
|
|
kind: 'special',
|
|
effect: 'reinforcements',
|
|
},
|
|
{
|
|
id: 'ecaz-harass-withdraw',
|
|
name: 'Harass & Withdraw',
|
|
kind: 'special',
|
|
effect: 'harassWithdraw',
|
|
},
|
|
]);
|
|
const combined = [...baseDeck(), ...ixDeck(), ...cards];
|
|
assert.equal(combined.length, 50);
|
|
assert.equal(new Set(combined.map((card) => card.id)).size, 50);
|
|
assert.equal(treacheryDeck().length, 33);
|
|
assert.equal(treacheryDeck(['ix']).length, 47);
|
|
assert.equal(treacheryDeck(['ecaz']).length, 33);
|
|
for (const unsupported of [ECAZ_TREACHERY_VARIANT.id, 'unknown'])
|
|
assert.throws(() => treacheryDeck([unsupported]), /not implemented/);
|
|
assert.equal(ECAZ_TREACHERY_VARIANT.independentOfFactions, true);
|
|
assert.equal(ECAZ_TREACHERY_VARIANT.independentOfOtherVariants, true);
|
|
assert.equal(ECAZ_TREACHERY_VARIANT.activation, 'audit-prototype');
|
|
});
|
|
|
|
void test('Ecaz faction selection preserves each ordinary deck and never includes its separate unfinished variant', () => {
|
|
for (const other of [[], ['ix'], ['choam'], ['ix', 'choam']]) {
|
|
const cards = treacheryDeck([...other, 'ecaz']);
|
|
assert.deepEqual(cards, treacheryDeck(other));
|
|
assert.equal(new Set(cards.map((card) => card.id)).size, cards.length);
|
|
assert.equal(
|
|
cards.some((card) => card.id.startsWith('ecaz-')),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
cards.length,
|
|
other.includes('ix') ? 47 : other.includes('choam') ? 35 : 33,
|
|
);
|
|
}
|
|
assert.throws(
|
|
() => treacheryDeck(['ecaz', ECAZ_TREACHERY_VARIANT.id]),
|
|
/not implemented/,
|
|
);
|
|
});
|
|
|
|
void test('factory instances and their mutable cards are independent of canonical definitions', () => {
|
|
const first = ecazTreacheryCards();
|
|
first[0].name = 'Changed';
|
|
first.splice(1, 1);
|
|
const second = ecazTreacheryCards();
|
|
assert.equal(second.length, 3);
|
|
assert.equal(second[0].name, 'Recruits');
|
|
assert.equal(ECAZ_TREACHERY_DEFINITIONS[0].card.name, 'Recruits');
|
|
});
|
|
|
|
void test('slot occupancy never reclassifies the new special cards as weapons or defenses', () => {
|
|
for (const card of ecazTreacheryCards()) {
|
|
const definition = ecazTreacheryDefinition(card)!;
|
|
assert.equal(definition.weaponCategory, false);
|
|
assert.equal(definition.defenseCategory, false);
|
|
assert.equal(WEAPON_KINDS.includes(card.kind), false);
|
|
assert.equal(DEFENSE_KINDS.includes(card.kind), false);
|
|
assert.deepEqual(
|
|
definition.battleSlots,
|
|
card.effect === 'recruits' ? [] : ['weapon', 'defense'],
|
|
);
|
|
assert.equal(definition.discard, 'after-use');
|
|
}
|
|
});
|
|
|
|
void test('presentation lookup requires the canonical physical ID, kind and effect', () => {
|
|
const card = ecazTreacheryCards()[0];
|
|
assert.equal(ecazTreacheryDefinition(card)?.card.name, 'Recruits');
|
|
assert.equal(
|
|
ecazTreacheryDefinition({ ...card, id: 'treachery-0' }),
|
|
undefined,
|
|
);
|
|
assert.equal(
|
|
ecazTreacheryDefinition({ ...card, effect: 'harvester' }),
|
|
undefined,
|
|
);
|
|
assert.equal(
|
|
ecazTreacheryDefinition({ ...card, kind: 'worthless' }),
|
|
undefined,
|
|
);
|
|
assert.equal(ecazTreacheryDefinition(baseDeck()[0]), undefined);
|
|
});
|
|
|
|
|
|
void test('missing, mismatched and merely similar Ecaz identities do not receive a canonical gameplay guide', () => {
|
|
for (const card of ecazTreacheryCards()) {
|
|
const bad: Card[] = [
|
|
{ ...card, id: '' },
|
|
{ ...card, id: 'unrelated-card' },
|
|
{ ...card, effect: undefined },
|
|
{ ...card, effect: 'unrelated-effect' },
|
|
{ ...card, kind: 'worthless' },
|
|
{ ...card, name: 'Unrelated name' },
|
|
{ ...card, id: card.id.toUpperCase() },
|
|
{ ...card, effect: card.effect.toUpperCase() },
|
|
];
|
|
for (const supplied of bad) {
|
|
const presentation = cardPresentation(supplied);
|
|
assert.equal(presentation.gameplay, undefined);
|
|
assert.notEqual(
|
|
presentation.guidance,
|
|
ecazTreacheryDefinition(card)!.summary,
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|