Files
elysium/apps/api/test/services/prestige.spec.ts
T
hikari b67eae9d46 test: add 100% coverage for apps/api and packages/types
Adds full Vitest test suites with @vitest/coverage-v8, targeting 100%
statement/branch/function/line coverage. Uses v8 ignore comments for
genuinely unreachable defensive branches.
2026-03-07 19:59:48 -08:00

246 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* eslint-disable max-lines-per-function -- Test suites naturally have many cases */
/* eslint-disable max-lines -- 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 {
buildPostPrestigeState,
calculateMilestoneBonus,
calculatePrestigeThreshold,
calculateProductionMultiplier,
calculateRunestones,
computeRunestoneMultipliers,
isEligibleForPrestige,
} from "../../src/services/prestige.js";
import type { GameState } from "@elysium/types";
const makePlayer = (totalGoldEarned: number) => ({
discordId: "test_id",
username: "testuser",
discriminator: "0",
avatar: null,
totalGoldEarned,
totalClicks: 0,
characterName: "Tester",
});
const makeMinimalState = (overrides: Partial<GameState> = {}): GameState =>
({
player: makePlayer(0),
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("calculatePrestigeThreshold", () => {
it("returns base threshold at count 0", () => {
expect(calculatePrestigeThreshold(0)).toBe(1_000_000);
});
it("returns 5× at count 1", () => {
expect(calculatePrestigeThreshold(1)).toBe(5_000_000);
});
it("returns 25× at count 2", () => {
expect(calculatePrestigeThreshold(2)).toBe(25_000_000);
});
it("applies threshold multiplier correctly", () => {
expect(calculatePrestigeThreshold(0, 2)).toBe(2_000_000);
});
});
describe("isEligibleForPrestige", () => {
it("returns true when totalGoldEarned meets threshold", () => {
const state = makeMinimalState({ player: makePlayer(1_000_000) });
expect(isEligibleForPrestige(state)).toBe(true);
});
it("returns false when totalGoldEarned is below threshold", () => {
const state = makeMinimalState({ player: makePlayer(999_999) });
expect(isEligibleForPrestige(state)).toBe(false);
});
it("uses echoPrestigeThresholdMultiplier from transcendence when present", () => {
const state = makeMinimalState({
player: makePlayer(2_000_000),
transcendence: {
count: 1, echoes: 0, purchasedUpgradeIds: [],
echoIncomeMultiplier: 1, echoCombatMultiplier: 1,
echoPrestigeThresholdMultiplier: 2,
echoPrestigeRunestoneMultiplier: 1, echoMetaMultiplier: 1,
},
});
// threshold = 1_000_000 × 2 = 2_000_000 — exactly meets
expect(isEligibleForPrestige(state)).toBe(true);
});
});
describe("calculateRunestones", () => {
it("calculates basic runestones formula", () => {
// floor(sqrt(4_000_000 / 1_000_000)) × 10 = floor(2) × 10 = 20
const result = calculateRunestones(4_000_000, 0, []);
expect(result).toBe(20);
});
it("applies echo runestone multiplier", () => {
// floor(sqrt(4) × 10) = 20; × 2 = 40
const result = calculateRunestones(4_000_000, 0, [], 2);
expect(result).toBe(40);
});
it("applies purchased runestone upgrade multiplier", () => {
// With "runestones_1" purchased (multiplier 1.25): floor(20 × 1.25) = 25
const result = calculateRunestones(4_000_000, 0, ["runestone_gain_1"]);
expect(result).toBeGreaterThan(20);
});
});
describe("calculateProductionMultiplier", () => {
it("returns 1 at count 0", () => {
expect(calculateProductionMultiplier(0)).toBe(1);
});
it("returns 1.15 at count 1", () => {
expect(calculateProductionMultiplier(1)).toBeCloseTo(1.15);
});
it("scales exponentially", () => {
expect(calculateProductionMultiplier(10)).toBeCloseTo(Math.pow(1.15, 10));
});
});
describe("calculateMilestoneBonus", () => {
it("returns 0 for non-milestone prestiges", () => {
expect(calculateMilestoneBonus(1)).toBe(0);
expect(calculateMilestoneBonus(3)).toBe(0);
expect(calculateMilestoneBonus(4)).toBe(0);
});
it("returns 25 at prestige 5", () => {
expect(calculateMilestoneBonus(5)).toBe(25);
});
it("returns 50 at prestige 10", () => {
expect(calculateMilestoneBonus(10)).toBe(50);
});
it("returns 75 at prestige 15", () => {
expect(calculateMilestoneBonus(15)).toBe(75);
});
});
describe("computeRunestoneMultipliers", () => {
it("returns all 1s with empty ids", () => {
const result = computeRunestoneMultipliers([]);
expect(result.runestonesIncomeMultiplier).toBe(1);
expect(result.runestonesClickMultiplier).toBe(1);
expect(result.runestonesEssenceMultiplier).toBe(1);
expect(result.runestonesCrystalMultiplier).toBe(1);
});
it("applies income upgrade when purchased", () => {
const result = computeRunestoneMultipliers(["income_1"]);
expect(result.runestonesIncomeMultiplier).toBeGreaterThan(1);
expect(result.runestonesClickMultiplier).toBe(1);
});
it("applies click upgrade when purchased", () => {
const result = computeRunestoneMultipliers(["click_power_1"]);
expect(result.runestonesClickMultiplier).toBeGreaterThan(1);
expect(result.runestonesIncomeMultiplier).toBe(1);
});
it("applies essence upgrade when purchased", () => {
const result = computeRunestoneMultipliers(["essence_1"]);
expect(result.runestonesEssenceMultiplier).toBeGreaterThan(1);
});
it("applies crystals upgrade when purchased", () => {
const result = computeRunestoneMultipliers(["crystal_1"]);
expect(result.runestonesCrystalMultiplier).toBeGreaterThan(1);
});
});
describe("buildPostPrestigeState", () => {
it("increments prestige count", () => {
const state = makeMinimalState({ player: makePlayer(4_000_000) });
const { newPrestigeData } = buildPostPrestigeState(state, "Tester");
expect(newPrestigeData.count).toBe(1);
});
it("sums runestones earned", () => {
const state = makeMinimalState({ player: makePlayer(4_000_000) });
const { newPrestigeData, runestonesEarned } = buildPostPrestigeState(state, "Tester");
expect(runestonesEarned).toBeGreaterThan(0);
expect(newPrestigeData.runestones).toBe(runestonesEarned);
});
it("adds milestone runestones at prestige 5", () => {
const state = makeMinimalState({
player: makePlayer(100_000_000),
prestige: { count: 4, runestones: 0, productionMultiplier: 1, purchasedUpgradeIds: [] },
});
const { milestoneRunestones } = buildPostPrestigeState(state, "Tester");
expect(milestoneRunestones).toBe(25);
});
it("persists codex from current state", () => {
const codex = { entries: [{ id: "e1", unlockedAt: 1000, sourceType: "exploration" as const }] };
const state = makeMinimalState({ codex });
const { newState } = buildPostPrestigeState(state, "Tester");
expect(newState.codex).toEqual(codex);
});
it("persists story from current state", () => {
const story = { unlockedChapterIds: ["ch1"], completedChapters: [] };
const state = makeMinimalState({ story });
const { newState } = buildPostPrestigeState(state, "Tester");
expect(newState.story).toEqual(story);
});
it("persists transcendence from current state", () => {
const transcendence = {
count: 1, echoes: 10, purchasedUpgradeIds: [],
echoIncomeMultiplier: 1, echoCombatMultiplier: 1,
echoPrestigeThresholdMultiplier: 1,
echoPrestigeRunestoneMultiplier: 1, echoMetaMultiplier: 1,
};
const state = makeMinimalState({ transcendence });
const { newState } = buildPostPrestigeState(state, "Tester");
expect(newState.transcendence).toEqual(transcendence);
});
it("preserves autoPrestigeEnabled when set", () => {
const state = makeMinimalState({
prestige: { count: 0, runestones: 0, productionMultiplier: 1, purchasedUpgradeIds: [], autoPrestigeEnabled: true },
});
const { newPrestigeData } = buildPostPrestigeState(state, "Tester");
expect(newPrestigeData.autoPrestigeEnabled).toBe(true);
});
it("omits autoPrestigeEnabled when not set", () => {
const state = makeMinimalState();
const { newPrestigeData } = buildPostPrestigeState(state, "Tester");
expect(newPrestigeData.autoPrestigeEnabled).toBeUndefined();
});
it("preserves apotheosis data across prestige", () => {
const apotheosis = { count: 2 };
const state = makeMinimalState({ apotheosis });
const { newState } = buildPostPrestigeState(state, "Tester");
expect(newState.apotheosis).toEqual(apotheosis);
});
});