返回 DeepSeek-Reasonix
remote-composer-presentation.test.tsx
根目录 / desktop / frontend / src / __tests__ / remote-composer-presentation.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 { Composer } from "../components/Composer";
6 import { LocaleProvider } from "../lib/i18n";
7 import { ToastProvider } from "../lib/toast";
8 import { projectConversation } from "../app-runtime/conversationProjection";
9 import { initialState } from "../lib/useController";
10 import { installDesktopHostStub } from "./desktopHostStub";
11
12 const dom = new JSDOM("<div id='root'></div>", { url: "http://localhost/", pretendToBeVisual: true });
13 Object.assign(globalThis, { window: dom.window, document: dom.window.document, localStorage: dom.window.localStorage,
14 IS_REACT_ACT_ENVIRONMENT: true, requestAnimationFrame: () => 1, cancelAnimationFrame() {},
15 ResizeObserver: class { observe() {} disconnect() {} unobserve() {} },
16 });
17 for (const name of ["Node", "Element", "HTMLElement", "HTMLTextAreaElement", "Event", "CustomEvent", "MutationObserver", "File"]) {
18 Object.defineProperty(globalThis, name, { configurable: true, value: Reflect.get(dom.window, name) });
19 }
20 Object.defineProperty(globalThis, "navigator", { configurable: true, value: dom.window.navigator });
21 window.matchMedia = (() => ({ matches: true, addEventListener() {}, removeEventListener() {} })) as typeof window.matchMedia;
22 Object.defineProperty(dom.window.HTMLElement.prototype, "attachEvent", { value() {} });
23 Object.defineProperty(dom.window.HTMLElement.prototype, "detachEvent", { value() {} });
24 const calls: string[] = [];
25 let localWrites = 0;
26 const forbidden = async () => { localWrites++; throw new Error("remote surface called local file/inbox mutation"); };
27 installDesktopHostStub({ SavePastedFile: forbidden, SavePastedImage: forbidden,
28 SavePastedFileForTarget: forbidden, SavePastedImageForTarget: forbidden,
29 ModelsForTab: async () => [], ListInboxItems: async () => [],
30 EnqueueInboxFollowup: forbidden, EnqueueInboxSteer: forbidden, EnqueueInboxSteerForTurn: forbidden,
31 EnqueueInboxFollowupWithInvocations: forbidden,
32 });
33 const root = createRoot(document.getElementById("root")!);
34 const noop = () => {};
35 const view = projectConversation({ local: initialState, remote: { transcript: initialState, running: true,
36 modelLabel: "remote fixture", commands: [] }, activeTabId: "remote-A", backgroundRuntimes: [], connectingLabel: "connecting" });
37 try {
38 await act(async () => root.render(<LocaleProvider><ToastProvider><Composer {...view.composer}
39 tabId="remote-A" sessionKey="remote-A" collaborationMode="normal" toolApprovalMode="ask" goal="" ready
40 onSend={() => { calls.push("send"); }} onSteer={async (text, tab) => { calls.push(`steer:${tab}:${text}`); }}
41 onCancel={noop} onCycleMode={noop} onSetMode={noop} onSetCollaborationMode={noop}
42 onSetToolApprovalMode={noop} onClearGoal={noop}
43 onSwitchModel={noop} onSetEffort={noop} insertRequest={{ id: 1, text: "remote guidance", mode: "replace" }}
44 /></ToastProvider></LocaleProvider>));
45 const input = document.querySelector<HTMLInputElement>("input[type=file]")!;
46 assert.equal(input.disabled, true);
47 assert.equal(document.querySelector<HTMLElement>(".composer-wrap")!.hasAttribute("data-native-drop-target"), false);
48 await act(async () => {
49 Object.defineProperty(input, "files", { value: [new File(["fixture"], "fixture.txt")] });
50 input.dispatchEvent(new Event("change", { bubbles: true }));
51 });
52 const button = document.querySelector<HTMLButtonElement>(".composer__btn--send")!;
53 assert.equal(button.disabled, false);
54 await act(async () => button.click());
55 assert.deepEqual(calls, ["steer:remote-A:remote guidance"], "running remote input uses the remote steer port, never conversational submit");
56 assert.equal(localWrites, 0);
57 assert.equal(document.querySelector<HTMLTextAreaElement>("#composer-input")!.value, "");
58 console.log("remote Composer: real file control, native drop boundary and running guidance preserve source routing");
59 } finally { await act(async () => root.unmount()); dom.window.close(); }
60
60 lines Plain Text