test_app_context.rs

  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}