The birth editor on /chart now loads when the pen row is clicked (warmed on hover/focus), the people page's add/edit form is fetched after mount, and the people city column reads the on-demand table. /chart first load -116 KB gzip, /people -111 KB; home +0.19%, still static. A static import graph test keeps the table out of both pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DbBXoz1Qk3y9buDv1mkmTu
90 lines
3.9 KiB
TypeScript
90 lines
3.9 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import { emptyProfile } from "../src/lib/home-types.ts";
|
|
import { ensureChinaLocations, resetChinaLocationsForTests } from "../src/lib/china-locations-client.ts";
|
|
import { peopleCityLabel } from "../src/lib/people-archive-view.ts";
|
|
|
|
// BUG-1469 (issue #120): BUG-1130 took the China city table out of the home
|
|
// first screen, but /chart still loaded it statically through the birth
|
|
// editor and /people through the add / edit form and the city column.
|
|
// These tests walk the static import graph, so a new static path back to the
|
|
// table fails here, whichever file it goes through.
|
|
|
|
const src = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../src");
|
|
const extensions = [".ts", ".tsx", ".json", "/index.ts", "/index.tsx"];
|
|
|
|
function resolveImport(from: string, specifier: string): string | null {
|
|
let base: string;
|
|
if (specifier.startsWith("@/")) base = path.join(src, specifier.slice(2));
|
|
else if (specifier.startsWith(".")) base = path.resolve(path.dirname(from), specifier);
|
|
else return null;
|
|
if (existsSync(base) && statSync(base).isFile()) return base;
|
|
for (const extension of extensions) {
|
|
if (existsSync(base + extension)) return base + extension;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Value imports and re-exports only: `import type`, `export type` and `import()` do not load code. */
|
|
function staticImports(file: string): string[] {
|
|
if (file.endsWith(".json")) return [];
|
|
const text = readFileSync(file, "utf8");
|
|
const found: string[] = [];
|
|
const pattern = /^\s*(?:import|export)\s+(?!type\b)(?:[^;'"]*?\sfrom\s+)?["']([^"']+)["']/gm;
|
|
for (const match of text.matchAll(pattern)) {
|
|
const resolved = resolveImport(file, match[1]);
|
|
if (resolved) found.push(resolved);
|
|
}
|
|
return found;
|
|
}
|
|
|
|
function staticallyReaches(entry: string, target: RegExp): string[] | null {
|
|
const seen = new Map<string, string | null>([[entry, null]]);
|
|
const queue = [entry];
|
|
while (queue.length > 0) {
|
|
const file = queue.shift()!;
|
|
if (target.test(file)) {
|
|
const chain: string[] = [];
|
|
for (let at: string | null = file; at; at = seen.get(at) ?? null) chain.unshift(path.relative(src, at));
|
|
return chain;
|
|
}
|
|
for (const next of staticImports(file)) {
|
|
if (seen.has(next)) continue;
|
|
seen.set(next, file);
|
|
queue.push(next);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const cityTable = /[\\/]data[\\/]china-locations(\.json|\.ts)?$/;
|
|
|
|
for (const entry of ["components/chart-page/chart-page-view.tsx", "components/people/people-page.tsx"]) {
|
|
test(`${entry} does not load the China city table statically (BUG-1469)`, () => {
|
|
const chain = staticallyReaches(path.join(src, entry), cityTable);
|
|
assert.equal(chain, null, chain ? `static path: ${chain.join(" → ")}` : "");
|
|
});
|
|
}
|
|
|
|
test("the walker does see the table where it is still static, so the check above is not vacuous", () => {
|
|
assert.ok(staticallyReaches(path.join(src, "components/birth-place-picker.tsx"), cityTable));
|
|
});
|
|
|
|
test("the people city column reads the on-demand table and falls back to the saved label (BUG-1469)", async () => {
|
|
resetChinaLocationsForTests();
|
|
// Codes plus the label the picker saves: the label's city segment, same name as the table's.
|
|
const saved = { ...emptyProfile, provinceCode: "130000", cityCode: "130100", birthPlaceLabel: "中国 · 河北省 · 石家庄市 · 长安区" };
|
|
assert.equal(peopleCityLabel(saved), "石家庄");
|
|
// Codes only (an old profile): blank until the table is loaded, which the cold boot does before the list settles.
|
|
const codesOnly = { ...emptyProfile, provinceCode: "130000", cityCode: "130100" };
|
|
assert.equal(peopleCityLabel(codesOnly), "");
|
|
await ensureChinaLocations();
|
|
assert.equal(peopleCityLabel(codesOnly), "石家庄");
|
|
assert.equal(peopleCityLabel(saved), "石家庄");
|
|
resetChinaLocationsForTests();
|
|
});
|