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