test_app_context.rs

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