import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import test from "node:test"; import { fileURLToPath } from "node:url"; import { messageFromError } from "../src/composables/useForm.ts"; import { isChunkLoadError, shouldReloadForChunkError } from "../src/router/guards.ts"; import { resolveDocumentTitle } from "../src/router/title.ts"; const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); test("R6 route meta title and chunk recovery match Sub2API navigation pattern", () => { assert.equal(resolveDocumentTitle("Gate"), "Gate - HWLAB Cloud Web"); assert.equal(resolveDocumentTitle(" "), "平台控制台 - HWLAB Cloud Web"); assert.equal(isChunkLoadError(new Error("Failed to fetch dynamically imported module")), true); assert.equal(isChunkLoadError(new Error("Loading CSS chunk 42 failed")), true); assert.equal(isChunkLoadError(new Error("ordinary route guard failure")), false); const memory = new Map(); const storage = { getItem: (key: string) => memory.get(key) ?? null, setItem: (key: string, value: string) => { memory.set(key, value); } }; assert.equal(shouldReloadForChunkError(100_000, storage), true); assert.equal(shouldReloadForChunkError(105_000, storage), false); assert.equal(shouldReloadForChunkError(111_000, storage), true); }); test("R6 useForm normalizes nested API errors for toast/error surfaces", () => { assert.equal(messageFromError(new Error("plain failure")), "plain failure"); assert.equal(messageFromError({ error: { message: "nested message" } }), "nested message"); assert.equal(messageFromError({ error: { code: "permission_denied" } }), "permission_denied"); assert.equal(messageFromError({ reason: "blocked_by_gate" }), "blocked_by_gate"); }); test("R6 common module map keeps HWLAB views on shared table/dialog/form primitives", () => { const expectations: Array<[string, string[]]> = [ ["src/views/admin/ProviderProfilesView.vue", ["DataTable", "Pagination", "BaseDialog", "ConfirmDialog", "TablePageLayout", "useTableLoader", "useForm"]], ["src/components/access/AccessMatrixPanel.vue", ["DataTable", "TablePageLayout"]], ["src/views/GateView.vue", ["DataTable", "Pagination", "BaseDialog", "TablePageLayout", "useTableLoader"]], ["src/views/PerformanceView.vue", ["DataTable", "TablePageLayout", "useTableLoader"]], ["src/views/SkillsView.vue", ["DataTable", "Pagination", "BaseDialog", "TablePageLayout", "useTableLoader", "useForm"]] ]; for (const [relativePath, terms] of expectations) { const source = read(relativePath); for (const term of terms) assert.match(source, new RegExp(`\\b${term}\\b`, "u"), `${relativePath} should use ${term}`); assert.doesNotMatch(source, / { const issueBody = readIssueBodyFixture(); assert.match(issueBody, /Wei-Shaw\/sub2api@v0\.1\.136=a2f76e4/u); assert.match(issueBody, /router\/index\.ts/u); assert.match(issueBody, /components\/common/u); assert.match(issueBody, /useTableLoader\.ts/u); assert.match(issueBody, /useForm\.ts/u); }); function read(relativePath: string): string { return fs.readFileSync(path.join(rootDir, relativePath), "utf8"); } function readIssueBodyFixture(): string { return [ "Sub2API 对照: Wei-Shaw/sub2api@v0.1.136=a2f76e4", "router/index.ts api/client.ts components/common/* composables/useTableLoader.ts useForm.ts admin/user CRUD views" ].join("\n"); }