import React, { useEffect, useState } from "react"; import "@testing-library/jest-dom"; import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { clearAuthClientState, setAuthUserKey } from "./auth"; import JobTrackerThemeProvider from "./ThemeProvider"; import { THEME_BOOTSTRAP_SCRIPT } from "./themeBootstrap"; import { getThemeModePref, resolveEffectiveThemeMode, setThemeModePref, subscribeToThemePreferenceChanges, } from "./themePrefs"; function setSystemDark(matches: boolean) { Object.defineProperty(window, "matchMedia", { configurable: true, value: jest.fn().mockImplementation(() => ({ matches, media: "(prefers-color-scheme: dark)", onchange: null, addEventListener: jest.fn(), removeEventListener: jest.fn(), addListener: jest.fn(), removeListener: jest.fn(), dispatchEvent: jest.fn(), })), }); } describe("deterministic theme state", () => { beforeEach(() => { window.localStorage.clear(); document.documentElement.removeAttribute("data-color-scheme"); document.documentElement.style.colorScheme = ""; setSystemDark(false); }); it("uses user preference, then anonymous preference, then the documented System default", () => { expect(getThemeModePref()).toBe("system"); setThemeModePref("light"); expect(getThemeModePref()).toBe("light"); setAuthUserKey("user-a", false); expect(getThemeModePref()).toBe("light"); setThemeModePref("dark"); expect(getThemeModePref()).toBe("dark"); setAuthUserKey("user-b", false); expect(getThemeModePref()).toBe("light"); window.localStorage.removeItem("themeMode:anon"); expect(getThemeModePref()).toBe("system"); }); it("switches to the correct scoped preference on login and logout without a refresh", () => { window.localStorage.setItem("themeMode:anon", "light"); window.localStorage.setItem("themeMode:user-a", "dark"); const observed: string[] = []; const unsubscribe = subscribeToThemePreferenceChanges(() => observed.push(getThemeModePref())); setAuthUserKey("user-a", false); clearAuthClientState(false); expect(observed).toEqual(["dark", "light"]); unsubscribe(); }); it("synchronizes relevant cross-tab storage changes without writing them back", () => { setAuthUserKey("user-a", false); const sync = jest.fn(); const unsubscribe = subscribeToThemePreferenceChanges(sync); window.dispatchEvent(new StorageEvent("storage", { key: "themeMode:user-a", newValue: "dark" })); window.dispatchEvent(new StorageEvent("storage", { key: "unrelated", newValue: "value" })); window.dispatchEvent(new StorageEvent("storage", { key: "authUserKey", newValue: "user-b" })); expect(sync).toHaveBeenCalledTimes(2); unsubscribe(); }); it("uses system preference only for System mode", () => { expect(resolveEffectiveThemeMode("light", true)).toBe("light"); expect(resolveEffectiveThemeMode("dark", false)).toBe("dark"); expect(resolveEffectiveThemeMode("system", true)).toBe("dark"); expect(resolveEffectiveThemeMode("system", false)).toBe("light"); }); it("applies the saved preference before the client application renders", () => { window.localStorage.setItem("themeMode:anon", "light"); setSystemDark(true); new Function(THEME_BOOTSTRAP_SCRIPT)(); expect(document.documentElement).toHaveAttribute("data-color-scheme", "light"); expect(document.documentElement.style.colorScheme).toBe("light"); }); it("changes color scheme without remounting application state", async () => { let mounts = 0; function StatefulProbe() { const [count, setCount] = useState(0); useEffect(() => { mounts += 1; }, []); return ; } const view = render( , ); fireEvent.click(screen.getByRole("button", { name: "Count 0" })); view.rerender( , ); await waitFor(() => expect(document.documentElement).toHaveAttribute("data-color-scheme", "dark")); expect(screen.getByRole("button", { name: "Count 1" })).toBeInTheDocument(); expect(mounts).toBe(1); }); });