import { expect, test } from "bun:test"; import { Browser } from "happy-dom"; import { renderThemeRuntime, resolveThemeConfig } from "../src/theme.ts"; test("shared preferences replace stale host cookies and update another open app", async () => { const browser = new Browser({ settings: { disableCSSFileLoading: true } }); try { const first = browser.newPage(); first.url = "https://account.example.com/"; const second = browser.newPage(); second.url = "https://link.example.com/"; const a = first.mainFrame.window; const b = second.mainFrame.window; // Happy DOM coalesces equal-name host/domain cookies. Model their distinct // browser storage keys so the legacy-shadow migration is exercised. const jar = new Map< string, { name: string; value: string; domain: string; hostOnly: boolean } >(); function cookies(win: typeof a) { Object.defineProperty(win.document, "cookie", { get() { return [...jar.values()] .filter((c) => c.hostOnly ? c.domain === win.location.hostname : win.location.hostname === c.domain || win.location.hostname.endsWith("." + c.domain), ) .map((c) => `${c.name}=${c.value}`) .join("; "); }, set(raw: string) { const [pair, ...options] = raw.split(";"); const split = pair!.indexOf("="); const name = pair!.slice(0, split), value = pair!.slice(split + 1); const domainOption = options.find((v) => v.trim().toLowerCase().startsWith("domain=")); const domain = domainOption ? domainOption.trim().slice(7).replace(/^\./, "") : win.location.hostname; const key = `${name}|${domain}`; if (options.some((v) => v.trim().toLowerCase() === "max-age=0")) jar.delete(key); else jar.set(key, { name, value, domain, hostOnly: !domainOption }); }, }); } cookies(a); cookies(b); a.document.cookie = "wrn-theme=light;path=/"; b.document.cookie = "wrn-theme=light;path=/"; a.document.cookie = "wrn-theme=dark;path=/;domain=.example.com;secure"; a.document.cookie = "wrn-accent=rose;path=/;domain=.example.com;secure"; const js = renderThemeRuntime( resolveThemeConfig( { palette: "emerald" }, { defaults: { domain: ".example.com", secure: true } }, ), ); for (const win of [a, b]) { win.document.documentElement.setAttribute("data-theme", "light"); win.document.documentElement.setAttribute("data-accent", "emerald"); win.eval(js); expect(win.document.documentElement.getAttribute("data-theme")).toBe("dark"); expect(win.document.cookie.match(/wrn-theme=/g)?.length).toBe(1); } a.eval( 'wrnTheme.set("light");wrnAccent.set("violet");wrnCookies.set("wrn-lang","hi","language")', ); expect(b.document.cookie).toContain("wrn-theme-mode=manual"); b.dispatchEvent(new b.Event("focus")); expect(b.document.documentElement.getAttribute("data-theme")).toBe("light"); expect(b.document.documentElement.getAttribute("data-accent")).toBe("violet"); expect(b.document.cookie).toContain("wrn-lang=hi"); a.eval("wrnTheme.followSystem()"); expect(b.document.cookie).toContain("wrn-theme-mode=system"); const outsider = browser.newPage(); outsider.url = "https://unrelated.example.net/"; cookies(outsider.mainFrame.window); expect(outsider.mainFrame.window.document.cookie).toBe(""); } finally { await browser.close(); } });