1use super::*;
2use fs::FakeFs;
3use gpui::TestAppContext;
4use project::{DisableAiSettings, ProjectGroupKey};
5use serde_json::json;
6use settings::SettingsStore;
7
8fn init_test(cx: &mut TestAppContext) {
9 cx.update(|cx| {
10 let settings_store = SettingsStore::test(cx);
11 cx.set_global(settings_store);
12 theme_settings::init(theme::LoadThemes::JustBase, cx);
13 DisableAiSettings::register(cx);
14 });
15}
16
17#[gpui::test]
18async fn test_sidebar_disabled_when_disable_ai_is_enabled(cx: &mut TestAppContext) {
19 init_test(cx);
20 let fs = FakeFs::new(cx.executor());
21 let project = Project::test(fs, [], cx).await;
22
23 let (multi_workspace, cx) =
24 cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
25
26 multi_workspace.read_with(cx, |mw, cx| {
27 assert!(mw.multi_workspace_enabled(cx));
28 });
29
30 multi_workspace.update_in(cx, |mw, _window, cx| {
31 mw.open_sidebar(cx);
32 assert!(mw.sidebar_open());
33 });
34
35 cx.update(|_window, cx| {
36 DisableAiSettings::override_global(DisableAiSettings { disable_ai: true }, cx);
37 });
38 cx.run_until_parked();
39
40 multi_workspace.read_with(cx, |mw, cx| {
41 assert!(
42 !mw.sidebar_open(),
43 "Sidebar should be closed when disable_ai is true"
44 );
45 assert!(
46 !mw.multi_workspace_enabled(cx),
47 "Multi-workspace should be disabled when disable_ai is true"
48 );
49 });
50
51 multi_workspace.update_in(cx, |mw, window, cx| {
52 mw.toggle_sidebar(window, cx);
53 });
54 multi_workspace.read_with(cx, |mw, _cx| {
55 assert!(
56 !mw.sidebar_open(),
57 "Sidebar should remain closed when toggled with disable_ai true"
58 );
59 });
60
61 cx.update(|_window, cx| {
62 DisableAiSettings::override_global(DisableAiSettings { disable_ai: false }, cx);
63 });
64 cx.run_until_parked();
65
66 multi_workspace.read_with(cx, |mw, cx| {
67 assert!(
68 mw.multi_workspace_enabled(cx),
69 "Multi-workspace should be enabled after re-enabling AI"
70 );
71 assert!(
72 !mw.sidebar_open(),
73 "Sidebar should still be closed after re-enabling AI (not auto-opened)"
74 );
75 });
76
77 multi_workspace.update_in(cx, |mw, window, cx| {
78 mw.toggle_sidebar(window, cx);
79 });
80 multi_workspace.read_with(cx, |mw, _cx| {
81 assert!(
82 mw.sidebar_open(),
83 "Sidebar should open when toggled after re-enabling AI"
84 );
85 });
86}
87
88#[gpui::test]
89async fn test_project_group_keys_initial(cx: &mut TestAppContext) {
90 init_test(cx);
91 let fs = FakeFs::new(cx.executor());
92 fs.insert_tree("/root_a", json!({ "file.txt": "" })).await;
93 let project = Project::test(fs, ["/root_a".as_ref()], cx).await;
94
95 let expected_key = project.read_with(cx, |project, cx| project.project_group_key(cx));
96
97 let (multi_workspace, cx) =
98 cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
99
100 multi_workspace.update(cx, |mw, cx| {
101 mw.open_sidebar(cx);
102 });
103
104 multi_workspace.read_with(cx, |mw, _cx| {
105 let keys: Vec<&ProjectGroupKey> = mw.project_group_keys().collect();
106 assert_eq!(keys.len(), 1, "should have exactly one key on creation");
107 assert_eq!(*keys[0], expected_key);
108 });
109}
110
111#[gpui::test]
112async fn test_project_group_keys_add_workspace(cx: &mut TestAppContext) {
113 init_test(cx);
114 let fs = FakeFs::new(cx.executor());
115 fs.insert_tree("/root_a", json!({ "file.txt": "" })).await;
116 fs.insert_tree("/root_b", json!({ "file.txt": "" })).await;
117 let project_a = Project::test(fs.clone(), ["/root_a".as_ref()], cx).await;
118 let project_b = Project::test(fs.clone(), ["/root_b".as_ref()], cx).await;
119
120 let key_a = project_a.read_with(cx, |p, cx| p.project_group_key(cx));
121 let key_b = project_b.read_with(cx, |p, cx| p.project_group_key(cx));
122 assert_ne!(
123 key_a, key_b,
124 "different roots should produce different keys"
125 );
126
127 let (multi_workspace, cx) =
128 cx.add_window_view(|window, cx| MultiWorkspace::test_new(project_a, window, cx));
129
130 multi_workspace.update(cx, |mw, cx| {
131 mw.open_sidebar(cx);
132 });
133
134 multi_workspace.read_with(cx, |mw, _cx| {
135 assert_eq!(mw.project_group_keys().count(), 1);
136 });
137
138 // Adding a workspace with a different project root adds a new key.
139 multi_workspace.update_in(cx, |mw, window, cx| {
140 mw.test_add_workspace(project_b, window, cx);
141 });
142
143 multi_workspace.read_with(cx, |mw, _cx| {
144 let keys: Vec<&ProjectGroupKey> = mw.project_group_keys().collect();
145 assert_eq!(
146 keys.len(),
147 2,
148 "should have two keys after adding a second workspace"
149 );
150 assert_eq!(*keys[0], key_b);
151 assert_eq!(*keys[1], key_a);
152 });
153}
154
155#[gpui::test]
156async fn test_project_group_keys_duplicate_not_added(cx: &mut TestAppContext) {
157 init_test(cx);
158 let fs = FakeFs::new(cx.executor());
159 fs.insert_tree("/root_a", json!({ "file.txt": "" })).await;
160 let project_a = Project::test(fs.clone(), ["/root_a".as_ref()], cx).await;
161 // A second project entity pointing at the same path produces the same key.
162 let project_a2 = Project::test(fs.clone(), ["/root_a".as_ref()], cx).await;
163
164 let key_a = project_a.read_with(cx, |p, cx| p.project_group_key(cx));
165 let key_a2 = project_a2.read_with(cx, |p, cx| p.project_group_key(cx));
166 assert_eq!(key_a, key_a2, "same root path should produce the same key");
167
168 let (multi_workspace, cx) =
169 cx.add_window_view(|window, cx| MultiWorkspace::test_new(project_a, window, cx));
170
171 multi_workspace.update(cx, |mw, cx| {
172 mw.open_sidebar(cx);
173 });
174
175 multi_workspace.update_in(cx, |mw, window, cx| {
176 mw.test_add_workspace(project_a2, window, cx);
177 });
178
179 multi_workspace.read_with(cx, |mw, _cx| {
180 let keys: Vec<&ProjectGroupKey> = mw.project_group_keys().collect();
181 assert_eq!(
182 keys.len(),
183 1,
184 "duplicate key should not be added when a workspace with the same root is inserted"
185 );
186 });
187}
188
189#[gpui::test]
190async fn test_project_group_keys_on_worktree_added(cx: &mut TestAppContext) {
191 init_test(cx);
192 let fs = FakeFs::new(cx.executor());
193 fs.insert_tree("/root_a", json!({ "file.txt": "" })).await;
194 fs.insert_tree("/root_b", json!({ "file.txt": "" })).await;
195 let project = Project::test(fs, ["/root_a".as_ref()], cx).await;
196
197 let initial_key = project.read_with(cx, |p, cx| p.project_group_key(cx));
198
199 let (multi_workspace, cx) =
200 cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
201
202 multi_workspace.update(cx, |mw, cx| {
203 mw.open_sidebar(cx);
204 });
205
206 // Add a second worktree to the same project.
207 let (worktree, _) = project
208 .update(cx, |project, cx| {
209 project.find_or_create_worktree("/root_b", true, cx)
210 })
211 .await
212 .unwrap();
213 worktree
214 .read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
215 .await;
216 cx.run_until_parked();
217
218 let updated_key = project.read_with(cx, |p, cx| p.project_group_key(cx));
219 assert_ne!(
220 initial_key, updated_key,
221 "key should change after adding a worktree"
222 );
223
224 multi_workspace.read_with(cx, |mw, _cx| {
225 let keys: Vec<&ProjectGroupKey> = mw.project_group_keys().collect();
226 assert_eq!(
227 keys.len(),
228 2,
229 "should have both the original and updated key"
230 );
231 assert_eq!(*keys[0], updated_key);
232 assert_eq!(*keys[1], initial_key);
233 });
234}
235
236#[gpui::test]
237async fn test_project_group_keys_on_worktree_removed(cx: &mut TestAppContext) {
238 init_test(cx);
239 let fs = FakeFs::new(cx.executor());
240 fs.insert_tree("/root_a", json!({ "file.txt": "" })).await;
241 fs.insert_tree("/root_b", json!({ "file.txt": "" })).await;
242 let project = Project::test(fs, ["/root_a".as_ref(), "/root_b".as_ref()], cx).await;
243
244 let initial_key = project.read_with(cx, |p, cx| p.project_group_key(cx));
245
246 let (multi_workspace, cx) =
247 cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
248
249 multi_workspace.update(cx, |mw, cx| {
250 mw.open_sidebar(cx);
251 });
252
253 // Remove one worktree.
254 let worktree_b_id = project.read_with(cx, |project, cx| {
255 project
256 .worktrees(cx)
257 .find(|wt| wt.read(cx).root_name().as_unix_str() == "root_b")
258 .unwrap()
259 .read(cx)
260 .id()
261 });
262 project.update(cx, |project, cx| {
263 project.remove_worktree(worktree_b_id, cx);
264 });
265 cx.run_until_parked();
266
267 let updated_key = project.read_with(cx, |p, cx| p.project_group_key(cx));
268 assert_ne!(
269 initial_key, updated_key,
270 "key should change after removing a worktree"
271 );
272
273 multi_workspace.read_with(cx, |mw, _cx| {
274 let keys: Vec<&ProjectGroupKey> = mw.project_group_keys().collect();
275 assert_eq!(
276 keys.len(),
277 2,
278 "should accumulate both the original and post-removal key"
279 );
280 assert_eq!(*keys[0], updated_key);
281 assert_eq!(*keys[1], initial_key);
282 });
283}
284
285#[gpui::test]
286async fn test_project_group_keys_across_multiple_workspaces_and_worktree_changes(
287 cx: &mut TestAppContext,
288) {
289 init_test(cx);
290 let fs = FakeFs::new(cx.executor());
291 fs.insert_tree("/root_a", json!({ "file.txt": "" })).await;
292 fs.insert_tree("/root_b", json!({ "file.txt": "" })).await;
293 fs.insert_tree("/root_c", json!({ "file.txt": "" })).await;
294 let project_a = Project::test(fs.clone(), ["/root_a".as_ref()], cx).await;
295 let project_b = Project::test(fs.clone(), ["/root_b".as_ref()], cx).await;
296
297 let key_a = project_a.read_with(cx, |p, cx| p.project_group_key(cx));
298 let key_b = project_b.read_with(cx, |p, cx| p.project_group_key(cx));
299
300 let (multi_workspace, cx) =
301 cx.add_window_view(|window, cx| MultiWorkspace::test_new(project_a.clone(), window, cx));
302
303 multi_workspace.update(cx, |mw, cx| {
304 mw.open_sidebar(cx);
305 });
306
307 multi_workspace.update_in(cx, |mw, window, cx| {
308 mw.test_add_workspace(project_b, window, cx);
309 });
310
311 multi_workspace.read_with(cx, |mw, _cx| {
312 assert_eq!(mw.project_group_keys().count(), 2);
313 });
314
315 // Now add a worktree to project_a. This should produce a third key.
316 let (worktree, _) = project_a
317 .update(cx, |project, cx| {
318 project.find_or_create_worktree("/root_c", true, cx)
319 })
320 .await
321 .unwrap();
322 worktree
323 .read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
324 .await;
325 cx.run_until_parked();
326
327 let key_a_updated = project_a.read_with(cx, |p, cx| p.project_group_key(cx));
328 assert_ne!(key_a, key_a_updated);
329
330 multi_workspace.read_with(cx, |mw, _cx| {
331 let keys: Vec<&ProjectGroupKey> = mw.project_group_keys().collect();
332 assert_eq!(
333 keys.len(),
334 3,
335 "should have key_a, key_b, and the updated key_a with root_c"
336 );
337 assert_eq!(*keys[0], key_a_updated);
338 assert_eq!(*keys[1], key_b);
339 assert_eq!(*keys[2], key_a);
340 });
341}