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, //); assert.match(globalError, //); 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=\{\}/); }); 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(/

{ for (const source of [error, globalError, notFound]) { const copy = source.match(/>\s*([^<>{}]*[\u4e00-\u9fff][^<>{}]*)\s*= 2); for (const line of copy) { assert.match(line, chinese); assert.doesNotMatch(line, /[!!]/); assert.doesNotMatch(line, /\p{Extended_Pictographic}/u); } } });