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