editor_test_context.rs

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