test_app_context.rs

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