9c296f1e3f
Framework-level UX fixes found while auditing staging (BUG-216..220).
- chat: route 44 previously discarded composer notices to sonner with
dedupe, so recovery, cancel and archive feedback is actually visible
(BUG-216)
- chat: anchor stream auto-scroll to bottom proximity and add a
jump-to-latest control, so reading history is no longer interrupted
on every token (BUG-218)
- reports: replace the silent 120s poll cutoff with an explicit
timed-out state, an 8m budget, stepped backoff and an elapsed
counter (BUG-217)
- reports: pause polling while the tab is hidden, via a shared hook
- app: add root error, global-error and not-found boundaries (BUG-219)
- admin: add antd SSR style extraction and the React 19 render adapter,
and move admin-only css out of the global stylesheet (BUG-220)
- membership: run bootstrap fetches concurrently and pause payment
polling while hidden
- build: configure optimizePackageImports
Verified on top of 2d370f2e: tsc, eslint, next build, and the related
frontend contract suites.
Co-authored-by: Cursor <cursoragent@cursor.com>
64 lines
3.7 KiB
TypeScript
64 lines
3.7 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
|
|
const membershipPage = readFileSync(new URL("../src/app/membership/page.tsx", import.meta.url), "utf8");
|
|
const ordersPage = readFileSync(new URL("../src/app/membership/orders/page.tsx", 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(membershipPage, /await Promise\.allSettled\(\[fetchAccountData\(\), fetchPackages\(\)\]\);/);
|
|
assert.doesNotMatch(membershipPage, /await fetchAccountData\(\);\s*await fetchPackages\(\)/);
|
|
assert.doesNotMatch(membershipPage, /Promise\.all\(\[/);
|
|
});
|
|
|
|
test("a failing bootstrap request cannot hide the other section", () => {
|
|
assert.match(membershipPage, /setAccountError\(caught instanceof Error/);
|
|
assert.match(membershipPage, /setPackagesError\("套餐支付暂时不可用,请稍后重试"\)/);
|
|
assert.match(membershipPage, /\{account \? `\$\{account\.credits\} 点` : accountError \|\| "正在读取…"\}/);
|
|
assert.match(membershipPage, /\{packagesError && <p className="form-error" role="alert">\{packagesError\}<\/p>\}/);
|
|
});
|
|
|
|
test("membership and orders navigate through next/link instead of a document reload", () => {
|
|
assert.match(membershipPage, /import Link from "next\/link"/);
|
|
assert.match(membershipPage, /<Link className="membership-orders-entry" href="\/membership\/orders">/);
|
|
assert.match(ordersPage, /import Link from "next\/link"/);
|
|
assert.match(ordersPage, /<Link className="membership-back" href="\/membership" replace/);
|
|
assert.doesNotMatch(membershipPage, /window\.location\.assign\("\/membership/);
|
|
assert.doesNotMatch(ordersPage, /window\.location\.assign\("\/membership/);
|
|
});
|
|
|
|
test("401 redirects stay hard navigations so no client state survives the sign-out", () => {
|
|
assert.match(membershipPage, /if \(response\.status === 401\) \{\s*window\.location\.assign\("\/login"\);/);
|
|
assert.match(ordersPage, /if \(response\.status === 401\) \{\s*window\.location\.assign\("\/login"\);/);
|
|
assert.equal(countMatches(membershipPage, /window\.location\.assign\("\/login"\)/g), 3);
|
|
assert.equal(countMatches(ordersPage, /window\.location\.assign\("\/login"\)/g), 1);
|
|
});
|
|
|
|
test("browser back and the external cashier keep their native behaviour", () => {
|
|
assert.match(membershipPage, /if \(window\.history\.length > 1\) window\.history\.back\(\);/);
|
|
assert.match(membershipPage, /else window\.location\.assign\("\/"\);/);
|
|
assert.match(membershipPage, /window\.open\(payload\.payUrl, "_blank", "noopener,noreferrer"\)/);
|
|
});
|
|
|
|
test("the hard navigation inventory does not grow", () => {
|
|
assert.equal(countMatches(membershipPage, /window\.location\./g), 4);
|
|
assert.equal(countMatches(ordersPage, /window\.location\./g), 1);
|
|
});
|
|
|
|
test("payment polling pauses on a hidden tab and refreshes when it returns", () => {
|
|
assert.match(membershipPage, /if \(!document\.hidden\) startPolling\(\);/);
|
|
assert.match(membershipPage, /document\.addEventListener\("visibilitychange", onVisibilityChange\);/);
|
|
assert.match(membershipPage, /if \(document\.hidden\) \{\s*stopPolling\(\);\s*return;\s*\}/);
|
|
assert.match(membershipPage, /void checkPaymentStatus\(\);\s*startPolling\(\);/);
|
|
assert.match(membershipPage, /window\.setInterval\(\(\) => void checkPaymentStatus\(\), 3000\);/);
|
|
});
|
|
|
|
test("polling cleanup releases both the timer and the visibility listener", () => {
|
|
assert.match(membershipPage, /if \(timer\) window\.clearInterval\(timer\);/);
|
|
assert.match(membershipPage, /stopPolling\(\);\s*document\.removeEventListener\("visibilitychange", onVisibilityChange\);/);
|
|
});
|