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