Skip to repository content553 lines · 17.1 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T02:35:46.635Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
open_path_prompt_tests.rs
1use std::sync::{Arc, atomic};
2
3use fuzzy::{StringMatch, StringMatchCandidate};
4use gpui::{AppContext, Entity, TestAppContext, VisualTestContext};
5use picker::{Picker, PickerDelegate};
6use project::Project;
7use serde_json::json;
8use util::path;
9use workspace::{AppState, MultiWorkspace};
10
11use crate::{CandidateInfo, DirectoryState, 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 ".b1": ".B1",
24 "a1": "A1",
25 "a2": "A2",
26 "a3": "A3",
27 "dir1": {},
28 "dir2": {
29 "c": "C",
30 "d1": "D1",
31 "d2": "D2",
32 "d3": "D3",
33 "dir3": {},
34 "dir4": {}
35 }
36 }),
37 )
38 .await;
39
40 let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
41
42 let (picker, cx) = build_open_path_prompt(project, false, false, cx);
43
44 insert_query(path!("sadjaoislkdjasldj"), &picker, cx).await;
45 assert_eq!(collect_match_candidates(&picker, cx), Vec::<String>::new());
46
47 let query = path!("/root");
48 insert_query(query, &picker, cx).await;
49 assert_eq!(collect_match_candidates(&picker, cx), vec!["root"]);
50
51 #[cfg(not(windows))]
52 let expected_separator = "./";
53 #[cfg(windows)]
54 let expected_separator = ".\\";
55
56 // If the query ends with a slash, the picker should show the contents of the directory and not show any of the hidden entries.
57 let query = path!("/root/");
58 insert_query(query, &picker, cx).await;
59 assert_eq!(
60 collect_match_candidates(&picker, cx),
61 vec![expected_separator, "dir1", "dir2", "a1", "a2", "a3"]
62 );
63
64 // Show candidates for the query "a".
65 let query = path!("/root/a");
66 insert_query(query, &picker, cx).await;
67 assert_eq!(
68 collect_match_candidates(&picker, cx),
69 vec!["a1", "a2", "a3"]
70 );
71
72 // Show candidates for the query "d".
73 let query = path!("/root/d");
74 insert_query(query, &picker, cx).await;
75 assert_eq!(collect_match_candidates(&picker, cx), vec!["dir1", "dir2"]);
76
77 let query = path!("/root/dir2");
78 insert_query(query, &picker, cx).await;
79 assert_eq!(collect_match_candidates(&picker, cx), vec!["dir2"]);
80
81 let query = path!("/root/dir2/");
82 insert_query(query, &picker, cx).await;
83 assert_eq!(
84 collect_match_candidates(&picker, cx),
85 vec![expected_separator, "dir3", "dir4", "c", "d1", "d2", "d3"]
86 );
87
88 // Show candidates for the query "d".
89 let query = path!("/root/dir2/d");
90 insert_query(query, &picker, cx).await;
91 assert_eq!(
92 collect_match_candidates(&picker, cx),
93 vec!["dir3", "dir4", "d1", "d2", "d3"]
94 );
95
96 let query = path!("/root/dir2/di");
97 insert_query(query, &picker, cx).await;
98 assert_eq!(collect_match_candidates(&picker, cx), vec!["dir3", "dir4"]);
99
100 // Don't show candidates for the query ".".
101 let query = path!("/root/.");
102 insert_query(query, &picker, cx).await;
103 assert_eq!(collect_match_candidates(&picker, cx), Vec::<String>::new());
104
105 // Don't show any candidates for the query ".a".
106 let query = path!("/root/.a");
107 insert_query(query, &picker, cx).await;
108 assert_eq!(collect_match_candidates(&picker, cx), Vec::<String>::new());
109
110 // Show candidates for the query "./".
111 // Should show current directory and contents.
112 let query = path!("/root/./");
113 insert_query(query, &picker, cx).await;
114 assert_eq!(
115 collect_match_candidates(&picker, cx),
116 vec![expected_separator, "dir1", "dir2", "a1", "a2", "a3"]
117 );
118
119 // Show candidates for the query "../". Show parent contents.
120 let query = path!("/root/dir1/../");
121 insert_query(query, &picker, cx).await;
122 assert_eq!(
123 collect_match_candidates(&picker, cx),
124 vec![expected_separator, "dir1", "dir2", "a1", "a2", "a3"]
125 );
126}
127
128#[gpui::test]
129async fn test_open_path_prompt_completion(cx: &mut TestAppContext) {
130 let app_state = init_test(cx);
131 app_state
132 .fs
133 .as_fake()
134 .insert_tree(
135 path!("/root"),
136 json!({
137 "a": "A",
138 "dir1": {},
139 "dir2": {
140 "c": "C",
141 "d": "D",
142 "dir3": {},
143 "dir4": {}
144 }
145 }),
146 )
147 .await;
148
149 let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
150
151 let (picker, cx) = build_open_path_prompt(project, false, false, cx);
152
153 // Confirm completion for the query "/root", since it's a directory, it should add a trailing slash.
154 let query = path!("/root");
155 insert_query(query, &picker, cx).await;
156 assert_eq!(
157 confirm_completion(query, 0, &picker, cx).unwrap(),
158 path!("/root/")
159 );
160
161 // Confirm completion for the query "/root/", selecting the first candidate "dir1", since it's a directory, it should add a trailing slash.
162 let query = path!("/root/");
163 insert_query(query, &picker, cx).await;
164 assert_eq!(
165 confirm_completion(query, 0, &picker, cx),
166 None,
167 "First entry is `./` and when we confirm completion, it is tabbed below"
168 );
169 assert_eq!(
170 confirm_completion(query, 1, &picker, cx).unwrap(),
171 path!("/root/dir1/"),
172 "Second entry is the first directory"
173 );
174
175 // Confirm completion for the query "/root/", selecting the third candidate "a", since it's a file, it should not add a trailing slash.
176 let query = path!("/root/");
177 insert_query(query, &picker, cx).await;
178 assert_eq!(
179 confirm_completion(query, 3, &picker, cx).unwrap(),
180 path!("/root/a")
181 );
182
183 let query = path!("/root/a");
184 insert_query(query, &picker, cx).await;
185 assert_eq!(
186 confirm_completion(query, 0, &picker, cx).unwrap(),
187 path!("/root/a")
188 );
189
190 let query = path!("/root/d");
191 insert_query(query, &picker, cx).await;
192 assert_eq!(
193 confirm_completion(query, 1, &picker, cx).unwrap(),
194 path!("/root/dir2/")
195 );
196
197 let query = path!("/root/dir2");
198 insert_query(query, &picker, cx).await;
199 assert_eq!(
200 confirm_completion(query, 0, &picker, cx).unwrap(),
201 path!("/root/dir2/")
202 );
203
204 let query = path!("/root/dir2/");
205 insert_query(query, &picker, cx).await;
206 assert_eq!(
207 confirm_completion(query, 1, &picker, cx).unwrap(),
208 path!("/root/dir2/dir3/")
209 );
210
211 let query = path!("/root/dir2/");
212 insert_query(query, &picker, cx).await;
213 assert_eq!(
214 confirm_completion(query, 3, &picker, cx).unwrap(),
215 path!("/root/dir2/c")
216 );
217
218 let query = path!("/root/dir2/d");
219 insert_query(query, &picker, cx).await;
220 assert_eq!(
221 confirm_completion(query, 0, &picker, cx).unwrap(),
222 path!("/root/dir2/dir3/")
223 );
224
225 let query = path!("/root/dir2/d");
226 insert_query(query, &picker, cx).await;
227 assert_eq!(
228 confirm_completion(query, 1, &picker, cx).unwrap(),
229 path!("/root/dir2/dir4/")
230 );
231
232 let query = path!("/root/dir2/di");
233 insert_query(query, &picker, cx).await;
234 assert_eq!(
235 confirm_completion(query, 1, &picker, cx).unwrap(),
236 path!("/root/dir2/dir4/")
237 );
238}
239
240#[gpui::test]
241#[cfg_attr(not(target_os = "windows"), ignore)]
242async fn test_open_path_prompt_on_windows(cx: &mut TestAppContext) {
243 let app_state = init_test(cx);
244 app_state
245 .fs
246 .as_fake()
247 .insert_tree(
248 path!("/root"),
249 json!({
250 "a": "A",
251 "dir1": {},
252 "dir2": {}
253 }),
254 )
255 .await;
256
257 let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
258
259 let (picker, cx) = build_open_path_prompt(project, false, false, cx);
260
261 // Support both forward and backward slashes.
262 let query = "C:/root/";
263 insert_query(query, &picker, cx).await;
264 assert_eq!(
265 collect_match_candidates(&picker, cx),
266 vec![".\\", "dir1", "dir2", "a"]
267 );
268 assert_eq!(
269 confirm_completion(query, 0, &picker, cx),
270 None,
271 "First entry is `.\\` and when we confirm completion, it is tabbed below"
272 );
273 assert_eq!(
274 confirm_completion(query, 1, &picker, cx).unwrap(),
275 "C:/root/dir1\\",
276 "Second entry is the first directory"
277 );
278
279 let query = "C:\\root/";
280 insert_query(query, &picker, cx).await;
281 assert_eq!(
282 collect_match_candidates(&picker, cx),
283 vec![".\\", "dir1", "dir2", "a"]
284 );
285 assert_eq!(
286 confirm_completion(query, 1, &picker, cx).unwrap(),
287 "C:\\root/dir1\\"
288 );
289
290 let query = "C:\\root\\";
291 insert_query(query, &picker, cx).await;
292 assert_eq!(
293 collect_match_candidates(&picker, cx),
294 vec![".\\", "dir1", "dir2", "a"]
295 );
296 assert_eq!(
297 confirm_completion(query, 1, &picker, cx).unwrap(),
298 "C:\\root\\dir1\\"
299 );
300
301 // Confirm completion for the query "C:/root/d", selecting the second candidate "dir2", since it's a directory, it should add a trailing slash.
302 let query = "C:/root/d";
303 insert_query(query, &picker, cx).await;
304 assert_eq!(collect_match_candidates(&picker, cx), vec!["dir1", "dir2"]);
305 assert_eq!(
306 confirm_completion(query, 1, &picker, cx).unwrap(),
307 "C:/root/dir2\\"
308 );
309
310 let query = "C:\\root/d";
311 insert_query(query, &picker, cx).await;
312 assert_eq!(collect_match_candidates(&picker, cx), vec!["dir1", "dir2"]);
313 assert_eq!(
314 confirm_completion(query, 0, &picker, cx).unwrap(),
315 "C:\\root/dir1\\"
316 );
317
318 let query = "C:\\root\\d";
319 insert_query(query, &picker, cx).await;
320 assert_eq!(collect_match_candidates(&picker, cx), vec!["dir1", "dir2"]);
321 assert_eq!(
322 confirm_completion(query, 0, &picker, cx).unwrap(),
323 "C:\\root\\dir1\\"
324 );
325}
326
327#[gpui::test]
328async fn test_new_path_prompt(cx: &mut TestAppContext) {
329 let app_state = init_test(cx);
330 app_state
331 .fs
332 .as_fake()
333 .insert_tree(
334 path!("/root"),
335 json!({
336 "a1": "A1",
337 "a2": "A2",
338 "a3": "A3",
339 "dir1": {},
340 "dir2": {
341 "c": "C",
342 "d1": "D1",
343 "d2": "D2",
344 "d3": "D3",
345 "dir3": {},
346 "dir4": {}
347 }
348 }),
349 )
350 .await;
351
352 let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
353
354 let (picker, cx) = build_open_path_prompt(project, true, false, cx);
355
356 insert_query(path!("/root"), &picker, cx).await;
357 assert_eq!(collect_match_candidates(&picker, cx), vec!["root"]);
358
359 insert_query(path!("/root/d"), &picker, cx).await;
360 assert_eq!(
361 collect_match_candidates(&picker, cx),
362 vec!["d", "dir1", "dir2"]
363 );
364
365 insert_query(path!("/root/dir1"), &picker, cx).await;
366 assert_eq!(collect_match_candidates(&picker, cx), vec!["dir1"]);
367
368 insert_query(path!("/root/dir12"), &picker, cx).await;
369 assert_eq!(collect_match_candidates(&picker, cx), vec!["dir12"]);
370
371 insert_query(path!("/root/dir1"), &picker, cx).await;
372 assert_eq!(collect_match_candidates(&picker, cx), vec!["dir1"]);
373}
374
375#[gpui::test]
376async fn test_open_path_prompt_panics_with_stale_highlight_positions(cx: &mut TestAppContext) {
377 let app_state = init_test(cx);
378 app_state
379 .fs
380 .as_fake()
381 .insert_tree(path!("/root"), json!({}))
382 .await;
383
384 let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
385 let (picker, cx) = build_open_path_prompt(project, false, false, cx);
386
387 picker.update_in(cx, |picker, window, cx| {
388 picker.delegate.prompt_root = "/".to_string();
389 picker.delegate.directory_state = DirectoryState::List {
390 parent_path: picker.delegate.prompt_root.clone(),
391 entries: vec![CandidateInfo {
392 path: StringMatchCandidate::new(0, "éclair"),
393 is_dir: false,
394 }],
395 error: None,
396 };
397 picker.delegate.string_matches = vec![StringMatch {
398 candidate_id: 0,
399 score: 0.0,
400 positions: vec![1],
401 string: "ab".to_string(),
402 }];
403
404 picker.delegate.render_match(0, false, window, cx);
405 });
406}
407
408#[gpui::test]
409async fn test_open_path_prompt_with_show_hidden(cx: &mut TestAppContext) {
410 let app_state = init_test(cx);
411 app_state
412 .fs
413 .as_fake()
414 .insert_tree(
415 path!("/root"),
416 json!({
417 "directory_1": {},
418 "directory_2": {},
419 ".hidden": {}
420 }),
421 )
422 .await;
423
424 let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
425
426 let (picker, cx) = build_open_path_prompt(project, false, true, cx);
427
428 #[cfg(not(windows))]
429 let expected_separator = "./";
430 #[cfg(windows)]
431 let expected_separator = ".\\";
432
433 insert_query(path!("/root/"), &picker, cx).await;
434 assert_eq!(
435 collect_match_candidates(&picker, cx),
436 vec![expected_separator, ".hidden", "directory_1", "directory_2"]
437 );
438
439 insert_query(path!("/root/."), &picker, cx).await;
440 assert_eq!(collect_match_candidates(&picker, cx), vec![".hidden"]);
441}
442
443#[gpui::test]
444async fn test_dismiss_cancels_in_flight_match(cx: &mut TestAppContext) {
445 let app_state = init_test(cx);
446 app_state
447 .fs
448 .as_fake()
449 .insert_tree(
450 path!("/root"),
451 json!({
452 "a1": "A1",
453 "a2": "A2",
454 "a3": "A3",
455 }),
456 )
457 .await;
458
459 let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
460 let (picker, cx) = build_open_path_prompt(project, false, false, cx);
461
462 insert_query(path!("/root/a"), &picker, cx).await;
463
464 let cancel_flag = picker.read_with(cx, |picker, _| picker.delegate.cancel_flag.clone());
465 assert!(
466 !cancel_flag.load(atomic::Ordering::Acquire),
467 "cancel flag should be clear while the prompt is active"
468 );
469
470 picker.update_in(cx, |picker, window, cx| {
471 picker.delegate.dismissed(window, cx);
472 });
473
474 assert!(
475 cancel_flag.load(atomic::Ordering::Acquire),
476 "dismissing the prompt must cancel the in-flight fuzzy match"
477 );
478}
479
480fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
481 cx.update(|cx| {
482 let state = AppState::test(cx);
483 theme_settings::init(theme::LoadThemes::JustBase, cx);
484
485 editor::init(cx);
486 state
487 })
488}
489
490fn build_open_path_prompt(
491 project: Entity<Project>,
492 creating_path: bool,
493 show_hidden: bool,
494 cx: &mut TestAppContext,
495) -> (Entity<Picker<OpenPathDelegate>>, &mut VisualTestContext) {
496 let (tx, _) = futures::channel::oneshot::channel();
497 let lister = project::DirectoryLister::Project(project.clone());
498
499 let (multi_workspace, cx) =
500 cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
501 let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
502 (
503 workspace.update_in(cx, |_, window, cx| {
504 let delegate = OpenPathDelegate::new(tx, lister.clone(), creating_path, cx);
505 let delegate = if show_hidden {
506 delegate.show_hidden()
507 } else {
508 delegate
509 };
510 cx.new(|cx| {
511 let picker = Picker::uniform_list(delegate, window, cx).embedded();
512 let query = lister.default_query(cx);
513 picker.set_query(&query, window, cx);
514 picker
515 })
516 }),
517 cx,
518 )
519}
520
521async fn insert_query(
522 query: &str,
523 picker: &Entity<Picker<OpenPathDelegate>>,
524 cx: &mut VisualTestContext,
525) {
526 picker
527 .update_in(cx, |f, window, cx| {
528 f.delegate.update_matches(query.to_string(), window, cx)
529 })
530 .await;
531}
532
533fn confirm_completion(
534 query: &str,
535 select: usize,
536 picker: &Entity<Picker<OpenPathDelegate>>,
537 cx: &mut VisualTestContext,
538) -> Option<String> {
539 picker.update_in(cx, |f, window, cx| {
540 if f.delegate.selected_index() != select {
541 f.delegate.set_selected_index(select, window, cx);
542 }
543 f.delegate.confirm_completion(query.to_string(), window, cx)
544 })
545}
546
547fn collect_match_candidates(
548 picker: &Entity<Picker<OpenPathDelegate>>,
549 cx: &mut VisualTestContext,
550) -> Vec<String> {
551 picker.update(cx, |f, _| f.delegate.collect_match_candidates())
552}
553