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