1use crate::{
2 display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
3 RowExt,
4};
5use collections::BTreeMap;
6use futures::Future;
7use gpui::{
8 AnyWindowHandle, AppContext, Keystroke, ModelContext, Pixels, Point, View, ViewContext,
9 VisualTestContext,
10};
11use indoc::indoc;
12use itertools::Itertools;
13use language::{Buffer, BufferSnapshot, LanguageRegistry};
14use multi_buffer::ExcerptRange;
15use parking_lot::RwLock;
16use project::{FakeFs, Project};
17use std::{
18 any::TypeId,
19 ops::{Deref, DerefMut, Range},
20 sync::{
21 atomic::{AtomicUsize, Ordering},
22 Arc,
23 },
24};
25
26use ui::Context;
27use util::{
28 assert_set_eq,
29 test::{generate_marked_text, marked_text_ranges},
30};
31
32use super::{build_editor, build_editor_with_project};
33
34pub struct EditorTestContext {
35 pub cx: gpui::VisualTestContext,
36 pub window: AnyWindowHandle,
37 pub editor: View<Editor>,
38 pub assertion_cx: AssertionContextManager,
39}
40
41impl EditorTestContext {
42 pub async fn new(cx: &mut gpui::TestAppContext) -> EditorTestContext {
43 let fs = FakeFs::new(cx.executor());
44 // fs.insert_file("/file", "".to_owned()).await;
45 fs.insert_tree(
46 "/root",
47 serde_json::json!({
48 "file": "",
49 }),
50 )
51 .await;
52 let project = Project::test(fs, ["/root".as_ref()], cx).await;
53 let buffer = project
54 .update(cx, |project, cx| {
55 project.open_local_buffer("/root/file", cx)
56 })
57 .await
58 .unwrap();
59 let editor = cx.add_window(|cx| {
60 let editor =
61 build_editor_with_project(project, MultiBuffer::build_from_buffer(buffer, cx), cx);
62 editor.focus(cx);
63 editor
64 });
65 let editor_view = editor.root_view(cx).unwrap();
66 Self {
67 cx: VisualTestContext::from_window(*editor.deref(), cx),
68 window: editor.into(),
69 editor: editor_view,
70 assertion_cx: AssertionContextManager::new(),
71 }
72 }
73
74 pub fn new_multibuffer<const COUNT: usize>(
75 cx: &mut gpui::TestAppContext,
76 excerpts: [&str; COUNT],
77 ) -> EditorTestContext {
78 let mut multibuffer = MultiBuffer::new(0, language::Capability::ReadWrite);
79 let buffer = cx.new_model(|cx| {
80 for excerpt in excerpts.into_iter() {
81 let (text, ranges) = marked_text_ranges(excerpt, false);
82 let buffer = cx.new_model(|cx| Buffer::local(text, cx));
83 multibuffer.push_excerpts(
84 buffer,
85 ranges.into_iter().map(|range| ExcerptRange {
86 context: range,
87 primary: None,
88 }),
89 cx,
90 );
91 }
92 multibuffer
93 });
94
95 let editor = cx.add_window(|cx| {
96 let editor = build_editor(buffer, cx);
97 editor.focus(cx);
98 editor
99 });
100
101 let editor_view = editor.root_view(cx).unwrap();
102 Self {
103 cx: VisualTestContext::from_window(*editor.deref(), cx),
104 window: editor.into(),
105 editor: editor_view,
106 assertion_cx: AssertionContextManager::new(),
107 }
108 }
109
110 pub fn condition(
111 &self,
112 predicate: impl FnMut(&Editor, &AppContext) -> bool,
113 ) -> impl Future<Output = ()> {
114 self.editor
115 .condition::<crate::EditorEvent>(&self.cx, predicate)
116 }
117
118 #[track_caller]
119 pub fn editor<F, T>(&mut self, read: F) -> T
120 where
121 F: FnOnce(&Editor, &ViewContext<Editor>) -> T,
122 {
123 self.editor
124 .update(&mut self.cx, |this, cx| read(&this, &cx))
125 }
126
127 #[track_caller]
128 pub fn update_editor<F, T>(&mut self, update: F) -> T
129 where
130 F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
131 {
132 self.editor.update(&mut self.cx, update)
133 }
134
135 pub fn multibuffer<F, T>(&mut self, read: F) -> T
136 where
137 F: FnOnce(&MultiBuffer, &AppContext) -> T,
138 {
139 self.editor(|editor, cx| read(editor.buffer().read(cx), cx))
140 }
141
142 pub fn update_multibuffer<F, T>(&mut self, update: F) -> T
143 where
144 F: FnOnce(&mut MultiBuffer, &mut ModelContext<MultiBuffer>) -> T,
145 {
146 self.update_editor(|editor, cx| editor.buffer().update(cx, update))
147 }
148
149 pub fn buffer_text(&mut self) -> String {
150 self.multibuffer(|buffer, cx| buffer.snapshot(cx).text())
151 }
152
153 pub fn display_text(&mut self) -> String {
154 self.update_editor(|editor, cx| editor.display_text(cx))
155 }
156
157 pub fn buffer<F, T>(&mut self, read: F) -> T
158 where
159 F: FnOnce(&Buffer, &AppContext) -> T,
160 {
161 self.multibuffer(|multibuffer, cx| {
162 let buffer = multibuffer.as_singleton().unwrap().read(cx);
163 read(buffer, cx)
164 })
165 }
166
167 pub fn language_registry(&mut self) -> Arc<LanguageRegistry> {
168 self.editor(|editor, cx| {
169 editor
170 .project
171 .as_ref()
172 .unwrap()
173 .read(cx)
174 .languages()
175 .clone()
176 })
177 }
178
179 pub fn update_buffer<F, T>(&mut self, update: F) -> T
180 where
181 F: FnOnce(&mut Buffer, &mut ModelContext<Buffer>) -> T,
182 {
183 self.update_multibuffer(|multibuffer, cx| {
184 let buffer = multibuffer.as_singleton().unwrap();
185 buffer.update(cx, update)
186 })
187 }
188
189 pub fn buffer_snapshot(&mut self) -> BufferSnapshot {
190 self.buffer(|buffer, _| buffer.snapshot())
191 }
192
193 pub fn add_assertion_context(&self, context: String) -> ContextHandle {
194 self.assertion_cx.add_context(context)
195 }
196
197 pub fn assertion_context(&self) -> String {
198 self.assertion_cx.context()
199 }
200
201 // unlike cx.simulate_keystrokes(), this does not run_until_parked
202 // so you can use it to test detailed timing
203 pub fn simulate_keystroke(&mut self, keystroke_text: &str) {
204 let keystroke = Keystroke::parse(keystroke_text).unwrap();
205 self.cx.dispatch_keystroke(self.window, keystroke);
206 }
207
208 pub fn run_until_parked(&mut self) {
209 self.cx.background_executor.run_until_parked();
210 }
211
212 pub fn ranges(&mut self, marked_text: &str) -> Vec<Range<usize>> {
213 let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
214 assert_eq!(self.buffer_text(), unmarked_text);
215 ranges
216 }
217
218 pub fn display_point(&mut self, marked_text: &str) -> DisplayPoint {
219 let ranges = self.ranges(marked_text);
220 let snapshot = self
221 .editor
222 .update(&mut self.cx, |editor, cx| editor.snapshot(cx));
223 ranges[0].start.to_display_point(&snapshot)
224 }
225
226 pub fn pixel_position(&mut self, marked_text: &str) -> Point<Pixels> {
227 let display_point = self.display_point(marked_text);
228 self.pixel_position_for(display_point)
229 }
230
231 pub fn pixel_position_for(&mut self, display_point: DisplayPoint) -> Point<Pixels> {
232 self.update_editor(|editor, cx| {
233 let newest_point = editor.selections.newest_display(cx).head();
234 let pixel_position = editor.pixel_position_of_newest_cursor.unwrap();
235 let line_height = editor
236 .style()
237 .unwrap()
238 .text
239 .line_height_in_pixels(cx.rem_size());
240 let snapshot = editor.snapshot(cx);
241 let details = editor.text_layout_details(cx);
242
243 let y = pixel_position.y
244 + line_height * (display_point.row().as_f32() - newest_point.row().as_f32());
245 let x = pixel_position.x + snapshot.x_for_display_point(display_point, &details)
246 - snapshot.x_for_display_point(newest_point, &details);
247 Point::new(x, y)
248 })
249 }
250
251 // Returns anchors for the current buffer using `«` and `»`
252 pub fn text_anchor_range(&mut self, marked_text: &str) -> Range<language::Anchor> {
253 let ranges = self.ranges(marked_text);
254 let snapshot = self.buffer_snapshot();
255 snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
256 }
257
258 pub fn set_diff_base(&mut self, diff_base: Option<&str>) {
259 self.update_buffer(|buffer, cx| buffer.set_diff_base(diff_base.map(ToOwned::to_owned), cx));
260 }
261
262 /// Change the editor's text and selections using a string containing
263 /// embedded range markers that represent the ranges and directions of
264 /// each selection.
265 ///
266 /// Returns a context handle so that assertion failures can print what
267 /// editor state was needed to cause the failure.
268 ///
269 /// See the `util::test::marked_text_ranges` function for more information.
270 pub fn set_state(&mut self, marked_text: &str) -> ContextHandle {
271 let state_context = self.add_assertion_context(format!(
272 "Initial Editor State: \"{}\"",
273 marked_text.escape_debug()
274 ));
275 let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
276 self.editor.update(&mut self.cx, |editor, cx| {
277 editor.set_text(unmarked_text, cx);
278 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
279 s.select_ranges(selection_ranges)
280 })
281 });
282 state_context
283 }
284
285 /// Only change the editor's selections
286 pub fn set_selections_state(&mut self, marked_text: &str) -> ContextHandle {
287 let state_context = self.add_assertion_context(format!(
288 "Initial Editor State: \"{}\"",
289 marked_text.escape_debug()
290 ));
291 let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
292 self.editor.update(&mut self.cx, |editor, cx| {
293 assert_eq!(editor.text(cx), unmarked_text);
294 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
295 s.select_ranges(selection_ranges)
296 })
297 });
298 state_context
299 }
300
301 /// Make an assertion about the editor's text and the ranges and directions
302 /// of its selections using a string containing embedded range markers.
303 ///
304 /// See the `util::test::marked_text_ranges` function for more information.
305 #[track_caller]
306 pub fn assert_editor_state(&mut self, marked_text: &str) {
307 let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true);
308 let buffer_text = self.buffer_text();
309
310 if buffer_text != unmarked_text {
311 panic!("Unmarked text doesn't match buffer text\nBuffer text: {buffer_text:?}\nUnmarked text: {unmarked_text:?}\nRaw buffer text\n{buffer_text}\nRaw unmarked text\n{unmarked_text}");
312 }
313
314 self.assert_selections(expected_selections, marked_text.to_string())
315 }
316
317 pub fn editor_state(&mut self) -> String {
318 generate_marked_text(self.buffer_text().as_str(), &self.editor_selections(), true)
319 }
320
321 #[track_caller]
322 pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
323 let expected_ranges = self.ranges(marked_text);
324 let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
325 let snapshot = editor.snapshot(cx);
326 editor
327 .background_highlights
328 .get(&TypeId::of::<Tag>())
329 .map(|h| h.1.clone())
330 .unwrap_or_else(|| Arc::default())
331 .into_iter()
332 .map(|range| range.to_offset(&snapshot.buffer_snapshot))
333 .collect()
334 });
335 assert_set_eq!(actual_ranges, expected_ranges);
336 }
337
338 #[track_caller]
339 pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
340 let expected_ranges = self.ranges(marked_text);
341 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
342 let actual_ranges: Vec<Range<usize>> = snapshot
343 .text_highlight_ranges::<Tag>()
344 .map(|ranges| ranges.as_ref().clone().1)
345 .unwrap_or_default()
346 .into_iter()
347 .map(|range| range.to_offset(&snapshot.buffer_snapshot))
348 .collect();
349 assert_set_eq!(actual_ranges, expected_ranges);
350 }
351
352 #[track_caller]
353 pub fn assert_editor_selections(&mut self, expected_selections: Vec<Range<usize>>) {
354 let expected_marked_text =
355 generate_marked_text(&self.buffer_text(), &expected_selections, true);
356 self.assert_selections(expected_selections, expected_marked_text)
357 }
358
359 #[track_caller]
360 fn editor_selections(&mut self) -> Vec<Range<usize>> {
361 self.editor
362 .update(&mut self.cx, |editor, cx| {
363 editor.selections.all::<usize>(cx)
364 })
365 .into_iter()
366 .map(|s| {
367 if s.reversed {
368 s.end..s.start
369 } else {
370 s.start..s.end
371 }
372 })
373 .collect::<Vec<_>>()
374 }
375
376 #[track_caller]
377 fn assert_selections(
378 &mut self,
379 expected_selections: Vec<Range<usize>>,
380 expected_marked_text: String,
381 ) {
382 let actual_selections = self.editor_selections();
383 let actual_marked_text =
384 generate_marked_text(&self.buffer_text(), &actual_selections, true);
385 if expected_selections != actual_selections {
386 panic!(
387 indoc! {"
388
389 {}Editor has unexpected selections.
390
391 Expected selections:
392 {}
393
394 Actual selections:
395 {}
396 "},
397 self.assertion_context(),
398 expected_marked_text,
399 actual_marked_text,
400 );
401 }
402 }
403}
404
405impl Deref for EditorTestContext {
406 type Target = gpui::VisualTestContext;
407
408 fn deref(&self) -> &Self::Target {
409 &self.cx
410 }
411}
412
413impl DerefMut for EditorTestContext {
414 fn deref_mut(&mut self) -> &mut Self::Target {
415 &mut self.cx
416 }
417}
418
419/// Tracks string context to be printed when assertions fail.
420/// Often this is done by storing a context string in the manager and returning the handle.
421#[derive(Clone)]
422pub struct AssertionContextManager {
423 id: Arc<AtomicUsize>,
424 contexts: Arc<RwLock<BTreeMap<usize, String>>>,
425}
426
427impl AssertionContextManager {
428 pub fn new() -> Self {
429 Self {
430 id: Arc::new(AtomicUsize::new(0)),
431 contexts: Arc::new(RwLock::new(BTreeMap::new())),
432 }
433 }
434
435 pub fn add_context(&self, context: String) -> ContextHandle {
436 let id = self.id.fetch_add(1, Ordering::Relaxed);
437 let mut contexts = self.contexts.write();
438 contexts.insert(id, context);
439 ContextHandle {
440 id,
441 manager: self.clone(),
442 }
443 }
444
445 pub fn context(&self) -> String {
446 let contexts = self.contexts.read();
447 format!("\n{}\n", contexts.values().join("\n"))
448 }
449}
450
451/// Used to track the lifetime of a piece of context so that it can be provided when an assertion fails.
452/// For example, in the EditorTestContext, `set_state` returns a context handle so that if an assertion fails,
453/// the state that was set initially for the failure can be printed in the error message
454pub struct ContextHandle {
455 id: usize,
456 manager: AssertionContextManager,
457}
458
459impl Drop for ContextHandle {
460 fn drop(&mut self) {
461 let mut contexts = self.manager.contexts.write();
462 contexts.remove(&self.id);
463 }
464}