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,
 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}