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