1use std::path::PathBuf;
2
3use super::*;
4use fs::FakeFs;
5use gpui::TestAppContext;
6use project::{DisableAiSettings, ProjectGroupKey};
7use serde_json::json;
8use settings::SettingsStore;
9use util::path;
10
11fn init_test(cx: &mut TestAppContext) {
12 cx.update(|cx| {
13 let settings_store = SettingsStore::test(cx);
14 cx.set_global(settings_store);
15 theme_settings::init(theme::LoadThemes::JustBase, cx);
16 DisableAiSettings::register(cx);
17 });
18}
19
20#[gpui::test]
21async fn test_sidebar_disabled_when_disable_ai_is_enabled(cx: &mut TestAppContext) {
22 init_test(cx);
23 let fs = FakeFs::new(cx.executor());
24 let project = Project::test(fs, [], cx).await;
25
26 let (multi_workspace, cx) =
27 cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
28
29 multi_workspace.read_with(cx, |mw, cx| {
30 assert!(mw.multi_workspace_enabled(cx));
31 });
32
33 multi_workspace.update_in(cx, |mw, _window, cx| {
34 mw.open_sidebar(cx);
35 assert!(mw.sidebar_open());
36 });
37
38 cx.update(|_window, cx| {
39 DisableAiSettings::override_global(DisableAiSettings { disable_ai: true }, cx);
40 });
41 cx.run_until_parked();
42
43 multi_workspace.read_with(cx, |mw, cx| {
44 assert!(
45 !mw.sidebar_open(),
46 "Sidebar should be closed when disable_ai is true"
47 );
48 assert!(
49 !mw.multi_workspace_enabled(cx),
50 "Multi-workspace should be disabled when disable_ai is true"
51 );
52 });
53
54 multi_workspace.update_in(cx, |mw, window, cx| {
55 mw.toggle_sidebar(window, cx);
56 });
57 multi_workspace.read_with(cx, |mw, _cx| {
58 assert!(
59 !mw.sidebar_open(),
60 "Sidebar should remain closed when toggled with disable_ai true"
61 );
62 });
63
64 cx.update(|_window, cx| {
65 DisableAiSettings::override_global(DisableAiSettings { disable_ai: false }, cx);
66 });
67 cx.run_until_parked();
68
69 multi_workspace.read_with(cx, |mw, cx| {
70 assert!(
71 mw.multi_workspace_enabled(cx),
72 "Multi-workspace should be enabled after re-enabling AI"
73 );
74 assert!(
75 !mw.sidebar_open(),
76 "Sidebar should still be closed after re-enabling AI (not auto-opened)"
77 );
78 });
79
80 multi_workspace.update_in(cx, |mw, window, cx| {
81 mw.toggle_sidebar(window, cx);
82 });
83 multi_workspace.read_with(cx, |mw, _cx| {
84 assert!(
85 mw.sidebar_open(),
86 "Sidebar should open when toggled after re-enabling AI"
87 );
88 });
89}
90
91#[gpui::test]
92async fn test_project_group_keys_initial(cx: &mut TestAppContext) {
93 init_test(cx);
94 let fs = FakeFs::new(cx.executor());
95 fs.insert_tree("/root_a", json!({ "file.txt": "" })).await;
96 let project = Project::test(fs, ["/root_a".as_ref()], cx).await;
97
98 let expected_key = project.read_with(cx, |project, cx| project.project_group_key(cx));
99
100 let (multi_workspace, cx) =
101 cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
102
103 multi_workspace.update(cx, |mw, cx| {
104 mw.open_sidebar(cx);
105 });
106
107 multi_workspace.read_with(cx, |mw, _cx| {
108 let keys: Vec<&ProjectGroupKey> = mw.project_group_keys().collect();
109 assert_eq!(keys.len(), 1, "should have exactly one key on creation");
110 assert_eq!(*keys[0], expected_key);
111 });
112}
113
114#[gpui::test]
115async fn test_project_group_keys_add_workspace(cx: &mut TestAppContext) {
116 init_test(cx);
117 let fs = FakeFs::new(cx.executor());
118 fs.insert_tree("/root_a", json!({ "file.txt": "" })).await;
119 fs.insert_tree("/root_b", json!({ "file.txt": "" })).await;
120 let project_a = Project::test(fs.clone(), ["/root_a".as_ref()], cx).await;
121 let project_b = Project::test(fs.clone(), ["/root_b".as_ref()], cx).await;
122
123 let key_a = project_a.read_with(cx, |p, cx| p.project_group_key(cx));
124 let key_b = project_b.read_with(cx, |p, cx| p.project_group_key(cx));
125 assert_ne!(
126 key_a, key_b,
127 "different roots should produce different keys"
128 );
129
130 let (multi_workspace, cx) =
131 cx.add_window_view(|window, cx| MultiWorkspace::test_new(project_a, window, cx));
132
133 multi_workspace.update(cx, |mw, cx| {
134 mw.open_sidebar(cx);
135 });
136
137 multi_workspace.read_with(cx, |mw, _cx| {
138 assert_eq!(mw.project_group_keys().count(), 1);
139 });
140
141 // Adding a workspace with a different project root adds a new key.
142 multi_workspace.update_in(cx, |mw, window, cx| {
143 mw.test_add_workspace(project_b, window, cx);
144 });
145
146 multi_workspace.read_with(cx, |mw, _cx| {
147 let keys: Vec<&ProjectGroupKey> = mw.project_group_keys().collect();
148 assert_eq!(
149 keys.len(),
150 2,
151 "should have two keys after adding a second workspace"
152 );
153 assert_eq!(*keys[0], key_b);
154 assert_eq!(*keys[1], key_a);
155 });
156}
157
158#[gpui::test]
159async fn test_open_new_window_does_not_open_sidebar_on_existing_window(cx: &mut TestAppContext) {
160 init_test(cx);
161
162 let app_state = cx.update(AppState::test);
163 let fs = app_state.fs.as_fake();
164 fs.insert_tree(path!("/project_a"), json!({ "file.txt": "" }))
165 .await;
166 fs.insert_tree(path!("/project_b"), json!({ "file.txt": "" }))
167 .await;
168
169 let project = Project::test(app_state.fs.clone(), [path!("/project_a").as_ref()], cx).await;
170
171 let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
172
173 window
174 .read_with(cx, |mw, _cx| {
175 assert!(!mw.sidebar_open(), "sidebar should start closed",);
176 })
177 .unwrap();
178
179 cx.update(|cx| {
180 open_paths(
181 &[PathBuf::from(path!("/project_b"))],
182 app_state,
183 OpenOptions {
184 open_mode: OpenMode::NewWindow,
185 ..OpenOptions::default()
186 },
187 cx,
188 )
189 })
190 .await
191 .unwrap();
192
193 window
194 .read_with(cx, |mw, _cx| {
195 assert!(
196 !mw.sidebar_open(),
197 "opening a project in a new window must not open the sidebar on the original window",
198 );
199 })
200 .unwrap();
201}
202
203#[gpui::test]
204async fn test_project_group_keys_duplicate_not_added(cx: &mut TestAppContext) {
205 init_test(cx);
206 let fs = FakeFs::new(cx.executor());
207 fs.insert_tree("/root_a", json!({ "file.txt": "" })).await;
208 let project_a = Project::test(fs.clone(), ["/root_a".as_ref()], cx).await;
209 // A second project entity pointing at the same path produces the same key.
210 let project_a2 = Project::test(fs.clone(), ["/root_a".as_ref()], cx).await;
211
212 let key_a = project_a.read_with(cx, |p, cx| p.project_group_key(cx));
213 let key_a2 = project_a2.read_with(cx, |p, cx| p.project_group_key(cx));
214 assert_eq!(key_a, key_a2, "same root path should produce the same key");
215
216 let (multi_workspace, cx) =
217 cx.add_window_view(|window, cx| MultiWorkspace::test_new(project_a, window, cx));
218
219 multi_workspace.update(cx, |mw, cx| {
220 mw.open_sidebar(cx);
221 });
222
223 multi_workspace.update_in(cx, |mw, window, cx| {
224 mw.test_add_workspace(project_a2, window, cx);
225 });
226
227 multi_workspace.read_with(cx, |mw, _cx| {
228 let keys: Vec<&ProjectGroupKey> = mw.project_group_keys().collect();
229 assert_eq!(
230 keys.len(),
231 1,
232 "duplicate key should not be added when a workspace with the same root is inserted"
233 );
234 });
235}