返回 DeepSeek-Reasonix
remote-composer-commands.test.tsx
根目录 / desktop / frontend / src / __tests__ / remote-composer-commands.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 { useRemoteComposerSend, useRemoteComposerRuntimeActions } from "../lib/useRemoteComposerIntegration";
6 import { useSessionOperations } from "../app-runtime/useSessionOperations";
7 import type { RemoteSessionApi } from "../lib/useRemoteSession";
8 import { useRemoteNavigationCommand } from "../lib/remoteNavigationCommands";
9 import { RemoteNavigationHarness } from "./helpers/RemoteNavigationHarness";
10 import { LocaleProvider } from "../lib/i18n";
11 import { installDesktopHostStub } from "./desktopHostStub";
12
13 const dom = new JSDOM("<div id='root'></div>");
14 Object.assign(globalThis, { window: dom.window, document: dom.window.document, IS_REACT_ACT_ENVIRONMENT: true });
15 const root = createRoot(document.getElementById("root")!);
16 function deferred() { let resolve!: () => void; const promise = new Promise<void>(done => { resolve = done; }); return { promise, resolve }; }
17 let gate = deferred();
18 const calls: string[] = [];
19 installDesktopHostStub({
20 RegisterNavigationIntent: async () => { calls.push("navigation-intent"); },
21 OpenRemoteProjectTab: async (host: string, workspace: string, options: { newSession?: boolean }) => { calls.push(`new:${host}:${workspace}:${options.newSession}`); },
22 });
23 const session = {
24 setModel: async (value: string) => { calls.push(`model:${value}`); },
25 setEffort: async (value: string) => { calls.push(`effort:${value}`); },
26 compact: async (value: string) => { calls.push(`compact:${value}`); },
27 runManagementCommand: async (value: string, hydrate?: boolean) => { calls.push(`manage:${value}:${hydrate}`); },
28 pauseGoal: async () => { calls.push("remote-pause"); },
29 resumeGoal: async () => { calls.push("remote-resume"); },
30 retryHydration: async () => { calls.push("hydrate"); },
31 } as unknown as RemoteSessionApi;
32 let send!: ReturnType<typeof useRemoteComposerSend>;
33 let runtime!: ReturnType<typeof useRemoteComposerRuntimeActions>;
34 let action: Promise<unknown> | undefined;
35 function Probe({ tab, generation = "1", remote = true }: { tab: string; generation?: string; remote?: boolean }) {
36 const navigateRemote = useRemoteNavigationCommand();
37 const target = { tabId: tab, sessionKey: tab + generation };
38 const operations = useSessionOperations({ visible: target, resources: ["A", "B"].map(tabId => ({ tabId, sessionKey: tabId + generation })) });
39 send = useRemoteComposerSend({ hostId: "fixture", workspace: "fixture" }, tab, "goal", "", session,
40 async (display, submit) => { calls.push(`send:${tab}:${display}:${submit}`); },
41 async (id, goal) => { calls.push(`goal:${id}:${goal}`); await gate.promise; },
42 () => { calls.push("clear"); }, { target, operations, navigateRemote });
43 runtime = useRemoteComposerRuntimeActions({ target, operations, remote, session,
44 runGoalAction: run => { action = Promise.resolve(run()); },
45 pauseLocal: async id => { calls.push(`pause:${id}`); }, resumeLocal: async id => { calls.push(`resume:${id}`); },
46 setLocalEffort: async (id, level) => { calls.push(`local-effort:${id}:${level}`); }, showError: message => { throw Error(message); } });
47 return null;
48 }
49 const paint = (tab: string, generation = "1", remote = true) => act(async () => root.render(<LocaleProvider><RemoteNavigationHarness><Probe tab={tab} generation={generation} remote={remote} /></RemoteNavigationHarness></LocaleProvider>));
50 try {
51 await paint("A");
52 await send("/model model-fixture"); await send("/effort high"); await send("/compact fixture");
53 await send("/context"); await send("/clear");
54 assert.deepEqual(calls, ["model:model-fixture", "effort:high", "compact:fixture", "manage:/context:false", "clear"]);
55 calls.length = 0;
56 await send("/new");
57 assert.deepEqual(calls, ["navigation-intent", "new:fixture:fixture:true"], "new-session uses the common owner; Serve lifecycle hydrates the target instead of the captured source callback");
58 calls.length = 0;
59 const pending = send("display", " submit bytes ");
60 assert.deepEqual(calls, ["goal:A:submit bytes"]);
61 await paint("B"); gate.resolve(); await pending;
62 assert.deepEqual(calls, ["goal:A:submit bytes", "send:A:display: submit bytes "], "goal and send keep source A and preserve provider-visible submit bytes");
63 calls.length = 0; gate = deferred(); await paint("A");
64 const replaced = send("next"); await paint("A", "2"); gate.resolve(); await replaced;
65 assert.deepEqual(calls, ["goal:A:next"], "replacement session receives no stale post-goal submit");
66 calls.length = 0;
67 runtime.pauseGoal(); await action; runtime.resumeGoal(); await action;
68 assert.deepEqual(calls, ["remote-pause", "remote-resume"]);
69 calls.length = 0; await paint("A", "2", false);
70 runtime.pauseGoal(); await action; runtime.resumeGoal(); await action; runtime.setEffort("max");
71 await act(async () => {});
72 assert.deepEqual(calls, ["pause:A", "resume:A", "local-effort:A:max"]);
73 calls.length = 0; gate = deferred(); await paint("A");
74 const disposed = send("disposed"); await act(async () => root.unmount()); gate.resolve(); await disposed;
75 runtime.pauseGoal();
76 assert.deepEqual(calls, ["goal:A:disposed"]);
77 console.log("remote composer commands: management routing, source Goal/send, byte preservation, runtime ports and disposal passed");
78 } finally { dom.window.close(); }
79
79 lines Plain Text