返回 DeepSeek-Reasonix
remote-composer-profile-sync.test.tsx
根目录 / desktop / frontend / src / __tests__ / remote-composer-profile-sync.test.tsx
1 // Run: tsx src/__tests__/remote-composer-profile-sync.test.tsx
2 // useRemoteComposerProfileSync adopts the backend profile when a remote tab
3 // rotates to another session, but a tab switch away and back is not a
4 // rotation: the tab's in-flight mode/goal choice (pending flags) must survive
5 // A -> B -> A, and forgotten tabs must not read as rotations when reopened.
6 import assert from "node:assert/strict";
7 import React, { act, useState } from "react";
8 import { createRoot } from "react-dom/client";
9 import { JSDOM } from "jsdom";
10 import { useRemoteComposerProfileSync } from "../lib/useRemoteComposerIntegration";
11 import type { ComposerProfile, ComposerProfilesByTab } from "../lib/composerProfile";
12 import type { RemoteSessionApi } from "../lib/useRemoteSession";
13
14 const dom = new JSDOM("<div id='root'></div>");
15 Object.assign(globalThis, { window: dom.window, document: dom.window.document, IS_REACT_ACT_ENVIRONMENT: true });
16
17 type RemoteProfile = NonNullable<RemoteSessionApi["composerProfile"]>;
18 const backend: RemoteProfile = { collaborationMode: "normal", toolApprovalMode: "ask", goal: "", qualityFloor: "standard" };
19 const chosen = (tabId: string): ComposerProfile => ({ collaborationMode: "plan", goalDraftMode: false, toolApprovalMode: "ask", goal: "", qualityFloor: "standard", pending: { collaborationMode: true } });
20 let profiles: ComposerProfilesByTab = {};
21 let setProfilesRef: React.Dispatch<React.SetStateAction<ComposerProfilesByTab>> | undefined;
22 function Probe({ activeTabId, sessionRoute }: { activeTabId: string; sessionRoute: string }) {
23 const [state, setState] = useState<ComposerProfilesByTab>({ A: chosen("A"), B: chosen("B") });
24 profiles = state;
25 setProfilesRef = setState;
26 const profile = state[activeTabId];
27 useRemoteComposerProfileSync({ activeTabId, sessionRoute, remote: true, remoteProfile: backend,
28 collaborationMode: profile?.collaborationMode ?? "normal", toolApprovalMode: profile?.toolApprovalMode ?? "ask",
29 goal: profile?.goal ?? "", qualityFloor: profile?.qualityFloor ?? "standard", pending: profile?.pending ?? {}, setProfiles: setState });
30 return null;
31 }
32 const root = createRoot(document.getElementById("root")!);
33 const show = (activeTabId: string, sessionRoute: string) => act(async () => { root.render(React.createElement(Probe, { activeTabId, sessionRoute })); });
34 const edit = (update: (current: ComposerProfilesByTab) => ComposerProfilesByTab) => act(async () => { setProfilesRef?.(update); });
35
36 try {
37 await show("A", "session-id:a1");
38 assert.equal(profiles.A.pending.collaborationMode, true, "first sync of a tab reconciles over its existing choice");
39 assert.equal(profiles.A.collaborationMode, "plan");
40 await show("B", "session-id:b1");
41 assert.equal(profiles.B.pending.collaborationMode, true, "B's first sync keeps B's choice");
42 await show("A", "session-id:a1");
43 assert.equal(profiles.A.pending.collaborationMode, true, "A -> B -> A preserves A's pending choice");
44 assert.equal(profiles.A.collaborationMode, "plan", "A -> B -> A keeps the chosen mode visible");
45 assert.equal(profiles.B.pending.collaborationMode, true, "switching back to A leaves B untouched");
46 await show("A", "session-id:a2");
47 assert.deepEqual(profiles.A.pending, {}, "A's own session rotation adopts the backend profile");
48 assert.equal(profiles.A.collaborationMode, "normal", "the rotated session shows the backend mode");
49 assert.equal(profiles.B.pending.collaborationMode, true, "A's rotation does not touch B");
50 // Closing B drops it from the profile table; the next sync (any status or
51 // route change on the active tab) forgets B's route, so a later tab with
52 // the same id on another session reads as a first sync, not a rotation.
53 await edit(({ B: _closed, ...rest }) => rest);
54 await show("A", "session-id:a3");
55 await edit((current) => ({ ...current, B: chosen("B") }));
56 await show("B", "session-id:b2");
57 assert.equal(profiles.B.pending.collaborationMode, true, "a tab id forgotten after its removal is not treated as a session rotation");
58 await show("B", "session-id:b3");
59 assert.deepEqual(profiles.B.pending, {}, "the re-registered tab still adopts the backend on its next real rotation");
60 console.log("remote composer profile sync: per-tab route memory, A->B->A preservation, rotation adoption and pruning passed");
61 } finally {
62 await act(async () => root.unmount());
63 }
64
64 lines Plain Text