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