返回 DeepSeek-Reasonix
workspace-panel-commands.test.tsx
根目录 / desktop / frontend / src / __tests__ / workspace-panel-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 { useWorkspacePanelCommands } from "../app-runtime/useWorkspacePanelCommands";
6 import { useSessionNavigationCommands, type SessionNavigationCommandsInput } from "../app-runtime/useSessionNavigationCommands";
7 import { loadWorkspacePanelOpen, saveWorkspacePanelOpen, useLayoutStore } from "../store/layout";
8 import { useActivityBarStore } from "../store/activityBar";
9 import { useRemoteStore } from "../store/remote";
10 import type { RemoteHostView } from "../lib/types";
11
12 const dom = new JSDOM("<div id='root'></div>", { url: "http://localhost" });
13 Object.assign(globalThis, { window: dom.window, document: dom.window.document, localStorage: dom.window.localStorage,
14 IS_REACT_ACT_ENVIRONMENT: true });
15 const root = createRoot(document.getElementById("root")!);
16 let commands!: ReturnType<typeof useWorkspacePanelCommands>;
17 let closes = 0; let widthClears = 0;
18 const closeOverlays = () => { closes++; };
19 const clearLiveWidth = () => { widthClears++; };
20 let restoredWidth = 0;
21 const globalRoot = "/fixture/global-workspace";
22 let navigation!: ReturnType<typeof useSessionNavigationCommands>;
23 let navigationRequest: unknown;
24 let draftRequest: unknown;
25 const setTreeWidth = (width: number) => { restoredWidth = width; };
26 function Probe({ workspace, visible, sessionId }: { workspace: string; visible: boolean; sessionId: string }) {
27 commands = useWorkspacePanelCommands({ sessionId, workspaceRoot: workspace, visible, closeOverlays, clearLiveWidth,
28 availableWidth: 800, clampTreeWidth: (width) => width, setTreeWidth, gridOpen: visible, t: (key: string) => key } as never);
29 navigation = useSessionNavigationCommands({
30 activeTab: { id: "fixture", scope: workspace === globalRoot ? "global" : "project", workspaceRoot: workspace },
31 closeTransientOverlays: closeOverlays, clearImDetail: () => {}, prepareBlankWorkspace: commands.prepareBlankWorkspace,
32 enterConversation: () => {},
33 draft: { open: async (scope, workspaceRoot) => { draftRequest = { scope, workspaceRoot }; }, dismiss: () => {} },
34 navigation: { enqueueNavigation: async request => { navigationRequest = request; } },
35 } as SessionNavigationCommandsInput);
36 return null;
37 }
38 const paint = (workspace: string, visible = false, sessionId = `session:${workspace}`) =>
39 act(async () => root.render(<Probe workspace={workspace} visible={visible} sessionId={sessionId} />));
40 try {
41 saveWorkspacePanelOpen(false, "A"); saveWorkspacePanelOpen(true, "B");
42 await paint("A");
43 const first = commands;
44 assert.equal(useLayoutStore.getState().workspacePanelOpen, false);
45 await act(async () => commands.openRightDockMode("changed"));
46 assert.equal(loadWorkspacePanelOpen("A"), true);
47 await paint("A", true);
48 await act(async () => { commands.toggleWorkspaceMaximized(); commands.handleWorkspacePreviewModeChange(true); });
49 assert.equal(useLayoutStore.getState().workspacePanelMaximized, true);
50 await act(async () => commands.openRightDockMode("context"));
51 assert.equal(useLayoutStore.getState().workspacePanelMaximized, false);
52 assert.equal(useLayoutStore.getState().workspacePreviewActive, false);
53 await act(async () => commands.toggleWorkspacePanel());
54 assert.equal(loadWorkspacePanelOpen("A"), false);
55 assert.equal(widthClears, 1);
56 await paint("B");
57 assert.equal(useLayoutStore.getState().workspacePanelOpen, true, "different project restores its own preference");
58 await paint("B", true);
59 assert.deepEqual(useActivityBarStore.getState().tabs.map(tab => tab.type), ["context"],
60 "an expanded empty dock opens Overview when a session becomes active");
61 const overviewTabId = useActivityBarStore.getState().activeTabId!;
62 await act(async () => useActivityBarStore.getState().closeTab(overviewTabId));
63 await paint("B", true);
64 assert.deepEqual(useActivityBarStore.getState().tabs, [],
65 "closing the default tab does not reopen it during the same session");
66 assert.equal(useLayoutStore.getState().workspacePanelOpen, false,
67 "closing the final dock tab collapses the entire workspace panel");
68 assert.equal(loadWorkspacePanelOpen("B"), false, "automatic collapse persists for the current project");
69 await paint("B", true, "session:B:next");
70 assert.deepEqual(useActivityBarStore.getState().tabs, [], "a new session preserves the collapsed dock");
71 await act(async () => commands.toggleWorkspacePanel());
72 await paint("B", true, "session:B:next");
73 assert.deepEqual(useActivityBarStore.getState().tabs.map(tab => tab.type), ["context"],
74 "explicitly reopening the dock in a new session seeds Overview");
75 await act(async () => commands.openRightDockMode("files"));
76 await act(async () => useActivityBarStore.getState().closeTab(useActivityBarStore.getState().activeTabId!));
77 assert.equal(useLayoutStore.getState().workspacePanelOpen, true, "closing one of multiple tabs keeps the dock open");
78 await act(async () => {
79 commands.openRightDockMode("changed");
80 commands.toggleWorkspaceMaximized();
81 });
82 const beforeCloseAll = widthClears;
83 await act(async () => {
84 useActivityBarStore.getState().tabs.forEach(tab => useActivityBarStore.getState().closeTab(tab.id));
85 assert.equal(useLayoutStore.getState().workspacePanelOpen, false, "close-all collapses synchronously");
86 });
87 assert.equal(useLayoutStore.getState().workspacePanelMaximized, false, "closing all tabs resets maximized mode");
88 assert.equal(widthClears, beforeCloseAll + 1, "automatic collapse clears the live resize width once");
89 await act(async () => commands.openDockEntry("files"));
90 assert.equal(useLayoutStore.getState().workspacePanelOpen, true, "opening an entry expands the dock again");
91 assert.deepEqual(useActivityBarStore.getState().tabs.map(tab => tab.type), ["file"]);
92 await act(async () => {
93 useActivityBarStore.getState().closeTab(useActivityBarStore.getState().activeTabId!);
94 commands.openRightDockMode("changed");
95 });
96 assert.equal(useLayoutStore.getState().workspacePanelOpen, true, "a later open in the same batch wins over the close");
97 assert.equal(loadWorkspacePanelOpen("B"), true);
98 saveWorkspacePanelOpen(true, "empty-project");
99 await paint("empty-project");
100 assert.equal(useLayoutStore.getState().workspacePanelOpen, true,
101 "restoring an empty project is not mistaken for closing the final tab");
102 assert.equal(loadWorkspacePanelOpen("B"), true, "project restoration does not overwrite the previous preference");
103 await paint("A");
104 assert.equal(useLayoutStore.getState().workspacePanelOpen, false);
105 assert.equal(commands.closeWorkspacePanel, first.closeWorkspacePanel);
106 assert.equal(commands.openRightDockMode, first.openRightDockMode);
107 await act(async () => commands.openRightDockMode("files"));
108 const hosts = [{ id: "offline" }, { id: "online" }] as RemoteHostView[];
109 await act(async () => {
110 useRemoteStore.getState().setHosts(hosts);
111 useRemoteStore.getState().applyStatus({ hostId: "online", state: "connected" });
112 commands.openRemoteDock();
113 });
114 assert.equal(useRemoteStore.getState().explorerHostId, "online");
115 assert.equal(useRemoteStore.getState().explorerOpen, false, "request is consumed by the same dock owner");
116 assert.equal(useLayoutStore.getState().rightDockMode, "remote");
117 await act(async () => { commands.restoreWorkspaceDockWidths(640, 0); });
118 assert.equal(restoredWidth, 640, "dock width restore clamps through the owner and writes the layout store port");
119 await act(async () => useRemoteStore.getState().setHosts([]));
120 assert.equal(useLayoutStore.getState().rightDockMode, "files");
121 await paint("A");
122 await act(async () => commands.openRightDockMode("changed"));
123 saveWorkspacePanelOpen(true, "B");
124 await act(async () => {
125 commands.toggleWorkspaceMaximized();
126 commands.prepareBlankWorkspace("B");
127 });
128 assert.equal(useLayoutStore.getState().workspacePanelOpen, false, "new-session intent collapses the dock immediately");
129 assert.equal(useLayoutStore.getState().workspacePanelMaximized, false);
130 assert.equal(loadWorkspacePanelOpen("A"), true, "another project's preference is untouched");
131 await paint("B");
132 assert.equal(useLayoutStore.getState().workspacePanelOpen, false, "destination restoration cannot reopen the blank-session dock");
133 await act(async () => commands.openRightDockMode("files"));
134 await paint("B");
135 assert.equal(useLayoutStore.getState().workspacePanelOpen, true, "manual open stays open on subsequent renders");
136 await paint("A");
137 assert.equal(useLayoutStore.getState().workspacePanelOpen, true, "ordinary project navigation retains restoration behavior");
138 saveWorkspacePanelOpen(true, "");
139 saveWorkspacePanelOpen(true, globalRoot);
140 await act(async () => navigation.openBlankSession("global", globalRoot));
141 assert.deepEqual(draftRequest, { scope: "global", workspaceRoot: "" }, "global draft requests retain the empty root contract");
142 assert.equal(navigationRequest, undefined, "local new-session navigation does not create a formal blank session");
143 assert.equal(loadWorkspacePanelOpen(""), true, "global creation does not overwrite the legacy fallback for other projects");
144 await paint(globalRoot);
145 assert.equal(useLayoutStore.getState().workspacePanelOpen, false, "global destination restoration cannot reopen the new-session dock");
146 await act(async () => commands.openRightDockMode("files"));
147 await paint("A");
148 await paint(globalRoot);
149 assert.equal(useLayoutStore.getState().workspacePanelOpen, true, "manual global preference still restores on ordinary navigation");
150 await act(async () => navigation.handleNewTab());
151 assert.equal(loadWorkspacePanelOpen(globalRoot), false, "new-session toolbar uses the active global directory");
152 assert.deepEqual(draftRequest, { scope: "global", workspaceRoot: "" });
153 await paint("A");
154 assert.equal(useLayoutStore.getState().workspacePanelOpen, true, "global creation preserves the source project's preference");
155 await act(async () => root.unmount());
156 const before = { closes, widthClears, layout: useLayoutStore.getState() };
157 first.openRightDockMode("changed"); first.toggleWorkspaceMaximized(); first.closeWorkspacePanel();
158 assert.deepEqual({ closes, widthClears, layout: useLayoutStore.getState() }, before, "disposed entries cannot change layout or project preferences");
159 console.log("workspace commands: scoped restoration, preview/maximize, remote requests and synchronous disposal passed");
160 } finally { dom.window.close(); }
161
161 lines Plain Text