| 1 | import { readFileSync } from "node:fs"; |
| 2 | import { describe, expect, it } from "vitest"; |
| 3 | import { siteCss } from "./site-css"; |
| 4 | |
| 5 | const CSS = siteCss(); |
| 6 | |
| 7 | function selectorBlock(selector: string): string { |
| 8 | const match = CSS.match(new RegExp(`(?:^|\n)${selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\{([^}]*)\\}`, "s")); |
| 9 | if (!match) throw new Error(`Missing CSS selector: ${selector}`); |
| 10 | return match[1]; |
| 11 | } |
| 12 | |
| 13 | |
| 14 | |
| 15 | |
| 16 | const PINNED_DARK = ':root[data-theme="dark"]'; |
| 17 | |
| 18 | function allVars(selector: string): Record<string, string> { |
| 19 | return Object.fromEntries( |
| 20 | [...selectorBlock(selector).matchAll(/--([\w-]+):\s*([^;]+);/g)].map((m) => [m[1], m[2].trim()]), |
| 21 | ); |
| 22 | } |
| 23 | |
| 24 | describe("site-wide theme contract", () => { |
| 25 | it("is paper by default; dark is a reader's pin, never the OS", () => { |
| 26 | // No region-scoped dark sheet: the docs portal follows the root theme. |
| 27 | expect(CSS).not.toMatch(/html\[data-theme="dark"\] \.docs-(portal|theme)/); |
| 28 | expect(CSS).toMatch(/(?:^|\n):root \{[^}]*color-scheme:\s*light;/); |
| 29 | expect(CSS).not.toMatch(/@media \(prefers-color-scheme: dark\)/); |
| 30 | expect(Object.keys(allVars(PINNED_DARK))).toContain("bg"); |
| 31 | expect(selectorBlock(PINNED_DARK)).toMatch(/color-scheme:\s*dark/); |
| 32 | }); |
| 33 | |
| 34 | it("re-inks the nav wordmark from the mark ink in every appearance", () => { |
| 35 | // wordmark.svg is fixed #142352 ink; the header draws it as a mask so |
| 36 | // --mark-ink (the page's text colour) paints it on paper and on the ocean. |
| 37 | expect(CSS).toMatch(/\.paper-wordmark-logo,\s*\.wordmark\s*\{[^}]*background: var\(--mark-ink\);/); |
| 38 | }); |
| 39 | |
| 40 | it("shows the toggle on every page with one light|dark storage contract", () => { |
| 41 | const toggle = readFileSync(new URL("../components/theme-toggle.tsx", import.meta.url), "utf8"); |
| 42 | expect(toggle).not.toMatch(/isDocsPath|return null/); |
| 43 | expect(toggle).toMatch(/type Mode = "light" \| "dark"/); |
| 44 | expect(toggle).toContain('const KEY = "cw-theme"'); |
| 45 | const layout = readFileSync(new URL("../app/[locale]/layout.tsx", import.meta.url), "utf8"); |
| 46 | // The boot script pins only an explicit light/dark; anything else (a |
| 47 | // legacy "system" or "auto", nothing) stays on paper. |
| 48 | expect(layout).toContain("localStorage.getItem('cw-theme');if(t==='light'||t==='dark')"); |
| 49 | }); |
| 50 | }); |
| 51 | |
| 52 | describe("docs theme contrast contract", () => { |
| 53 | // The docs read the legacy names; they must resolve to the roles, whose |
| 54 | // contrast gpui-role-tokens.test.ts checks in every scheme. |
| 55 | it("points the docs accent and buttons at the role tokens", () => { |
| 56 | const root = Object.fromEntries( |
| 57 | [...CSS.matchAll(/(?:^|\n):root \{([^}]*)\}/g)].flatMap((m) => |
| 58 | [...m[1].matchAll(/--([\w-]+):\s*([^;]+);/g)].map((v) => [v[1], v[2].trim()]), |
| 59 | ), |
| 60 | ); |
| 61 | expect(root["docs-accent"]).toBe("var(--accent)"); |
| 62 | expect(root["docs-button-text"]).toBe("var(--text)"); |
| 63 | expect(root["docs-button-bg"]).toBe("var(--panel)"); |
| 64 | expect(CSS).toMatch(/\.docs-sidebar-link:hover,\s*\.docs-sidebar-link-current\s*{[^}]*color:\s*var\(--docs-accent\)/s); |
| 65 | expect(selectorBlock(".docs-theme .portal-button-secondary")).toContain("color: var(--docs-button-text)"); |
| 66 | }); |
| 67 | }); |
| 68 |