editor_test_context.rs

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