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 editor_text(&mut self) -> String {
113        self.editor
114            .update(self.cx, |editor, cx| editor.snapshot(cx).text())
115    }
116
117    pub fn simulate_keystroke(&mut self, keystroke_text: &str) {
118        let keystroke = Keystroke::parse(keystroke_text).unwrap();
119        let input = if keystroke.modified() {
120            None
121        } else {
122            Some(keystroke.key.clone())
123        };
124        self.cx
125            .dispatch_keystroke(self.window_id, keystroke, input, false);
126    }
127
128    pub fn simulate_keystrokes<const COUNT: usize>(&mut self, keystroke_texts: [&str; COUNT]) {
129        for keystroke_text in keystroke_texts.into_iter() {
130            self.simulate_keystroke(keystroke_text);
131        }
132    }
133
134    // Sets the editor state via a marked string.
135    // `|` characters represent empty selections
136    // `[` to `}` represents a non empty selection with the head at `}`
137    // `{` to `]` represents a non empty selection with the head at `{`
138    pub fn set_state(&mut self, text: &str) {
139        self.editor.update(self.cx, |editor, cx| {
140            let (unmarked_text, mut selection_ranges) = marked_text_ranges_by(
141                &text,
142                vec!['|'.into(), ('[', '}').into(), ('{', ']').into()],
143            );
144            editor.set_text(unmarked_text, cx);
145
146            let mut selections: Vec<Range<usize>> =
147                selection_ranges.remove(&'|'.into()).unwrap_or_default();
148            selections.extend(
149                selection_ranges
150                    .remove(&('{', ']').into())
151                    .unwrap_or_default()
152                    .into_iter()
153                    .map(|range| range.end..range.start),
154            );
155            selections.extend(
156                selection_ranges
157                    .remove(&('[', '}').into())
158                    .unwrap_or_default(),
159            );
160
161            editor.change_selections(Some(Autoscroll::Fit), cx, |s| s.select_ranges(selections));
162        })
163    }
164
165    // Asserts the editor state via a marked string.
166    // `|` characters represent empty selections
167    // `[` to `}` represents a non empty selection with the head at `}`
168    // `{` to `]` represents a non empty selection with the head at `{`
169    pub fn assert_editor_state(&mut self, text: &str) {
170        let (unmarked_text, mut selection_ranges) = marked_text_ranges_by(
171            &text,
172            vec!['|'.into(), ('[', '}').into(), ('{', ']').into()],
173        );
174        let editor_text = self.editor_text();
175        assert_eq!(
176            editor_text, unmarked_text,
177            "Unmarked text doesn't match editor text"
178        );
179
180        let expected_empty_selections = selection_ranges.remove(&'|'.into()).unwrap_or_default();
181        let expected_reverse_selections = selection_ranges
182            .remove(&('{', ']').into())
183            .unwrap_or_default();
184        let expected_forward_selections = selection_ranges
185            .remove(&('[', '}').into())
186            .unwrap_or_default();
187
188        self.assert_selections(
189            expected_empty_selections,
190            expected_reverse_selections,
191            expected_forward_selections,
192            Some(text.to_string()),
193        )
194    }
195
196    pub fn assert_editor_selections(&mut self, expected_selections: Vec<Selection<usize>>) {
197        let mut empty_selections = Vec::new();
198        let mut reverse_selections = Vec::new();
199        let mut forward_selections = Vec::new();
200
201        for selection in expected_selections {
202            let range = selection.range();
203            if selection.is_empty() {
204                empty_selections.push(range);
205            } else if selection.reversed {
206                reverse_selections.push(range);
207            } else {
208                forward_selections.push(range)
209            }
210        }
211
212        self.assert_selections(
213            empty_selections,
214            reverse_selections,
215            forward_selections,
216            None,
217        )
218    }
219
220    fn assert_selections(
221        &mut self,
222        expected_empty_selections: Vec<Range<usize>>,
223        expected_reverse_selections: Vec<Range<usize>>,
224        expected_forward_selections: Vec<Range<usize>>,
225        asserted_text: Option<String>,
226    ) {
227        let (empty_selections, reverse_selections, forward_selections) =
228            self.editor.read_with(self.cx, |editor, cx| {
229                let mut empty_selections = Vec::new();
230                let mut reverse_selections = Vec::new();
231                let mut forward_selections = Vec::new();
232
233                for selection in editor.selections.all::<usize>(cx) {
234                    let range = selection.range();
235                    if selection.is_empty() {
236                        empty_selections.push(range);
237                    } else if selection.reversed {
238                        reverse_selections.push(range);
239                    } else {
240                        forward_selections.push(range)
241                    }
242                }
243
244                (empty_selections, reverse_selections, forward_selections)
245            });
246
247        let asserted_selections = asserted_text.unwrap_or_else(|| {
248            self.insert_markers(
249                &expected_empty_selections,
250                &expected_reverse_selections,
251                &expected_forward_selections,
252            )
253        });
254        let actual_selections =
255            self.insert_markers(&empty_selections, &reverse_selections, &forward_selections);
256
257        let unmarked_text = self.editor_text();
258        let all_eq: Result<(), SetEqError<String>> =
259            set_eq!(expected_empty_selections, empty_selections)
260                .map_err(|err| {
261                    err.map(|missing| {
262                        let mut error_text = unmarked_text.clone();
263                        error_text.insert(missing.start, '|');
264                        error_text
265                    })
266                })
267                .and_then(|_| {
268                    set_eq!(expected_reverse_selections, reverse_selections).map_err(|err| {
269                        err.map(|missing| {
270                            let mut error_text = unmarked_text.clone();
271                            error_text.insert(missing.start, '{');
272                            error_text.insert(missing.end, ']');
273                            error_text
274                        })
275                    })
276                })
277                .and_then(|_| {
278                    set_eq!(expected_forward_selections, forward_selections).map_err(|err| {
279                        err.map(|missing| {
280                            let mut error_text = unmarked_text.clone();
281                            error_text.insert(missing.start, '[');
282                            error_text.insert(missing.end, '}');
283                            error_text
284                        })
285                    })
286                });
287
288        match all_eq {
289            Err(SetEqError::LeftMissing(location_text)) => {
290                panic!(
291                    indoc! {"
292                        Editor has extra selection
293                        Extra Selection Location:
294                        {}
295                        Asserted selections:
296                        {}
297                        Actual selections:
298                        {}"},
299                    location_text, asserted_selections, actual_selections,
300                );
301            }
302            Err(SetEqError::RightMissing(location_text)) => {
303                panic!(
304                    indoc! {"
305                        Editor is missing empty selection
306                        Missing Selection Location:
307                        {}
308                        Asserted selections:
309                        {}
310                        Actual selections:
311                        {}"},
312                    location_text, asserted_selections, actual_selections,
313                );
314            }
315            _ => {}
316        }
317    }
318
319    fn insert_markers(
320        &mut self,
321        empty_selections: &Vec<Range<usize>>,
322        reverse_selections: &Vec<Range<usize>>,
323        forward_selections: &Vec<Range<usize>>,
324    ) -> String {
325        let mut editor_text_with_selections = self.editor_text();
326        let mut selection_marks = BTreeMap::new();
327        for range in empty_selections {
328            selection_marks.insert(&range.start, '|');
329        }
330        for range in reverse_selections {
331            selection_marks.insert(&range.start, '{');
332            selection_marks.insert(&range.end, ']');
333        }
334        for range in forward_selections {
335            selection_marks.insert(&range.start, '[');
336            selection_marks.insert(&range.end, '}');
337        }
338        for (offset, mark) in selection_marks.into_iter().rev() {
339            editor_text_with_selections.insert(*offset, mark);
340        }
341
342        editor_text_with_selections
343    }
344
345    pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) {
346        self.cx.update(|cx| {
347            let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned());
348            let expected_content = expected_content.map(|content| content.to_owned());
349            assert_eq!(actual_content, expected_content);
350        })
351    }
352}
353
354impl<'a> Deref for EditorTestContext<'a> {
355    type Target = gpui::TestAppContext;
356
357    fn deref(&self) -> &Self::Target {
358        self.cx
359    }
360}
361
362impl<'a> DerefMut for EditorTestContext<'a> {
363    fn deref_mut(&mut self) -> &mut Self::Target {
364        &mut self.cx
365    }
366}