import { describe, expect, test } from "bun:test" import { runInNewContext } from "node:vm" import { announcementCopy, announcements } from "~/component/site/announcement" import { preloadSettings, themeScript } from "~/component/site/preload-settings" import { bannerDismissScript } from "~/component/site/site-banner-script" import { migrateSettings, siteSettingsDefaults, siteSettingsSchema } from "~/component/site/settings" import { z } from "zod" // The pre-paint scripts run inline while the page is parsed, as strings built from functions in // settings.ts and nav-config.ts. A function that reaches for anything outside its arguments throws // there, the scripts swallow it, and every saved setting then waits for the page's code (Nov 2026: // one missing reference did exactly that). These tests run the exact strings Site renders, in a // context with nothing but the few browser objects they use. type Element = ReturnType function element(tag: string, matchAll = false) { const attributes = new Map() const style = new Map() const children: unknown[] = [] return { tagName: tag.toUpperCase(), nodeType: 1, isConnected: true, textContent: "", id: "", content: "", media: "", dataset: {} as Record, attributes, children, style: { values: style, setProperty: (name: string, value: string) => void style.set(name, value), removeProperty: (name: string) => void style.delete(name), }, setAttribute: (name: string, value: string) => void attributes.set(name, value), getAttribute: (name: string) => attributes.get(name) ?? null, hasAttribute: (name: string) => attributes.has(name), removeAttribute: (name: string) => void attributes.delete(name), // A node that matches every selector runs every rule (the root's rules run on the root itself). matches: () => matchAll, closest: () => null, querySelector: () => null, querySelectorAll: () => [], appendChild: (child: unknown) => (children.push(child), child), } } function run(script: string, saved: unknown, theme?: string, stored: Record = {}) { const errors: unknown[] = [] // The rule loop and the whole script swallow errors on purpose; the test reports them instead. const swallowed = ["catch{return}", "catch{}})()"] swallowed.forEach((text) => expect(script.includes(text) || !script.includes("const saved=(")).toBe(true)) const code = script .replace("catch{return}", "catch(error){__errors.push(error);return}") .replace(/catch\{\}\}\)\(\)$/, "catch(error){__errors.push(error)}})()") const root = element("div") const head = element("head") const observers: ((records: { addedNodes: unknown[] }[], observer: unknown) => void)[] = [] const storage = new Map(Object.entries({ "opencode-site-settings-v3": JSON.stringify(saved) })) if (theme) storage.set("opencode-theme", theme) Object.entries(stored).forEach(([key, value]) => storage.set(key, value)) // The light and dark theme-color tags the theme script fills in. const metas = ["(prefers-color-scheme: light)", "(prefers-color-scheme: dark)"].map((media) => Object.assign(element("meta"), { media }), ) runInNewContext(code, { __errors: errors, localStorage: { getItem: (key: string) => storage.get(key) ?? null }, location: { search: "" }, URLSearchParams, screen: { width: 1440, height: 900 }, document: { currentScript: { parentElement: root }, head, createElement: (tag: string) => element(tag), getElementById: () => null, querySelector: () => null, querySelectorAll: (selector: string) => (selector === 'meta[name="theme-color"]' ? metas : []), }, MutationObserver: class { constructor(callback: (records: { addedNodes: unknown[] }[], observer: unknown) => void) { observers.push(callback) } observe() {} disconnect() {} }, }) // A node added after the script, matching every rule's selector. const added = element("section", true) observers.forEach((callback) => callback([{ addedNodes: [added] }], { disconnect() {} })) return { errors, root, added, head, metas, styles: (head.children as Element[]).map((child) => child.textContent).join(""), } } /** A saved value different from the default for every enum setting, from the schema itself. */ function changedSettings() { const shape = siteSettingsSchema().shape as Record const defaults = siteSettingsDefaults as Record return Object.fromEntries( Object.entries(shape).flatMap(([key, field]) => { const inner = field instanceof z.ZodCatch ? (field.removeCatch() as z.ZodType) : field if (!(inner instanceof z.ZodEnum)) return [] const other = (inner.options as unknown[]).find((option) => option !== defaults[key]) return other === undefined ? [] : [[key, other]] }), ) } describe("pre-paint settings script", () => { test("runs every rule without an error, whatever is saved", () => { const changed = changedSettings() expect(Object.keys(changed).length).toBeGreaterThan(50) for (const saved of [{}, siteSettingsDefaults, changed]) { const result = run(preloadSettings, saved) expect(result.errors.map(String)).toEqual([]) } }) test("applies representative saved settings to the root before first paint", () => { // Saved after every migration (their revision markers, whatever they are now), so the values // below are a visitor's own picks. const current = migrateSettings({}) as Record const result = run(preloadSettings, { ...current, homeAnnouncement: "on", announcementStyle: "banner", docsVariants: { ...(current.docsVariants as object), config: "2" }, navConfig: siteSettingsDefaults.navConfig.map((entry, index) => ({ ...entry, visible: index !== 0 })), arcadeMode: "image", }) expect(result.errors.map(String)).toEqual([]) expect(result.root.hasAttribute("data-banner")).toBe(true) expect(result.root.getAttribute("data-docs-variants")).toContain("config-2") // The nav list's rules: the first item hidden. expect(result.styles).toContain(`li[data-nav-id="${siteSettingsDefaults.navConfig[0].id}"]{order:`) expect(result.styles).toContain("display:none") }) test("puts a saved mono font ahead of the stack and preloads a web font", () => { const web = run(preloadSettings, { monoFont: "IBM Plex Mono", monoFontRevision: 2 }) expect(web.errors.map(String)).toEqual([]) expect(web.root.style.values.get("--mono-choice")).toBe(`"IBM Plex Mono",`) expect(web.head.children.some((child) => (child as Element).tagName === "LINK")).toBe(true) const local = run(preloadSettings, { monoFont: 'Geist Mono"', monoFontRevision: 2 }) expect(local.root.style.values.get("--mono-choice")).toBe(`"Geist Mono",`) expect(local.head.children.some((child) => (child as Element).tagName === "LINK")).toBe(false) expect(run(preloadSettings, { monoFont: "OpenTUI Mono" }).root.style.values.has("--mono-choice")).toBe(false) // A mono font saved before OpenTUI Mono became the default resets to it once. expect(run(preloadSettings, { monoFont: "Geist Mono" }).root.style.values.has("--mono-choice")).toBe(false) }) test("migrates old settings without an error", () => { const result = run(preloadSettings, { arcadeMode: "hybrid", cardVideoCut: "straight", navOrder: ["docs"] }) expect(result.errors.map(String)).toEqual([]) }) test("theme script sets the saved theme", () => { const result = run(themeScript(true), { lightBackground: "#fafafa" }, "dark") expect(result.errors.map(String)).toEqual([]) expect(result.root.dataset.theme).toBe("dark") }) test("theme script follows the saved light background on the development server", () => { const result = run(themeScript(true), { lightBackground: "#fafafa" }, "light") expect(result.metas.map((meta) => meta.content)).toEqual(["#fafafa", "#fafafa"]) }) }) // Production has no Settings or Design mode: Site renders only the theme and banner scripts, and // neither reads a designer's saved settings or copy. The tests run as production // (import.meta.env.DEV is unset outside Vite). describe("pre-paint scripts in production", () => { test("the theme script keeps the visitor's theme but ignores a saved light background", () => { const result = run(themeScript(false), { lightBackground: "#fafafa" }, "light") expect(result.errors.map(String)).toEqual([]) expect(result.root.dataset.theme).toBe("light") expect(result.metas.map((meta) => meta.content)).toEqual([ siteSettingsDefaults.lightBackground, siteSettingsDefaults.lightBackground, ]) }) test("the banner stays dismissed, whatever Design mode copy is saved", () => { // A designer's saved choice of another message, which production ignores. const copy = JSON.stringify({ choice: { "home.announcement": 2 }, custom: {} }) const banner = (dismissed: string) => { const root = element("div") root.setAttribute("data-banner", "") const storage: Record = { "opencode-announcement-dismissed": dismissed, "opencode-site-design-copy-v2": copy, } runInNewContext(bannerDismissScript, { localStorage: { getItem: (key: string) => storage[key] ?? null }, document: { currentScript: { parentElement: root } }, }) return root.hasAttribute("data-banner") } expect(banner(announcementCopy(announcements[0]))).toBe(false) expect(banner("An older message")).toBe(true) }) test("the scripts never read saved settings or Design mode copy", () => { for (const script of [themeScript(false), bannerDismissScript]) { expect(script).not.toContain("opencode-site-settings-v3") expect(script).not.toContain("opencode-site-design-copy-v2") } }) })