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