返回 CodeWhale
typography-contract.test.ts
根目录 / web / lib / typography-contract.test.ts
1 import { readdirSync, readFileSync, statSync } from "node:fs";
2 import { describe, expect, it } from "vitest";
3 import { resolveWhale } from "./whale-tokens";
4 import { siteCss } from "./site-css";
5
6 // The site's type follows GPUI set_theme: Shannon Sans for every role, the
7 // system monospace stack at 13px for code, no serif and no all-caps labels.
8 const CSS = siteCss();
9 const LAYOUT = readFileSync(new URL("../app/[locale]/layout.tsx", import.meta.url), "utf8");
10 const TAILWIND = readFileSync(new URL("../tailwind.config.ts", import.meta.url), "utf8");
11 const FONTS = new URL("../public/brand/fonts/", import.meta.url);
12
13 function rootVar(name: string): string {
14 const match = CSS.match(new RegExp(`--${name}:\\s*([^;]+);`));
15 if (!match) throw new Error(`Missing --${name}`);
16 return match[1].replace(/\s+/g, " ");
17 }
18
19 describe("typography contract", () => {
20 it("loads no serif or webfont mono and sets no all-caps", () => {
21 expect(CSS).not.toMatch(/Newsreader|IBM Plex|JetBrains|--font-serif/);
22 expect(CSS).not.toMatch(/text-transform:\s*uppercase/);
23 expect(LAYOUT).not.toMatch(/next\/font\/google/);
24 expect(TAILWIND).not.toMatch(/Newsreader|JetBrains|widest|wider/);
25 expect(TAILWIND).toMatch(/textTransform:\s*false/);
26 });
27
28 it("leaves no caller on the ungenerated all-caps or wide-tracking utilities", () => {
29 // Tailwind no longer emits these, so a surviving class is a silent no-op
30 // that reads as intent. Labels are sentence case at normal tracking.
31 const offenders: string[] = [];
32 for (const dir of ["../app/", "../components/"]) {
33 for (const entry of readdirSync(new URL(dir, import.meta.url), { recursive: true })) {
34 const path = String(entry);
35 if (!path.endsWith(".tsx")) continue;
36 const source = readFileSync(new URL(dir + path, import.meta.url), "utf8");
37 if (/\b(uppercase|tracking-wider|tracking-widest)\b/.test(source)) offenders.push(dir + path);
38 }
39 }
40 expect(offenders).toEqual([]);
41 });
42
43 it("resolves every role to Shannon Sans and code to the system mono stack", () => {
44 expect(rootVar("font-body")).toMatch(/^var\(--font-shannon-latin\), var\(--font-shannon-ext\),/);
45 expect(rootVar("font-display")).toBe("var(--font-body)");
46 expect(rootVar("font-mono")).toMatch(/^ui-monospace,/);
47 expect(rootVar("font-cjk")).not.toMatch(/Serif|(?<!sans-)serif/);
48 expect(resolveWhale(rootVar("text-mono"))).toBe("0.8125rem");
49 expect(rootVar("text-prose")).toBe("1.0625rem");
50 });
51
52 it("preloads only the Latin subset of Shannon Sans", () => {
53 const faces = [...LAYOUT.matchAll(/localFont\(\{([\s\S]*?)\n\}\);/g)].map((m) => m[1]);
54 expect(faces).toHaveLength(2);
55 const [latin, ext] = faces;
56 expect(latin).toContain("ShannonSans-Variable-latin.woff2");
57 expect(latin).not.toContain("preload: false");
58 expect(ext).toContain("ShannonSans-Variable-ext.woff2");
59 expect(ext).toContain("preload: false");
60 for (const face of faces) expect(face).toContain('prop: "unicode-range"');
61 // The Latin face is the only font on the critical path; keep it small.
62 expect(statSync(new URL("ShannonSans-Variable-latin.woff2", FONTS)).size).toBeLessThan(80_000);
63 expect(statSync(new URL("ShannonSans-Variable-ext.woff2", FONTS)).size).toBeGreaterThan(0);
64 });
65 });
66
66 lines TYPESCRIPT