返回 DeepSeek-Reasonix
composerInboxHarness.tsx
根目录 / desktop / frontend / src / __tests__ / composerInboxHarness.tsx
1 import { JSDOM } from "jsdom";
2 import React from "react";
3 import { act } from "react";
4 import { createRoot } from "react-dom/client";
5 import { Composer } from "../components/Composer";
6 import { LocaleProvider } from "../lib/i18n";
7 import { ToastProvider } from "../lib/toast";
8 import { installDesktopHostStub } from "./desktopHostStub";
9 import type { CollaborationMode, ToolApprovalMode } from "../lib/types";
10
11 const flushTimers = () => new Promise<void>(resolve => setTimeout(resolve, 0));
12
13 class TestResizeObserver {
14 observe() {}
15 unobserve() {}
16 disconnect() {}
17 }
18
19 export function installDom(language = "en-US") {
20 const dom = new JSDOM("<!doctype html><html><body><div id=\"root\"></div></body></html>", {
21 pretendToBeVisual: true,
22 url: "http://localhost/",
23 });
24 (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
25 globalThis.window = dom.window as unknown as Window & typeof globalThis;
26 globalThis.document = dom.window.document;
27 Object.defineProperty(globalThis, "navigator", { configurable: true, value: dom.window.navigator });
28 Object.defineProperty(dom.window.navigator, "language", { configurable: true, value: language });
29 globalThis.Node = dom.window.Node;
30 globalThis.HTMLElement = dom.window.HTMLElement;
31 globalThis.HTMLTextAreaElement = dom.window.HTMLTextAreaElement;
32 globalThis.Event = dom.window.Event;
33 globalThis.CustomEvent = dom.window.CustomEvent;
34 globalThis.KeyboardEvent = dom.window.KeyboardEvent;
35 globalThis.InputEvent = dom.window.InputEvent;
36 globalThis.MouseEvent = dom.window.MouseEvent;
37 globalThis.PointerEvent = dom.window.MouseEvent as unknown as typeof PointerEvent;
38 globalThis.MutationObserver = dom.window.MutationObserver;
39 globalThis.File = dom.window.File;
40 globalThis.FileReader = dom.window.FileReader;
41 globalThis.localStorage = dom.window.localStorage;
42 globalThis.requestAnimationFrame = dom.window.requestAnimationFrame.bind(dom.window);
43 globalThis.cancelAnimationFrame = dom.window.cancelAnimationFrame.bind(dom.window);
44 globalThis.ResizeObserver = TestResizeObserver;
45 Object.defineProperty(dom.window.HTMLElement.prototype, "attachEvent", { configurable: true, value: () => {} });
46 Object.defineProperty(dom.window.HTMLElement.prototype, "detachEvent", { configurable: true, value: () => {} });
47 Object.defineProperty(window, "matchMedia", {
48 configurable: true,
49 value: () => ({
50 matches: true,
51 media: "(prefers-reduced-motion: reduce)",
52 onchange: null,
53 addEventListener() {},
54 removeEventListener() {},
55 addListener() {},
56 removeListener() {},
57 dispatchEvent: () => false,
58 }),
59 });
60 return dom;
61 }
62
63 export function installBridgeApp(methods: Record<string, unknown>) {
64 installDesktopHostStub({
65 Commands: async () => [],
66 Models: async () => [],
67 ModelsForTab: async () => [],
68 ListDir: async () => [],
69 ListDirForTab: async () => [],
70 ListDirForTarget: async () => [],
71 SearchFileRefs: async () => [],
72 SearchFileRefsForTab: async () => [],
73 SearchFileRefsForTarget: async () => [],
74 ...methods,
75 });
76 }
77
78 export async function renderComposer(props: Partial<Parameters<typeof Composer>[0]> = {}, strictMode = false) {
79 const rootEl = document.getElementById("root");
80 if (!rootEl) throw new Error("missing root");
81 const root = createRoot(rootEl);
82 let currentProps: Parameters<typeof Composer>[0] = {
83 running: false,
84 collaborationMode: "normal" as CollaborationMode,
85 toolApprovalMode: "ask" as ToolApprovalMode,
86
87 goal: "",
88 cwd: "/repo",
89 modelLabel: "DeepSeek-R1",
90 tabId: "tab-a",
91 sessionKey: "session-a",
92 inboxSessionPath: "session-a",
93 onSend: () => {},
94 onCancel: async () => ({ discardedItemIds: [] }),
95 onCycleMode: () => {},
96 onSetMode: () => {},
97 onSetCollaborationMode: () => {},
98 onSetToolApprovalMode: () => {},
99 onClearGoal: () => {},
100 onSwitchModel: () => {},
101 onSetEffort: () => {},
102
103 ready: true,
104 ...props,
105 };
106 const paint = async (nextProps: Partial<Parameters<typeof Composer>[0]> = {}) => {
107 currentProps = { ...currentProps, ...nextProps };
108 const view = (
109 <LocaleProvider>
110 <ToastProvider>
111 <Composer {...currentProps} />
112 </ToastProvider>
113 </LocaleProvider>
114 );
115 await act(async () => {
116 root.render(strictMode ? <React.StrictMode>{view}</React.StrictMode> : view);
117 await flushTimers();
118 });
119 };
120 await paint();
121 return { root, rerender: paint };
122 }
123
123 lines Plain Text