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