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