globals.css 从 7249 行减到 7011 行,删了 56 个已经没人用的类,16 个关键帧都还在用。手写控件名单从 9 个文件收到 2 个:天空绘制和校正顶栏探头。星盘和星图的图标豁免仍在规则里。设计说明改成以 shadcn base-nova 和 Motion 预设为准。页面用法没有再改。
自测:tsc --noEmit 退出码 0;frontend 下 npm run lint 0 error、122 warning;白名单、样式隔离、时间线和错误边界测试 18 条通过、失败 0、取消 0;npm run build 后 / 仍是 Static。首屏 JS gzip-9 相对 728ead88 从 644683 到 648993(+0.67%)。没有跑全量 npm test、Python 快速门和 test:db。
Co-Authored-By: Grok (agent loop)
79 lines
3.3 KiB
TypeScript
79 lines
3.3 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import { readFileSync } from "node:fs";
|
||
import test from "node:test";
|
||
import { fileURLToPath } from "node:url";
|
||
|
||
import { ESLint } from "eslint";
|
||
|
||
/**
|
||
* K7 ceiling. The list may still shrink. Adding one fails this test.
|
||
* 原值:K0 的 62 个文件。
|
||
* 新值:天空绘制和校正顶栏探头这两个文件。
|
||
* 原因:K7 清空白名单。星盘和星图的图标豁免在规则里,不在这份名单。
|
||
* 天空过场必须留着原生 dialog.showModal 和 createPortal,合同测试锁住这段,换组件会改绘制。
|
||
* 校正探头必须留着 createPortal 挂到顶栏,合同测试锁住这句;改成弹窗会改 K6 的交互。
|
||
*/
|
||
const BASELINE = [
|
||
"src/components/birth-sky/birth-sky-converge.tsx",
|
||
"src/components/rectification-agentic-chat.tsx",
|
||
] as const;
|
||
|
||
const RULE = "jyotisha-ui/no-handwritten-control";
|
||
const frontendRoot = fileURLToPath(new URL("..", import.meta.url));
|
||
const eslint = new ESLint({ cwd: frontendRoot });
|
||
|
||
const drawingSnippet = `export function Chart() {
|
||
return (
|
||
<div>
|
||
<button type="button">保存</button>
|
||
<span>☰</span>
|
||
<svg viewBox="0 0 10 10"><text>×</text></svg>
|
||
</div>
|
||
);
|
||
}
|
||
`;
|
||
|
||
async function ruleMessages(filename: string, code: string) {
|
||
const [result] = await eslint.lintText(code, { filePath: filename });
|
||
return (result?.messages ?? []).filter((message) => message.ruleId === RULE);
|
||
}
|
||
|
||
test("the handwritten-control whitelist can only shrink", () => {
|
||
const live = JSON.parse(readFileSync(new URL("../eslint/ui-primitive-whitelist.json", import.meta.url), "utf8")) as string[];
|
||
assert.deepEqual(live, [...live].sort());
|
||
assert.equal(new Set(live).size, live.length);
|
||
assert.ok(live.length <= BASELINE.length);
|
||
for (const file of live) assert.ok(BASELINE.includes(file as (typeof BASELINE)[number]), file);
|
||
});
|
||
|
||
test("a new file under src/components with a native button fails lint", async () => {
|
||
const messages = await ruleMessages(
|
||
"src/components/ui-kit-k0-probe.tsx",
|
||
`export function Probe() { return <button type="button">保存</button>; }\n`,
|
||
);
|
||
assert.ok(messages.some((message) => message.message.includes("<button>")));
|
||
});
|
||
|
||
test("src/components/ui is outside the handwritten-control rule", async () => {
|
||
const messages = await ruleMessages(
|
||
"src/components/ui/ui-kit-k0-probe.tsx",
|
||
`export function Probe() { return <button type="button">保存</button>; }\n`,
|
||
);
|
||
assert.equal(messages.length, 0);
|
||
});
|
||
|
||
test("chart and star-map SVG drawings are exempt from the icon rule only", async () => {
|
||
const ruleSource = readFileSync(new URL("../eslint/no-handwritten-control.mjs", import.meta.url), "utf8");
|
||
assert.match(ruleSource, /src\/components\/personal-report\/vedic-chart-svg\.tsx/);
|
||
assert.match(ruleSource, /src\/components\/chart-page\/western-wheel-svg\.tsx/);
|
||
for (const filename of [
|
||
"src/components/personal-report/vedic-chart-svg.tsx",
|
||
"src/components/chart-page/western-wheel-svg.tsx",
|
||
]) {
|
||
const messages = await ruleMessages(filename, drawingSnippet);
|
||
assert.ok(messages.some((message) => message.message.includes("<button>")), filename);
|
||
assert.ok(messages.some((message) => message.message.includes("☰")), filename);
|
||
assert.equal(messages.some((message) => message.message.includes("×")), false, filename);
|
||
}
|
||
});
|