import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; const hookSource = readFileSync(new URL("../src/hooks/use-billing-panel.ts", import.meta.url), "utf8"); const panelSource = readFileSync(new URL("../src/components/billing-panel.tsx", import.meta.url), "utf8"); const billingSource = `${hookSource}\n${panelSource}`; const nextConfig = readFileSync(new URL("../next.config.ts", import.meta.url), "utf8"); function countMatches(source: string, pattern: RegExp) { return source.match(pattern)?.length ?? 0; } test("account and packages are requested concurrently on mount", () => { assert.match(hookSource, /await Promise\.allSettled\(\[fetchAccountData\(\), fetchPackages\(\)\]\);/); assert.doesNotMatch(hookSource, /await fetchAccountData\(\);\s*await fetchPackages\(\)/); assert.doesNotMatch(hookSource, /Promise\.all\(\[/); }); test("a failing bootstrap request cannot hide the other section", () => { assert.match(hookSource, /setAccountError\(caught instanceof Error/); assert.match(hookSource, /setPackagesError\("套餐支付暂时不可用,请稍后重试"\)/); // 原值: 摘要在整页 hero,失败时显示「正在读取…」 // 新值: 弹窗打开时账户已在内存,摘要直接渲染;套餐区失败只出 packagesError // 原因: 决策 8,不得出现「正在加载」 assert.match(panelSource, /余额 \{credits\} 点/); assert.match(panelSource, /\{packagesError &&
\{packagesError\}<\/p>\}/); assert.doesNotMatch(panelSource, /正在读取/); }); test("membership and orders navigate through next/link instead of a document reload", () => { // 原值: Link 在 /membership 与 /membership/orders 之间 // 新值: 页签切换;旧路径由 next.config redirects 接到 ?settings=billing // 原因: 删除整页 assert.doesNotMatch(billingSource, /import Link from "next\/link"/); assert.doesNotMatch(billingSource, /href="\/membership/); assert.doesNotMatch(billingSource, /window\.location\.assign\("\/membership/); assert.match(nextConfig, /source: "\/membership"/); assert.match(panelSource, /value="orders"/); }); test("401 redirects stay hard navigations so no client state survives the sign-out", () => { assert.match(hookSource, /if \(response\.status === 401\) \{\s*window\.location\.assign\("\/login"\);/); assert.equal(countMatches(hookSource, /window\.location\.assign\("\/login"\)/g), 4); }); test("browser back and the external cashier keep their native behaviour", () => { // 原值: goBack 的 history.back / assign("/") // 新值: 无 goBack;收银台仍 window.open // 原因: 账单在当前页,后退是浏览器原生;BUG-251 要求收银台新标签 assert.doesNotMatch(billingSource, /window\.history\.back\(\)/); assert.match(hookSource, /window\.open\(payload\.payUrl, "_blank", "noopener,noreferrer"\)/); }); test("the hard navigation inventory does not grow", () => { // 原值: 会员页 4 次 window.location. + 订单页 1 次 // 新值: hook 仅 4 次 assign("/login"),面板 0 次 // 原因: 去掉 goBack 的 assign("/") 后硬跳转只剩认证 assert.equal(countMatches(hookSource, /window\.location\./g), 4); assert.equal(countMatches(panelSource, /window\.location\./g), 0); }); test("payment polling pauses on a hidden tab and refreshes when it returns", () => { assert.match(hookSource, /if \(!document\.hidden\) startPolling\(\);/); assert.match(hookSource, /document\.addEventListener\("visibilitychange", onVisibilityChange\);/); assert.match(hookSource, /if \(document\.hidden\) \{\s*stopPolling\(\);\s*return;\s*\}/); assert.match(hookSource, /void checkPaymentStatus\(\);\s*startPolling\(\);/); assert.match(hookSource, /window\.setInterval\(\(\) => void checkPaymentStatus\(\), 3000\);/); }); test("polling cleanup releases both the timer and the visibility listener", () => { assert.match(hookSource, /if \(timer\) window\.clearInterval\(timer\);/); assert.match(hookSource, /stopPolling\(\);\s*document\.removeEventListener\("visibilitychange", onVisibilityChange\);/); });