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)
100 lines
5.3 KiB
TypeScript
100 lines
5.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readdirSync, readFileSync } from "node:fs";
|
|
import { extname, join } from "node:path";
|
|
import test from "node:test";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const layout = readFileSync(new URL("../src/app/layout.tsx", import.meta.url), "utf8");
|
|
const siteStyles = readFileSync(new URL("../src/app/site-styles.ts", import.meta.url), "utf8");
|
|
const adminLayout = readFileSync(new URL("../src/app/admin/layout.tsx", import.meta.url), "utf8");
|
|
const adminCss = readFileSync(new URL("../src/app/admin/admin.css", import.meta.url), "utf8");
|
|
const homePage = readFileSync(new URL("../src/app/(app)/page.tsx", import.meta.url), "utf8");
|
|
const loginPage = readFileSync(new URL("../src/app/login/page.tsx", import.meta.url), "utf8");
|
|
|
|
test("Inter is loaded through next/font and applied as a CSS variable", () => {
|
|
assert.match(layout, /import localFont from "next\/font\/local"/);
|
|
assert.doesNotMatch(layout, /from ["']next\/font\/google["']/);
|
|
assert.match(layout, /src: "\.\/fonts\/InterVariable-latin\.woff2"/);
|
|
assert.match(layout, /display: "swap"/);
|
|
assert.match(layout, /variable: "--font-inter"/);
|
|
// 原值:/className=\{inter\.variable\}/ —— <html> 上只挂一个字体变量。
|
|
// 新值:/className=\{`\$\{inter\.variable\}[^`]*`\}/ —— 允许同一个模板串里再挂别的
|
|
// 字体变量,但 inter.variable 必须仍是第一个。
|
|
// 原因:本轮新增了 vendored 的 9 字形行星符号子集(--font-planet-glyphs),两个
|
|
// next/font 变量必须挂在同一个 <html> 上。断言主语(Inter 以 CSS 变量形式
|
|
// 挂在根元素)没变,只是不再要求 className 只有它一个。
|
|
assert.match(layout, /className=\{`\$\{inter\.variable\}[^`]*`\}/);
|
|
assert.match(readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8"), /var\(--font-inter, Inter\)/);
|
|
assert.match(
|
|
readFileSync(new URL("../src/components/admin/admin-app.tsx", import.meta.url), "utf8"),
|
|
/var\(--font-inter, Inter\)/,
|
|
);
|
|
});
|
|
|
|
test("Inter is vendored locally so production builds do not fetch Google Fonts", () => {
|
|
const font = readFileSync(new URL("../src/app/fonts/InterVariable-latin.woff2", import.meta.url));
|
|
assert.equal(Buffer.from(font.subarray(0, 4)).toString(), "wOF2");
|
|
assert.ok(font.byteLength > 20_000);
|
|
const srcRoot = fileURLToPath(new URL("../src/", import.meta.url));
|
|
const stack = [srcRoot];
|
|
while (stack.length > 0) {
|
|
const dir = stack.pop()!;
|
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
const fullPath = join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
stack.push(fullPath);
|
|
continue;
|
|
}
|
|
if (![".ts", ".tsx", ".js", ".jsx", ".css"].includes(extname(entry.name))) continue;
|
|
const source = readFileSync(fullPath, "utf8");
|
|
assert.doesNotMatch(
|
|
source,
|
|
/from ["']next\/font\/google["']|fonts\.googleapis\.com/,
|
|
`${fullPath} must not fetch Google Fonts at build time`,
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
test("site chrome owns globals.css; admin does not import it", () => {
|
|
assert.match(siteStyles, /import "\.\/globals\.css"/);
|
|
assert.match(siteStyles, /import "\.\/birth-time-choice\.css"/);
|
|
assert.doesNotMatch(layout, /globals\.css|birth-time-choice\.css|site-styles/);
|
|
// 原值:首页 page.tsx import site-styles
|
|
// 新值:(app)/layout.tsx import,首页不再各自引进
|
|
// 原因:T2 四页共用一份外壳
|
|
const appLayout = readFileSync(new URL("../src/app/(app)/layout.tsx", import.meta.url), "utf8");
|
|
assert.match(appLayout, /import "\.\.\/site-styles"/);
|
|
assert.doesNotMatch(homePage, /site-styles/);
|
|
assert.match(loginPage, /import "\.\.\/site-styles"/);
|
|
assert.doesNotMatch(adminLayout, /globals\.css|site-styles/);
|
|
assert.doesNotMatch(
|
|
readFileSync(new URL("../src/app/error.tsx", import.meta.url), "utf8"),
|
|
/site-styles|globals\.css/,
|
|
);
|
|
assert.doesNotMatch(
|
|
readFileSync(new URL("../src/app/not-found.tsx", import.meta.url), "utf8"),
|
|
/site-styles|globals\.css/,
|
|
);
|
|
assert.match(adminCss, // 原值 `--font-body: StyreneB, var(--font-inter, Inter)` / 新值:去掉 StyreneB
|
|
// 原因:StyreneB 是 Anthropic 授权字体,本仓从未加载它(无 @font-face、public/ 无字体文件),
|
|
// 留在栈首只是死配置。见 BUG-737。
|
|
/--font-body: var\(--font-inter, Inter\)/);
|
|
});
|
|
|
|
test("root error and not-found pages do not depend on the site stylesheet", () => {
|
|
const errorPage = readFileSync(new URL("../src/app/error.tsx", import.meta.url), "utf8");
|
|
const notFoundPage = readFileSync(new URL("../src/app/not-found.tsx", import.meta.url), "utf8");
|
|
// 原值:error.tsx 与 not-found 都不得 import Button
|
|
// 新值:error.tsx 可以 import Button,颜色仍写在内联样式里;not-found 仍不得 import Button
|
|
// 原因:K7 清空白名单。这一页不引进整站样式表,避免聊天样式进后台路由
|
|
for (const source of [errorPage, notFoundPage]) {
|
|
assert.doesNotMatch(source, /className="flex/);
|
|
assert.doesNotMatch(source, /min-h-svh/);
|
|
assert.doesNotMatch(source, /text-muted-foreground/);
|
|
assert.doesNotMatch(source, /site-styles|globals\.css/);
|
|
}
|
|
assert.match(errorPage, /import \{ Button \} from "@\/components\/ui\/button";/);
|
|
assert.doesNotMatch(notFoundPage, /@\/components\/ui\/button/);
|
|
});
|