test_app_context.rs

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