multi_workspace_tests.rs

  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.update(cx, |mw, cx| {
103        mw.open_sidebar(cx);
104    });
105
106    multi_workspace.read_with(cx, |mw, _cx| {
107        let keys: Vec<&ProjectGroupKey> = mw.project_group_keys().collect();
108        assert_eq!(keys.len(), 1, "should have exactly one key on creation");
109        assert_eq!(*keys[0], expected_key);
110    });
111}
112
113#[gpui::test]
114async fn test_project_group_keys_add_workspace(cx: &mut TestAppContext) {
115    init_test(cx);
116    let fs = FakeFs::new(cx.executor());
117    fs.insert_tree("/root_a", json!({ "file.txt": "" })).await;
118    fs.insert_tree("/root_b", json!({ "file.txt": "" })).await;
119    let project_a = Project::test(fs.clone(), ["/root_a".as_ref()], cx).await;
120    let project_b = Project::test(fs.clone(), ["/root_b".as_ref()], cx).await;
121
122    let key_a = project_a.read_with(cx, |p, cx| p.project_group_key(cx));
123    let key_b = project_b.read_with(cx, |p, cx| p.project_group_key(cx));
124    assert_ne!(
125        key_a, key_b,
126        "different roots should produce different keys"
127    );
128
129    let (multi_workspace, cx) =
130        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project_a, window, cx));
131
132    multi_workspace.update(cx, |mw, cx| {
133        mw.open_sidebar(cx);
134    });
135
136    multi_workspace.read_with(cx, |mw, _cx| {
137        assert_eq!(mw.project_group_keys().count(), 1);
138    });
139
140    // Adding a workspace with a different project root adds a new key.
141    multi_workspace.update_in(cx, |mw, window, cx| {
142        mw.test_add_workspace(project_b, window, cx);
143    });
144
145    multi_workspace.read_with(cx, |mw, _cx| {
146        let keys: Vec<&ProjectGroupKey> = mw.project_group_keys().collect();
147        assert_eq!(
148            keys.len(),
149            2,
150            "should have two keys after adding a second workspace"
151        );
152        assert_eq!(*keys[0], key_a);
153        assert_eq!(*keys[1], key_b);
154    });
155}
156
157#[gpui::test]
158async fn test_project_group_keys_duplicate_not_added(cx: &mut TestAppContext) {
159    init_test(cx);
160    let fs = FakeFs::new(cx.executor());
161    fs.insert_tree("/root_a", json!({ "file.txt": "" })).await;
162    let project_a = Project::test(fs.clone(), ["/root_a".as_ref()], cx).await;
163    // A second project entity pointing at the same path produces the same key.
164    let project_a2 = Project::test(fs.clone(), ["/root_a".as_ref()], cx).await;
165
166    let key_a = project_a.read_with(cx, |p, cx| p.project_group_key(cx));
167    let key_a2 = project_a2.read_with(cx, |p, cx| p.project_group_key(cx));
168    assert_eq!(key_a, key_a2, "same root path should produce the same key");
169
170    let (multi_workspace, cx) =
171        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project_a, window, cx));
172
173    multi_workspace.update(cx, |mw, cx| {
174        mw.open_sidebar(cx);
175    });
176
177    multi_workspace.update_in(cx, |mw, window, cx| {
178        mw.test_add_workspace(project_a2, window, cx);
179    });
180
181    multi_workspace.read_with(cx, |mw, _cx| {
182        let keys: Vec<&ProjectGroupKey> = mw.project_group_keys().collect();
183        assert_eq!(
184            keys.len(),
185            1,
186            "duplicate key should not be added when a workspace with the same root is inserted"
187        );
188    });
189}
190
191#[gpui::test]
192async fn test_project_group_keys_on_worktree_added(cx: &mut TestAppContext) {
193    init_test(cx);
194    let fs = FakeFs::new(cx.executor());
195    fs.insert_tree("/root_a", json!({ "file.txt": "" })).await;
196    fs.insert_tree("/root_b", json!({ "file.txt": "" })).await;
197    let project = Project::test(fs, ["/root_a".as_ref()], cx).await;
198
199    let initial_key = project.read_with(cx, |p, cx| p.project_group_key(cx));
200
201    let (multi_workspace, cx) =
202        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
203
204    multi_workspace.update(cx, |mw, cx| {
205        mw.open_sidebar(cx);
206    });
207
208    // Add a second worktree to the same project.
209    let (worktree, _) = project
210        .update(cx, |project, cx| {
211            project.find_or_create_worktree("/root_b", true, cx)
212        })
213        .await
214        .unwrap();
215    worktree
216        .read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
217        .await;
218    cx.run_until_parked();
219
220    let updated_key = project.read_with(cx, |p, cx| p.project_group_key(cx));
221    assert_ne!(
222        initial_key, updated_key,
223        "key should change after adding a worktree"
224    );
225
226    multi_workspace.read_with(cx, |mw, _cx| {
227        let keys: Vec<&ProjectGroupKey> = mw.project_group_keys().collect();
228        assert_eq!(
229            keys.len(),
230            2,
231            "should have both the original and updated key"
232        );
233        assert_eq!(*keys[0], initial_key);
234        assert_eq!(*keys[1], updated_key);
235    });
236}
237
238#[gpui::test]
239async fn test_project_group_keys_on_worktree_removed(cx: &mut TestAppContext) {
240    init_test(cx);
241    let fs = FakeFs::new(cx.executor());
242    fs.insert_tree("/root_a", json!({ "file.txt": "" })).await;
243    fs.insert_tree("/root_b", json!({ "file.txt": "" })).await;
244    let project = Project::test(fs, ["/root_a".as_ref(), "/root_b".as_ref()], cx).await;
245
246    let initial_key = project.read_with(cx, |p, cx| p.project_group_key(cx));
247
248    let (multi_workspace, cx) =
249        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
250
251    multi_workspace.update(cx, |mw, cx| {
252        mw.open_sidebar(cx);
253    });
254
255    // Remove one worktree.
256    let worktree_b_id = project.read_with(cx, |project, cx| {
257        project
258            .worktrees(cx)
259            .find(|wt| wt.read(cx).root_name().as_unix_str() == "root_b")
260            .unwrap()
261            .read(cx)
262            .id()
263    });
264    project.update(cx, |project, cx| {
265        project.remove_worktree(worktree_b_id, cx);
266    });
267    cx.run_until_parked();
268
269    let updated_key = project.read_with(cx, |p, cx| p.project_group_key(cx));
270    assert_ne!(
271        initial_key, updated_key,
272        "key should change after removing a worktree"
273    );
274
275    multi_workspace.read_with(cx, |mw, _cx| {
276        let keys: Vec<&ProjectGroupKey> = mw.project_group_keys().collect();
277        assert_eq!(
278            keys.len(),
279            2,
280            "should accumulate both the original and post-removal key"
281        );
282        assert_eq!(*keys[0], initial_key);
283        assert_eq!(*keys[1], updated_key);
284    });
285}
286
287#[gpui::test]
288async fn test_project_group_keys_across_multiple_workspaces_and_worktree_changes(
289    cx: &mut TestAppContext,
290) {
291    init_test(cx);
292    let fs = FakeFs::new(cx.executor());
293    fs.insert_tree("/root_a", json!({ "file.txt": "" })).await;
294    fs.insert_tree("/root_b", json!({ "file.txt": "" })).await;
295    fs.insert_tree("/root_c", json!({ "file.txt": "" })).await;
296    let project_a = Project::test(fs.clone(), ["/root_a".as_ref()], cx).await;
297    let project_b = Project::test(fs.clone(), ["/root_b".as_ref()], cx).await;
298
299    let key_a = project_a.read_with(cx, |p, cx| p.project_group_key(cx));
300    let key_b = project_b.read_with(cx, |p, cx| p.project_group_key(cx));
301
302    let (multi_workspace, cx) =
303        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project_a.clone(), window, cx));
304
305    multi_workspace.update(cx, |mw, cx| {
306        mw.open_sidebar(cx);
307    });
308
309    multi_workspace.update_in(cx, |mw, window, cx| {
310        mw.test_add_workspace(project_b, window, cx);
311    });
312
313    multi_workspace.read_with(cx, |mw, _cx| {
314        assert_eq!(mw.project_group_keys().count(), 2);
315    });
316
317    // Now add a worktree to project_a. This should produce a third key.
318    let (worktree, _) = project_a
319        .update(cx, |project, cx| {
320            project.find_or_create_worktree("/root_c", true, cx)
321        })
322        .await
323        .unwrap();
324    worktree
325        .read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
326        .await;
327    cx.run_until_parked();
328
329    let key_a_updated = project_a.read_with(cx, |p, cx| p.project_group_key(cx));
330    assert_ne!(key_a, key_a_updated);
331
332    multi_workspace.read_with(cx, |mw, _cx| {
333        let keys: Vec<&ProjectGroupKey> = mw.project_group_keys().collect();
334        assert_eq!(
335            keys.len(),
336            3,
337            "should have key_a, key_b, and the updated key_a with root_c"
338        );
339        assert_eq!(*keys[0], key_a);
340        assert_eq!(*keys[1], key_b);
341        assert_eq!(*keys[2], key_a_updated);
342    });
343}