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.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}