Files
Jyotisha/frontend/tests/home-shell-growth-contract.test.ts
T
jesse-ux da35b3e4af refactor(home): split bootstrap, recovery polling, and shells
Move the home startup sequence and consultation recovery poll into
plain functions, and the error and onboarding screens into components.
Landing rules, the 20s fatal screen, retry, and the chart settings
deep link stay the same.
2026-09-25 02:59:11 +08:00

81 lines
3.6 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-25 after home-page-split on codex/home-page-split-20260925.
// Line count: (pageSource.match(/\n/g) ?? []).length, same as `wc -l` = 1417.
// Hook counts use \buseState[<(] / \buseRef[<(] so `useState<Type>(` is not missed.
// Former line baseline was 1846 (cap 1996) after batch 2 @ 37e6c519.
// This split moved bootstrap, recovery polling, and the error / onboarding
// screens out of Home(). useState stayed 34 (cap 36) and useRef stayed 37.
const PAGE_LINE_COUNT_BASELINE = 1417;
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,/);
});