报告语言改成分段页签,切换后不留下上一种语言的正文。导出从右侧或底部滑出,不再显示文件大小和说明句。星历用箭头按钮按方向翻页,卡片边框不被裁掉。登录的输入、按钮和六位验证码换成组件库。星星汇聚不改。 自测:tsc 退出码 0;lint 0 error、122 个警告;相关测试 185 条里 184 通过,另 1 条是既有的 Windows 管理路径问题;英文版 10 条和删除 5 条重跑通过;build 后首页仍是 Static;首页 JS gzip-9 为 642174,相对 639609 多 0.40%。隐私标记测试通过。 Co-Authored-By: Grok (agent loop)
74 lines
3.0 KiB
TypeScript
74 lines
3.0 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import { readFileSync } from "node:fs";
|
||
import test from "node:test";
|
||
|
||
const read = (relative: string) => readFileSync(new URL(relative, import.meta.url), "utf8");
|
||
|
||
const error = read("../src/app/error.tsx");
|
||
const globalError = read("../src/app/global-error.tsx");
|
||
const notFound = read("../src/app/not-found.tsx");
|
||
|
||
const chinese = /[\u4e00-\u9fff]/;
|
||
|
||
test("the app root ships error, global-error and not-found boundaries", () => {
|
||
for (const source of [error, globalError, notFound]) {
|
||
assert.ok(source.length > 0);
|
||
assert.match(source, /export default function \w+\(/);
|
||
}
|
||
});
|
||
|
||
test("root error boundary is a client component that can reset and route home", () => {
|
||
assert.match(error, /^"use client";/);
|
||
assert.match(error, /reset,\s*\}: \{[\s\S]*reset: \(\) => void;/);
|
||
assert.match(error, /onClick=\{\(\) => reset\(\)\}/);
|
||
assert.match(error, /href="\/"/);
|
||
assert.match(error, /error\.digest/);
|
||
assert.doesNotMatch(error, /error\.stack/);
|
||
});
|
||
|
||
test("global error boundary replaces the root layout without external styling", () => {
|
||
assert.match(globalError, /^"use client";/);
|
||
assert.match(globalError, /<html lang="zh-CN">/);
|
||
assert.match(globalError, /<body/);
|
||
assert.match(globalError, /<\/body>/);
|
||
assert.match(globalError, /<\/html>/);
|
||
// 原值:global-error 不得出现任何 import
|
||
// 新值:只允许 import { Button } from "@/components/ui/button"
|
||
// 原因:K5 把崩溃页的两个按钮换成组件库。颜色仍写在本文件的内联样式里,不依赖全局样式表
|
||
assert.equal(globalError.match(/^import /gm)?.length, 1);
|
||
assert.match(globalError, /import \{ Button \} from "@\/components\/ui\/button";/);
|
||
assert.doesNotMatch(globalError, /className="[a-z-]*(flex|text-|bg-|min-h-)/);
|
||
assert.match(globalError, /minHeight: "44px"/);
|
||
assert.match(globalError, /onClick=\{\(\) => reset\(\)\}/);
|
||
assert.match(globalError, /window\.location\.assign\("\/"\)/);
|
||
});
|
||
|
||
test("root not found stays a server component and links back to the chat home", () => {
|
||
assert.doesNotMatch(notFound, /"use client"/);
|
||
assert.doesNotMatch(notFound, /useState|useEffect|onClick/);
|
||
assert.match(notFound, /404/);
|
||
assert.match(notFound, /render=\{<Link href="\/" \/>\}/);
|
||
});
|
||
|
||
test("every boundary announces itself accessibly with one heading", () => {
|
||
for (const source of [error, globalError]) {
|
||
assert.match(source, /role="alert"/);
|
||
}
|
||
for (const source of [error, globalError, notFound]) {
|
||
assert.equal(source.match(/<h1/g)?.length, 1);
|
||
assert.doesNotMatch(source, /<h[3-6]/);
|
||
}
|
||
});
|
||
|
||
test("boundary copy is simplified chinese in the product voice", () => {
|
||
for (const source of [error, globalError, notFound]) {
|
||
const copy = source.match(/>\s*([^<>{}]*[\u4e00-\u9fff][^<>{}]*)\s*</g) ?? [];
|
||
assert.ok(copy.length >= 2);
|
||
for (const line of copy) {
|
||
assert.match(line, chinese);
|
||
assert.doesNotMatch(line, /[!!]/);
|
||
assert.doesNotMatch(line, /\p{Extended_Pictographic}/u);
|
||
}
|
||
}
|
||
});
|