返回 DeepSeek-Reasonix
workspace-split.test.ts
根目录 / desktop / frontend / src / __tests__ / workspace-split.test.ts
1 // Run: tsx src/__tests__/workspace-split.test.ts
2
3 import {
4 initialWorkspaceSplitTreeWidth,
5 resolveWorkspaceSplitTreeWidth,
6 shouldInitializeWorkspaceSplitOnFileSelect,
7 workspaceSplitCanFit,
8 workspaceSplitTreeWidthFromPointer,
9 } from "../lib/workspaceSplit";
10 import { resolveWorkspacePanelWidth } from "../lib/workspaceLayout";
11 import { closeWorkspacePreviewTab } from "../lib/workspacePreviewTabs";
12 import { shouldScrollWorkspaceTreeSelection } from "../lib/workspaceTreeReveal";
13 import { mergeWorkspaceSearchResults } from "../lib/workspaceTreeSearch";
14
15 let passed = 0;
16 let failed = 0;
17
18 function eq(a: unknown, b: unknown, label: string) {
19 if (a === b) {
20 process.stdout.write(` PASS ${label}\n`);
21 passed += 1;
22 } else {
23 process.stdout.write(` FAIL ${label}: expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}\n`);
24 failed += 1;
25 }
26 }
27
28 console.log("\nworkspace file split");
29
30 const TREE_MIN_WIDTH = 140;
31 const TREE_RAIL_WIDTH = 44;
32 const PREVIEW_MIN_WIDTH = 140;
33
34 eq(
35 initialWorkspaceSplitTreeWidth({
36 panelWidth: 660,
37 railWidth: TREE_RAIL_WIDTH,
38 savedTreeWidth: null,
39 treeMinWidth: TREE_MIN_WIDTH,
40 previewMinWidth: PREVIEW_MIN_WIDTH,
41 }),
42 308,
43 "first split divides the file area evenly after reserving the tree rail",
44 );
45
46 eq(
47 initialWorkspaceSplitTreeWidth({
48 panelWidth: 660,
49 railWidth: TREE_RAIL_WIDTH,
50 savedTreeWidth: 620,
51 treeMinWidth: TREE_MIN_WIDTH,
52 previewMinWidth: PREVIEW_MIN_WIDTH,
53 }),
54 476,
55 "tree width is clamped so the preview keeps its minimum width",
56 );
57
58 eq(
59 workspaceSplitTreeWidthFromPointer({
60 clientX: 400,
61 panelLeft: 100,
62 panelWidth: 660,
63 railWidth: TREE_RAIL_WIDTH,
64 treeMinWidth: TREE_MIN_WIDTH,
65 previewMinWidth: PREVIEW_MIN_WIDTH,
66 }),
67 256,
68 "tree resize pointer coordinates start after the tree rail",
69 );
70
71 eq(
72 workspaceSplitTreeWidthFromPointer({
73 clientX: 700,
74 panelLeft: 100,
75 panelWidth: 660,
76 railWidth: TREE_RAIL_WIDTH,
77 treeMinWidth: TREE_MIN_WIDTH,
78 previewMinWidth: PREVIEW_MIN_WIDTH,
79 }),
80 476,
81 "tree resize pointer clamps against the preview minimum after reserving the rail",
82 );
83
84 eq(
85 resolveWorkspaceSplitTreeWidth({
86 mode: "even",
87 currentTreeWidth: 140,
88 panelWidth: 660,
89 railWidth: TREE_RAIL_WIDTH,
90 treeMinWidth: TREE_MIN_WIDTH,
91 previewMinWidth: PREVIEW_MIN_WIDTH,
92 }),
93 308,
94 "default split recomputes evenly after the parent preview width applies",
95 );
96
97 eq(
98 resolveWorkspaceSplitTreeWidth({
99 mode: "manual",
100 currentTreeWidth: 256,
101 panelWidth: 660,
102 railWidth: TREE_RAIL_WIDTH,
103 treeMinWidth: TREE_MIN_WIDTH,
104 previewMinWidth: PREVIEW_MIN_WIDTH,
105 }),
106 256,
107 "manual split width is preserved when the parent width changes",
108 );
109
110 eq(
111 resolveWorkspacePanelWidth({
112 open: true,
113 maximized: false,
114 preferredWidth: 660,
115 minWidth: 300,
116 availableWidth: 228,
117 }),
118 228,
119 "outer file area can still shrink below split target width",
120 );
121
122 eq(
123 workspaceSplitCanFit({
124 panelWidth: 323,
125 railWidth: TREE_RAIL_WIDTH,
126 treeMinWidth: TREE_MIN_WIDTH,
127 previewMinWidth: PREVIEW_MIN_WIDTH,
128 }),
129 false,
130 "file tree collapses before rail tree and preview would overflow a narrow panel",
131 );
132
133 eq(
134 workspaceSplitCanFit({
135 panelWidth: 324,
136 railWidth: TREE_RAIL_WIDTH,
137 treeMinWidth: TREE_MIN_WIDTH,
138 previewMinWidth: PREVIEW_MIN_WIDTH,
139 }),
140 true,
141 "file tree remains available at the exact split minimum width",
142 );
143
144 eq(
145 workspaceSplitCanFit({
146 panelWidth: undefined,
147 railWidth: TREE_RAIL_WIDTH,
148 treeMinWidth: TREE_MIN_WIDTH,
149 previewMinWidth: PREVIEW_MIN_WIDTH,
150 }),
151 true,
152 "file tree stays visible until the measured panel width is known",
153 );
154
155 const closedRecentPreview = closeWorkspacePreviewTab(["a.ts", "b.ts", "c.ts"], "c.ts");
156 eq(closedRecentPreview.selectedPath, "b.ts", "closing the current preview falls back to the previous recent file");
157 eq(closedRecentPreview.openTabs.join(","), "a.ts,b.ts", "closing one preview preserves the other recent files");
158
159 eq(
160 shouldInitializeWorkspaceSplitOnFileSelect({ previewVisible: false, treeVisible: true }),
161 true,
162 "opening the first preview initializes an even file split",
163 );
164
165 eq(
166 shouldInitializeWorkspaceSplitOnFileSelect({ previewVisible: true, treeVisible: true }),
167 false,
168 "switching files while preview and tree are visible preserves the manual split",
169 );
170
171 eq(
172 shouldInitializeWorkspaceSplitOnFileSelect({ previewVisible: true, treeVisible: false }),
173 true,
174 "revealing the hidden tree initializes an even file split",
175 );
176
177 const effectiveEvenTreeWidth = resolveWorkspaceSplitTreeWidth({
178 mode: "even",
179 currentTreeWidth: TREE_MIN_WIDTH,
180 panelWidth: 660,
181 railWidth: TREE_RAIL_WIDTH,
182 treeMinWidth: TREE_MIN_WIDTH,
183 previewMinWidth: PREVIEW_MIN_WIDTH,
184 });
185
186 eq(
187 resolveWorkspaceSplitTreeWidth({
188 mode: "manual",
189 currentTreeWidth: effectiveEvenTreeWidth,
190 panelWidth: 660,
191 railWidth: TREE_RAIL_WIDTH,
192 treeMinWidth: TREE_MIN_WIDTH,
193 previewMinWidth: PREVIEW_MIN_WIDTH,
194 }),
195 effectiveEvenTreeWidth,
196 "entering manual resize from even split keeps the currently rendered tree width",
197 );
198
199 eq(
200 shouldScrollWorkspaceTreeSelection({
201 selectedPath: "src/App.tsx",
202 pendingRevealPath: "src/App.tsx",
203 actualTreeVisible: true,
204 selectedIndex: 42,
205 }),
206 true,
207 "pending file reveal scrolls once the selected row is present",
208 );
209
210 eq(
211 shouldScrollWorkspaceTreeSelection({
212 selectedPath: "src/App.tsx",
213 pendingRevealPath: null,
214 actualTreeVisible: true,
215 selectedIndex: 42,
216 }),
217 false,
218 "manual folder changes do not re-scroll to the previous selected file",
219 );
220
221 eq(
222 shouldScrollWorkspaceTreeSelection({
223 selectedPath: "src/App.tsx",
224 pendingRevealPath: "src/App.tsx",
225 actualTreeVisible: true,
226 selectedIndex: -1,
227 }),
228 false,
229 "pending file reveal waits until async directory loading exposes the row",
230 );
231
232 eq(
233 mergeWorkspaceSearchResults(
234 [{ path: "README.md", entry: { name: "README.md", isDir: false } }],
235 [
236 { name: "src/deep/README.md", isDir: false },
237 { name: "README.md", isDir: false },
238 ],
239 ).map((row) => `${row.path}:${row.entry.name}`).join("|"),
240 "README.md:README.md|src/deep/README.md:README.md",
241 "workspace filter merges backend deep search results without duplicating loaded rows",
242 );
243
244 eq(
245 mergeWorkspaceSearchResults(
246 [{ path: "docs/assets/", entry: { name: "assets", isDir: true } }],
247 [
248 { name: "docs/assets", isDir: true },
249 { name: "docs/guides", isDir: true },
250 ],
251 ).map((row) => `${row.path}:${row.entry.name}:${row.entry.isDir}`).join("|"),
252 "docs/assets/:assets:true|docs/guides/:guides:true",
253 "workspace filter normalizes directory search results to tree row paths",
254 );
255
256 console.log(`\n${passed} passed, ${failed} failed`);
257 if (failed > 0) process.exit(1);
258
258 lines TYPESCRIPT