import { describe, it, expect, vi } from "vitest"; import { get } from "svelte/store"; import { setMockInvokeResult, emitMockEvent } from "../../../vitest.setup"; import { achievementsStore, unlockedAchievements, lockedAchievements, achievementsByRarity, achievementProgress, initAchievementsListener, } from "./achievements"; import type { AchievementUnlockedEvent } from "$lib/types/achievements"; import { playAchievementSound } from "$lib/sounds/achievement"; vi.mock("$lib/sounds/achievement", () => ({ playAchievementSound: vi.fn(), })); // Helper to build a minimal unlock event function makeEvent(id: AchievementUnlockedEvent["achievement"]["id"]): AchievementUnlockedEvent { return { achievement: { id, name: "Test", description: "Test achievement", icon: "🏆", unlocked_at: null, }, }; } describe("achievementsStore initial state", () => { it("all achievements start as locked", () => { const state = get(achievementsStore); expect(state.achievements["FirstSteps"].unlocked).toBe(false); expect(state.achievements["GrowingStrong"].unlocked).toBe(false); }); it("totalUnlocked starts at 0", () => { expect(get(achievementsStore).totalUnlocked).toBe(0); }); it("lastUnlocked starts as null", () => { expect(get(achievementsStore).lastUnlocked).toBeNull(); }); }); describe("derived stores initial state", () => { it("unlockedAchievements is initially empty", () => { expect(get(unlockedAchievements)).toEqual([]); }); it("lockedAchievements contains all achievements initially", () => { const locked = get(lockedAchievements); const total = Object.keys(get(achievementsStore).achievements).length; expect(locked.length).toBe(total); }); it("achievementsByRarity groups achievements into rarity buckets", () => { const byRarity = get(achievementsByRarity); expect(byRarity.common).toBeInstanceOf(Array); expect(byRarity.rare).toBeInstanceOf(Array); expect(byRarity.epic).toBeInstanceOf(Array); expect(byRarity.legendary).toBeInstanceOf(Array); expect(byRarity.common.length).toBeGreaterThan(0); }); it("achievementProgress shows zero unlocked initially", () => { const progress = get(achievementProgress); expect(progress.unlocked).toBe(0); expect(progress.total).toBeGreaterThan(0); expect(progress.percentage).toBe(0); }); }); describe("achievementsStore.unlockAchievement", () => { it("marks the achievement as unlocked and updates totalUnlocked", () => { achievementsStore.unlockAchievement(makeEvent("GrowingStrong")); const state = get(achievementsStore); expect(state.achievements["GrowingStrong"].unlocked).toBe(true); expect(state.totalUnlocked).toBe(1); expect(state.lastUnlocked?.id).toBe("GrowingStrong"); }); it("sets unlockedAt from the event's unlocked_at timestamp", () => { achievementsStore.unlockAchievement({ achievement: { id: "BlossomingCoder", name: "Blossoming Coder", description: "100k tokens", icon: "🌸", unlocked_at: "2026-01-15T12:00:00.000Z", }, }); const state = get(achievementsStore); expect(state.achievements["BlossomingCoder"].unlockedAt).toBeInstanceOf(Date); }); it("does nothing when the achievement is already unlocked", () => { achievementsStore.unlockAchievement(makeEvent("TokenMaster")); const firstTotal = get(achievementsStore).totalUnlocked; achievementsStore.unlockAchievement(makeEvent("TokenMaster")); expect(get(achievementsStore).totalUnlocked).toBe(firstTotal); }); it("calls playAchievementSound when playSound is true (default)", () => { achievementsStore.unlockAchievement(makeEvent("TokenBillionaire")); expect(playAchievementSound).toHaveBeenCalled(); }); it("does not call playAchievementSound when playSound is false", () => { achievementsStore.unlockAchievement(makeEvent("TokenTreasure"), false); expect(playAchievementSound).not.toHaveBeenCalled(); }); it("logs an error when playAchievementSound throws", () => { vi.mocked(playAchievementSound).mockImplementationOnce(() => { throw new Error("Sound failed"); }); const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); achievementsStore.unlockAchievement(makeEvent("HelloWorld")); expect(consoleSpy).toHaveBeenCalledWith("Failed to play achievement sound:", expect.any(Error)); consoleSpy.mockRestore(); }); }); describe("derived stores after unlocks", () => { it("unlockedAchievements includes previously unlocked achievements", () => { const unlocked = get(unlockedAchievements); expect(unlocked.some((a) => a.id === "GrowingStrong")).toBe(true); }); it("lockedAchievements excludes previously unlocked achievements", () => { const locked = get(lockedAchievements); expect(locked.some((a) => a.id === "GrowingStrong")).toBe(false); }); it("achievementProgress reflects the current unlocked count", () => { const progress = get(achievementProgress); expect(progress.unlocked).toBeGreaterThan(0); expect(progress.percentage).toBeGreaterThan(0); }); }); describe("achievementsStore.updateProgress", () => { it("updates the progress value for an achievement", () => { achievementsStore.updateProgress("FirstMessage", 50); expect(get(achievementsStore).achievements["FirstMessage"].progress).toBe(50); }); }); describe("achievementsStore.reset", () => { it("resets totalUnlocked to 0 and lastUnlocked to null", () => { achievementsStore.reset(); const state = get(achievementsStore); expect(state.totalUnlocked).toBe(0); expect(state.lastUnlocked).toBeNull(); }); }); describe("initAchievementsListener", () => { it("unlocks an achievement when the achievement:unlocked event fires", async () => { await initAchievementsListener(); emitMockEvent("achievement:unlocked", makeEvent("FirstSteps")); expect(get(achievementsStore).achievements["FirstSteps"].unlocked).toBe(true); }); it("loads saved achievements from the backend without playing sounds", async () => { setMockInvokeResult("load_saved_achievements", [makeEvent("ConversationStarter")]); await initAchievementsListener(); expect(get(achievementsStore).achievements["ConversationStarter"].unlocked).toBe(true); expect(playAchievementSound).not.toHaveBeenCalled(); }); it("logs an error when loading saved achievements fails", async () => { setMockInvokeResult("load_saved_achievements", new Error("Storage unavailable")); const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); await initAchievementsListener(); expect(consoleSpy).toHaveBeenCalledWith( "Failed to load saved achievements:", expect.any(Error) ); consoleSpy.mockRestore(); }); });