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, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache, Handle,
  8    ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakHandle,
  9    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(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
 96        let handled = self
 97            .cx
 98            .borrow_mut()
 99            .update_window(window_id, |cx| {
100                if cx.dispatch_keystroke(&keystroke) {
101                    return true;
102                }
103
104                if cx.dispatch_event(
105                    Event::KeyDown(KeyDownEvent {
106                        keystroke: keystroke.clone(),
107                        is_held,
108                    }),
109                    false,
110                ) {
111                    return true;
112                }
113
114                false
115            })
116            .unwrap_or(false);
117
118        if !handled && !keystroke.cmd && !keystroke.ctrl {
119            WindowInputHandler {
120                app: self.cx.clone(),
121                window_id,
122            }
123            .replace_text_in_range(None, &keystroke.key)
124        }
125    }
126
127    pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
128        &self,
129        window_id: usize,
130        callback: F,
131    ) -> Option<T> {
132        self.cx.borrow().read_window(window_id, callback)
133    }
134
135    pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
136        &mut self,
137        window_id: usize,
138        callback: F,
139    ) -> Option<T> {
140        self.cx.borrow_mut().update_window(window_id, callback)
141    }
142
143    pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
144    where
145        T: Entity,
146        F: FnOnce(&mut ModelContext<T>) -> T,
147    {
148        self.cx.borrow_mut().add_model(build_model)
149    }
150
151    pub fn add_window<T, F>(&mut self, build_root_view: F) -> WindowHandle<T>
152    where
153        T: View,
154        F: FnOnce(&mut ViewContext<T>) -> T,
155    {
156        let window = self
157            .cx
158            .borrow_mut()
159            .add_window(Default::default(), build_root_view);
160        self.simulate_window_activation(Some(window.window_id()));
161
162        WindowHandle::new(window.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 window_ids(&self) -> Vec<usize> {
195        self.cx.borrow().windows.keys().copied().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 simulate_prompt_answer(&self, window_id: usize, answer: usize) {
266        use postage::prelude::Sink as _;
267
268        let mut done_tx = self
269            .platform_window_mut(window_id)
270            .pending_prompts
271            .borrow_mut()
272            .pop_front()
273            .expect("prompt was not called");
274        done_tx.try_send(answer).ok();
275    }
276
277    pub fn has_pending_prompt(&self, window_id: usize) -> bool {
278        let window = self.platform_window_mut(window_id);
279        let prompts = window.pending_prompts.borrow_mut();
280        !prompts.is_empty()
281    }
282
283    pub fn current_window_title(&self, window_id: usize) -> Option<String> {
284        self.platform_window_mut(window_id).title.clone()
285    }
286
287    pub fn simulate_window_close(&self, window_id: usize) -> bool {
288        let handler = self
289            .platform_window_mut(window_id)
290            .should_close_handler
291            .take();
292        if let Some(mut handler) = handler {
293            let should_close = handler();
294            self.platform_window_mut(window_id).should_close_handler = Some(handler);
295            should_close
296        } else {
297            false
298        }
299    }
300
301    pub fn simulate_window_resize(&self, window_id: usize, size: Vector2F) {
302        let mut window = self.platform_window_mut(window_id);
303        window.size = size;
304        let mut handlers = mem::take(&mut window.resize_handlers);
305        drop(window);
306        for handler in &mut handlers {
307            handler();
308        }
309        self.platform_window_mut(window_id).resize_handlers = handlers;
310    }
311
312    pub fn simulate_window_activation(&self, to_activate: Option<usize>) {
313        self.cx.borrow_mut().update(|cx| {
314            let other_window_ids = cx
315                .windows
316                .keys()
317                .filter(|window_id| Some(**window_id) != to_activate)
318                .copied()
319                .collect::<Vec<_>>();
320
321            for window_id in other_window_ids {
322                cx.window_changed_active_status(window_id, false)
323            }
324
325            if let Some(to_activate) = to_activate {
326                cx.window_changed_active_status(to_activate, true)
327            }
328        });
329    }
330
331    pub fn is_window_edited(&self, window_id: usize) -> bool {
332        self.platform_window_mut(window_id).edited
333    }
334
335    pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
336        self.cx.borrow().leak_detector()
337    }
338
339    pub fn assert_dropped(&self, handle: impl WeakHandle) {
340        self.cx
341            .borrow()
342            .leak_detector()
343            .lock()
344            .assert_dropped(handle.id())
345    }
346
347    /// Drop a handle, assuming it is the last. If it is not the last, panic with debug information about
348    /// where the stray handles were created.
349    pub fn drop_last<T, W: WeakHandle, H: Handle<T, Weak = W>>(&mut self, handle: H) {
350        let weak = handle.downgrade();
351        self.update(|_| drop(handle));
352        self.assert_dropped(weak);
353    }
354
355    fn platform_window_mut(&self, window_id: usize) -> std::cell::RefMut<platform::test::Window> {
356        std::cell::RefMut::map(self.cx.borrow_mut(), |state| {
357            let window = state.windows.get_mut(&window_id).unwrap();
358            let test_window = window
359                .platform_window
360                .as_any_mut()
361                .downcast_mut::<platform::test::Window>()
362                .unwrap();
363            test_window
364        })
365    }
366
367    pub fn set_condition_duration(&mut self, duration: Option<Duration>) {
368        self.condition_duration = duration;
369    }
370
371    pub fn condition_duration(&self) -> Duration {
372        self.condition_duration.unwrap_or_else(|| {
373            if std::env::var("CI").is_ok() {
374                Duration::from_secs(2)
375            } else {
376                Duration::from_millis(500)
377            }
378        })
379    }
380
381    pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) {
382        self.update(|cx| {
383            let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned());
384            let expected_content = expected_content.map(|content| content.to_owned());
385            assert_eq!(actual_content, expected_content);
386        })
387    }
388
389    pub fn add_assertion_context(&self, context: String) -> ContextHandle {
390        self.assertion_context.add_context(context)
391    }
392
393    pub fn assertion_context(&self) -> String {
394        self.assertion_context.context()
395    }
396}
397
398impl BorrowAppContext for TestAppContext {
399    fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
400        self.cx.borrow().read_with(f)
401    }
402
403    fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
404        self.cx.borrow_mut().update(f)
405    }
406}
407
408impl BorrowWindowContext for TestAppContext {
409    type Result<T> = T;
410
411    fn read_window_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
412        self.cx
413            .borrow()
414            .read_window(window_id, f)
415            .expect("window was closed")
416    }
417
418    fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
419        &mut self,
420        window_id: usize,
421        f: F,
422    ) -> T {
423        self.cx
424            .borrow_mut()
425            .update_window(window_id, f)
426            .expect("window was closed")
427    }
428}
429
430impl<T: Entity> ModelHandle<T> {
431    pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
432        let (tx, mut rx) = futures::channel::mpsc::unbounded();
433        let mut cx = cx.cx.borrow_mut();
434        let subscription = cx.observe(self, move |_, _| {
435            tx.unbounded_send(()).ok();
436        });
437
438        let duration = if std::env::var("CI").is_ok() {
439            Duration::from_secs(5)
440        } else {
441            Duration::from_secs(1)
442        };
443
444        let executor = cx.background().clone();
445        async move {
446            executor.start_waiting();
447            let notification = crate::util::timeout(duration, rx.next())
448                .await
449                .expect("next notification timed out");
450            drop(subscription);
451            notification.expect("model dropped while test was waiting for its next notification")
452        }
453    }
454
455    pub fn next_event(&self, cx: &TestAppContext) -> impl Future<Output = T::Event>
456    where
457        T::Event: Clone,
458    {
459        let (tx, mut rx) = futures::channel::mpsc::unbounded();
460        let mut cx = cx.cx.borrow_mut();
461        let subscription = cx.subscribe(self, move |_, event, _| {
462            tx.unbounded_send(event.clone()).ok();
463        });
464
465        let duration = if std::env::var("CI").is_ok() {
466            Duration::from_secs(5)
467        } else {
468            Duration::from_secs(1)
469        };
470
471        cx.foreground.start_waiting();
472        async move {
473            let event = crate::util::timeout(duration, rx.next())
474                .await
475                .expect("next event timed out");
476            drop(subscription);
477            event.expect("model dropped while test was waiting for its next event")
478        }
479    }
480
481    pub fn condition(
482        &self,
483        cx: &TestAppContext,
484        mut predicate: impl FnMut(&T, &AppContext) -> bool,
485    ) -> impl Future<Output = ()> {
486        let (tx, mut rx) = futures::channel::mpsc::unbounded();
487
488        let mut cx = cx.cx.borrow_mut();
489        let subscriptions = (
490            cx.observe(self, {
491                let tx = tx.clone();
492                move |_, _| {
493                    tx.unbounded_send(()).ok();
494                }
495            }),
496            cx.subscribe(self, {
497                move |_, _, _| {
498                    tx.unbounded_send(()).ok();
499                }
500            }),
501        );
502
503        let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
504        let handle = self.downgrade();
505        let duration = if std::env::var("CI").is_ok() {
506            Duration::from_secs(5)
507        } else {
508            Duration::from_secs(1)
509        };
510
511        async move {
512            crate::util::timeout(duration, async move {
513                loop {
514                    {
515                        let cx = cx.borrow();
516                        let cx = &*cx;
517                        if predicate(
518                            handle
519                                .upgrade(cx)
520                                .expect("model dropped with pending condition")
521                                .read(cx),
522                            cx,
523                        ) {
524                            break;
525                        }
526                    }
527
528                    cx.borrow().foreground().start_waiting();
529                    rx.next()
530                        .await
531                        .expect("model dropped with pending condition");
532                    cx.borrow().foreground().finish_waiting();
533                }
534            })
535            .await
536            .expect("condition timed out");
537            drop(subscriptions);
538        }
539    }
540}
541
542impl<T: View> ViewHandle<T> {
543    pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
544        use postage::prelude::{Sink as _, Stream as _};
545
546        let (mut tx, mut rx) = postage::mpsc::channel(1);
547        let mut cx = cx.cx.borrow_mut();
548        let subscription = cx.observe(self, move |_, _| {
549            tx.try_send(()).ok();
550        });
551
552        let duration = if std::env::var("CI").is_ok() {
553            Duration::from_secs(5)
554        } else {
555            Duration::from_secs(1)
556        };
557
558        async move {
559            let notification = crate::util::timeout(duration, rx.recv())
560                .await
561                .expect("next notification timed out");
562            drop(subscription);
563            notification.expect("model dropped while test was waiting for its next notification")
564        }
565    }
566
567    pub fn condition(
568        &self,
569        cx: &TestAppContext,
570        mut predicate: impl FnMut(&T, &AppContext) -> bool,
571    ) -> impl Future<Output = ()> {
572        use postage::prelude::{Sink as _, Stream as _};
573
574        let (tx, mut rx) = postage::mpsc::channel(1024);
575        let timeout_duration = cx.condition_duration();
576
577        let mut cx = cx.cx.borrow_mut();
578        let subscriptions = (
579            cx.observe(self, {
580                let mut tx = tx.clone();
581                move |_, _| {
582                    tx.blocking_send(()).ok();
583                }
584            }),
585            cx.subscribe(self, {
586                let mut tx = tx.clone();
587                move |_, _, _| {
588                    tx.blocking_send(()).ok();
589                }
590            }),
591        );
592
593        let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
594        let handle = self.downgrade();
595
596        async move {
597            crate::util::timeout(timeout_duration, async move {
598                loop {
599                    {
600                        let cx = cx.borrow();
601                        let cx = &*cx;
602                        if predicate(
603                            handle
604                                .upgrade(cx)
605                                .expect("view dropped with pending condition")
606                                .read(cx),
607                            cx,
608                        ) {
609                            break;
610                        }
611                    }
612
613                    cx.borrow().foreground().start_waiting();
614                    rx.recv()
615                        .await
616                        .expect("view dropped with pending condition");
617                    cx.borrow().foreground().finish_waiting();
618                }
619            })
620            .await
621            .expect("condition timed out");
622            drop(subscriptions);
623        }
624    }
625}
626
627/// Tracks string context to be printed when assertions fail.
628/// Often this is done by storing a context string in the manager and returning the handle.
629#[derive(Clone)]
630pub struct AssertionContextManager {
631    id: Arc<AtomicUsize>,
632    contexts: Arc<RwLock<BTreeMap<usize, String>>>,
633}
634
635impl AssertionContextManager {
636    pub fn new() -> Self {
637        Self {
638            id: Arc::new(AtomicUsize::new(0)),
639            contexts: Arc::new(RwLock::new(BTreeMap::new())),
640        }
641    }
642
643    pub fn add_context(&self, context: String) -> ContextHandle {
644        let id = self.id.fetch_add(1, Ordering::Relaxed);
645        let mut contexts = self.contexts.write();
646        contexts.insert(id, context);
647        ContextHandle {
648            id,
649            manager: self.clone(),
650        }
651    }
652
653    pub fn context(&self) -> String {
654        let contexts = self.contexts.read();
655        format!("\n{}\n", contexts.values().join("\n"))
656    }
657}
658
659/// Used to track the lifetime of a piece of context so that it can be provided when an assertion fails.
660/// For example, in the EditorTestContext, `set_state` returns a context handle so that if an assertion fails,
661/// the state that was set initially for the failure can be printed in the error message
662pub struct ContextHandle {
663    id: usize,
664    manager: AssertionContextManager,
665}
666
667impl Drop for ContextHandle {
668    fn drop(&mut self) {
669        let mut contexts = self.manager.contexts.write();
670        contexts.remove(&self.id);
671    }
672}