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
571                    .platform_window
572                    .as_test()
573                    .unwrap()
574                    .window_title
575                    .clone()
576            })
577            .unwrap()
578    }
579
580    pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
581        self.cx.simulate_keystrokes(self.window, keystrokes)
582    }
583
584    pub fn simulate_input(&mut self, input: &str) {
585        self.cx.simulate_input(self.window, input)
586    }
587
588    pub fn simulate_activation(&mut self) {
589        self.simulate_window_events(&mut |handlers| {
590            handlers
591                .active_status_change
592                .iter_mut()
593                .for_each(|f| f(true));
594        })
595    }
596
597    pub fn simulate_deactivation(&mut self) {
598        self.simulate_window_events(&mut |handlers| {
599            handlers
600                .active_status_change
601                .iter_mut()
602                .for_each(|f| f(false));
603        })
604    }
605
606    fn simulate_window_events(&mut self, f: &mut dyn FnMut(&mut TestWindowHandlers)) {
607        let handlers = self
608            .cx
609            .update_window(self.window, |_, cx| {
610                cx.window
611                    .platform_window
612                    .as_test()
613                    .unwrap()
614                    .handlers
615                    .clone()
616            })
617            .unwrap();
618        f(&mut *handlers.lock());
619    }
620}
621
622impl<'a> Context for VisualTestContext<'a> {
623    type Result<T> = <TestAppContext as Context>::Result<T>;
624
625    fn build_model<T: 'static>(
626        &mut self,
627        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
628    ) -> Self::Result<Model<T>> {
629        self.cx.build_model(build_model)
630    }
631
632    fn update_model<T, R>(
633        &mut self,
634        handle: &Model<T>,
635        update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
636    ) -> Self::Result<R>
637    where
638        T: 'static,
639    {
640        self.cx.update_model(handle, update)
641    }
642
643    fn read_model<T, R>(
644        &self,
645        handle: &Model<T>,
646        read: impl FnOnce(&T, &AppContext) -> R,
647    ) -> Self::Result<R>
648    where
649        T: 'static,
650    {
651        self.cx.read_model(handle, read)
652    }
653
654    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
655    where
656        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
657    {
658        self.cx.update_window(window, f)
659    }
660
661    fn read_window<T, R>(
662        &self,
663        window: &WindowHandle<T>,
664        read: impl FnOnce(View<T>, &AppContext) -> R,
665    ) -> Result<R>
666    where
667        T: 'static,
668    {
669        self.cx.read_window(window, read)
670    }
671}
672
673impl<'a> VisualContext for VisualTestContext<'a> {
674    fn build_view<V>(
675        &mut self,
676        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
677    ) -> Self::Result<View<V>>
678    where
679        V: 'static + Render,
680    {
681        self.window
682            .update(self.cx, |_, cx| cx.build_view(build_view))
683            .unwrap()
684    }
685
686    fn update_view<V: 'static, R>(
687        &mut self,
688        view: &View<V>,
689        update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
690    ) -> Self::Result<R> {
691        self.window
692            .update(self.cx, |_, cx| cx.update_view(view, update))
693            .unwrap()
694    }
695
696    fn replace_root_view<V>(
697        &mut self,
698        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
699    ) -> Self::Result<View<V>>
700    where
701        V: 'static + Render,
702    {
703        self.window
704            .update(self.cx, |_, cx| cx.replace_root_view(build_view))
705            .unwrap()
706    }
707
708    fn focus_view<V: crate::FocusableView>(&mut self, view: &View<V>) -> Self::Result<()> {
709        self.window
710            .update(self.cx, |_, cx| {
711                view.read(cx).focus_handle(cx).clone().focus(cx)
712            })
713            .unwrap()
714    }
715
716    fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
717    where
718        V: crate::ManagedView,
719    {
720        self.window
721            .update(self.cx, |_, cx| {
722                view.update(cx, |_, cx| cx.emit(crate::DismissEvent))
723            })
724            .unwrap()
725    }
726}
727
728impl AnyWindowHandle {
729    pub fn build_view<V: Render + 'static>(
730        &self,
731        cx: &mut TestAppContext,
732        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
733    ) -> View<V> {
734        self.update(cx, |_, cx| cx.build_view(build_view)).unwrap()
735    }
736}
737
738pub struct EmptyView {}
739
740impl Render for EmptyView {
741    type Element = Div;
742
743    fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> Self::Element {
744        div()
745    }
746}