feat(ui): 稳健跨 OS 衬线兜底栈 + 双主题 token 奇偶校验

- 决策:不自托管 CJK webfont(数 MB + 构建期外拉,违反 CSP 友好/首屏)
  改用跨 OS 系统衬线栈,保证中文标题可靠命中衬线,零 webfont 字节
- 新增 themeParity 测试:paper/night 两块 --color-* 必须成对,防漏配破面
This commit is contained in:
Yaojia Wang
2026-07-11 07:18:23 +02:00
parent b9f9134748
commit fd6551b0ba
2 changed files with 65 additions and 3 deletions

View File

@@ -0,0 +1,42 @@
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
// 双主题 token 奇偶校验P1-8paper 与 night 两块的 --color-* 变量必须成对,
// 任一模式漏配某个颜色 token 都会让该主题回退为无效变量而破面——此测试提前拦截。
const cssPath = fileURLToPath(
new URL("../../app/globals.css", import.meta.url),
);
const css = readFileSync(cssPath, "utf8");
function colorVarsInBlock(selector: string): Set<string> {
const start = css.indexOf(selector);
if (start === -1) {
throw new Error(`未找到主题块选择器:${selector}`);
}
const open = css.indexOf("{", start);
const close = css.indexOf("}", open);
const body = css.slice(open + 1, close);
const names = body.match(/--color-[\w-]+/g) ?? [];
return new Set(names);
}
describe("theme token parity", () => {
const paper = colorVarsInBlock('[data-theme="paper"]');
const night = colorVarsInBlock('[data-theme="night"]');
it("defines at least the core palette in each theme", () => {
expect(paper.size).toBeGreaterThan(10);
expect(night.size).toBeGreaterThan(10);
});
it("keeps paper and night color tokens paired (no theme drops a --color-* var)", () => {
const missingInNight = [...paper].filter((name) => !night.has(name));
const missingInPaper = [...night].filter((name) => !paper.has(name));
expect(missingInNight, "night 缺失paper 有)").toEqual([]);
expect(missingInPaper, "paper 缺失night 有)").toEqual([]);
});
});