editor_test_context.rs

  1use crate::{
  2    display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
  3};
  4use futures::Future;
  5use gpui::{
  6    AnyWindowHandle, AppContext, ForegroundExecutor, Keystroke, ModelContext, View, ViewContext,
  7};
  8use indoc::indoc;
  9use language::{Buffer, BufferSnapshot};
 10use project::{FakeFs, Project};
 11use std::{
 12    any::TypeId,
 13    ops::{Deref, DerefMut, Range},
 14};
 15use util::{
 16    assert_set_eq,
 17    test::{generate_marked_text, marked_text_ranges},
 18};
 19
 20use super::build_editor_with_project;
 21
 22pub struct EditorTestContext<'a> {
 23    pub cx: &'a mut gpui::TestAppContext,
 24    pub window: AnyWindowHandle,
 25    pub editor: View<Editor>,
 26}
 27
 28impl<'a> EditorTestContext<'a> {
 29    pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
 30        let fs = FakeFs::new(cx.background());
 31        // fs.insert_file("/file", "".to_owned()).await;
 32        fs.insert_tree(
 33            "/root",
 34            gpui::serde_json::json!({
 35                "file": "",
 36            }),
 37        )
 38        .await;
 39        let project = Project::test(fs, ["/root".as_ref()], cx).await;
 40        let buffer = project
 41            .update(cx, |project, cx| {
 42                project.open_local_buffer("/root/file", cx)
 43            })
 44            .await
 45            .unwrap();
 46        let window = cx.add_window(|cx| {
 47            cx.focus_self();
 48            build_editor_with_project(project, MultiBuffer::build_from_buffer(buffer, cx), cx)
 49        });
 50        let editor = window.root(cx);
 51        Self {
 52            cx,
 53            window: window.into(),
 54            editor,
 55        }
 56    }
 57
 58    pub fn condition(
 59        &self,
 60        predicate: impl FnMut(&Editor, &AppContext) -> bool,
 61    ) -> impl Future<Output = ()> {
 62        self.editor.condition(self.cx, predicate)
 63    }
 64
 65    pub fn editor<F, T>(&self, read: F) -> T
 66    where
 67        F: FnOnce(&Editor, &ViewContext<Editor>) -> T,
 68    {
 69        self.editor.read_with(self.cx, read)
 70    }
 71
 72    pub fn update_editor<F, T>(&mut self, update: F) -> T
 73    where
 74        F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
 75    {
 76        self.editor.update(self.cx, update)
 77    }
 78
 79    pub fn multibuffer<F, T>(&self, read: F) -> T
 80    where
 81        F: FnOnce(&MultiBuffer, &AppContext) -> T,
 82    {
 83        self.editor(|editor, cx| read(editor.buffer().read(cx), cx))
 84    }
 85
 86    pub fn update_multibuffer<F, T>(&mut self, update: F) -> T
 87    where
 88        F: FnOnce(&mut MultiBuffer, &mut ModelContext<MultiBuffer>) -> T,
 89    {
 90        self.update_editor(|editor, cx| editor.buffer().update(cx, update))
 91    }
 92
 93    pub fn buffer_text(&self) -> String {
 94        self.multibuffer(|buffer, cx| buffer.snapshot(cx).text())
 95    }
 96
 97    pub fn buffer<F, T>(&self, read: F) -> T
 98    where
 99        F: FnOnce(&Buffer, &AppContext) -> T,
