import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { cssDeclarations } from "./css-contract-test-support.ts"; /** * TASK-serif-headings-20260928: headings (every `var(--font-display)` rule) * take the self-hosted "Jyotisha Serif SC"; body, buttons, tables, inputs, * labels and admin stay sans. A few headings are sans on purpose. This file * pins those exceptions so a later "make every heading serif" sweep has to * change a test — and say why — instead of flipping them silently. */ const read = (path: string) => readFileSync(new URL(path, import.meta.url), "utf8"); const css = read("../src/app/globals.css"); function exactRule(selector: string): string { // cssDeclarations matches by suffix; these exceptions need the exact selector // so the generic `.message-markdown h2` rule cannot satisfy them. const withoutComments = css.replace(/\/\*[\s\S]*?\*\//g, " "); const bodies: string[] = []; for (const [, selectorList, body] of withoutComments.matchAll(/([^{}]+)\{([^{}]*)\}/g)) { const targets = selectorList.split(",").map((value) => value.trim().replace(/\s+/g, " ")); if (targets.includes(selector)) bodies.push(body.trim()); } assert.notEqual(bodies.length, 0, `missing CSS selector: ${selector}`); return bodies.join(" "); } test("rectification narrative headings stay on the body face", () => { for (const selector of [ ".conversational-narrative .message-markdown h2", ".conversational-narrative .message-markdown h3", ]) { const body = exactRule(selector); assert.match(body, /font-family:\s*var\(--font-body\)/, `${selector} is a deliberate sans heading`); assert.match(body, /font-weight:\s*600/); assert.doesNotMatch(body, /--font-display/); } }); test("report chart-card titles stay on the body face", () => { const body = exactRule(".personal-report-chart-card h3"); assert.match(body, /font-family:\s*var\(--font-body\)/); assert.doesNotMatch(body, /--font-display/); }); test("assistant answer headings and report cover / section titles take the heading face", () => { assert.match(exactRule(".message-markdown h2"), /font-family:\s*var\(--font-display\)/); assert.match(cssDeclarations(".personal-report-cover h1"), /font-family:\s*var\(--font-display\)/); assert.match(cssDeclarations(".personal-report-section-heading h2"), /font-family:\s*var\(--font-display\)/); assert.match(exactRule(".personal-report-md-article h1"), /font-family:\s*var\(--font-display\)/); assert.match(exactRule(".personal-report-md-article h2"), /font-family:\s*var\(--font-display\)/); }); test("body text keeps the sans stack", () => { assert.match(cssDeclarations("body"), /font-family:\s*var\(--font-body\)/); const bodyStack = css.match(/^\s*--font-body:\s*([^;]+);/m)?.[1] ?? ""; assert.doesNotMatch(bodyStack, /Jyotisha Serif SC|serif"|Songti|SimSun/); }); test("admin never loads the heading face", () => { const adminCss = read("../src/app/admin/admin.css"); const adminApp = read("../src/components/admin/admin-app.tsx"); const adminLayout = read("../src/app/admin/layout.tsx"); for (const source of [adminCss, adminApp, adminLayout]) { assert.doesNotMatch(source, /Jyotisha Serif SC|serif-sc|--font-display/); } // The @font-face list rides with globals.css (site-styles.ts), never with the // root layout that admin also sits under. assert.match(read("../src/app/site-styles.ts"), /import "\.\/fonts\/serif-sc\/serif-sc\.css"/); assert.doesNotMatch(read("../src/app/layout.tsx"), /serif-sc/); }); test("root boundary pages keep their inline sans stack", () => { for (const file of ["error.tsx", "not-found.tsx", "forbidden.tsx", "global-error.tsx"]) { const source = read(`../src/app/${file}`); assert.doesNotMatch(source, /Jyotisha Serif SC|serif-sc|Songti|SimSun/, `${file} is a documented sans exception`); } }); test("the heading face swaps in without preload", () => { const faces = read("../src/app/fonts/serif-sc/serif-sc.css"); const blocks = [...faces.matchAll(/@font-face\s*\{([^}]*)\}/g)].map((m) => m[1]); assert.ok(blocks.length >= 10); for (const block of blocks) { assert.match(block, /font-family:\s*"Jyotisha Serif SC"/); assert.match(block, /font-display:\s*swap/); assert.match(block, /font-weight:\s*500 700/); assert.match(block, /url\("\.\/jyotisha-serif-sc-\d{2}\.woff2"\)/, "slices are bundled by Next, not served from public/"); } assert.doesNotMatch(read("../src/app/layout.tsx"), /rel="preload"[^>]*woff2|serif-sc.*preload/); });