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    /// Change the editor's text and selections using a string containing
155    /// embedded range markers that represent the ranges and directions of
156    /// each selection.
157    ///
158    /// See the `util::test::marked_text_ranges` function for more information.
159    pub fn set_state(&mut self, marked_text: &str) -> ContextHandle {
160        let _state_context = self.add_assertion_context(format!(
161            "Editor State: \"{}\"",
162            marked_text.escape_debug().to_string()
163        ));
164        let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
165        self.editor.update(self.cx, |editor, cx| {
166            editor.set_text(unmarked_text, cx);
167            editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
168                s.select_ranges(selection_ranges)
169            })
170        });
171        _state_context
172    }
173
174    /// Make an assertion about the editor's text and the ranges and directions
175    /// of its selections using a string containing embedded range markers.
176    ///
177    /// See the `util::test::marked_text_ranges` function for more information.
178    pub fn assert_editor_state(&mut self, marked_text: &str) {
179        let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true);
180        let buffer_text = self.buffer_text();
181        assert_eq!(
182            buffer_text, unmarked_text,
183            "Unmarked text doesn't match buffer text"
184        );
185        self.assert_selections(expected_selections, marked_text.to_string())
186    }
187
188    pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
189        let expected_ranges = self.ranges(marked_text);
190        let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
191            let snapshot = editor.snapshot(cx);
192            editor
193                .background_highlights
194                .get(&TypeId::of::<Tag>())
195                .map(|h| h.1.clone())
196                .unwrap_or_default()
197                .into_iter()
198                .map(|range| range.to_offset(&snapshot.buffer_snapshot))
199                .collect()
200        });
201        assert_set_eq!(actual_ranges, expected_ranges);
202    }
203
204    pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
205        let expected_ranges = self.ranges(marked_text);
206        let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
207        let actual_ranges: Vec<Range<usize>> = snapshot
208            .highlight_ranges::<Tag>()
209            .map(|ranges| ranges.as_ref().clone().1)
210            .unwrap_or_default()
211            .into_iter()
212            .map(|range| range.to_offset(&snapshot.buffer_snapshot))
213            .collect();
214        assert_set_eq!(actual_ranges, expected_ranges);
215    }
216
217    pub fn assert_editor_selections(&mut self, expected_selections: Vec<Range<usize>>) {
218        let expected_marked_text =
219            generate_marked_text(&self.buffer_text(), &expected_selections, true);
220        self.assert_selections(expected_selections, expected_marked_text)
221    }
222
223    fn assert_selections(
224        &mut self,
225        expected_selections: Vec<Range<usize>>,
226        expected_marked_text: String,
227    ) {
228        let actual_selections = self
229            .editor
230            .read_with(self.cx, |editor, cx| editor.selections.all::<usize>(cx))
231            .into_iter()
232            .map(|s| {
233                if s.reversed {
234                    s.end..s.start
235                } else {
236                    s.start..s.end
237                }
238            })
239            .collect::<Vec<_>>();
240        let actual_marked_text =
241            generate_marked_text(&self.buffer_text(), &actual_selections, true);
242        if expected_selections != actual_selections {
243            panic!(
244                indoc! {"
245                    {}Editor has unexpected selections.
246                    
247                    Expected selections:
248                    {}
249                    
250                    Actual selections:
251                    {}
252                    "},
253                self.assertion_context(),
254                expected_marked_text,
255                actual_marked_text,
256            );
257        }
258    }
259}
260
261impl<'a> Deref for EditorTestContext<'a> {
262    type Target = gpui::TestAppContext;
263
264    fn deref(&self) -> &Self::Target {
265        self.cx
266    }
267}
268
269impl<'a> DerefMut for EditorTestContext<'a> {
270    fn deref_mut(&mut self) -> &mut Self::Target {
271        &mut self.cx
272    }
273}