test_app_context.rs

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