/* 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 { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { DailyChallengeState, GameState } from "@elysium/types"; // We reset modules so the module picks up fake timers when re-imported beforeEach(() => { vi.resetModules(); vi.useFakeTimers(); }); afterEach(() => { vi.useRealTimers(); }); const makeState = (dailyChallenges?: DailyChallengeState): GameState => ({ dailyChallenges } as unknown as GameState); const LA_MIDNIGHT_2024_01_15 = new Date("2024-01-15T08:00:00.000Z"); // LA midnight = UTC+8 const LA_MIDNIGHT_2024_01_16 = new Date("2024-01-16T08:00:00.000Z"); describe("generateDailyChallenges", () => { it("returns exactly 3 challenges", async () => { vi.setSystemTime(LA_MIDNIGHT_2024_01_15); const { generateDailyChallenges } = await import("../../src/services/dailyChallenges.js"); const result = generateDailyChallenges("2024-01-15"); expect(result).toHaveLength(3); }); it("all challenges start with progress 0 and completed false", async () => { vi.setSystemTime(LA_MIDNIGHT_2024_01_15); const { generateDailyChallenges } = await import("../../src/services/dailyChallenges.js"); const result = generateDailyChallenges("2024-01-15"); for (const challenge of result) { expect(challenge.progress).toBe(0); expect(challenge.completed).toBe(false); } }); it("is deterministic for the same date", async () => { vi.setSystemTime(LA_MIDNIGHT_2024_01_15); const { generateDailyChallenges } = await import("../../src/services/dailyChallenges.js"); const a = generateDailyChallenges("2024-01-15"); const b = generateDailyChallenges("2024-01-15"); expect(a.map((c) => c.id)).toEqual(b.map((c) => c.id)); }); it("generates different challenges for different dates", async () => { vi.setSystemTime(LA_MIDNIGHT_2024_01_15); const { generateDailyChallenges } = await import("../../src/services/dailyChallenges.js"); const day1 = generateDailyChallenges("2024-01-15"); const day2 = generateDailyChallenges("2024-01-16"); // They should differ in at least one challenge ID (types vary by seed) expect(day1.map((c) => c.type)).not.toEqual(day2.map((c) => c.type)); }); }); describe("getOrResetDailyChallenges", () => { it("returns existing challenges when date matches today", async () => { vi.setSystemTime(LA_MIDNIGHT_2024_01_15); const { getOrResetDailyChallenges } = await import("../../src/services/dailyChallenges.js"); const existing: DailyChallengeState = { date: "2024-01-15", challenges: [{ id: "old_challenge", type: "clicks", label: "l", target: 100, progress: 50, completed: false, rewardCrystals: 1 }], }; const state = makeState(existing); const result = getOrResetDailyChallenges(state); expect(result).toBe(existing); }); it("generates fresh challenges when date is yesterday", async () => { vi.setSystemTime(LA_MIDNIGHT_2024_01_16); const { getOrResetDailyChallenges } = await import("../../src/services/dailyChallenges.js"); const stale: DailyChallengeState = { date: "2024-01-15", challenges: [], }; const state = makeState(stale); const result = getOrResetDailyChallenges(state); expect(result.date).toBe("2024-01-16"); expect(result.challenges).toHaveLength(3); }); it("generates fresh challenges when dailyChallenges is undefined", async () => { vi.setSystemTime(LA_MIDNIGHT_2024_01_15); const { getOrResetDailyChallenges } = await import("../../src/services/dailyChallenges.js"); const state = makeState(undefined); const result = getOrResetDailyChallenges(state); expect(result.challenges).toHaveLength(3); expect(result.date).toBe("2024-01-15"); }); }); describe("updateChallengeProgress", () => { const makeChallenge = ( type: DailyChallengeState["challenges"][0]["type"], progress: number, completed: boolean, ) => ({ id: `${type}_test`, type, label: "Test", target: 100, progress, completed, rewardCrystals: 10, }); const makeState2 = (challenges: DailyChallengeState["challenges"]): DailyChallengeState => ({ date: "2024-01-15", challenges, }); it("increments progress for matching non-completed challenges", async () => { vi.setSystemTime(LA_MIDNIGHT_2024_01_15); const { updateChallengeProgress } = await import("../../src/services/dailyChallenges.js"); const state = makeState2([makeChallenge("clicks", 0, false)]); const { updatedChallenges } = updateChallengeProgress(state, "clicks", 10); expect(updatedChallenges.challenges[0]!.progress).toBe(10); }); it("does not modify already-completed challenges", async () => { vi.setSystemTime(LA_MIDNIGHT_2024_01_15); const { updateChallengeProgress } = await import("../../src/services/dailyChallenges.js"); const state = makeState2([makeChallenge("clicks", 100, true)]); const { updatedChallenges } = updateChallengeProgress(state, "clicks", 50); expect(updatedChallenges.challenges[0]!.progress).toBe(100); }); it("does not modify challenges of a different type", async () => { vi.setSystemTime(LA_MIDNIGHT_2024_01_15); const { updateChallengeProgress } = await import("../../src/services/dailyChallenges.js"); const state = makeState2([makeChallenge("bossesDefeated", 0, false)]); const { updatedChallenges } = updateChallengeProgress(state, "clicks", 10); expect(updatedChallenges.challenges[0]!.progress).toBe(0); }); it("awards crystals when challenge completes", async () => { vi.setSystemTime(LA_MIDNIGHT_2024_01_15); const { updateChallengeProgress } = await import("../../src/services/dailyChallenges.js"); const state = makeState2([makeChallenge("clicks", 90, false)]); const { crystalsAwarded } = updateChallengeProgress(state, "clicks", 20); expect(crystalsAwarded).toBe(10); }); it("caps progress at target value", async () => { vi.setSystemTime(LA_MIDNIGHT_2024_01_15); const { updateChallengeProgress } = await import("../../src/services/dailyChallenges.js"); const state = makeState2([makeChallenge("clicks", 95, false)]); const { updatedChallenges } = updateChallengeProgress(state, "clicks", 100); expect(updatedChallenges.challenges[0]!.progress).toBe(100); }); it("returns zero crystals when no challenge completes", async () => { vi.setSystemTime(LA_MIDNIGHT_2024_01_15); const { updateChallengeProgress } = await import("../../src/services/dailyChallenges.js"); const state = makeState2([makeChallenge("clicks", 0, false)]); const { crystalsAwarded } = updateChallengeProgress(state, "clicks", 10); expect(crystalsAwarded).toBe(0); }); });