返回 DeepSeek-Reasonix
session-composer-lifecycle.test.tsx
根目录 / desktop / frontend / src / __tests__ / session-composer-lifecycle.test.tsx
1 import assert from "node:assert/strict";
2 import { readFileSync } from "node:fs";
3 import React, { act } from "react";
4 import { createRoot } from "react-dom/client";
5 import { JSDOM } from "jsdom";
6 import { useSessionComposerLifecycle } from "../app-runtime/useSessionComposerLifecycle";
7 import { useSessionComposerPersistence } from "../lib/sessionComposerPersistence";
8 import { makeSessionUIMock } from "../lib/sessionUIMock";
9 import { installDesktopHostStub } from "./desktopHostStub";
10
11 const dom = new JSDOM("<div id='root'></div>", { url: "http://localhost" });
12 Object.assign(globalThis, { window: dom.window, document: dom.window.document, IS_REACT_ACT_ENVIRONMENT: true });
13 const backend = makeSessionUIMock(async () => {});
14 let failSave = false;
15 const legacyCalls: string[] = [];
16 const retired = (name: string) => async () => { legacyCalls.push(name); throw Error("corrupt old draft database"); };
17 const stub = installDesktopHostStub({ ...backend,
18 SaveSessionComposerState: async (request: Parameters<typeof backend.SaveSessionComposerState>[0]) => {
19 if (failSave) throw Error("formal input save failed");
20 return backend.SaveSessionComposerState(request);
21 },
22 ListSessionDraftSummaries: retired("list"), RestoreSessionDraft: retired("restore"), SaveSessionDraft: retired("save"),
23 });
24 const ref = { hostId: "local", sessionId: "formal-lifecycle" };
25 let editor!: ReturnType<typeof useSessionComposerPersistence>;
26 function Probe() {
27 useSessionComposerLifecycle();
28 editor = useSessionComposerPersistence(ref, ref.sessionId);
29 return null;
30 }
31 const root = createRoot(document.getElementById("root")!);
32 const patch = (text: string) => {
33 const target = editor.target!;
34 target.onPatch!(target.draftId, target.generation, { text });
35 };
36 try {
37 await act(async () => { root.render(<Probe />); });
38 await act(async () => { patch("keep formal input"); });
39 let release!: () => void;
40 const pending = new Promise<void>(resolve => { release = resolve; });
41 const target = editor.target!;
42 target.trackTask!(target.draftId, target.generation, pending.then(() => patch("attachment finished")));
43 let finished = false;
44 let flushing!: Promise<void>;
45 act(() => { flushing = window.__reasonixFlushSessionDraft!().then(() => { finished = true; }); });
46 assert.equal(finished, false);
47 await act(async () => { release(); await flushing; });
48 assert.equal(JSON.parse((await backend.GetSessionComposerState(ref)).contentJson).text, "attachment finished");
49 await act(async () => { window.__reasonixResumeSessionDraftEditing!(); });
50 failSave = true;
51 await act(async () => patch("keep on failure"));
52 await act(async () => { await assert.rejects(window.__reasonixFlushSessionDraft!(), /formal input save failed/); });
53 failSave = false;
54 await act(async () => { await editor.retry(); });
55 assert.equal(editor.blocked, false, "failed exit resumes formal editing");
56 assert.deepEqual(legacyCalls, [], "startup and shutdown never inspect retired drafts");
57
58 // The production mount must use this owner, otherwise isolated hook tests
59 // would miss a reintroduced legacy startup/read/exit path.
60 const runtime = readFileSync(new URL("../AppRuntime.tsx", import.meta.url), "utf8");
61 assert.match(runtime, /useSessionComposerLifecycle\(\)/);
62 assert.doesNotMatch(runtime, /useSessionDraftSurface|acceptedDraftSession/);
63 const view = readFileSync(new URL("../app-shell/AppRuntimeView.tsx", import.meta.url), "utf8");
64 assert.doesNotMatch(view, /draft\.surface|PreviousInputRecovery|DraftTopicbarActions/);
65 await act(async () => root.unmount());
66 assert.equal(window.__reasonixFlushSessionDraft, undefined);
67 assert.equal(window.__reasonixResumeSessionDraftEditing, undefined);
68 console.log("PASS formal input lifecycle: exit attachment barrier, save failure, unmount, no legacy reads");
69 } finally {
70 stub.uninstall();
71 dom.window.close();
72 }
73
73 lines Plain Text