import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; // 双主题 token 奇偶校验(P1-8):paper 与 night 两块的 --color-* 变量必须成对, // 任一模式漏配某个颜色 token 都会让该主题回退为无效变量而破面——此测试提前拦截。 const cssPath = fileURLToPath( new URL("../../app/globals.css", import.meta.url), ); const css = readFileSync(cssPath, "utf8"); function colorVarsInBlock(selector: string): Set { 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([]); }); });