open_path_prompt_tests.rs

  1use std::sync::Arc;
  2
  3use gpui::{AppContext, Entity, TestAppContext, VisualTestContext};
  4use picker::{Picker, PickerDelegate};
  5use project::Project;
  6use serde_json::json;
  7use ui::rems;
  8use util::path;
  9use workspace::{AppState, Workspace};
 10
 11use crate::OpenPathDelegate;
 12
 13#[gpui::test]
 14async fn test_open_path_prompt(cx: &mut TestAppContext) {
 15    let app_state = init_test(cx);
 16    app_state
 17        .fs
 18        .as_fake()
 19        .insert_tree(
 20            path!("/root"),
 21            json!({
 22                "a1": "A1",
 23                "a2": "A2",
 24                "a3": "A3",
 25                "dir1": {},
 26                "dir2": {
 27                    "c": "C",
 28                    "d1": "D1",
 29                    "d2": "D2",
 30                    "d3": "D3",
 31                    "dir3": {},
 32                    "dir4": {}
 33                }
 34            }),
 35        )
 36        .await;
 37
 38    let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
 39
 40    let (picker, cx) = build_open_path_prompt(project, cx);
 41
 42    let query = path!("/root");
 43    insert_query(query, &picker, cx).await;
 44    assert_eq!(collect_match_candidates(&picker, cx), vec!["root"]);
 45
 46    // If the query ends with a slash, the picker should show the contents of the directory.
 47    let query = path!("/root/");
 48    insert_query(query, &picker, cx).await;
 49    assert_eq!(
 50        collect_match_candidates(&picker, cx),
 51        vec!["a1", "a2", "a3", "dir1", "dir2"]
 52    );
 53
 54    // Show candidates for the query "a".
 55    let query = path!("/root/a");
 56    insert_query(query, &picker, cx).await;
 57    assert_eq!(
 58        collect_match_candidates(&picker, cx),
 59        vec!["a1", "a2", "a3"]
 60    );
 61
 62    // Show candidates for the query "d".
 63    let query = path!("/root/d");
 64    insert_query(query, &picker, cx).await;
 65    assert_eq!(collect_match_candidates(&picker, cx), vec!["dir1", "dir2"]);
 66
 67    let query = path!("/root/dir2");
 68    insert_query(query, &picker, cx).await;
 69    assert_eq!(collect_match_candidates(&picker, cx), vec!["dir2"]);
 70
 71    let query = path!("/root/dir2/");
 72    insert_query(query, &picker, cx).await;
 73    assert_eq!(
 74        collect_match_candidates(&picker, cx),
 75        vec!["c", "d1", "d2", "d3", "dir3", "dir4"]
 76    );
 77
 78    // Show candidates for the query "d".
 79    let query = path!("/root/dir2/d");
 80    insert_query(query, &picker, cx).await;
 81    assert_eq!(
 82        collect_match_candidates(&picker, cx),
 83        vec!["d1", "d2", "d3", "dir3", "dir4"]
 84    );
 85
 86    let query = path!("/root/dir2/di");
 87    insert_query(query, &picker, cx).await;
 88    assert_eq!(collect_match_candidates(&picker, cx), vec!["dir3", "dir4"]);
 89}
 90
 91#[gpui::test]
 92async fn test_open_path_prompt_completion(cx: &mut TestAppContext) {
 93    let app_state = init_test(cx);
 94    app_state
 95        .fs
 96        .as_fake()
 97        .insert_tree(
 98            path!("/root"),
 99            json!({
100                "a": "A",
101                "dir1": {},
102                "dir2": {
103                    "c": "C",
104                    "d": "D",
105                    "dir3": {},
106                    "dir4": {}
107                }
108            }),
109        )
110        .await;
111
112    let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
113
114    let (picker, cx) = build_open_path_prompt(project, cx);
115
116    // Confirm completion for the query "/root", since it's a directory, it should add a trailing slash.
117    let query = path!("/root");
118    insert_query(query, &picker, cx).await;
119    assert_eq!(confirm_completion(query, 0, &picker, cx), path!("/root/"));
120
121    // Confirm completion for the query "/root/", selecting the first candidate "a", since it's a file, it should not add a trailing slash.
122    let query = path!("/root/");
123    insert_query(query, &picker, cx).await;
124    assert_eq!(confirm_completion(query, 0, &picker, cx), path!("/root/a"));
125
126    // Confirm completion for the query "/root/", selecting the second candidate "dir1", since it's a directory, it should add a trailing slash.
127    let query = path!("/root/");
128    insert_query(query, &picker, cx).await;
129    assert_eq!(
130        confirm_completion(query, 1, &picker, cx),
131        path!("/root/dir1/")
132    );
133
134    let query = path!("/root/a");
135    insert_query(query, &picker, cx).await;
136    assert_eq!(confirm_completion(query, 0, &picker, cx), path!("/root/a"));
137
138    let query = path!("/root/d");
139    insert_query(query, &picker, cx).await;
140    assert_eq!(
141        confirm_completion(query, 1, &picker, cx),
142        path!("/root/dir2/")
143    );
144
145    let query = path!("/root/dir2");
146    insert_query(query, &picker, cx).await;
147    assert_eq!(
148        confirm_completion(query, 0, &picker, cx),
149        path!("/root/dir2/")
150    );
151
152    let query = path!("/root/dir2/");
153    insert_query(query, &picker, cx).await;
154    assert_eq!(
155        confirm_completion(query, 0, &picker, cx),
156        path!("/root/dir2/c")
157    );
158
159    let query = path!("/root/dir2/");
160    insert_query(query, &picker, cx).await;
161    assert_eq!(
162        confirm_completion(query, 2, &picker, cx),
163        path!("/root/dir2/dir3/")
164    );
165
166    let query = path!("/root/dir2/d");
167    insert_query(query, &picker, cx).await;
168    assert_eq!(
169        confirm_completion(query, 0, &picker, cx),
170        path!("/root/dir2/d")
171    );
172
173    let query = path!("/root/dir2/d");
174    insert_query(query, &picker, cx).await;
175    assert_eq!(
176        confirm_completion(query, 1, &picker, cx),
177        path!("/root/dir2/dir3/")
178    );
179
180    let query = path!("/root/dir2/di");
181    insert_query(query, &picker, cx).await;
182    assert_eq!(
183        confirm_completion(query, 1, &picker, cx),
184        path!("/root/dir2/dir4/")
185    );
186}
187
188#[gpui::test]
189#[cfg(target_os = "windows")]
190async fn test_open_path_prompt_on_windows(cx: &mut TestAppContext) {
191    let app_state = init_test(cx);
192    app_state
193        .fs
194        .as_fake()
195        .insert_tree(
196            path!("/root"),
197            json!({
198                "a": "A",
199                "dir1": {},
200                "dir2": {}
201            }),
202        )
203        .await;
204
205    let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
206
207    let (picker, cx) = build_open_path_prompt(project, cx);
208
209    // Support both forward and backward slashes.
210    let query = "C:/root/";
211    insert_query(query, &picker, cx).await;
212    assert_eq!(
213        collect_match_candidates(&picker, cx),
214        vec!["a", "dir1", "dir2"]
215    );
216    assert_eq!(confirm_completion(query, 0, &picker, cx), "C:/root/a");
217
218    let query = "C:\\root/";
219    insert_query(query, &picker, cx).await;
220    assert_eq!(
221        collect_match_candidates(&picker, cx),
222        vec!["a", "dir1", "dir2"]
223    );
224    assert_eq!(confirm_completion(query, 0, &picker, cx), "C:\\root/a");
225
226    let query = "C:\\root\\";
227    insert_query(query, &picker, cx).await;
228    assert_eq!(
229        collect_match_candidates(&picker, cx),
230        vec!["a", "dir1", "dir2"]
231    );
232    assert_eq!(confirm_completion(query, 0, &picker, cx), "C:\\root\\a");
233
234    // Confirm completion for the query "C:/root/d", selecting the second candidate "dir2", since it's a directory, it should add a trailing slash.
235    let query = "C:/root/d";
236    insert_query(query, &picker, cx).await;
237    assert_eq!(collect_match_candidates(&picker, cx), vec!["dir1", "dir2"]);
238    assert_eq!(confirm_completion(query, 1, &picker, cx), "C:/root/dir2\\");
239
240    let query = "C:\\root/d";
241    insert_query(query, &picker, cx).await;
242    assert_eq!(collect_match_candidates(&picker, cx), vec!["dir1", "dir2"]);
243    assert_eq!(confirm_completion(query, 0, &picker, cx), "C:\\root/dir1\\");
244
245    let query = "C:\\root\\d";
246    insert_query(query, &picker, cx).await;
247    assert_eq!(collect_match_candidates(&picker, cx), vec!["dir1", "dir2"]);
248    assert_eq!(
249        confirm_completion(query, 0, &picker, cx),
250        "C:\\root\\dir1\\"
251    );
252}
253
254fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
255    cx.update(|cx| {
256        let state = AppState::test(cx);
257        theme::init(theme::LoadThemes::JustBase, cx);
258        language::init(cx);
259        super::init(cx);
260        editor::init(cx);
261        workspace::init_settings(cx);
262        Project::init_settings(cx);
263        state
264    })
265}
266
267fn build_open_path_prompt(
268    project: Entity<Project>,
269    cx: &mut TestAppContext,
270) -> (Entity<Picker<OpenPathDelegate>>, &mut VisualTestContext) {
271    let (tx, _) = futures::channel::oneshot::channel();
272    let lister = project::DirectoryLister::Project(project.clone());
273    let delegate = OpenPathDelegate::new(tx, lister.clone());
274
275    let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
276    (
277        workspace.update_in(cx, |_, window, cx| {
278            cx.new(|cx| {
279                let picker = Picker::uniform_list(delegate, window, cx)
280                    .width(rems(34.))
281                    .modal(false);
282                let query = lister.default_query(cx);
283                picker.set_query(query, window, cx);
284                picker
285            })
286        }),
287        cx,
288    )
289}
290
291async fn insert_query(
292    query: &str,
293    picker: &Entity<Picker<OpenPathDelegate>>,
294    cx: &mut VisualTestContext,
295) {
296    picker
297        .update_in(cx, |f, window, cx| {
298            f.delegate.update_matches(query.to_string(), window, cx)
299        })
300        .await;
301}
302
303fn confirm_completion(
304    query: &str,
305    select: usize,
306    picker: &Entity<Picker<OpenPathDelegate>>,
307    cx: &mut VisualTestContext,
308) -> String {
309    picker
310        .update_in(cx, |f, window, cx| {
311            if f.delegate.selected_index() != select {
312                f.delegate.set_selected_index(select, window, cx);
313            }
314            f.delegate.confirm_completion(query.to_string(), window, cx)
315        })
316        .unwrap()
317}
318
319fn collect_match_candidates(
320    picker: &Entity<Picker<OpenPathDelegate>>,
321    cx: &mut VisualTestContext,
322) -> Vec<String> {
323    picker.update(cx, |f, _| f.delegate.collect_match_candidates())
324}