返回 DeepSeek-Reasonix
browser-dock-mode.test.tsx
根目录 / desktop / frontend / src / __tests__ / browser-dock-mode.test.tsx
1 // Run: tsx src/__tests__/browser-dock-mode.test.tsx
2 import assert from "node:assert/strict";
3 import React, { act } from "react";
4 import { createRoot } from "react-dom/client";
5 import { JSDOM } from "jsdom";
6
7 import { WorkspaceDockRegion, type WorkspaceDockRegionProps } from "../app-shell/WorkspaceDockRegion";
8 import type { DesktopBrowserHost } from "../lib/browserHost";
9 import type { ReasonixDesktopHost } from "../lib/desktopHost";
10 import { LocaleProvider, type Translator } from "../lib/i18n";
11 import { availableDockEntries } from "../lib/dockEntries";
12 import { useLayoutStore, type RightDockMode } from "../store/layout";
13 import { useActivityBarStore } from "../store/activityBar";
14
15 const dom = new JSDOM("<div id='root'></div>", { url: "http://localhost", pretendToBeVisual: true });
16 class TestResizeObserver { observe() {} unobserve() {} disconnect() {} }
17 Object.assign(globalThis, { window: dom.window, document: dom.window.document, localStorage: dom.window.localStorage,
18 IS_REACT_ACT_ENVIRONMENT: true, ResizeObserver: TestResizeObserver });
19 (dom.window as unknown as { ResizeObserver: unknown }).ResizeObserver = TestResizeObserver;
20
21 const noop = () => {};
22 const browser: DesktopBrowserHost = {
23 list: async () => [], open: async () => { throw new Error("unused"); }, close: async () => {}, activate: async () => {},
24 navigate: async () => {}, setZoom: async () => {}, toggleDevTools: async () => {}, resume: async () => {}, takeover: async () => {},
25 setLayout: noop, setOverlay: noop, onTabs: () => noop, onDownload: () => noop,
26 };
27 const electron: ReasonixDesktopHost = {
28 kind: "electron",
29 contract: { protocolVersion: 1, digest: "test", commands: [] },
30 platform: { os: "linux", arch: "x64", versions: {} },
31 invoke: async () => undefined,
32 on: () => noop,
33 native: {
34 openExternal: async () => {},
35 clipboard: { writeText: async () => true, readText: async () => "" },
36 window: { setTheme: noop, setBackgroundColour: noop, getBounds: async () => ({ x: 0, y: 0, width: 0, height: 0, maximised: false }),
37 isMaximised: async () => false, minimise: noop, toggleMaximise: noop, close: noop },
38 getPathForFile: () => "",
39 onServiceState: () => noop,
40 },
41 browser,
42 };
43
44 const modes: RightDockMode[] = [];
45 const picks: string[] = [];
46 const props = (mode: RightDockMode): WorkspaceDockRegionProps => ({
47 visible: true, overlay: false, mode, showContext: true,
48 t: ((key: string) => key) as Translator,
49 onPickEntry: (entryId: string) => { picks.push(entryId); },
50 remote: {} as WorkspaceDockRegionProps["remote"], context: {} as WorkspaceDockRegionProps["context"],
51 workspace: { tabId: "A" } as WorkspaceDockRegionProps["workspace"], workspaceKey: "k",
52 });
53 const root = createRoot(document.getElementById("root")!);
54 const paint = (mode: RightDockMode) => act(async () => root.render(<LocaleProvider><WorkspaceDockRegion {...props(mode)} /></LocaleProvider>));
55 const tabLabels = () => [...document.querySelectorAll(".workbench-dock__tab-label")].map((el) => el.textContent);
56 const settle = () => act(async () => { await new Promise((resolve) => setTimeout(resolve, 20)); });
57
58 console.log("\nbrowser dock mode");
59 try {
60 await paint("files");
61 assert.deepEqual(tabLabels(), [], "an empty tab list renders no dock tabs");
62 assert.equal(availableDockEntries(false).some((entry) => entry.defaultTab === "browser"), false,
63 "no shell host: the browser entry is not offered");
64 assert.deepEqual(modes, [], "an empty dock issues no mode command");
65
66 // With no tab open the dock offers the tab picker instead of a blank panel.
67 const picker = document.querySelector(".tab-picker");
68 assert.ok(picker, "an empty dock renders the tab picker");
69 const pickerEntries = [...picker!.querySelectorAll<HTMLButtonElement>(".tab-picker__item")];
70 assert.deepEqual(pickerEntries.map((button) => button.textContent), ["Overview", "Files", "Changes"],
71 "the picker lists every openable view, browser excluded without a shell host");
72 await act(async () => pickerEntries[0].click());
73 assert.deepEqual(picks, ["context"], "picking an entry opens that view through the shared command");
74
75 window.reasonixDesktop = electron;
76 assert.equal(availableDockEntries(true).some((entry) => entry.defaultTab === "browser"), true,
77 "the Electron host offers the browser entry");
78
79 await act(async () => { useActivityBarStore.getState().addTab("browser", "Browser"); });
80 await paint("browser");
81 await settle();
82 assert.deepEqual(tabLabels(), ["Browser"], "the browser tab renders in the dock");
83 assert.ok(document.querySelector(".browser-panel"), "the browser tab mounts the lazy panel in the dock body");
84 assert.ok(document.querySelector(".workbench-dock--browser"), "the dock carries the mode modifier");
85
86 useLayoutStore.getState().setRightDockMode("browser");
87 assert.equal(useLayoutStore.getState().rightDockMode, "browser", "the layout store accepts the browser dock mode");
88
89 const browserTabId = useActivityBarStore.getState().tabs[0].id;
90 await act(async () => { useActivityBarStore.getState().closeTab(browserTabId); });
91 await paint("files");
92 await settle();
93 assert.equal(document.querySelector(".browser-panel"), null, "closing the browser tab unmounts the panel");
94
95 delete window.reasonixDesktop;
96 await act(async () => root.unmount());
97 console.log("browser dock mode: gating, tab render and lazy mount passed");
98 } finally {
99 dom.window.close();
100 }
101
101 lines Plain Text