返回 CodeWhale
docs-theme-contract.test.ts
根目录 / web / lib / docs-theme-contract.test.ts
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
68 lines TYPESCRIPT