Files
Jyotisha/frontend/tests/root-error-boundaries-contract.test.ts
T
jesse-uxandGrok (agent loop) 1516ad56ec
Independent Staging Quality Gate / validate (push) Failing after 9m11s
Independent Staging Quality Gate / publish (push) Skipped
报告、星历和登录页换成组件库(#109)。
报告语言改成分段页签,切换后不留下上一种语言的正文。导出从右侧或底部滑出,不再显示文件大小和说明句。星历用箭头按钮按方向翻页,卡片边框不被裁掉。登录的输入、按钮和六位验证码换成组件库。星星汇聚不改。

自测: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)
2026-10-25 05:56:25 +08:00

74 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}
});