1use crate::{
2 display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
3};
4use futures::Future;
5use gpui::{
6 keymap_matcher::Keystroke, AnyWindowHandle, AppContext, ContextHandle, ModelContext,
7 ViewContext, ViewHandle,
8};
9use indoc::indoc;
10use language::{Buffer, BufferSnapshot};
11use project::{FakeFs, Project};
12use std::{
13 any::TypeId,
14 ops::{Deref, DerefMut, Range},
15};
16use util::{
17 assert_set_eq,
18 test::{generate_marked_text, marked_text_ranges},
19};
20
21use super::build_editor;
22
23pub struct EditorTestContext<'a> {
24 pub cx: &'a mut gpui::TestAppContext,
25 pub window: AnyWindowHandle,
26 pub editor: ViewHandle<Editor>,
27}
28
29impl<'a> EditorTestContext<'a> {
30 pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
31 let fs = FakeFs::new(cx.background());
32 let project = Project::test(fs, [], cx).await;
33 let buffer = project
34 .update(cx, |project, cx| project.create_buffer("", None, cx))
35 .unwrap();
36 let window = cx.add_window(|cx| {
37 cx.focus_self();
38 build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx)
39 });
40 let editor = window.root(cx);
41 Self {
42 cx,
43 window: window.into(),
44 editor,
45 }
46 }
47
48 pub fn condition(
49 &self,
50 predicate: impl FnMut(&Editor, &AppContext) -> bool,
51 ) -> impl Future<Output = ()> {
52 self.editor.condition(self.cx, predicate)
53 }
54
55 pub fn editor<F, T>(&self, read: F) -> T
56 where
57 F: FnOnce(&Editor, &ViewContext<Editor>) -> T,
58 {
59 self.editor.read_with(self.cx, read)
60 }
61
62 pub fn update_editor<F, T>(&mut self, update: F) -> T
63 where
64 F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
65 {
66 self.editor.update(self.cx, update)
67 }
68
69 pub fn multibuffer<F, T>(&self, read: F) -> T
70 where
71 F: FnOnce(&MultiBuffer, &AppContext) -> T,
72 {
73 self.editor(|editor, cx| read(editor.buffer().read(cx), cx))
74 }
75
76 pub fn update_multibuffer<F, T>(&mut self, update: F) -> T
77 where
78 F: FnOnce(&mut MultiBuffer, &mut ModelContext<MultiBuffer>) -> T,
79 {
80 self.update_editor(|editor, cx| editor.buffer().update(cx, update))
81 }
82
83 pub fn buffer_text(&self) -> String {
84 self.multibuffer(|buffer, cx| buffer.snapshot(cx).text())
85 }
86
87 pub fn buffer<F, T>(&self, read: F) -> T
88 where
89 F: FnOnce(&Buffer, &AppContext) -> T,
90 {
91 self.multibuffer(|multibuffer, cx| {
92 let buffer = multibuffer.as_singleton().unwrap().read(cx);
93 read(buffer, cx)
94 })
95 }
96
97 pub fn update_buffer<F, T>(&mut self, update: F) -> T
98 where
99 F: FnOnce(&mut Buffer, &mut ModelContext<Buffer>) -> T,
100 {
101 self.update_multibuffer(|multibuffer, cx| {
102 let buffer = multibuffer.as_singleton().unwrap();
103 buffer.update(cx, update)
104 })
105 }
106
107 pub fn buffer_snapshot(&self) -> BufferSnapshot {
108 self.buffer(|buffer, _| buffer.snapshot())
109 }
110
111 pub fn simulate_keystroke(&mut self, keystroke_text: &str) -> ContextHandle {
112 let keystroke_under_test_handle =
113 self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
114 let keystroke = Keystroke::parse(keystroke_text).unwrap();
115
116 self.cx.dispatch_keystroke(self.window, keystroke, false);
117 keystroke_under_test_handle
118 }
119
120 pub fn simulate_keystrokes<const COUNT: usize>(
121 &mut self,
122 keystroke_texts: [&str; COUNT],
123 ) -> ContextHandle {
124 let keystrokes_under_test_handle =
125 self.add_assertion_context(format!("Simulated Keystrokes: {:?}", keystroke_texts));
126 for keystroke_text in keystroke_texts.into_iter() {
127 self.simulate_keystroke(keystroke_text);
128 }
129 keystrokes_under_test_handle
130 }
131
132 pub fn ranges(&self, marked_text: &str) -> Vec<Range<usize>> {
133 let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
134 assert_eq!(self.buffer_text(), unmarked_text);
135 ranges
136 }
137
138 pub fn display_point(&mut self, marked_text: &str) -> DisplayPoint {
139 let ranges = self.ranges(marked_text);
140 let snapshot = self
141 .editor
142 .update(self.cx, |editor, cx| editor.snapshot(cx));
143 ranges[0].start.to_display_point(&snapshot)
144 }
145
146 // Returns anchors for the current buffer using `«` and `»`
147 pub fn text_anchor_range(&self, marked_text: &str) -> Range<language::Anchor> {
148 let ranges = self.ranges(marked_text);
149 let snapshot = self.buffer_snapshot();
150 snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
151 }
152
153 pub fn set_diff_base(&mut self, diff_base: Option<&str>) {
154 let diff_base = diff_base.map(String::from);
155 self.update_buffer(|buffer, cx| buffer.set_diff_base(diff_base, cx));
156 }
157
158 /// Change the editor's text and selections using a string containing
159 /// embedded range markers that represent the ranges and directions of
160 /// each selection.
161 ///
162 /// Returns a context handle so that assertion failures can print what
163 /// editor state was needed to cause the failure.
164 ///
165 /// See the `util::test::marked_text_ranges` function for more information.
166 pub fn set_state(&mut self, marked_text: &str) -> ContextHandle {
167 let state_context = self.add_assertion_context(format!(
168 "Initial Editor State: \"{}\"",
169 marked_text.escape_debug().to_string()
170 ));
171 let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
172 self.editor.update(self.cx, |editor, cx| {
173 editor.set_text(unmarked_text, cx);
174 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
175 s.select_ranges(selection_ranges)
176 })
177 });
178 state_context
179 }
180
181 /// Only change the editor's selections
182 pub fn set_selections_state(&mut self, marked_text: &str) -> ContextHandle {
183 let state_context = self.add_assertion_context(format!(
184 "Initial Editor State: \"{}\"",
185 marked_text.escape_debug().to_string()
186 ));
187 let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
188 self.editor.update(self.cx, |editor, cx| {
189 assert_eq!(editor.text(cx), unmarked_text);
190 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
191 s.select_ranges(selection_ranges)
192 })
193 });
194 state_context
195 }
196
197 /// Make an assertion about the editor's text and the ranges and directions
198 /// of its selections using a string containing embedded range markers.
199 ///
200 /// See the `util::test::marked_text_ranges` function for more information.
201 #[track_caller]
202 pub fn assert_editor_state(&mut self, marked_text: &str) {
203 let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true);
204 let buffer_text = self.buffer_text();
205
206 if buffer_text != unmarked_text {
207 panic!("Unmarked text doesn't match buffer text\nBuffer text: {buffer_text:?}\nUnmarked text: {unmarked_text:?}\nRaw buffer text\n{buffer_text}Raw unmarked text\n{unmarked_text}");
208 }
209
210 self.assert_selections(expected_selections, marked_text.to_string())
211 }
212
213 pub fn editor_state(&mut self) -> String {
214 generate_marked_text(self.buffer_text().as_str(), &self.editor_selections(), true)
215 }
216
217 #[track_caller]
218 pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
219 let expected_ranges = self.ranges(marked_text);
220 let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
221 let snapshot = editor.snapshot(cx);
222 editor
223 .background_highlights
224 .get(&TypeId::of::<Tag>())
225 .map(|h| h.1.clone())
226 .unwrap_or_default()
227 .into_iter()
228 .map(|range| range.to_offset(&snapshot.buffer_snapshot))
229 .collect()
230 });
231 assert_set_eq!(actual_ranges, expected_ranges);
232 }
233
234 #[track_caller]
235 pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
236 let expected_ranges = self.ranges(marked_text);
237 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
238 let actual_ranges: Vec<Range<usize>> = snapshot
239 .text_highlight_ranges::<Tag>()
240 .map(|ranges| ranges.as_ref().clone().1)
241 .unwrap_or_default()
242 .into_iter()
243 .map(|range| range.to_offset(&snapshot.buffer_snapshot))
244 .collect();
245 assert_set_eq!(actual_ranges, expected_ranges);
246 }
247
248 #[track_caller]
249 pub fn assert_editor_selections(&mut self, expected_selections: Vec<Range<usize>>) {
250 let expected_marked_text =
251 generate_marked_text(&self.buffer_text(), &expected_selections, true);
252 self.assert_selections(expected_selections, expected_marked_text)
253 }
254
255 fn editor_selections(&self) -> Vec<Range<usize>> {
256 self.editor
257 .read_with(self.cx, |editor, cx| editor.selections.all::<usize>(cx))
258 .into_iter()
259 .map(|s| {
260 if s.reversed {
261 s.end..s.start
262 } else {
263 s.start..s.end
264 }
265 })
266 .collect::<Vec<_>>()
267 }
268
269 #[track_caller]
270 fn assert_selections(
271 &mut self,
272 expected_selections: Vec<Range<usize>>,
273 expected_marked_text: String,
274 ) {
275 let actual_selections = self.editor_selections();
276 let actual_marked_text =
277 generate_marked_text(&self.buffer_text(), &actual_selections, true);
278 if expected_selections != actual_selections {
279 panic!(
280 indoc! {"
281
282 {}Editor has unexpected selections.
283
284 Expected selections:
285 {}
286
287 Actual selections:
288 {}
289 "},
290 self.assertion_context(),
291 expected_marked_text,
292 actual_marked_text,
293 );
294 }
295 }
296}
297
298impl<'a> Deref for EditorTestContext<'a> {
299 type Target = gpui::TestAppContext;
300
301 fn deref(&self) -> &Self::Target {
302 self.cx
303 }
304}
305
306impl<'a> DerefMut for EditorTestContext<'a> {
307 fn deref_mut(&mut self) -> &mut Self::Target {
308 &mut self.cx
309 }
310}