multi_workspace_tests.rs

  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}