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