1use crate::{
2 executor,
3 geometry::vector::Vector2F,
4 keymap_matcher::{Binding, Keystroke},
5 platform,
6 platform::{Event, InputHandler, KeyDownEvent, Platform},
7 Action, AnyWindowHandle, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache,
8 Handle, ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle,
9 WeakHandle, WindowContext, WindowHandle,
10};
11use collections::BTreeMap;
12use futures::Future;
13use itertools::Itertools;
14use parking_lot::{Mutex, RwLock};
15use smallvec::SmallVec;
16use smol::stream::StreamExt;
17use std::{
18 any::Any,
19 cell::RefCell,
20 mem,
21 path::PathBuf,
22 rc::Rc,
23 sync::{
24 atomic::{AtomicUsize, Ordering},
25 Arc,
26 },
27 time::Duration,
28};
29
30use super::{
31 ref_counts::LeakDetector, window_input_handler::WindowInputHandler, AsyncAppContext, RefCounts,
32};
33
34#[derive(Clone)]
35pub struct TestAppContext {
36 cx: Rc<RefCell<AppContext>>,
37 foreground_platform: Rc<platform::test::ForegroundPlatform>,
38 condition_duration: Option<Duration>,
39 pub function_name: String,
40 assertion_context: AssertionContextManager,
41}
42
43impl TestAppContext {
44 pub fn new(
45 foreground_platform: Rc<platform::test::ForegroundPlatform>,
46 platform: Arc<dyn Platform>,
47 foreground: Rc<executor::Foreground>,
48 background: Arc<executor::Background>,
49 font_cache: Arc<FontCache>,
50 leak_detector: Arc<Mutex<LeakDetector>>,
51 first_entity_id: usize,
52 function_name: String,
53 ) -> Self {
54 let mut cx = AppContext::new(
55 foreground,
56 background,
57 platform,
58 foreground_platform.clone(),
59 font_cache,
60 RefCounts::new(leak_detector),
61 (),
62 );
63 cx.next_id = first_entity_id;
64 let cx = TestAppContext {
65 cx: Rc::new(RefCell::new(cx)),
66 foreground_platform,
67 condition_duration: None,
68 function_name,
69 assertion_context: AssertionContextManager::new(),
70 };
71 cx.cx.borrow_mut().weak_self = Some(Rc::downgrade(&cx.cx));
72 cx
73 }
74
75 pub fn dispatch_action<A: Action>(&mut self, window_id: usize, action: A) {
76 self.update_window(window_id, |window| {
77 window.dispatch_action(window.focused_view_id(), &action);
78 })
79 .expect("window not found");
80 }
81
82 pub fn available_actions(
83 &self,
84 window_id: usize,
85 view_id: usize,
86 ) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
87 self.read_window(window_id, |cx| cx.available_actions(view_id))
88 .unwrap_or_default()
89 }
90
91 pub fn dispatch_global_action<A: Action>(&mut self, action: A) {
92 self.update(|cx| cx.dispatch_global_action_any(&action));
93 }
94
95 pub fn dispatch_keystroke(
96 &mut self,
97 window: AnyWindowHandle,
98 keystroke: Keystroke,
99 is_held: bool,
100 ) {
101 let handled = window.update(self, |cx| {
102 if cx.dispatch_keystroke(&keystroke) {
103 return true;
104 }
105
106 if cx.dispatch_event(
107 Event::KeyDown(KeyDownEvent {
108 keystroke: keystroke.clone(),
109 is_held,
110 }),
111 false,
112 ) {
113 return true;
114 }
115
116 false
117 });
118
119 if !handled && !keystroke.cmd && !keystroke.ctrl {
120 WindowInputHandler {
121 app: self.cx.clone(),
122 window,
123 }
124 .replace_text_in_range(None, &keystroke.key)
125 }
126 }
127
128 pub fn window<V: View>(&self, window_id: usize) -> Option<WindowHandle<V>> {
129 self.cx
130 .borrow()
131 .read_window(window_id, |cx| cx.window())
132 .flatten()
133 }
134
135 pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
136 &self,
137 window_id: usize,
138 callback: F,
139 ) -> Option<T> {
140 self.cx.borrow().read_window(window_id, callback)
141 }
142
143 pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
144 &mut self,
145 window_id: usize,
146 callback: F,
147 ) -> Option<T> {
148 self.cx.borrow_mut().update_window(window_id, callback)
149 }
150
151 pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
152 where
153 T: Entity,
154 F: FnOnce(&mut ModelContext<T>) -> T,
155 {
156 self.cx.borrow_mut().add_model(build_model)
157 }
158
159 pub fn add_window<V, F>(&mut self, build_root_view: F) -> WindowHandle<V>
160 where
161 V: View,
162 F: FnOnce(&mut ViewContext<V>) -> V,
163 {
164 let window = self
165 .cx
166 .borrow_mut()
167 .add_window(Default::default(), build_root_view);
168 window.simulate_activation(self);
169 WindowHandle::new(window.id())
170 }
171
172 pub fn add_view<T, F>(&mut self, window_id: usize, build_view: F) -> ViewHandle<T>
173 where
174 T: View,
175 F: FnOnce(&mut ViewContext<T>) -> T,
176 {
177 self.update_window(window_id, |cx| cx.add_view(build_view))
178 .expect("window not found")
179 }
180
181 pub fn observe_global<E, F>(&mut self, callback: F) -> Subscription
182 where
183 E: Any,
184 F: 'static + FnMut(&mut AppContext),
185 {
186 self.cx.borrow_mut().observe_global::<E, F>(callback)
187 }
188
189 pub fn set_global<T: 'static>(&mut self, state: T) {
190 self.cx.borrow_mut().set_global(state);
191 }
192
193 pub fn subscribe_global<E, F>(&mut self, callback: F) -> Subscription
194 where
195 E: Any,
196 F: 'static + FnMut(&E, &mut AppContext),
197 {
198 self.cx.borrow_mut().subscribe_global(callback)
199 }
200
201 pub fn windows(&self) -> Vec<AnyWindowHandle> {
202 self.cx.borrow().windows().collect()
203 }
204
205 pub fn remove_all_windows(&mut self) {
206 self.update(|cx| cx.windows.clear());
207 }
208
209 pub fn read<T, F: FnOnce(&AppContext) -> T>(&self, callback: F) -> T {
210 callback(&*self.cx.borrow())
211 }
212
213 pub fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, callback: F) -> T {
214 let mut state = self.cx.borrow_mut();
215 // Don't increment pending flushes in order for effects to be flushed before the callback
216 // completes, which is helpful in tests.
217 let result = callback(&mut *state);
218 // Flush effects after the callback just in case there are any. This can happen in edge
219 // cases such as the closure dropping handles.
220 state.flush_effects();
221 result
222 }
223
224 pub fn to_async(&self) -> AsyncAppContext {
225 AsyncAppContext(self.cx.clone())
226 }
227
228 pub fn font_cache(&self) -> Arc<FontCache> {
229 self.cx.borrow().font_cache.clone()
230 }
231
232 pub fn foreground_platform(&self) -> Rc<platform::test::ForegroundPlatform> {
233 self.foreground_platform.clone()
234 }
235
236 pub fn platform(&self) -> Arc<dyn platform::Platform> {
237 self.cx.borrow().platform.clone()
238 }
239
240 pub fn foreground(&self) -> Rc<executor::Foreground> {
241 self.cx.borrow().foreground().clone()
242 }
243
244 pub fn background(&self) -> Arc<executor::Background> {
245 self.cx.borrow().background().clone()
246 }
247
248 pub fn spawn<F, Fut, T>(&self, f: F) -> Task<T>
249 where
250 F: FnOnce(AsyncAppContext) -> Fut,
251 Fut: 'static + Future<Output = T>,
252 T: 'static,
253 {
254 let foreground = self.foreground();
255 let future = f(self.to_async());
256 let cx = self.to_async();
257 foreground.spawn(async move {
258 let result = future.await;
259 cx.0.borrow_mut().flush_effects();
260 result
261 })
262 }
263
264 pub fn simulate_new_path_selection(&self, result: impl FnOnce(PathBuf) -> Option<PathBuf>) {
265 self.foreground_platform.simulate_new_path_selection(result);
266 }
267
268 pub fn did_prompt_for_new_path(&self) -> bool {
269 self.foreground_platform.as_ref().did_prompt_for_new_path()
270 }
271
272 pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
273 self.cx.borrow().leak_detector()
274 }
275
276 pub fn assert_dropped(&self, handle: impl WeakHandle) {
277 self.cx
278 .borrow()
279 .leak_detector()
280 .lock()
281 .assert_dropped(handle.id())
282 }
283
284 /// Drop a handle, assuming it is the last. If it is not the last, panic with debug information about
285 /// where the stray handles were created.
286 pub fn drop_last<T, W: WeakHandle, H: Handle<T, Weak = W>>(&mut self, handle: H) {
287 let weak = handle.downgrade();
288 self.update(|_| drop(handle));
289 self.assert_dropped(weak);
290 }
291
292 pub fn set_condition_duration(&mut self, duration: Option<Duration>) {
293 self.condition_duration = duration;
294 }
295
296 pub fn condition_duration(&self) -> Duration {
297 self.condition_duration.unwrap_or_else(|| {
298 if std::env::var("CI").is_ok() {
299 Duration::from_secs(2)
300 } else {
301 Duration::from_millis(500)
302 }
303 })
304 }
305
306 pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) {
307 self.update(|cx| {
308 let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned());
309 let expected_content = expected_content.map(|content| content.to_owned());
310 assert_eq!(actual_content, expected_content);
311 })
312 }
313
314 pub fn add_assertion_context(&self, context: String) -> ContextHandle {
315 self.assertion_context.add_context(context)
316 }
317
318 pub fn assertion_context(&self) -> String {
319 self.assertion_context.context()
320 }
321}
322
323impl BorrowAppContext for TestAppContext {
324 fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
325 self.cx.borrow().read_with(f)
326 }
327
328 fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
329 self.cx.borrow_mut().update(f)
330 }
331}
332
333impl BorrowWindowContext for TestAppContext {
334 type Result<T> = T;
335
336 fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
337 self.cx
338 .borrow()
339 .read_window(window_id, f)
340 .expect("window was closed")
341 }
342
343 fn read_window_optional<T, F>(&self, window_id: usize, f: F) -> Option<T>
344 where
345 F: FnOnce(&WindowContext) -> Option<T>,
346 {
347 BorrowWindowContext::read_window(self, window_id, f)
348 }
349
350 fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
351 &mut self,
352 window_id: usize,
353 f: F,
354 ) -> T {
355 self.cx
356 .borrow_mut()
357 .update_window(window_id, f)
358 .expect("window was closed")
359 }
360
361 fn update_window_optional<T, F>(&mut self, window_id: usize, f: F) -> Option<T>
362 where
363 F: FnOnce(&mut WindowContext) -> Option<T>,
364 {
365 BorrowWindowContext::update_window(self, window_id, f)
366 }
367}
368
369impl<T: Entity> ModelHandle<T> {
370 pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
371 let (tx, mut rx) = futures::channel::mpsc::unbounded();
372 let mut cx = cx.cx.borrow_mut();
373 let subscription = cx.observe(self, move |_, _| {
374 tx.unbounded_send(()).ok();
375 });
376
377 let duration = if std::env::var("CI").is_ok() {
378 Duration::from_secs(5)
379 } else {
380 Duration::from_secs(1)
381 };
382
383 let executor = cx.background().clone();
384 async move {
385 executor.start_waiting();
386 let notification = crate::util::timeout(duration, rx.next())
387 .await
388 .expect("next notification timed out");
389 drop(subscription);
390 notification.expect("model dropped while test was waiting for its next notification")
391 }
392 }
393
394 pub fn next_event(&self, cx: &TestAppContext) -> impl Future<Output = T::Event>
395 where
396 T::Event: Clone,
397 {
398 let (tx, mut rx) = futures::channel::mpsc::unbounded();
399 let mut cx = cx.cx.borrow_mut();
400 let subscription = cx.subscribe(self, move |_, event, _| {
401 tx.unbounded_send(event.clone()).ok();
402 });
403
404 let duration = if std::env::var("CI").is_ok() {
405 Duration::from_secs(5)
406 } else {
407 Duration::from_secs(1)
408 };
409
410 cx.foreground.start_waiting();
411 async move {
412 let event = crate::util::timeout(duration, rx.next())
413 .await
414 .expect("next event timed out");
415 drop(subscription);
416 event.expect("model dropped while test was waiting for its next event")
417 }
418 }
419
420 pub fn condition(
421 &self,
422 cx: &TestAppContext,
423 mut predicate: impl FnMut(&T, &AppContext) -> bool,
424 ) -> impl Future<Output = ()> {
425 let (tx, mut rx) = futures::channel::mpsc::unbounded();
426
427 let mut cx = cx.cx.borrow_mut();
428 let subscriptions = (
429 cx.observe(self, {
430 let tx = tx.clone();
431 move |_, _| {
432 tx.unbounded_send(()).ok();
433 }
434 }),
435 cx.subscribe(self, {
436 move |_, _, _| {
437 tx.unbounded_send(()).ok();
438 }
439 }),
440 );
441
442 let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
443 let handle = self.downgrade();
444 let duration = if std::env::var("CI").is_ok() {
445 Duration::from_secs(5)
446 } else {
447 Duration::from_secs(1)
448 };
449
450 async move {
451 crate::util::timeout(duration, async move {
452 loop {
453 {
454 let cx = cx.borrow();
455 let cx = &*cx;
456 if predicate(
457 handle
458 .upgrade(cx)
459 .expect("model dropped with pending condition")
460 .read(cx),
461 cx,
462 ) {
463 break;
464 }
465 }
466
467 cx.borrow().foreground().start_waiting();
468 rx.next()
469 .await
470 .expect("model dropped with pending condition");
471 cx.borrow().foreground().finish_waiting();
472 }
473 })
474 .await
475 .expect("condition timed out");
476 drop(subscriptions);
477 }
478 }
479}
480
481impl AnyWindowHandle {
482 pub fn has_pending_prompt(&self, cx: &mut TestAppContext) -> bool {
483 let window = self.platform_window_mut(cx);
484 let prompts = window.pending_prompts.borrow_mut();
485 !prompts.is_empty()
486 }
487
488 pub fn current_title(&self, cx: &mut TestAppContext) -> Option<String> {
489 self.platform_window_mut(cx).title.clone()
490 }
491
492 pub fn simulate_close(&self, cx: &mut TestAppContext) -> bool {
493 let handler = self.platform_window_mut(cx).should_close_handler.take();
494 if let Some(mut handler) = handler {
495 let should_close = handler();
496 self.platform_window_mut(cx).should_close_handler = Some(handler);
497 should_close
498 } else {
499 false
500 }
501 }
502
503 pub fn simulate_resize(&self, size: Vector2F, cx: &mut TestAppContext) {
504 let mut window = self.platform_window_mut(cx);
505 window.size = size;
506 let mut handlers = mem::take(&mut window.resize_handlers);
507 drop(window);
508 for handler in &mut handlers {
509 handler();
510 }
511 self.platform_window_mut(cx).resize_handlers = handlers;
512 }
513
514 pub fn is_edited(&self, cx: &mut TestAppContext) -> bool {
515 self.platform_window_mut(cx).edited
516 }
517
518 pub fn simulate_prompt_answer(&self, answer: usize, cx: &mut TestAppContext) {
519 use postage::prelude::Sink as _;
520
521 let mut done_tx = self
522 .platform_window_mut(cx)
523 .pending_prompts
524 .borrow_mut()
525 .pop_front()
526 .expect("prompt was not called");
527 done_tx.try_send(answer).ok();
528 }
529
530 fn platform_window_mut<'a>(
531 &self,
532 cx: &'a mut TestAppContext,
533 ) -> std::cell::RefMut<'a, platform::test::Window> {
534 std::cell::RefMut::map(cx.cx.borrow_mut(), |state| {
535 let window = state.windows.get_mut(&self.window_id).unwrap();
536 let test_window = window
537 .platform_window
538 .as_any_mut()
539 .downcast_mut::<platform::test::Window>()
540 .unwrap();
541 test_window
542 })
543 }
544}
545
546impl<T: View> ViewHandle<T> {
547 pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
548 use postage::prelude::{Sink as _, Stream as _};
549
550 let (mut tx, mut rx) = postage::mpsc::channel(1);
551 let mut cx = cx.cx.borrow_mut();
552 let subscription = cx.observe(self, move |_, _| {
553 tx.try_send(()).ok();
554 });
555
556 let duration = if std::env::var("CI").is_ok() {
557 Duration::from_secs(5)
558 } else {
559 Duration::from_secs(1)
560 };
561
562 async move {
563 let notification = crate::util::timeout(duration, rx.recv())
564 .await
565 .expect("next notification timed out");
566 drop(subscription);
567 notification.expect("model dropped while test was waiting for its next notification")
568 }
569 }
570
571 pub fn condition(
572 &self,
573 cx: &TestAppContext,
574 mut predicate: impl FnMut(&T, &AppContext) -> bool,
575 ) -> impl Future<Output = ()> {
576 use postage::prelude::{Sink as _, Stream as _};
577
578 let (tx, mut rx) = postage::mpsc::channel(1024);
579 let timeout_duration = cx.condition_duration();
580
581 let mut cx = cx.cx.borrow_mut();
582 let subscriptions = (
583 cx.observe(self, {
584 let mut tx = tx.clone();
585 move |_, _| {
586 tx.blocking_send(()).ok();
587 }
588 }),
589 cx.subscribe(self, {
590 let mut tx = tx.clone();
591 move |_, _, _| {
592 tx.blocking_send(()).ok();
593 }
594 }),
595 );
596
597 let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
598 let handle = self.downgrade();
599
600 async move {
601 crate::util::timeout(timeout_duration, async move {
602 loop {
603 {
604 let cx = cx.borrow();
605 let cx = &*cx;
606 if predicate(
607 handle
608 .upgrade(cx)
609 .expect("view dropped with pending condition")
610 .read(cx),
611 cx,
612 ) {
613 break;
614 }
615 }
616
617 cx.borrow().foreground().start_waiting();
618 rx.recv()
619 .await
620 .expect("view dropped with pending condition");
621 cx.borrow().foreground().finish_waiting();
622 }
623 })
624 .await
625 .expect("condition timed out");
626 drop(subscriptions);
627 }
628 }
629}
630
631/// Tracks string context to be printed when assertions fail.
632/// Often this is done by storing a context string in the manager and returning the handle.
633#[derive(Clone)]
634pub struct AssertionContextManager {
635 id: Arc<AtomicUsize>,
636 contexts: Arc<RwLock<BTreeMap<usize, String>>>,
637}
638
639impl AssertionContextManager {
640 pub fn new() -> Self {
641 Self {
642 id: Arc::new(AtomicUsize::new(0)),
643 contexts: Arc::new(RwLock::new(BTreeMap::new())),
644 }
645 }
646
647 pub fn add_context(&self, context: String) -> ContextHandle {
648 let id = self.id.fetch_add(1, Ordering::Relaxed);
649 let mut contexts = self.contexts.write();
650 contexts.insert(id, context);
651 ContextHandle {
652 id,
653 manager: self.clone(),
654 }
655 }
656
657 pub fn context(&self) -> String {
658 let contexts = self.contexts.read();
659 format!("\n{}\n", contexts.values().join("\n"))
660 }
661}
662
663/// Used to track the lifetime of a piece of context so that it can be provided when an assertion fails.
664/// For example, in the EditorTestContext, `set_state` returns a context handle so that if an assertion fails,
665/// the state that was set initially for the failure can be printed in the error message
666pub struct ContextHandle {
667 id: usize,
668 manager: AssertionContextManager,
669}
670
671impl Drop for ContextHandle {
672 fn drop(&mut self) {
673 let mut contexts = self.manager.contexts.write();
674 contexts.remove(&self.id);
675 }
676}