import "@testing-library/jest-dom/vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; // The store is a module singleton constructed on first import: it reads // localStorage and `matchMedia` in its constructor. Each test therefore // stubs `matchMedia` and resets the module registry, then imports a // freshly-constructed store via `freshStore`. const STORAGE_KEY = "galaxy-theme"; function stubMatchMedia(prefersLight: boolean): void { Object.defineProperty(window, "matchMedia", { writable: true, configurable: true, value: (query: string) => ({ matches: query.includes("light") ? prefersLight : false, media: query, onchange: null, addListener: () => {}, removeListener: () => {}, addEventListener: () => {}, removeEventListener: () => {}, dispatchEvent: () => false, }), }); } async function freshStore( prefersLight = false, ): Promise { stubMatchMedia(prefersLight); vi.resetModules(); return import("../src/lib/theme/theme.svelte"); } describe("theme store", () => { beforeEach(() => { localStorage.clear(); delete document.documentElement.dataset.theme; }); afterEach(() => { vi.restoreAllMocks(); }); it("defaults to system and applies the resolved theme", async () => { const { theme } = await freshStore(); expect(theme.choice).toBe("system"); // freshStore() leaves the OS at dark (prefersLight = false). expect(theme.resolved).toBe("dark"); expect(document.documentElement.dataset.theme).toBe("dark"); }); it("persists an explicit choice and writes data-theme", async () => { const { theme, THEME_STORAGE_KEY } = await freshStore(); expect(THEME_STORAGE_KEY).toBe(STORAGE_KEY); theme.setChoice("light"); expect(theme.choice).toBe("light"); expect(theme.resolved).toBe("light"); expect(document.documentElement.dataset.theme).toBe("light"); expect(localStorage.getItem(STORAGE_KEY)).toBe("light"); theme.setChoice("dark"); expect(theme.resolved).toBe("dark"); expect(document.documentElement.dataset.theme).toBe("dark"); expect(localStorage.getItem(STORAGE_KEY)).toBe("dark"); }); it("reads the stored choice on construction", async () => { localStorage.setItem(STORAGE_KEY, "light"); const { theme } = await freshStore(); expect(theme.choice).toBe("light"); expect(theme.resolved).toBe("light"); }); it("resolves system to the OS preference", async () => { const { theme } = await freshStore(true); theme.setChoice("system"); expect(theme.choice).toBe("system"); expect(theme.resolved).toBe("light"); expect(document.documentElement.dataset.theme).toBe("light"); }); it("falls back to system for an unrecognised stored value", async () => { localStorage.setItem(STORAGE_KEY, "neon"); const { theme } = await freshStore(); expect(theme.choice).toBe("system"); }); it("applies an ephemeral override without touching the persisted choice", async () => { localStorage.setItem(STORAGE_KEY, "dark"); const { theme } = await freshStore(); expect(theme.resolved).toBe("dark"); expect(theme.override).toBeNull(); theme.setOverride("light"); expect(theme.override).toBe("light"); expect(theme.resolved).toBe("light"); expect(document.documentElement.dataset.theme).toBe("light"); // Persisted choice is untouched. expect(theme.choice).toBe("dark"); expect(localStorage.getItem(STORAGE_KEY)).toBe("dark"); }); it("clearOverride re-projects the persisted choice", async () => { localStorage.setItem(STORAGE_KEY, "light"); const { theme } = await freshStore(); theme.setOverride("dark"); expect(theme.resolved).toBe("dark"); theme.clearOverride(); expect(theme.override).toBeNull(); expect(theme.resolved).toBe("light"); expect(document.documentElement.dataset.theme).toBe("light"); }); it("override shadows setChoice until cleared", async () => { const { theme } = await freshStore(); theme.setOverride("light"); theme.setChoice("dark"); // Override wins while it is non-null, but the choice is still // persisted for the next lobby visit. expect(theme.resolved).toBe("light"); expect(theme.choice).toBe("dark"); expect(localStorage.getItem(STORAGE_KEY)).toBe("dark"); theme.clearOverride(); expect(theme.resolved).toBe("dark"); }); });