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