125 lines
4.7 KiB
JavaScript
125 lines
4.7 KiB
JavaScript
import { createServer } from 'vite';
|
|
|
|
const server = await createServer({
|
|
logLevel: 'error',
|
|
server: { middlewareMode: true, hmr: false },
|
|
appType: 'custom'
|
|
});
|
|
|
|
try {
|
|
const {
|
|
defaultLongWaitBattleThreshold,
|
|
defaultRecentRecruitBattleWindow,
|
|
deriveSortieRosterHistory,
|
|
deriveSortieRosterHistoryByUnitId
|
|
} = await server.ssrLoadModule('/src/game/data/sortieRosterHistory.ts');
|
|
|
|
assert(defaultRecentRecruitBattleWindow === 1, 'Recent-recruit window must retain its one-battle default.');
|
|
assert(defaultLongWaitBattleThreshold === 3, 'Long-wait threshold must retain its three-battle default.');
|
|
|
|
const empty = deriveSortieRosterHistory(['liu-bei'], {});
|
|
assert(empty.length === 1, `Empty history must still return the requested roster unit: ${JSON.stringify(empty)}`);
|
|
assert(
|
|
empty[0].sortieCount === 0 &&
|
|
empty[0].firstSortiePending === true &&
|
|
empty[0].battlesSinceLastSortie === null &&
|
|
empty[0].recentRecruit === false &&
|
|
empty[0].badge.kind === 'first-sortie' &&
|
|
empty[0].badge.label === '미출전',
|
|
`Empty history must produce a stable first-sortie state: ${JSON.stringify(empty[0])}`
|
|
);
|
|
|
|
const firstSortieHistory = {
|
|
opening: battle('opening', 1, ['guan-yu', 'guan-yu'])
|
|
};
|
|
const frozenFirstSortieHistory = JSON.stringify(firstSortieHistory);
|
|
const firstSortie = deriveSortieRosterHistory(['guan-yu'], firstSortieHistory)[0];
|
|
assert(
|
|
firstSortie.sortieCount === 1 &&
|
|
firstSortie.firstSortiePending === false &&
|
|
firstSortie.battlesSinceLastSortie === 0 &&
|
|
firstSortie.lastSortieBattleId === 'opening' &&
|
|
firstSortie.badge.kind === 'active' &&
|
|
firstSortie.badge.label === '출전 1회',
|
|
`A first completed sortie must become the active state exactly once: ${JSON.stringify(firstSortie)}`
|
|
);
|
|
assert(
|
|
JSON.stringify(firstSortieHistory) === frozenFirstSortieHistory,
|
|
'History derivation must not mutate campaign battle-history-like input.'
|
|
);
|
|
|
|
const longWait = deriveSortieRosterHistory(['zhang-fei'], {
|
|
fourth: battle('fourth', 4),
|
|
first: battle('first', 1, ['zhang-fei']),
|
|
third: battle('third', 3),
|
|
second: battle('second', 2)
|
|
})[0];
|
|
assert(
|
|
longWait.sortieCount === 1 &&
|
|
longWait.battlesSinceLastSortie === 3 &&
|
|
longWait.lastSortieBattleId === 'first' &&
|
|
longWait.badge.kind === 'long-wait' &&
|
|
longWait.badge.label === '대기 3전',
|
|
`Three later battles must produce a long-wait badge even when record insertion is unordered: ${JSON.stringify(longWait)}`
|
|
);
|
|
|
|
const recentRecruit = deriveSortieRosterHistory(['jian-yong'], {
|
|
recruitment: battle('recruitment', 1, [], ['jian-yong'])
|
|
})[0];
|
|
assert(
|
|
recentRecruit.sortieCount === 0 &&
|
|
recentRecruit.firstSortiePending === true &&
|
|
recentRecruit.recentRecruit === true &&
|
|
recentRecruit.battlesSinceRecruit === 0 &&
|
|
recentRecruit.recruitBattleId === 'recruitment' &&
|
|
recentRecruit.badge.kind === 'recent-recruit' &&
|
|
recentRecruit.badge.label === 'NEW' &&
|
|
recentRecruit.summary === '최근 합류 · 첫 출전 대기',
|
|
`A latest-battle recruit must expose the concise NEW state: ${JSON.stringify(recentRecruit)}`
|
|
);
|
|
|
|
const experiencedRecentRecruit = deriveSortieRosterHistory(['guan-yu'], {
|
|
recruitment: battle('recruitment', 1, ['guan-yu'], ['guan-yu'])
|
|
})[0];
|
|
assert(
|
|
experiencedRecentRecruit.sortieCount === 1 &&
|
|
experiencedRecentRecruit.firstSortiePending === false &&
|
|
experiencedRecentRecruit.recentRecruit === true &&
|
|
experiencedRecentRecruit.badge.kind === 'recent-recruit' &&
|
|
experiencedRecentRecruit.badge.label === 'NEW',
|
|
`A recruit who fought before formally joining must retain the NEW roster badge: ${JSON.stringify(experiencedRecentRecruit)}`
|
|
);
|
|
|
|
const byUnitId = deriveSortieRosterHistoryByUnitId(
|
|
['jian-yong', 'liu-bei', 'jian-yong', ' '],
|
|
[battle('recruitment', 1, ['liu-bei'], ['jian-yong'])]
|
|
);
|
|
assert(
|
|
byUnitId.size === 2 &&
|
|
byUnitId.get('jian-yong')?.recentRecruit === true &&
|
|
byUnitId.get('liu-bei')?.sortieCount === 1,
|
|
`Map derivation must preserve unique roster entries and their states: ${JSON.stringify([...byUnitId])}`
|
|
);
|
|
|
|
console.log('Verified sortie roster history: empty, first sortie, long wait, recent recruit, ordering, and immutability.');
|
|
} finally {
|
|
await server.close();
|
|
}
|
|
|
|
function battle(battleId, day, selectedUnitIds = [], recruitUnitIds = []) {
|
|
return {
|
|
battleId,
|
|
completedAt: new Date(Date.UTC(2026, 0, day)).toISOString(),
|
|
sortiePerformance: { selectedUnitIds },
|
|
campaignRewards: {
|
|
recruits: recruitUnitIds.map((unitId) => ({ unitId }))
|
|
}
|
|
};
|
|
}
|
|
|
|
function assert(condition, message) {
|
|
if (!condition) {
|
|
throw new Error(message);
|
|
}
|
|
}
|