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}