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
 20// use 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.executor().clone());
 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.update(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}