Files
Jyotisha/frontend/tests/home-shell-growth-contract.test.ts
T
jesse-ux e4e73f56c0
Independent Staging Quality Gate / publish (push) Canceled after 0s
Independent Staging Quality Gate / validate (push) Canceled after 9m33s
fix(web): 四个页面共用一份会话列表,空会话不入列
对话、星盘、星历、报告进同一 (app) 外壳,列表只拉一次。服务端不再列出空咨询;新建复用已有空会话。新标题改成「生时校正 · M月D日」,侧栏副标题用创建时间。
2026-09-17 21:27:40 +08:00

84 lines
3.9 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
const pageSource = readFileSync(new URL("../src/app/(app)/page.tsx", import.meta.url), "utf8");
// Measured 2026-09-16 after home-state-lowering batch 2 on origin/staging @ 37e6c519.
// Line count: (pageSource.match(/\n/g) ?? []).length, same as `wc -l` = 1846.
// Hook counts use \buseState[<(] / \buseRef[<(] so `useState<Type>(` is not missed.
// Former caps were useState 52 / useRef 39 / lines 1931 after batch 1 @ f8e607c2,
// and useState 66 / useRef 41 / lines 1951 on freeze-metric @ 51a65d92.
// Batch 2 moved 4 synastry useStates into useSynastry, 6 profile useStates into
// useProfileOnboarding and 6 session useStates into useSessionManagement, and
// dropped the two guided-birth-time callback refs that the profile hook no longer
// needs now that it runs before useBirthTimeGuidedJourney.
const PAGE_LINE_COUNT_BASELINE = 1846;
const PAGE_LINE_COUNT_CAP = PAGE_LINE_COUNT_BASELINE + 150;
const HOME_USE_STATE_CAP = 36;
const HOME_USE_REF_CAP = 37;
const USE_STATE_RE = /\buseState[<(]/g;
const USE_REF_RE = /\buseRef[<(]/g;
function homeSource(source: string): string {
const marker = "export default function Home(";
const start = source.indexOf(marker);
assert.notEqual(start, -1, "page.tsx must export default function Home(");
return source.slice(start);
}
function countMatches(source: string, pattern: RegExp): number {
return source.match(new RegExp(pattern.source, "g"))?.length ?? 0;
}
test("Home() useState count must not grow", () => {
const n = countMatches(homeSource(pageSource), USE_STATE_RE);
assert.ok(
n <= HOME_USE_STATE_CAP,
`Home() has ${n} useState calls; cap is ${HOME_USE_STATE_CAP}. Extracted hooks and child components should own their own state.`,
);
});
test("Home() useRef count must not grow", () => {
const n = countMatches(homeSource(pageSource), USE_REF_RE);
assert.ok(
n <= HOME_USE_REF_CAP,
`Home() has ${n} useRef calls; cap is ${HOME_USE_REF_CAP}. Do not rewrite state as refs to dodge the useState freeze.`,
);
});
test("page.tsx line count stays within the coarse guardrail", () => {
const n = (pageSource.match(/\n/g) ?? []).length;
assert.ok(
n <= PAGE_LINE_COUNT_CAP,
`page.tsx has ${n} lines; cap is ${PAGE_LINE_COUNT_CAP} (${PAGE_LINE_COUNT_BASELINE} baseline + 150).`,
);
});
test("useSessionManagement still runs before useRectificationSurface", () => {
// Batch 1 established the order: useSessionManagement needs an opener for
// history rectification sessions, and useRectificationSurface is what supplies
// it, so the opener is handed over through rectificationSessionOpenerRef
// instead of by calling the surface hook first. Batch 2 moved hook calls
// around; this pins the constraint so a later reorder cannot silently break
// opening a rectification session from the sidebar.
const home = homeSource(pageSource);
const sessionCall = home.indexOf("= useSessionManagement({");
const surfaceCall = home.indexOf("= useRectificationSurface({");
const openerRef = home.indexOf("const rectificationSessionOpenerRef = useRef(");
assert.notEqual(sessionCall, -1, "Home must call useSessionManagement");
assert.notEqual(surfaceCall, -1, "Home must call useRectificationSurface");
assert.notEqual(openerRef, -1, "Home must declare rectificationSessionOpenerRef");
assert.ok(openerRef < sessionCall, "the opener ref must exist before useSessionManagement reads it");
assert.ok(
sessionCall < surfaceCall,
"useSessionManagement must run before useRectificationSurface; it receives the opener through rectificationSessionOpenerRef",
);
assert.match(
home.slice(sessionCall, surfaceCall),
/openRectificationSession: \(exactSessionId\) => rectificationSessionOpenerRef\.current\(exactSessionId\)/,
);
assert.match(home.slice(surfaceCall), /rectificationSessionOpenerRef,/);
});