test.rs

  1use std::ops::{Deref, DerefMut, Range};
  2
  3use indoc::indoc;
  4
  5use collections::BTreeMap;
  6use gpui::{keymap::Keystroke, ModelHandle, ViewContext, ViewHandle};
  7use language::Selection;
  8use settings::Settings;
  9use util::{
 10    set_eq,
 11    test::{marked_text, marked_text_ranges, marked_text_ranges_by, SetEqError},
 12};
 13
 14use crate::{
 15    display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
 16    Autoscroll, DisplayPoint, Editor, EditorMode, MultiBuffer,
 17};
 18
 19#[cfg(test)]
 20#[ctor::ctor]
 21fn init_logger() {
 22    if std::env::var("RUST_LOG").is_ok() {
 23        env_logger::init();
 24    }
 25}
 26
 27// Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.
 28pub fn marked_display_snapshot(
 29    text: &str,
 30    cx: &mut gpui::MutableAppContext,
 31) -> (DisplaySnapshot, Vec<DisplayPoint>) {
 32    let (unmarked_text, markers) = marked_text(text);
 33
 34    let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
 35    let font_id = cx
 36        .font_cache()
 37        .select_font(family_id, &Default::default())
 38        .unwrap();
 39    let font_size = 14.0;
 40
 41    let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
 42    let display_map =
 43        cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
 44    let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
 45    let markers = markers
 46        .into_iter()
 47        .map(|offset| offset.to_display_point(&snapshot))
 48        .collect();
 49
 50    (snapshot, markers)
 51}
 52
 53pub fn select_ranges(editor: &mut Editor, marked_text: &str, cx: &mut ViewContext<Editor>) {
 54    let (umarked_text, text_ranges) = marked_text_ranges(marked_text);
 55    assert_eq!(editor.text(cx), umarked_text);
 56    editor.change_selections(None, cx, |s| s.select_ranges(text_ranges));
 57}
 58
 59pub fn assert_text_with_selections(
 60    editor: &mut Editor,
 61    marked_text: &str,
 62    cx: &mut ViewContext<Editor>,
 63) {
 64    let (unmarked_text, text_ranges) = marked_text_ranges(marked_text);
 65
 66    assert_eq!(editor.text(cx), unmarked_text);
 67    assert_eq!(editor.selections.ranges(cx), text_ranges);
 68}
 69
 70pub(crate) fn build_editor(
 71    buffer: ModelHandle<MultiBuffer>,
 72    cx: &mut ViewContext<Editor>,
 73) -> Editor {
 74    Editor::new(EditorMode::Full, buffer, None, None, None, cx)
 75}
 76
 77pub struct EditorTestContext<'a> {
 78    pub cx: &'a mut gpui::TestAppContext,
 79    pub window_id: usize,
 80    pub editor: ViewHandle<Editor>,
 81}
 82
 83impl<'a> EditorTestContext<'a> {
 84    pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
 85        let (window_id, editor) = cx.update(|cx| {
 86            cx.set_global(Settings::test(cx));
 87            crate::init(cx);
 88
 89            let (window_id, editor) = cx.add_window(Default::default(), |cx| {
 90                build_editor(MultiBuffer::build_simple("", cx), cx)
 91            });
 92
 93            editor.update(cx, |_, cx| cx.focus_self());
 94
 95            (window_id, editor)
 96        });
 97
 98        Self {
 99            cx,
100            window_id,
101            editor,
102        }
103    }
104
105    pub fn update_editor<F, T>(&mut self, update: F) -> T
106    where
107        F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
108    {
109        self.editor.update(self.cx, update)
110    }
111
112    pub fn buffer_text(&mut self) -> String {
113        self.editor.read_with(self.cx, |editor, cx| {
114            editor.buffer.read(cx).snapshot(cx).text()
115        })
116    }
117
118    pub fn simulate_keystroke(&mut self, keystroke_text: &str) {
119        let keystroke = Keystroke::parse(keystroke_text).unwrap();
120        let input = if keystroke.modified() {
121            None
122        } else {
123            Some(keystroke.key.clone())
124        };
125        self.cx
126            .dispatch_keystroke(self.window_id, keystroke, input, false);
127    }
128
129    pub fn simulate_keystrokes<const COUNT: usize>(&mut self, keystroke_texts: [&str; COUNT]) {
130        for keystroke_text in keystroke_texts.into_iter() {
131            self.simulate_keystroke(keystroke_text);
132        }
133    }
134
135    // Sets the editor state via a marked string.
136    // `|` characters represent empty selections
137    // `[` to `}` represents a non empty selection with the head at `}`
138    // `{` to `]` represents a non empty selection with the head at `{`
139    pub fn set_state(&mut self, text: &str) {
140        self.editor.update(self.cx, |editor, cx| {
141            let (unmarked_text, mut selection_ranges) = marked_text_ranges_by(
142                &text,
143                vec!['|'.into(), ('[', '}').into(), ('{', ']').into()],
144            );
145            editor.set_text(unmarked_text, cx);
146
147            let mut selections: Vec<Range<usize>> =
148                selection_ranges.remove(&'|'.into()).unwrap_or_default();
149            selections.extend(
150                selection_ranges
151                    .remove(&('{', ']').into())
152                    .unwrap_or_default()
153                    .into_iter()
154                    .map(|range| range.end..range.start),
155            );
156            selections.extend(
157                selection_ranges
158                    .remove(&('[', '}').into())
159                    .unwrap_or_default(),
160            );
161
162            editor.change_selections(Some(Autoscroll::Fit), cx, |s| s.select_ranges(selections));
163        })
164    }
165
166    // Asserts the editor state via a marked string.
167    // `|` characters represent empty selections
168    // `[` to `}` represents a non empty selection with the head at `}`
169    // `{` to `]` represents a non empty selection with the head at `{`
170    pub fn assert_editor_state(&mut self, text: &str) {
171        let (unmarked_text, mut selection_ranges) = marked_text_ranges_by(
172            &text,
173            vec!['|'.into(), ('[', '}').into(), ('{', ']').into()],
174        );
175        let buffer_text = self.buffer_text();
176        assert_eq!(
177            buffer_text, unmarked_text,
178            "Unmarked text doesn't match buffer text"
179        );
180
181        let expected_empty_selections = selection_ranges.remove(&'|'.into()).unwrap_or_default();
182        let expected_reverse_selections = selection_ranges
183            .remove(&('{', ']').into())
184            .unwrap_or_default();
185        let expected_forward_selections = selection_ranges
186            .remove(&('[', '}').into())
187            .unwrap_or_default();
188
189        self.assert_selections(
190            expected_empty_selections,
191            expected_reverse_selections,
192            expected_forward_selections,
193            Some(text.to_string()),
194        )
195    }
196
197    pub fn assert_editor_selections(&mut self, expected_selections: Vec<Selection<usize>>) {
198        let mut empty_selections = Vec::new();
199        let mut reverse_selections = Vec::new();
200        let mut forward_selections = Vec::new();
201
202        for selection in expected_selections {
203            let range = selection.range();
204            if selection.is_empty() {
205                empty_selections.push(range);
206            } else if selection.reversed {
207                reverse_selections.push(range);
208            } else {
209                forward_selections.push(range)
210            }
211        }
212
213        self.assert_selections(
214            empty_selections,
215            reverse_selections,
216            forward_selections,
217            None,
218        )
219    }
220
221    fn assert_selections(
222        &mut self,
223        expected_empty_selections: Vec<Range<usize>>,
224        expected_reverse_selections: Vec<Range<usize>>,
225        expected_forward_selections: Vec<Range<usize>>,
226        asserted_text: Option<String>,
227    ) {
228        let (empty_selections, reverse_selections, forward_selections) =
229            self.editor.read_with(self.cx, |editor, cx| {
230                let mut empty_selections = Vec::new();
231                let mut reverse_selections = Vec::new();
232                let mut forward_selections = Vec::new();
233
234                for selection in editor.selections.all::<usize>(cx) {
235                    let range = selection.range();
236                    if selection.is_empty() {
237                        empty_selections.push(range);
238                    } else if selection.reversed {
239                        reverse_selections.push(range);
240                    } else {
241                        forward_selections.push(range)
242                    }
243                }
244
245                (empty_selections, reverse_selections, forward_selections)
246            });
247
248        let asserted_selections = asserted_text.unwrap_or_else(|| {
249            self.insert_markers(
250                &expected_empty_selections,
251                &expected_reverse_selections,
252                &expected_forward_selections,
253            )
254        });
255        let actual_selections =
256            self.insert_markers(&empty_selections, &reverse_selections, &forward_selections);
257
258        let unmarked_text = self.buffer_text();
259        let all_eq: Result<(), SetEqError<String>> =
260            set_eq!(expected_empty_selections, empty_selections)
261                .map_err(|err| {
262                    err.map(|missing| {
263                        let mut error_text = unmarked_text.clone();
264                        error_text.insert(missing.start, '|');
265                        error_text
266                    })
267                })
268                .and_then(|_| {
269                    set_eq!(expected_reverse_selections, reverse_selections).map_err(|err| {
270                        err.map(|missing| {
271                            let mut error_text = unmarked_text.clone();
272                            error_text.insert(missing.start, '{');
273                            error_text.insert(missing.end, ']');
274                            error_text
275                        })
276                    })
277                })
278                .and_then(|_| {
279                    set_eq!(expected_forward_selections, forward_selections).map_err(|err| {
280                        err.map(|missing| {
281                            let mut error_text = unmarked_text.clone();
282                            error_text.insert(missing.start, '[');
283                            error_text.insert(missing.end, '}');
284                            error_text
285                        })
286                    })
287                });
288
289        match all_eq {
290            Err(SetEqError::LeftMissing(location_text)) => {
291                panic!(
292                    indoc! {"
293                        Editor has extra selection
294                        Extra Selection Location:
295                        {}
296                        Asserted selections:
297                        {}
298                        Actual selections:
299                        {}"},
300                    location_text, asserted_selections, actual_selections,
301                );
302            }
303            Err(SetEqError::RightMissing(location_text)) => {
304                panic!(
305                    indoc! {"
306                        Editor is missing empty selection
307                        Missing Selection Location:
308                        {}
309                        Asserted selections:
310                        {}
311                        Actual selections:
312                        {}"},
313                    location_text, asserted_selections, actual_selections,
314                );
315            }
316            _ => {}
317        }
318    }
319
320    fn insert_markers(
321        &mut self,
322        empty_selections: &Vec<Range<usize>>,
323        reverse_selections: &Vec<Range<usize>>,
324        forward_selections: &Vec<Range<usize>>,
325    ) -> String {
326        let mut editor_text_with_selections = self.buffer_text();
327        let mut selection_marks = BTreeMap::new();
328        for range in empty_selections {
329            selection_marks.insert(&range.start, '|');
330        }
331        for range in reverse_selections {
332            selection_marks.insert(&range.start, '{');
333            selection_marks.insert(&range.end, ']');
334        }
335        for range in forward_selections {
336            selection_marks.insert(&range.start, '[');
337            selection_marks.insert(&range.end, '}');
338        }
339        for (offset, mark) in selection_marks.into_iter().rev() {
340            editor_text_with_selections.insert(*offset, mark);
341        }
342
343        editor_text_with_selections
344    }
345
346    pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) {
347        self.cx.update(|cx| {
348            let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned());
349            let expected_content = expected_content.map(|content| content.to_owned());
350            assert_eq!(actual_content, expected_content);
351        })
352    }
353}
354
355impl<'a> Deref for EditorTestContext<'a> {
356    type Target = gpui::TestAppContext;
357
358    fn deref(&self) -> &Self::Target {
359        self.cx
360    }
361}
362
363impl<'a> DerefMut for EditorTestContext<'a> {
364    fn deref_mut(&mut self) -> &mut Self::Target {
365        &mut self.cx
366    }
367}