/* eslint-disable max-lines-per-function -- Test suites naturally have many cases */ /* eslint-disable @typescript-eslint/consistent-type-assertions -- Tests build minimal state objects */ import { describe, expect, it } from "vitest"; import { buildPostApotheosisState, isEligibleForApotheosis, } from "../../src/services/apotheosis.js"; import { defaultTranscendenceUpgrades } from "../../src/data/transcendenceUpgrades.js"; import type { GameState } from "@elysium/types"; const ALL_UPGRADE_IDS = defaultTranscendenceUpgrades.map((u) => u.id); const makePlayer = (overrides: Partial = {}) => ({ avatar: null, characterName: "T", discordId: "t", discriminator: "0", lifetimeAchievementsUnlocked: 0, lifetimeAdventurersRecruited: 0, lifetimeBossesDefeated: 0, lifetimeClicks: 0, lifetimeGoldEarned: 0, lifetimeQuestsCompleted: 0, totalClicks: 0, totalGoldEarned: 0, username: "u", ...overrides, }); const makeMinimalState = (overrides: Partial = {}): GameState => ({ player: makePlayer(), resources: { gold: 0, essence: 0, crystals: 0, runestones: 0 }, adventurers: [], upgrades: [], quests: [], bosses: [], equipment: [], achievements: [], zones: [], exploration: { areas: [], materials: [], craftedRecipeIds: [], craftedGoldMultiplier: 1, craftedEssenceMultiplier: 1, craftedClickMultiplier: 1, craftedCombatMultiplier: 1 }, companions: { unlockedCompanionIds: [], activeCompanionId: null }, prestige: { count: 0, runestones: 0, productionMultiplier: 1, purchasedUpgradeIds: [] }, baseClickPower: 1, lastTickAt: 0, schemaVersion: 1, ...overrides, } as GameState); describe("isEligibleForApotheosis", () => { it("returns true when all transcendence upgrades are purchased", () => { const state = makeMinimalState({ transcendence: { count: 1, echoes: 0, purchasedUpgradeIds: ALL_UPGRADE_IDS, echoIncomeMultiplier: 1, echoCombatMultiplier: 1, echoPrestigeThresholdMultiplier: 1, echoPrestigeRunestoneMultiplier: 1, echoMetaMultiplier: 1, }, }); expect(isEligibleForApotheosis(state)).toBe(true); }); it("returns false when one upgrade is missing", () => { const partialIds = ALL_UPGRADE_IDS.slice(0, -1); const state = makeMinimalState({ transcendence: { count: 1, echoes: 0, purchasedUpgradeIds: partialIds, echoIncomeMultiplier: 1, echoCombatMultiplier: 1, echoPrestigeThresholdMultiplier: 1, echoPrestigeRunestoneMultiplier: 1, echoMetaMultiplier: 1, }, }); expect(isEligibleForApotheosis(state)).toBe(false); }); it("returns false when transcendence is undefined", () => { const state = makeMinimalState({ transcendence: undefined }); expect(isEligibleForApotheosis(state)).toBe(false); }); it("returns false when purchasedUpgradeIds is empty", () => { const state = makeMinimalState({ transcendence: { count: 1, echoes: 0, purchasedUpgradeIds: [], echoIncomeMultiplier: 1, echoCombatMultiplier: 1, echoPrestigeThresholdMultiplier: 1, echoPrestigeRunestoneMultiplier: 1, echoMetaMultiplier: 1, }, }); expect(isEligibleForApotheosis(state)).toBe(false); }); }); describe("buildPostApotheosisState", () => { it("increments apotheosis count from 0", () => { const state = makeMinimalState(); const { updatedApotheosisData } = buildPostApotheosisState(state, "T"); expect(updatedApotheosisData.count).toBe(1); }); it("increments apotheosis count from existing value", () => { const state = makeMinimalState({ apotheosis: { count: 2 } }); const { updatedApotheosisData } = buildPostApotheosisState(state, "T"); expect(updatedApotheosisData.count).toBe(3); }); it("persists codex", () => { const codex = { entries: [{ id: "e1", unlockedAt: 1000, sourceType: "exploration" as const }] }; const state = makeMinimalState({ codex }); const { updatedState } = buildPostApotheosisState(state, "T"); expect(updatedState.codex).toEqual(codex); }); it("persists story", () => { const story = { unlockedChapterIds: ["ch1"], completedChapters: [] }; const state = makeMinimalState({ story }); const { updatedState } = buildPostApotheosisState(state, "T"); expect(updatedState.story).toEqual(story); }); it("persists active companion selection across apotheosis", () => { const companions = { unlockedCompanionIds: ["lyra", "pria"], activeCompanionId: "pria" }; const state = makeMinimalState({ companions }); const { updatedState } = buildPostApotheosisState(state, "T"); expect(updatedState.companions).toEqual(companions); }); it("falls back to fresh companion state when currentState.companions is undefined", () => { const state = makeMinimalState({ companions: undefined }); const { updatedState } = buildPostApotheosisState(state, "T"); expect(updatedState.companions).toEqual({ activeCompanionId: null, unlockedCompanionIds: [] }); }); it("wipes prestige data", () => { const state = makeMinimalState({ prestige: { count: 10, runestones: 1000, productionMultiplier: 3, purchasedUpgradeIds: [] }, }); const { updatedState } = buildPostApotheosisState(state, "T"); expect(updatedState.prestige.count).toBe(0); expect(updatedState.prestige.runestones).toBe(0); }); it("sets apotheosis count on new state", () => { const state = makeMinimalState({ apotheosis: { count: 0 } }); const { updatedState } = buildPostApotheosisState(state, "T"); expect(updatedState.apotheosis?.count).toBe(1); }); it("accumulates current-run gold and clicks into lifetime totals", () => { const state = makeMinimalState({ player: makePlayer({ totalGoldEarned: 4_000_000, lifetimeGoldEarned: 1_000_000, totalClicks: 500 }), }); const { updatedState } = buildPostApotheosisState(state, "T"); expect(updatedState.player.lifetimeGoldEarned).toBe(5_000_000); expect(updatedState.player.lifetimeClicks).toBe(500); }); it("accumulates defeated bosses, completed quests, and recruited adventurers into lifetime totals", () => { const boss = { id: "boss_1", status: "defeated" as const }; const quest = { id: "q_1", status: "completed" as const }; const adventurer = { id: "adv_1", count: 5 }; const state = makeMinimalState({ bosses: [ boss ] as GameState["bosses"], quests: [ quest ] as GameState["quests"], adventurers: [ adventurer ] as GameState["adventurers"], }); const { updatedState } = buildPostApotheosisState(state, "T"); expect(updatedState.player.lifetimeBossesDefeated).toBe(1); expect(updatedState.player.lifetimeQuestsCompleted).toBe(1); expect(updatedState.player.lifetimeAdventurersRecruited).toBe(5); }); it("preserves achievements and accumulates newly-unlocked ones into the lifetime total", () => { const achievement = { id: "ach_1", unlockedAt: Date.now() }; const state = makeMinimalState({ achievements: [ achievement ] as GameState["achievements"], }); const { updatedState } = buildPostApotheosisState(state, "T"); expect(updatedState.achievements).toEqual([ achievement ]); expect(updatedState.player.lifetimeAchievementsUnlocked).toBe(1); }); it("does not re-count achievements already folded into the lifetime total by a previous reset", () => { const achievement = { id: "ach_1", unlockedAt: Date.now() }; const state = makeMinimalState({ achievements: [ achievement ] as GameState["achievements"], player: makePlayer({ lifetimeAchievementsUnlocked: 1 }), }); const { updatedState } = buildPostApotheosisState(state, "T"); expect(updatedState.player.lifetimeAchievementsUnlocked).toBe(1); }); it("preserves equipment ownership history across apotheosis", () => { const equipment = { id: "rusty_sword", owned: true, everOwned: true }; const state = makeMinimalState({ equipment: [ equipment ] as GameState["equipment"], }); const { updatedState } = buildPostApotheosisState(state, "T"); const matchingItem = updatedState.equipment.find((item) => { return item.id === "rusty_sword"; }); expect(matchingItem?.everOwned).toBe(true); }); });