test_context.rs

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