Files
Jyotisha/frontend/tests/lagna-precheck-rectify-entry.test.tsx
T
Jesse_ChenandClaude Opus 5.5 55ebdd7a95
Independent Staging Quality Gate / publish (push) Canceled after 0s
Independent Staging Quality Gate / validate (push) Canceled after 9m27s
Home first screen no longer carries the rectification surface or react-markdown (issue #124, BUG-1474)
page.tsx imported the whole rectification surface statically; its verification
report also imported chat-markdown-view statically, which pulled react-markdown
into / even though chat-message-content already loaded it lazily.

- Home renders a same-named lazy wrapper; openRectificationCase starts the
  download with the open request and awaits it before setting the ids that
  mount the surface, so it still reveals once, drawn.
- The markdown loader moves to lib/chat-markdown-loader; the home bootstrap
  awaits it before revealing a conversation with messages (no plain-text flash
  on ?c=). The blank home does not wait.

/ first load, gzip-9 of every referenced JS/CSS: 704,165 -> 609,606 B (-13.4%),
still Static.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:25:43 +08:00

191 lines
6.4 KiB
TypeScript

/**
* Chart-page `/?rectify=1` uses the homepage open, which resumes a case
* already in progress. Intent "new" would surface a conflict instead.
* Fictional profile only. BUG-1446.
*/
import assert from "node:assert/strict";
import test from "node:test";
import React, { useRef, useState } from "react";
import {
EMPTY_RECTIFICATION_SHELL,
useRectificationSurface,
} from "../src/hooks/use-rectification-surface.ts";
import type { Account, Profile } from "../src/lib/home-types.ts";
import type { PublicLanguageModelCatalog } from "../src/lib/public-models.ts";
import { createClientLifecycleHarness } from "./react-client-lifecycle-test-support.ts";
import { loadRectificationSurface } from "../src/lib/rectification-surface-loader.ts";
Object.assign(globalThis, { React });
const SESSION_ID = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaa1";
const profile = {
name: "示例",
date: "1990-06-15",
time: "12:00",
reportedTime: "12:00",
birthTimeSource: "hospital_record",
birthTimePeriod: "",
declaredWindowStart: "",
declaredWindowEnd: "",
birthTimeClue: "",
uncertaintyBeforeMinutes: 2,
uncertaintyAfterMinutes: 2,
birthTimeStatus: "reported",
countryCode: "",
provinceCode: "",
cityCode: "",
districtCode: "",
birthPlaceLabel: "北京",
birthPlaceType: "city",
birthPlaceProvider: "test",
birthPlaceProviderId: "beijing",
timezoneId: "Asia/Shanghai",
timezoneSource: "manual",
latitude: 39.9,
longitude: 116.4,
timezoneOffset: 8,
rectificationCaseId: "",
ayanamsa: "raman",
} satisfies Profile;
const catalog: PublicLanguageModelCatalog = {
models: [{ id: "m", label: "M", description: "", creditCost: 1, isDefault: true }],
defaultModelId: "m",
};
function jsonResponse(body: unknown, status = 200) {
return {
ok: status >= 200 && status < 300,
status,
json: async () => body,
} as Response;
}
function installLocation(search: string) {
const location = {
pathname: "/",
search,
href: `https://jyotisha.chat/${search}`,
};
const history = {
replaceState(_data: null, _title: string, next: string) {
const url = new URL(next, "https://jyotisha.chat");
location.pathname = url.pathname;
location.search = url.search;
location.href = url.toString();
},
pushState(_data: null, _title: string, next: string) {
history.replaceState(_data, _title, next);
},
};
const view = globalThis.window as { location?: typeof location; history?: typeof history };
view.location = location;
view.history = history;
Object.assign(globalThis, { location, history });
}
function Probe() {
const [rectification, setRectification] = useState(EMPTY_RECTIFICATION_SHELL);
const activeSessionIdRef = useRef("");
const sessionSelectionSource = useRef<"user" | "history">("user");
const uiPreview = useRef(false);
const rectificationSessionOpenerRef = useRef<(exactSessionId: string) => Promise<void>>(async () => undefined);
useRectificationSurface({
account: { id: "user-1" } as unknown as Account,
activeSessionIdRef,
chartLibrary: [],
creatingSession: false,
modelCatalog: catalog,
persistSession: async () => undefined,
profile,
rectification,
setRectification,
sessionSelectionSource,
sessions: [],
setActiveSessionId: () => undefined,
setDraft: () => undefined,
setDraftEntrypoint: () => undefined,
setDraftTheme: () => undefined,
setOnboardingStep: () => undefined,
setProfileNotice: () => undefined,
setSessions: () => undefined,
uiPreview,
updateSession: () => undefined,
openAccountDialog: () => undefined,
refreshAccount: async () => undefined,
rectificationSessionOpenerRef,
});
return React.createElement("p", {
"data-testid": "rectify-result",
"data-error": rectification.error,
"data-session": rectification.sessionId ?? "",
});
}
async function openFromChart(disposition: "resumed" | "created") {
const posts: Array<{ intent?: string }> = [];
const previousFetch = globalThis.fetch;
const harness = createClientLifecycleHarness();
// 原值:打开流程不等校正界面代码,两次 idle(约 20 ms)就设好会话
// 新值:先把校正界面代码载入,再走同样的打开流程
// 原因:issue #124 校正界面改为按需下载,打开时要等它到齐才设会话(一次揭幕);
// tsx 现场转译 74 个文件远超 20 ms,本组测的是打开意图与会话,不是下载时长
await loadRectificationSurface();
installLocation("?rectify=1");
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
const url = String(input);
if (url === "/api/rectification/cases/open") {
posts.push(JSON.parse(String(init?.body ?? "{}")) as { intent?: string });
return jsonResponse({
disposition,
caseId: "case-1",
sessionId: SESSION_ID,
status: "draft",
shouldStartOpening: disposition === "created",
skillVersion: "10.0.33",
});
}
if (url.includes("/source")) return jsonResponse({ source: null });
if (url.startsWith("/api/sessions/")) return jsonResponse({ session: null });
return jsonResponse({});
}) as typeof fetch;
try {
await harness.render(React.createElement(Probe));
await harness.idle();
await harness.idle();
const node = harness.elements().find((element) => element.getAttribute("data-testid") === "rectify-result");
return {
posts,
box: {
errors: node?.getAttribute("data-error") ? [node.getAttribute("data-error") ?? ""] : [],
sessionId: node?.getAttribute("data-session") || null,
},
errors: harness.errors,
};
} finally {
globalThis.fetch = previousFetch;
await harness.close();
}
}
test("?rectify=1 resumes an open case with the homepage intent and does not show a conflict", async () => {
const { posts, box, errors } = await openFromChart("resumed");
assert.equal(errors.length, 0);
assert.equal(posts.length, 1);
assert.equal(posts[0]?.intent, "homepage");
assert.equal(box.sessionId, SESSION_ID);
assert.deepEqual(box.errors, []);
assert.equal(box.errors.join(""), "");
});
test("?rectify=1 starts a case with the homepage intent when none is open", async () => {
const { posts, box, errors } = await openFromChart("created");
assert.equal(errors.length, 0);
assert.equal(posts.length, 1);
assert.equal(posts[0]?.intent, "homepage");
assert.equal(box.sessionId, SESSION_ID);
assert.deepEqual(box.errors, []);
});