import { afterEach, describe, expect, it, vi } from "vitest"; import { createPreferencesStore, DEFAULT_PREFERENCES, PREFERENCES_KEY, reducedMotionOn, type PreferenceStorage, } from "../src/state/preferences"; /** An in-memory storage, like localStorage. */ function memoryStorage(): PreferenceStorage & { data: Map } { const data = new Map(); return { data, getItem: (key) => data.get(key) ?? null, setItem: (key, value) => void data.set(key, value), }; } /** jsdom has no matchMedia: stand one in that answers the reduced-motion query. */ const mockOsReducedMotion = (reduce: boolean) => vi.stubGlobal("matchMedia", (query: string) => ({ matches: reduce, media: query })); describe("device preferences (Step 10K.1)", () => { afterEach(() => vi.unstubAllGlobals()); it("starts with the defaults: locale clock, real-time countdowns, the device's reduced motion", () => { const store = createPreferencesStore(memoryStorage()); expect(store.getState()).toMatchObject(DEFAULT_PREFERENCES); mockOsReducedMotion(true); expect(reducedMotionOn(store.getState())).toBe(true); mockOsReducedMotion(false); expect(reducedMotionOn(store.getState())).toBe(false); // An override wins over the device. store.getState().set({ reducedMotion: true }); expect(reducedMotionOn(store.getState())).toBe(true); }); it("keeps saved values across a reload", () => { const storage = memoryStorage(); const first = createPreferencesStore(storage); first.getState().set({ clock: "24h", countdown: "both" }); first.getState().dismissTip("tutorial-intro"); first.getState().set({ mapCamera: { zoom: 3, panX: 10, panY: -4 } }); const reloaded = createPreferencesStore(storage); expect(reloaded.getState()).toMatchObject({ clock: "24h", countdown: "both", dismissedTips: ["tutorial-intro"], mapCamera: { zoom: 3, panX: 10, panY: -4 }, }); }); it("ignores broken saved data", () => { const storage = memoryStorage(); storage.setItem(PREFERENCES_KEY, "{not json"); expect(createPreferencesStore(storage).getState()).toMatchObject(DEFAULT_PREFERENCES); storage.setItem( PREFERENCES_KEY, JSON.stringify({ clock: "sundial", mapCamera: { zoom: "x" } }), ); expect(createPreferencesStore(storage).getState()).toMatchObject(DEFAULT_PREFERENCES); }); it("works in memory when storage throws, without errors", () => { const broken: PreferenceStorage = { getItem: () => { throw new Error("SecurityError"); }, setItem: () => { throw new Error("QuotaExceededError"); }, }; const store = createPreferencesStore(broken); expect(store.getState()).toMatchObject(DEFAULT_PREFERENCES); expect(() => store.getState().set({ clock: "12h" })).not.toThrow(); expect(store.getState().clock).toBe("12h"); expect(() => createPreferencesStore(null).getState().dismissTip("x")).not.toThrow(); }); });