100    {
101        self.multibuffer(|multibuffer, cx| {
102            let buffer = multibuffer.as_singleton().unwrap().read(cx);
103            read(buffer, cx)
104        })
105    }
106
107    pub fn update_buffer<F, T>(&mut self, update: F) -> T
108    where
109        F: FnOnce(&mut Buffer, &mut ModelContext<Buffer>) -> T,
110    {
111        self.update_multibuffer(|multibuffer, cx| {
112            let buffer = multibuffer.as_singleton().unwrap();
113            buffer.update(cx, update)
114        })
115    }
116
117    pub fn buffer_snapshot(&self) -> BufferSnapshot {
118        self.buffer(|buffer, _| buffer.snapshot())
119    }
120
121    // pub fn simulate_keystroke(&mut self, keystroke_text: &str) -> ContextHandle {
122    //     let keystroke_under_test_handle =
123    //         self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
124    //     let keystroke = Keystroke::parse(keystroke_text).unwrap();
125
126    //     self.cx.dispatch_keystroke(self.window, keystroke, false);
127
128    //     keystroke_under_test_handle
129    // }
130
131    // pub fn simulate_keystrokes<const COUNT: usize>(
132    //     &mut self,
133    //     keystroke_texts: [&str; COUNT],
134    // ) -> ContextHandle {
135    //     let keystrokes_under_test_handle =
136    //         self.add_assertion_context(format!("Simulated Keystrokes: {:?}", keystroke_texts));
137    //     for keystroke_text in keystroke_texts.into_iter() {
138    //         self.simulate_keystroke(keystroke_text);
139    //     }
140    //     // it is common for keyboard shortcuts to kick off async actions, so this ensures that they are complete
141    //     // before returning.
142    //     // NOTE: we don't do this in simulate_keystroke() because a possible cause of bugs is that typing too
143    //     // quickly races with async actions.
144    //     if let Foreground::Deterministic { cx_id: _, executor } = self.cx.foreground().as_ref() {
145    //         executor.run_until_parked();
146    //     } else {
147    //         unreachable!();
148    //     }
149
150    //     keystrokes_under_test_handle
151    // }
152
153    pub fn ranges(&self, marked_text: &str) -> Vec<Range<usize>> {
154        let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
155        assert_eq!(self.buffer_text(), unmarked_text);
156        ranges
157    }
158
159    pub fn display_point(&mut self, marked_text: &str) -> DisplayPoint {
160        let ranges = self.ranges(marked_text);
161        let snapshot = self
162            .editor
163            .update(self.cx, |editor, cx| editor.snapshot(cx));
164        ranges[0].start.to_display_point(&snapshot)
165    }
166
167    // Returns anchors for the current buffer using `«` and `»`
168    pub fn text_anchor_range(&self, marked_text: &str) -> Range<language::Anchor> {
169        let ranges = self.ranges(marked_text);
170        let snapshot = self.buffer_snapshot();
171        snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
172    }
173
174    pub fn set_diff_base(&mut self, diff_base: Option<&str>) {
175        let diff_base = diff_base.map(String::from);
176        self.update_buffer(|buffer, cx| buffer.set_diff_base(diff_base, cx));
177    }
178
179    // /// Change the editor's text and selections using a string containing
180    // /// embedded range markers that represent the ranges and directions of
181    // /// each selection.
182    // ///
183    // /// Returns a context handle so that assertion failures can print what
184    // /// editor state was needed to cause the failure.
185    // ///
186    // /// See the `util::test::marked_text_ranges` function for more information.
187    // pub fn set_state(&mut self, marked_text: &str) -> ContextHandle {
188    //     let state_context = self.add_assertion_context(format!(
189    //         "Initial Editor State: \"{}\"",
190    //         marked_text.escape_debug().to_string()
191    //     ));
192    //     let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
193    //     self.editor.update(self.cx, |editor, cx| {
194    //         editor.set_text(unmarked_text, cx);
195    //         editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
196    //             s.select_ranges(selection_ranges)
197    //         })
198    //     });
199    //     state_context
200    // }
201
202    // /// Only change the editor's selections
203    // pub fn set_selections_state(&mut self, marked_text: &str) -> ContextHandle {
204    //     let state_context = self.add_assertion_context(format!(
205    //         "Initial Editor State: \"{}\"",
206    //         marked_text.escape_debug().to_string()
207    //     ));
208    //     let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
209    //     self.editor.update(self.cx, |editor, cx| {
210    //         assert_eq!(editor.text(cx), unmarked_text);
211    //         editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
212    //             s.select_ranges(selection_ranges)
213    //         })
214    //     });
215    //     state_context
216    // }
217
218    // /// Make an assertion about the editor's text and the ranges and directions
219    // /// of its selections using a string containing embedded range markers.
220    // ///
221    // /// See the `util::test::marked_text_ranges` function for more information.
222    // #[track_caller]
223    // pub fn assert_editor_state(&mut self, marked_text: &str) {
224    //     let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true);
225    //     let buffer_text = self.buffer_text();
226
227    //     if buffer_text != unmarked_text {
228    //         panic!("Unmarked text doesn't match buffer text\nBuffer text: {buffer_text:?}\nUnmarked text: {unmarked_text:?}\nRaw buffer text\n{buffer_text}Raw unmarked text\n{unmarked_text}");
229    //     }
230
231    //     self.assert_selections(expected_selections, marked_text.to_string())
232    // }
233
234    // pub fn editor_state(&mut self) -> String {
235    //     generate_marked_text(self.buffer_text().as_str(), &self.editor_selections(), true)
236    // }
237
238    // #[track_caller]
239    // pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
240    //     let expected_ranges = self.ranges(marked_text);
241    //     let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
242    //         let snapshot = editor.snapshot(cx);
243    //         editor
244    //             .background_highlights
245    //             .get(&TypeId::of::<Tag>())
246    //             .map(|h| h.1.clone())
247    //             .unwrap_or_default()
248    //             .into_iter()
249    //             .map(|range| range.to_offset(&snapshot.buffer_snapshot))
250    //             .collect()
251    //     });
252    //     assert_set_eq!(actual_ranges, expected_ranges);
253    // }
254
255    // #[track_caller]
256    // pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
257    //     let expected_ranges = self.ranges(marked_text);
258    //     let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
259    //     let actual_ranges: Vec<Range<usize>> = snapshot
260    //         .text_highlight_ranges::<Tag>()
261    //         .map(|ranges| ranges.as_ref().clone().1)
262    //         .unwrap_or_default()
263    //         .into_iter()
264    //         .map(|range| range.to_offset(&snapshot.buffer_snapshot))
265    //         .collect();
266    //     assert_set_eq!(actual_ranges, expected_ranges);
267    // }
268
269    // #[track_caller]
270    // pub fn assert_editor_selections(&mut self, expected_selections: Vec<Range<usize>>) {
271    //     let expected_marked_text =
272    //         generate_marked_text(&self.buffer_text(), &expected_selections, true);
273    //     self.assert_selections(expected_selections, expected_marked_text)
274    // }
275
276    // fn editor_selections(&self) -> Vec<Range<usize>> {
277    //     self.editor
278    //         .read_with(self.cx, |editor, cx| editor.selections.all::<usize>(cx))
279    //         .into_iter()
280    //         .map(|s| {
281    //             if s.reversed {
282    //                 s.end..s.start
283    //             } else {
284    //                 s.start..s.end
285    //             }
286    //         })
287    //         .collect::<Vec<_>>()
288    // }
289
290    // #[track_caller]
291    // fn assert_selections(
292    //     &mut self,
293    //     expected_selections: Vec<Range<usize>>,
294    //     expected_marked_text: String,
295    // ) {
296    //     let actual_selections = self.editor_selections();
297    //     let actual_marked_text =
298    //         generate_marked_text(&self.buffer_text(), &actual_selections, true);
299    //     if expected_selections != actual_selections {
300    //         panic!(
301    //             indoc! {"
302
303    //                 {}Editor has unexpected selections.
304
305    //                 Expected selections:
306    //                 {}
307
308    //                 Actual selections:
309    //                 {}
310    //             "},
311    //             self.assertion_context(),
312    //             expected_marked_text,
313    //             actual_marked_text,
314    //         );
315    //     }
316    // }
317}
318
319impl<'a> Deref for EditorTestContext<'a> {
320    type Target = gpui::TestAppContext;
321
322    fn deref(&self) -> &Self::Target {
323        self.cx
324    }
325}
326
327impl<'a> DerefMut for EditorTestContext<'a> {
328    fn deref_mut(&mut self) -> &mut Self::Target {
329        &mut self.cx
330    }
331}