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