返回 DeepSeek-Reasonix
typography-preferences.test.ts
根目录 / desktop / frontend / src / __tests__ / typography-preferences.test.ts
1 // Run: tsx src/__tests__/typography-preferences.test.ts
2
3 import {
4 TYPOGRAPHY_STORAGE_KEY,
5 TYPOGRAPHY_REGION_META,
6 applyTypographyPreferences,
7 createDefaultTypographyPreferences,
8 fontStackForPreference,
9 isSafeCustomFontNameInput,
10 normalizeTypographyPreferences,
11 sanitizeCustomFontName,
12 } from "../lib/typographyPreferences";
13
14 let passed = 0;
15 let failed = 0;
16
17 function eq(actual: unknown, expected: unknown, label: string) {
18 if (JSON.stringify(actual) === JSON.stringify(expected)) {
19 process.stdout.write(` PASS ${label}\n`);
20 passed += 1;
21 } else {
22 process.stdout.write(` FAIL ${label}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}\n`);
23 failed += 1;
24 }
25 }
26
27 console.log("\nregional typography preferences");
28
29 const defaults = createDefaultTypographyPreferences();
30 eq(defaults.conversation.followGlobal, true, "regions follow global by default");
31 eq(defaults.code.fontSize, TYPOGRAPHY_REGION_META.code.baseSize, "code uses its semantic base size");
32 eq(defaults.metadata.fontSize, 12, "metadata defaults to the existing 12px supporting-text size");
33
34 const normalized = normalizeTypographyPreferences({
35 conversation: { followGlobal: false, fontFamily: "pingfang", fontSize: 99 },
36 metadata: { followGlobal: false, fontFamily: "custom", customFontName: " IBM Plex Sans ", fontSize: 8 },
37 });
38 eq(normalized.conversation.fontSize, TYPOGRAPHY_REGION_META.conversation.max, "oversized values clamp to the region maximum");
39 eq(normalized.metadata.fontSize, TYPOGRAPHY_REGION_META.metadata.min, "undersized values clamp to the region minimum");
40 eq(normalized.metadata.customFontName, "IBM Plex Sans", "custom names are normalized");
41 eq(normalized.interface.followGlobal, true, "missing regions retain backward-compatible defaults");
42
43 eq(sanitizeCustomFontName("Bad; font"), "", "unsafe CSS delimiters are rejected");
44 eq(isSafeCustomFontNameInput("IBM Plex Sans"), true, "safe custom font input remains editable");
45 eq(isSafeCustomFontNameInput("Bad; font"), false, "unsafe custom font input is blocked before state changes");
46 eq(fontStackForPreference(normalized.conversation).includes("PingFang SC"), true, "preset resolves to a usable font stack");
47
48 const applied = new Map<string, string>([["--typography-interface-size", "20px"]]);
49 const stored = new Map<string, string>();
50 Object.defineProperty(globalThis, "document", {
51 configurable: true,
52 value: {
53 documentElement: {
54 style: {
55 removeProperty: (name: string) => applied.delete(name),
56 setProperty: (name: string, value: string) => applied.set(name, value),
57 },
58 },
59 },
60 });
61 Object.defineProperty(globalThis, "localStorage", {
62 configurable: true,
63 value: { setItem: (key: string, value: string) => stored.set(key, value) },
64 });
65
66 const custom = createDefaultTypographyPreferences();
67 custom.conversation = { followGlobal: false, fontFamily: "pingfang", customFontName: "", fontSize: 24 };
68 custom.code = { followGlobal: false, fontFamily: "jetbrains", customFontName: "", fontSize: 15 };
69 custom.metadata = { followGlobal: false, fontFamily: "inherit", customFontName: "", fontSize: defaults.metadata.fontSize };
70 applyTypographyPreferences(custom);
71 eq(applied.get("--typography-conversation-size"), "24px", "custom regions expose an exact CSS size");
72 eq(applied.get("--typography-code-size"), "15px", "code exposes an exact CSS size independent of root tokens");
73 eq(applied.get("--typography-metadata-size"), "12px", "disabling metadata follow-global preserves its rendered size");
74 eq(applied.get("--typography-metadata-scale"), "1", "metadata uses the same base as the global supporting-text token");
75 eq(applied.has("--typography-interface-size"), false, "follow-global regions clear stale exact sizes");
76 eq(stored.has(TYPOGRAPHY_STORAGE_KEY), true, "applied preferences remain persisted");
77
78 console.log(`\n${passed} passed, ${failed} failed, ${passed + failed} total`);
79 if (failed > 0) process.exit(1);
80
80 lines TYPESCRIPT