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