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