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>
191 lines
6.4 KiB
TypeScript
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, []);
|
|
});
|