test_context.rs

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