test_context.rs

  1use crate::{
  2    div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
  3    BackgroundExecutor, Bounds, ClipboardItem, Context, Div, Entity, EventEmitter,
  4    ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model, ModelContext, Pixels, Platform,
  5    PlatformWindow, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow,
  6    TestWindowHandlers, TextSystem, View, ViewContext, VisualContext, WindowBounds, WindowContext,
  7    WindowHandle, WindowOptions,
  8};
  9use anyhow::{anyhow, bail};
 10use futures::{Stream, StreamExt};
 11use std::{future::Future, mem, ops::Deref, rc::Rc, sync::Arc, time::Duration};
 12
 13#[derive(Clone)]
 14pub struct TestAppContext {
 15    pub app: Rc<AppCell>,
 16    pub background_executor: BackgroundExecutor,
 17    pub foreground_executor: ForegroundExecutor,
 18    pub dispatcher: TestDispatcher,
 19    pub test_platform: Rc<TestPlatform>,
 20    text_system: Arc<TextSystem>,
 21}
 22
 23impl Context for TestAppContext {
 24    type Result<T> = T;
 25
 26    fn build_model<T: 'static>(
 27        &mut self,
 28        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
 29    ) -> Self::Result<Model<T>>
 30    where
 31        T: 'static,
 32    {
 33        let mut app = self.app.borrow_mut();
 34        app.build_model(build_model)
 35    }
 36
 37    fn update_model<T: 'static, R>(
 38        &mut self,
 39        handle: &Model<T>,
 40        update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
 41    ) -> Self::Result<R> {
 42        let mut app = self.app.borrow_mut();
 43        app.update_model(handle, update)
 44    }
 45
 46    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
 47    where
 48        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
 49    {
 50        let mut lock = self.app.borrow_mut();
 51        lock.update_window(window, f)
 52    }
 53
 54    fn read_model<T, R>(
 55        &self,
 56        handle: &Model<T>,
 57        read: impl FnOnce(&T, &AppContext) -> R,
 58    ) -> Self::Result<R>
 59    where
 60        T: 'static,
 61    {
 62        let app = self.app.borrow();
 63        app.read_model(handle, read)
 64    }
 65
 66    fn read_window<T, R>(
 67        &self,
 68        window: &WindowHandle<T>,
 69        read: impl FnOnce(View<T>, &AppContext) -> R,
 70    ) -> Result<R>
 71    where
 72        T: 'static,
 73    {
 74        let app = self.app.borrow();
 75        app.read_window(window, read)
 76    }
 77}
 78
 79impl TestAppContext {
 80    pub fn new(dispatcher: TestDispatcher) -> Self {
 81        let arc_dispatcher = Arc::new(dispatcher.clone());
 82        let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
 83        let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
 84        let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
 85        let asset_source = Arc::new(());
 86        let http_client = util::http::FakeHttpClient::with_404_response();
 87        let text_system = Arc::new(TextSystem::new(platform.text_system()));
 88
 89        Self {
 90            app: AppContext::new(platform.clone(), asset_source, http_client),
 91            background_executor,
 92            foreground_executor,
 93            dispatcher: dispatcher.clone(),
 94            test_platform: platform,
 95            text_system,
 96        }
 97    }
 98
 99    pub fn new_app(&self) -> TestAppContext {
100        Self::new(self.dispatcher.clone())
101    }
102
103    pub fn quit(&self) {
104        self.app.borrow_mut().shutdown();
105    }
106
107    pub fn refresh(&mut self) -> Result<()> {
108        let mut app = self.app.borrow_mut();
109        app.refresh();
110        Ok(())
111    }
112
113    pub fn executor(&self) -> BackgroundExecutor {
114        self.background_executor.clone()
115    }
116
117    pub fn foreground_executor(&self) -> &ForegroundExecutor {
118        &self.foreground_executor
119    }
120
121    pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {
122        let mut cx = self.app.borrow_mut();
123        cx.update(f)
124    }
125
126    pub fn read<R>(&self, f: impl FnOnce(&AppContext) -> R) -> R {
127        let cx = self.app.borrow();
128        f(&*cx)
129    }
130
131    pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
132    where
133        F: FnOnce(&mut ViewContext<V>) -> V,
134        V: 'static + Render,
135    {
136        let mut cx = self.app.borrow_mut();
137        cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window))
138    }
139
140    pub fn add_empty_window(&mut self) -> AnyWindowHandle {
141        let mut cx = self.app.borrow_mut();
142        cx.open_window(WindowOptions::default(), |cx| {
143            cx.build_view(|_| EmptyView {})
144        })
145        .any_handle
146    }
147
148    pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext)
149    where
150        F: FnOnce(&mut ViewContext<V>) -> V,
151        V: 'static + Render,
152    {
153        let mut cx = self.app.borrow_mut();
154        let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window));
155        drop(cx);
156        let view = window.root_view(self).unwrap();
157        let cx = Box::new(VisualTestContext::from_window(*window.deref(), self));
158        // it might be nice to try and cleanup these at the end of each test.
159        (view, Box::leak(cx))
160    }
161
162    pub fn text_system(&self) -> &Arc<TextSystem> {
163        &self.text_system
164    }
165
166    pub fn write_to_clipboard(&self, item: ClipboardItem) {
167        self.test_platform.write_to_clipboard(item)
168    }
169
170    pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
171        self.test_platform.read_from_clipboard()
172    }
173
174    pub fn simulate_new_path_selection(
175        &self,
176        select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
177    ) {
178        self.test_platform.simulate_new_path_selection(select_path);
179    }
180
181    pub fn simulate_prompt_answer(&self, button_ix: usize) {
182        self.test_platform.simulate_prompt_answer(button_ix);
183    }
184
185    pub fn has_pending_prompt(&self) -> bool {
186        self.test_platform.has_pending_prompt()
187    }
188
189    pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {
190        let (mut handlers, scale_factor) = self
191            .app
192            .borrow_mut()
193            .update_window(window_handle, |_, cx| {
194                let platform_window = cx.window.platform_window.as_test().unwrap();
195                let scale_factor = platform_window.scale_factor();
196                match &mut platform_window.bounds {
197                    WindowBounds::Fullscreen | WindowBounds::Maximized => {
198                        platform_window.bounds = WindowBounds::Fixed(Bounds {
199                            origin: Point::default(),
200                            size: size.map(|pixels| f64::from(pixels).into()),
201                        });
202                    }
203                    WindowBounds::Fixed(bounds) => {
204                        bounds.size = size.map(|pixels| f64::from(pixels).into());
205                    }
206                }
207
208                (
209                    mem::take(&mut platform_window.handlers.lock().resize),
210                    scale_factor,
211                )
212            })
213            .unwrap();
214
215        for handler in &mut handlers {
216            handler(size, scale_factor);
217        }
218
219        self.app
220            .borrow_mut()
221            .update_window(window_handle, |_, cx| {
222                let platform_window = cx.window.platform_window.as_test().unwrap();
223                platform_window.handlers.lock().resize = handlers;
224            })
225            .unwrap();
226    }
227
228    pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
229    where
230        Fut: Future<Output = R> + 'static,
231        R: 'static,
232    {
233        self.foreground_executor.spawn(f(self.to_async()))
234    }
235
236    pub fn has_global<G: 'static>(&self) -> bool {
237        let app = self.app.borrow();
238        app.has_global::<G>()
239    }
240
241    pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
242        let app = self.app.borrow();
243        read(app.global(), &app)
244    }
245
246    pub fn try_read_global<G: 'static, R>(
247        &self,
248        read: impl FnOnce(&G, &AppContext) -> R,
249    ) -> Option<R> {
250        let lock = self.app.borrow();
251        Some(read(lock.try_global()?, &lock))
252    }
253
254    pub fn set_global<G: 'static>(&mut self, global: G) {
255        let mut lock = self.app.borrow_mut();
256        lock.set_global(global);
257    }
258
259    pub fn update_global<G: 'static, R>(
260        &mut self,
261        update: impl FnOnce(&mut G, &mut AppContext) -> R,
262    ) -> R {
263        let mut lock = self.app.borrow_mut();
264        lock.update_global(update)
265    }
266
267    pub fn to_async(&self) -> AsyncAppContext {
268        AsyncAppContext {
269            app: Rc::downgrade(&self.app),
270            background_executor: self.background_executor.clone(),
271            foreground_executor: self.foreground_executor.clone(),
272        }
273    }
274
275    pub fn dispatch_action<A>(&mut self, window: AnyWindowHandle, action: A)
276    where
277        A: Action,
278    {
279        window
280            .update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
281            .unwrap();
282
283        self.background_executor.run_until_parked()
284    }
285
286    /// simulate_keystrokes takes a space-separated list of keys to type.
287    /// cx.simulate_keystrokes("cmd-shift-p b k s p enter")
288    /// will run backspace on the current editor through the command palette.
289    pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) {
290        for keystroke in keystrokes
291            .split(" ")
292            .map(Keystroke::parse)
293            .map(Result::unwrap)
294        {
295            self.dispatch_keystroke(window, keystroke.into(), false);
296        }
297
298        self.background_executor.run_until_parked()
299    }
300
301    /// simulate_input takes a string of text to type.
302    /// cx.simulate_input("abc")
303    /// will type abc into your current editor.
304    pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) {
305        for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) {
306            self.dispatch_keystroke(window, keystroke.into(), false);
307        }
308
309        self.background_executor.run_until_parked()
310    }
311
312    pub fn dispatch_keystroke(
313        &mut self,
314        window: AnyWindowHandle,
315        keystroke: Keystroke,
316        is_held: bool,
317    ) {
318        let keystroke2 = keystroke.clone();
319        let handled = window
320            .update(self, |_, cx| {
321                cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held }))
322            })
323            .is_ok_and(|handled| handled);
324        if handled {
325            return;
326        }
327
328        let input_handler = self.update_test_window(window, |window| window.input_handler.clone());
329        let Some(input_handler) = input_handler else {
330            panic!(
331                "dispatch_keystroke {:?} failed to dispatch action or input",
332                &keystroke2
333            );
334        };
335        let text = keystroke2.ime_key.unwrap_or(keystroke2.key);
336        input_handler.lock().replace_text_in_range(None, &text);
337    }
338
339    pub fn update_test_window<R>(
340        &mut self,
341        window: AnyWindowHandle,
342        f: impl FnOnce(&mut TestWindow) -> R,
343    ) -> R {
344        window
345            .update(self, |_, cx| {
346                f(cx.window
347                    .platform_window
348                    .as_any_mut()
349                    .downcast_mut::<TestWindow>()
350                    .unwrap())
351            })
352            .unwrap()
353    }
354
355    pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
356        let (tx, rx) = futures::channel::mpsc::unbounded();
357        self.update(|cx| {
358            cx.observe(entity, {
359                let tx = tx.clone();
360                move |_, _| {
361                    let _ = tx.unbounded_send(());
362                }
363            })
364            .detach();
365            cx.observe_release(entity, move |_, _| tx.close_channel())
366                .detach()
367        });
368        rx
369    }
370
371    pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
372        &mut self,
373        entity: &Model<T>,
374    ) -> futures::channel::mpsc::UnboundedReceiver<Evt>
375    where
376        Evt: 'static + Clone,
377    {
378        let (tx, rx) = futures::channel::mpsc::unbounded();
379        entity
380            .update(self, |_, cx: &mut ModelContext<T>| {
381                cx.subscribe(entity, move |_model, _handle, event, _cx| {
382                    let _ = tx.unbounded_send(event.clone());
383                })
384            })
385            .detach();
386        rx
387    }
388
389    pub async fn condition<T: 'static>(
390        &mut self,
391        model: &Model<T>,
392        mut predicate: impl FnMut(&mut T, &mut ModelContext<T>) -> bool,
393    ) {
394        let timer = self.executor().timer(Duration::from_secs(3));
395        let mut notifications = self.notifications(model);
396
397        use futures::FutureExt as _;
398        use smol::future::FutureExt as _;
399
400        async {
401            loop {
402                if model.update(self, &mut predicate) {
403                    return Ok(());
404                }
405
406                if notifications.next().await.is_none() {
407                    bail!("model dropped")
408                }
409            }
410        }
411        .race(timer.map(|_| Err(anyhow!("condition timed out"))))
412        .await
413        .unwrap();
414    }
415}
416
417impl<T: Send> Model<T> {
418    pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
419    where
420        Evt: Send + Clone + 'static,
421        T: EventEmitter<Evt>,
422    {
423        let (tx, mut rx) = futures::channel::mpsc::unbounded();
424        let _subscription = self.update(cx, |_, cx| {
425            cx.subscribe(self, move |_, _, event, _| {
426                tx.unbounded_send(event.clone()).ok();
427            })
428        });
429
430        // Run other tasks until the event is emitted.
431        loop {
432            match rx.try_next() {
433                Ok(Some(event)) => return event,
434                Ok(None) => panic!("model was dropped"),
435                Err(_) => {
436                    if !cx.executor().tick() {
437                        break;
438                    }
439                }
440            }
441        }
442        panic!("no event received")
443    }
444}
445
446impl<V: 'static> View<V> {
447    pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
448        use postage::prelude::{Sink as _, Stream as _};
449
450        let (mut tx, mut rx) = postage::mpsc::channel(1);
451        let mut cx = cx.app.app.borrow_mut();
452        let subscription = cx.observe(self, move |_, _| {
453            tx.try_send(()).ok();
454        });
455
456        let duration = if std::env::var("CI").is_ok() {
457            Duration::from_secs(5)
458        } else {
459            Duration::from_secs(1)
460        };
461
462        async move {
463            let notification = crate::util::timeout(duration, rx.recv())
464                .await
465                .expect("next notification timed out");
466            drop(subscription);
467            notification.expect("model dropped while test was waiting for its next notification")
468        }
469    }
470}
471
472impl<V> View<V> {
473    pub fn condition<Evt>(
474        &self,
475        cx: &TestAppContext,
476        mut predicate: impl FnMut(&V, &AppContext) -> bool,
477    ) -> impl Future<Output = ()>
478    where
479        Evt: 'static,
480        V: EventEmitter<Evt>,
481    {
482        use postage::prelude::{Sink as _, Stream as _};
483
484        let (tx, mut rx) = postage::mpsc::channel(1024);
485        let timeout_duration = Duration::from_millis(100); //todo!() cx.condition_duration();
486
487        let mut cx = cx.app.borrow_mut();
488        let subscriptions = (
489            cx.observe(self, {
490                let mut tx = tx.clone();
491                move |_, _| {
492                    tx.blocking_send(()).ok();
493                }
494            }),
495            cx.subscribe(self, {
496                let mut tx = tx.clone();
497                move |_, _: &Evt, _| {
498                    tx.blocking_send(()).ok();
499                }
500            }),
501        );
502
503        let cx = cx.this.upgrade().unwrap();
504        let handle = self.downgrade();
505
506        async move {
507            crate::util::timeout(timeout_duration, async move {
508                loop {
509                    {
510                        let cx = cx.borrow();
511                        let cx = &*cx;
512                        if predicate(
513                            handle
514                                .upgrade()
515                                .expect("view dropped with pending condition")
516                                .read(cx),
517                            cx,
518                        ) {
519                            break;
520                        }
521                    }
522
523                    // todo!(start_waiting)
524                    // cx.borrow().foreground_executor().start_waiting();
525                    rx.recv()
526                        .await
527                        .expect("view dropped with pending condition");
528                    // cx.borrow().foreground_executor().finish_waiting();
529                }
530            })
531            .await
532            .expect("condition timed out");
533            drop(subscriptions);
534        }
535    }
536}
537
538use derive_more::{Deref, DerefMut};
539#[derive(Deref, DerefMut)]
540pub struct VisualTestContext<'a> {
541    #[deref]
542    #[deref_mut]
543    cx: &'a mut TestAppContext,
544    window: AnyWindowHandle,
545}
546
547impl<'a> VisualTestContext<'a> {
548    pub fn update<R>(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R {
549        self.cx.update_window(self.window, |_, cx| f(cx)).unwrap()
550    }
551
552    pub fn from_window(window: AnyWindowHandle, cx: &'a mut TestAppContext) -> Self {
553        Self { cx, window }
554    }
555
556    pub fn run_until_parked(&self) {
557        self.cx.background_executor.run_until_parked();
558    }
559
560    pub fn dispatch_action<A>(&mut self, action: A)
561    where
562        A: Action,
563    {
564        self.cx.dispatch_action(self.window, action)
565    }
566
567    pub fn window_title(&mut self) -> Option<String> {
568        self.cx
569            .update_window(self.window, |_, cx| {
570                cx.window.platform_window.as_test().unwrap().title.clone()
571            })
572            .unwrap()
573    }
574
575    pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
576        self.cx.simulate_keystrokes(self.window, keystrokes)
577    }
578
579    pub fn simulate_input(&mut self, input: &str) {
580        self.cx.simulate_input(self.window, input)
581    }
582
583    pub fn simulate_activation(&mut self) {
584        self.simulate_window_events(&mut |handlers| {
585            handlers
586                .active_status_change
587                .iter_mut()
588                .for_each(|f| f(true));
589        })
590    }
591
592    pub fn simulate_deactivation(&mut self) {
593        self.simulate_window_events(&mut |handlers| {
594            handlers
595                .active_status_change
596                .iter_mut()
597                .for_each(|f| f(false));
598        })
599    }
600
601    fn simulate_window_events(&mut self, f: &mut dyn FnMut(&mut TestWindowHandlers)) {
602        let handlers = self
603            .cx
604            .update_window(self.window, |_, cx| {
605                cx.window
606                    .platform_window
607                    .as_test()
608                    .unwrap()
609                    .handlers
610                    .clone()
611            })
612            .unwrap();
613        f(&mut *handlers.lock());
614    }
615}
616
617impl<'a> Context for VisualTestContext<'a> {
618    type Result<T> = <TestAppContext as Context>::Result<T>;
619
620    fn build_model<T: 'static>(
621        &mut self,
622        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
623    ) -> Self::Result<Model<T>> {
624        self.cx.build_model(build_model)
625    }
626
627    fn update_model<T, R>(
628        &mut self,
629        handle: &Model<T>,
630        update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
631    ) -> Self::Result<R>
632    where
633        T: 'static,
634    {
635        self.cx.update_model(handle, update)
636    }
637
638    fn read_model<T, R>(
639        &self,
640        handle: &Model<T>,
641        read: impl FnOnce(&T, &AppContext) -> R,
642    ) -> Self::Result<R>
643    where
644        T: 'static,
645    {
646        self.cx.read_model(handle, read)
647    }
648
649    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
650    where
651        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
652    {
653        self.cx.update_window(window, f)
654    }
655
656    fn read_window<T, R>(
657        &self,
658        window: &WindowHandle<T>,
659        read: impl FnOnce(View<T>, &AppContext) -> R,
660    ) -> Result<R>
661    where
662        T: 'static,
663    {
664        self.cx.read_window(window, read)
665    }
666}
667
668impl<'a> VisualContext for VisualTestContext<'a> {
669    fn build_view<V>(
670        &mut self,
671        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
672    ) -> Self::Result<View<V>>
673    where
674        V: 'static + Render,
675    {
676        self.window
677            .update(self.cx, |_, cx| cx.build_view(build_view))
678            .unwrap()
679    }
680
681    fn update_view<V: 'static, R>(
682        &mut self,
683        view: &View<V>,
684        update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
685    ) -> Self::Result<R> {
686        self.window
687            .update(self.cx, |_, cx| cx.update_view(view, update))
688            .unwrap()
689    }
690
691    fn replace_root_view<V>(
692        &mut self,
693        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
694    ) -> Self::Result<View<V>>
695    where
696        V: 'static + Render,
697    {
698        self.window
699            .update(self.cx, |_, cx| cx.replace_root_view(build_view))
700            .unwrap()
701    }
702
703    fn focus_view<V: crate::FocusableView>(&mut self, view: &View<V>) -> Self::Result<()> {
704        self.window
705            .update(self.cx, |_, cx| {
706                view.read(cx).focus_handle(cx).clone().focus(cx)
707            })
708            .unwrap()
709    }
710
711    fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
712    where
713        V: crate::ManagedView,
714    {
715        self.window
716            .update(self.cx, |_, cx| {
717                view.update(cx, |_, cx| cx.emit(crate::DismissEvent))
718            })
719            .unwrap()
720    }
721}
722
723impl AnyWindowHandle {
724    pub fn build_view<V: Render + 'static>(
725        &self,
726        cx: &mut TestAppContext,
727        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
728    ) -> View<V> {
729        self.update(cx, |_, cx| cx.build_view(build_view)).unwrap()
730    }
731}
732
733pub struct EmptyView {}
734
735impl Render for EmptyView {
736    type Element = Div;
737
738    fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> Self::Element {
739        div()
740    }
741}