返回 DeepSeek-Reasonix
composer-source-operations.test.tsx
根目录 / desktop / frontend / src / __tests__ / composer-source-operations.test.tsx
1 import assert from "node:assert/strict";
2 import React, { act } from "react";
3 import { createRoot } from "react-dom/client";
4 import { JSDOM } from "jsdom";
5 import { useSessionOperations } from "../app-runtime/useSessionOperations";
6 import { useComposerModeActions } from "../lib/useComposerModeActions";
7 import { executeComposerMode, type ComposerModePorts } from "../app-runtime/composerModeOwner";
8
9 const dom = new JSDOM("<div id='root'></div>", { url: "http://localhost" });
10 Object.assign(globalThis, { window: dom.window, document: dom.window.document, IS_REACT_ACT_ENVIRONMENT: true });
11 const root = createRoot(document.getElementById("root")!);
12 const effects: string[] = [];
13 let release!: () => void;
14 let gate = new Promise<void>(resolve => { release = resolve; });
15 const resetGate = () => { effects.length = 0; gate = new Promise<void>(resolve => { release = resolve; }); };
16 const planIntentsRef = { current: {} };
17 const yoloRestoreRef = { current: {} };
18 // The hook owns rememberPlan/rememberApproval through these refs; the owner
19 // still accepts them as ports, exercised directly below.
20 const ports: Omit<ComposerModePorts, "rememberPlan" | "rememberApproval"> = {
21 setMode: async id => { effects.push(`mode:${id}`); },
22 setCollaboration: async id => { effects.push(`collaboration:${id}`); },
23 setApproval: async id => { effects.push(`approval:${id}`); },
24 clearGoal: async id => { effects.push(`clear:${id}`); await gate; },
25 setRemote: async id => { effects.push(`remote:${id}`); await gate; return ["approval-A"]; },
26 drainRemote: id => { effects.push(`drain:${id}`); },
27 patch: id => { effects.push(`patch:${id}`); },
28 };
29 let commands!: ReturnType<typeof useComposerModeActions>;
30 let operations!: ReturnType<typeof useSessionOperations>;
31 function Probe({ id, remote = false, generation = "" }: { id: string; remote?: boolean; generation?: string }) {
32 operations = useSessionOperations({ visible: { tabId: id, sessionKey: id + generation }, resources: ["A", "B"].map(tabId => ({ tabId, sessionKey: tabId + generation })) });
33 commands = useComposerModeActions({
34 remote, collaborationMode: "goal", toolApprovalMode: "ask", goal: "task",
35 target: { tabId: id, sessionKey: id + generation }, operations, ports,
36 planIntentsRef, yoloRestoreRef,
37 showError: message => effects.push(`error:${message}`),
38 });
39 return null;
40 }
41 async function paint(id: string, remote = false, generation = "") {
42 await act(async () => root.render(<Probe id={id} remote={remote} generation={generation} />));
43 }
44 try {
45 await paint("A");
46 const pending = commands.applyCollaborationMode("normal");
47 assert.deepEqual(effects, ["clear:A"]);
48 await paint("B");
49 release();
50 await act(async () => { await pending; });
51 assert.deepEqual(effects, ["clear:A", "collaboration:A", "patch:A"], "every continuation mutates the captured source, never B");
52 assert.equal(planIntentsRef.current["A"], undefined, "normal mode records no plan intent for the source tab");
53 resetGate();
54 await paint("A", true);
55 const remote = commands.applyCollaborationMode("normal");
56 await paint("B", true);
57 await paint("A", true);
58 release();
59 await act(async () => { await remote; });
60 assert.deepEqual(effects, ["remote:A", "patch:A"], "A→B→A preserves source data but never revives approval-drain UI ownership");
61
62 resetGate();
63 await paint("A");
64 const replaced = commands.applyCollaborationMode("normal");
65 await paint("A", false, ":new");
66 release();
67 await act(async () => { await replaced; });
68 assert.deepEqual(effects, ["clear:A"], "reused tab with a new session identity blocks every stale continuation");
69
70 resetGate();
71 await paint("A");
72 const rerendered = commands.applyCollaborationMode("normal");
73 const stop = await operations({ tabId: "A", sessionKey: "A" }, "stop", "A", async (id, authority) => {
74 authority.checkpoint(); effects.push(`stop:${id}`);
75 });
76 assert.equal(stop.status, "completed", "waiting profile does not block stop");
77 await paint("A");
78 release();
79 await act(async () => { await rerendered; });
80 assert.deepEqual(effects, ["clear:A", "stop:A", "collaboration:A", "patch:A"], "ordinary commit does not cancel an in-flight source request");
81
82 resetGate();
83 const stale = commands.applyCollaborationMode("normal");
84 const releaseStale = release;
85 gate = new Promise<void>(resolve => { release = resolve; });
86 const latest = commands.applyCollaborationMode("plan");
87 releaseStale();
88 await act(async () => { await stale; });
89 assert.deepEqual(effects, ["clear:A", "clear:A"], "superseded continuation has zero side effects");
90 release();
91 await act(async () => { await latest; });
92 assert.deepEqual(effects, ["clear:A", "clear:A", "collaboration:A", "patch:A"], "old finally cannot release the new request");
93
94 resetGate();
95 const disposed = commands.applyCollaborationMode("normal");
96 const oldEntry = commands;
97 await act(async () => root.unmount());
98 release();
99 await disposed;
100 oldEntry.applyMode("normal");
101 assert.deepEqual(effects, ["clear:A"], "unmount synchronously revokes commands and pending continuations");
102 const writes: unknown[][] = [];
103 const remotePorts: ComposerModePorts = { ...ports,
104 rememberPlan: id => { effects.push(`plan:${id}`); },
105 rememberApproval: id => { effects.push(`remember:${id}`); },
106 setRemote: async (...args) => { writes.push(args); return []; },
107 clearGoal: async () => { throw new Error("atomic remote transition cannot use local goal clearing"); },
108 setCollaboration: async () => { throw new Error("atomic remote transition cannot use local mode changes"); },
109 setMode: () => { throw new Error("remote mode cannot use local mode changes"); },
110 setApproval: () => { throw new Error("remote approval cannot use local mode changes"); },
111 };
112 for (const request of [{ kind: "collaboration", mode: "normal" }, { kind: "approval", mode: "yolo" }] as const) {
113 await executeComposerMode({ target: { tabId: "A", sessionKey: "A" }, request,
114 remote: true, collaborationMode: "goal", toolApprovalMode: "ask", goal: "task", ports: remotePorts,
115 }, { checkpoint() {}, ownsUI: () => true });
116 }
117 assert.deepEqual(writes, [["A", "normal", "ask", ""], ["A", "goal", "yolo", "task"]], "each remote change sends every axis in one atomic profile transaction");
118 console.log("composer source operations: source isolation, ABA, identity replacement, lanes, finally and disposal passed");
119 } finally {
120 if (document.getElementById("root")?.hasChildNodes()) await act(async () => root.unmount());
121 dom.window.close();
122 }
123
123 lines Plain Text