Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:02:25.261Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

multi_select_tests.rs

295 lines · 9.3 KB · rust
1//! Tests for multi-selecting files in the file finder: toggling items in and
2//! out of the selection, pinning the selection to the top across query
3//! changes, and the various ways of opening the whole selection.
4
5use gpui::{BorrowAppContext, Entity, TestAppContext, VisualTestContext};
6use picker::{MultiSelectNext, Picker, PickerDelegate as _};
7use pretty_assertions::assert_eq;
8use project::Project;
9use serde_json::json;
10use settings::SettingsStore;
11use util::path;
12use workspace::{MultiWorkspace, Workspace, pane};
13
14use crate::file_finder_tests::{self, open_file_picker};
15use crate::{FileFinder, FileFinderDelegate, SEARCH_DEBOUNCE};
16
17struct TestContext {
18    picker: Entity<Picker<FileFinderDelegate>>,
19    workspace: Entity<Workspace>,
20    cx: VisualTestContext,
21}
22
23impl TestContext {
24    /// The test tree is `a.rs`, `b.rs` and `c.rs` in the worktree root.
25    /// Preview tabs from the file finder are enabled to verify that batch
26    /// opens produce real tabs regardless.
27    async fn new(cx: &mut TestAppContext) -> TestContext {
28        let app_state = file_finder_tests::init_test(cx);
29        cx.update(|cx| {
30            cx.update_global::<SettingsStore, _>(|store, cx| {
31                store.update_user_settings(cx, |settings| {
32                    settings
33                        .preview_tabs
34                        .get_or_insert_default()
35                        .enable_preview_from_file_finder = Some(true);
36                });
37            })
38        });
39        app_state
40            .fs
41            .as_fake()
42            .insert_tree(
43                path!("/root"),
44                json!({
45                    "a.rs": "// a",
46                    "b.rs": "// b",
47                    "c.rs": "// c",
48                }),
49            )
50            .await;
51        let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
52        let window =
53            cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
54        let workspace = window
55            .read_with(cx, |multi_workspace, _| multi_workspace.workspace().clone())
56            .unwrap();
57        let mut cx = VisualTestContext::from_window(window.into(), cx);
58        let picker = open_file_picker(&workspace, &mut cx);
59        TestContext {
60            picker,
61            workspace,
62            cx,
63        }
64    }
65
66    fn reopen_finder(&mut self) {
67        self.picker = open_file_picker(&self.workspace, &mut self.cx);
68    }
69
70    fn search(&mut self, query: &str) {
71        self.picker.update_in(&mut self.cx, |picker, window, cx| {
72            picker.set_query(query, window, cx)
73        });
74        self.cx.executor().advance_clock(SEARCH_DEBOUNCE);
75        self.cx.run_until_parked();
76    }
77
78    fn select(&mut self, file_name: &str) {
79        let index = self.index_of(file_name);
80        self.picker.update_in(&mut self.cx, |picker, window, cx| {
81            picker.delegate.set_selected_index(index, window, cx);
82        });
83        self.cx.dispatch_action(MultiSelectNext);
84        self.cx.run_until_parked();
85    }
86
87    fn deselect(&mut self, file_name: &str) {
88        let index = self.index_of(file_name);
89        self.picker.update_in(&mut self.cx, |picker, window, cx| {
90            picker.delegate.toggle_item_selected(index, window, cx);
91        });
92        self.cx.run_until_parked();
93    }
94
95    fn confirm(&mut self) {
96        self.cx.dispatch_action(menu::Confirm);
97        self.cx.run_until_parked();
98    }
99
100    fn secondary_confirm(&mut self) {
101        self.cx.dispatch_action(menu::SecondaryConfirm);
102        self.cx.run_until_parked();
103    }
104
105    fn split_right(&mut self) {
106        self.cx.dispatch_action(pane::SplitRight::default());
107        self.cx.run_until_parked();
108    }
109
110    fn index_of(&mut self, file_name: &str) -> usize {
111        self.picker.update(&mut self.cx, |picker, _| {
112            picker
113                .delegate
114                .matches
115                .matches
116                .iter()
117                .position(|m| {
118                    m.relative_path()
119                        .is_some_and(|path| path.file_name() == Some(file_name))
120                })
121                .unwrap_or_else(|| panic!("{file_name} is not in the match list"))
122        })
123    }
124
125    fn match_names(&mut self) -> Vec<String> {
126        self.picker.update(&mut self.cx, |picker, _| {
127            picker
128                .delegate
129                .matches
130                .matches
131                .iter()
132                .filter_map(|m| Some(m.relative_path()?.file_name()?.to_owned()))
133                .collect()
134        })
135    }
136
137    #[track_caller]
138    fn assert_selected(&mut self, expected: &[&str]) {
139        let selected: Vec<String> = self.picker.update(&mut self.cx, |picker, _| {
140            picker
141                .delegate
142                .selected_matches
143                .iter()
144                .filter_map(|selected| Some(selected.0.relative_path()?.file_name()?.to_owned()))
145                .collect()
146        });
147        assert_eq!(selected, expected, "wrong files selected");
148    }
149
150    #[track_caller]
151    fn assert_finder_closed(&mut self) {
152        let finder_open = self.workspace.update(&mut self.cx, |workspace, cx| {
153            workspace.active_modal::<FileFinder>(cx).is_some()
154        });
155        assert!(!finder_open, "the finder should have been dismissed");
156    }
157
158    #[track_caller]
159    fn assert_active_pane_items(&mut self, expected: &[&str]) {
160        let mut items = self.pane_item_names(
161            &self
162                .workspace
163                .read_with(&self.cx, |workspace, _| workspace.active_pane().clone()),
164        );
165        let mut expected: Vec<String> = expected.iter().map(|name| name.to_string()).collect();
166        items.sort();
167        expected.sort();
168        assert_eq!(items, expected, "wrong items in the active pane");
169    }
170
171    fn pane_item_names(&mut self, pane: &Entity<pane::Pane>) -> Vec<String> {
172        pane.read_with(&self.cx, |pane, cx| {
173            pane.items()
174                .filter_map(|item| Some(item.project_path(cx)?.path.file_name()?.to_owned()))
175                .collect()
176        })
177    }
178
179    fn pane_count(&mut self) -> usize {
180        self.workspace
181            .read_with(&self.cx, |workspace, _| workspace.panes().len())
182    }
183}
184
185#[gpui::test]
186async fn open_selection_as_tabs(cx: &mut TestAppContext) {
187    let mut cx = TestContext::new(cx).await;
188
189    cx.search("rs");
190    cx.select("b.rs");
191    cx.select("c.rs");
192    cx.assert_selected(&["b.rs", "c.rs"]);
193
194    cx.confirm();
195    cx.assert_finder_closed();
196    // Both files must open as real tabs even though preview tabs from the
197    // file finder are enabled; a batch open must not reuse one preview tab.
198    cx.assert_active_pane_items(&["b.rs", "c.rs"]);
199}
200
201#[gpui::test]
202async fn tabbing_a_selected_row_deselects_it(cx: &mut TestAppContext) {
203    let mut cx = TestContext::new(cx).await;
204
205    cx.search("rs");
206    cx.select("b.rs");
207    cx.select("c.rs");
208    cx.select("b.rs");
209    cx.assert_selected(&["c.rs"]);
210}
211
212#[gpui::test]
213async fn selection_pins_to_top_across_queries(cx: &mut TestAppContext) {
214    let mut cx = TestContext::new(cx).await;
215
216    cx.search("c");
217    cx.select("c.rs");
218
219    // `c.rs` doesn't match the new query, but stays selected and pinned to
220    // the top of the results.
221    cx.search("b");
222    cx.assert_selected(&["c.rs"]);
223    assert_eq!(cx.match_names(), ["c.rs", "b.rs"]);
224}
225
226#[gpui::test]
227async fn deselecting_survives_queries(cx: &mut TestAppContext) {
228    let mut cx = TestContext::new(cx).await;
229
230    cx.search("c");
231    cx.select("c.rs");
232
233    cx.search("b");
234    cx.deselect("c.rs");
235    cx.assert_selected(&[]);
236
237    // A deselected file must not come back selected or pinned on requery.
238    cx.search("a");
239    cx.assert_selected(&[]);
240    assert_eq!(cx.match_names(), ["a.rs"]);
241}
242
243#[gpui::test]
244async fn create_new_file_row_is_not_selectable(cx: &mut TestAppContext) {
245    let mut cx = TestContext::new(cx).await;
246
247    // A query matching nothing produces only the "create new" row.
248    cx.search("zzz");
249    cx.picker.update_in(&mut cx.cx, |picker, window, cx| {
250        picker.delegate.set_selected_index(0, window, cx);
251    });
252    cx.cx.dispatch_action(MultiSelectNext);
253    cx.cx.run_until_parked();
254    cx.assert_selected(&[]);
255}
256
257#[gpui::test]
258async fn open_selection_in_one_split(cx: &mut TestAppContext) {
259    let mut cx = TestContext::new(cx).await;
260
261    cx.search("rs");
262    cx.select("b.rs");
263    cx.select("c.rs");
264
265    cx.split_right();
266    cx.assert_finder_closed();
267    // One new pane holding both files as tabs, not one pane per file.
268    assert_eq!(cx.pane_count(), 2);
269    cx.assert_active_pane_items(&["b.rs", "c.rs"]);
270}
271
272#[gpui::test]
273async fn secondary_confirm_opens_one_split_per_file(cx: &mut TestAppContext) {
274    let mut cx = TestContext::new(cx).await;
275
276    // Open a file normally first so the workspace has a non-empty pane to
277    // split off from.
278    cx.search("a");
279    cx.confirm();
280    cx.assert_active_pane_items(&["a.rs"]);
281
282    cx.reopen_finder();
283    cx.search("rs");
284    cx.select("b.rs");
285    cx.select("c.rs");
286
287    cx.secondary_confirm();
288    cx.assert_finder_closed();
289    assert_eq!(
290        cx.pane_count(),
291        3,
292        "each selected file opens in its own split"
293    );
294}
295
Served at tenant.openagents/omega Member data and write actions are omitted.