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};
 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 spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
174    where
175        Fut: Future<Output = R> + 'static,
176        R: 'static,
177    {
178        self.foreground_executor.spawn(f(self.to_async()))
179    }
180
181    pub fn has_global<G: 'static>(&self) -> bool {
182        let app = self.app.borrow();
183        app.has_global::<G>()
184    }
185
186    pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
187        let app = self.app.borrow();
188        read(app.global(), &app)
189    }
190
191    pub fn try_read_global<G: 'static, R>(
192        &self,
193        read: impl FnOnce(&G, &AppContext) -> R,
194    ) -> Option<R> {
195        let lock = self.app.borrow();
196        Some(read(lock.try_global()?, &lock))
197    }
198
199    pub fn set_global<G: 'static>(&mut self, global: G) {
200        let mut lock = self.app.borrow_mut();
201        lock.set_global(global);
202    }
203
204    pub fn update_global<G: 'static, R>(
205        &mut self,
206        update: impl FnOnce(&mut G, &mut AppContext) -> R,
207    ) -> R {
208        let mut lock = self.app.borrow_mut();
209        lock.update_global(update)
210    }
211
212    pub fn to_async(&self) -> AsyncAppContext {
213        AsyncAppContext {
214            app: Rc::downgrade(&self.app),
215            background_executor: self.background_executor.clone(),
216            foreground_executor: self.foreground_executor.clone(),
217        }
218    }
219
220    pub fn dispatch_action<A>(&mut self, window: AnyWindowHandle, action: A)
221    where
222        A: Action,
223    {
224        window
225            .update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
226            .unwrap();
227
228        self.background_executor.run_until_parked()
229    }
230
231    /// simulate_keystrokes takes a space-separated list of keys to type.
232    /// cx.simulate_keystrokes("cmd-shift-p b k s p enter")
233    /// will run backspace on the current editor through the command palette.
234    pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) {
235        for keystroke in keystrokes
236            .split(" ")
237            .map(Keystroke::parse)
238            .map(Result::unwrap)
239        {
240            self.dispatch_keystroke(window, keystroke.into(), false);
241        }
242
243        self.background_executor.run_until_parked()
244    }
245
246    /// simulate_input takes a string of text to type.
247    /// cx.simulate_input("abc")
248    /// will type abc into your current editor.
249    pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) {
250        for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) {
251            self.dispatch_keystroke(window, keystroke.into(), false);
252        }
253
254        self.background_executor.run_until_parked()
255    }
256
257    pub fn dispatch_keystroke(
258        &mut self,
259        window: AnyWindowHandle,
260        keystroke: Keystroke,
261        is_held: bool,
262    ) {
263        let keystroke2 = keystroke.clone();
264        let handled = window
265            .update(self, |_, cx| {
266                cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held }))
267            })
268            .is_ok_and(|handled| handled);
269        if handled {
270            return;
271        }
272
273        let input_handler = self.update_test_window(window, |window| window.input_handler.clone());
274        let Some(input_handler) = input_handler else {
275            panic!(
276                "dispatch_keystroke {:?} failed to dispatch action or input",
277                &keystroke2
278            );
279        };
280        let text = keystroke2.ime_key.unwrap_or(keystroke2.key);
281        input_handler.lock().replace_text_in_range(None, &text);
282    }
283
284    pub fn update_test_window<R>(
285        &mut self,
286        window: AnyWindowHandle,
287        f: impl FnOnce(&mut TestWindow) -> R,
288    ) -> R {
289        window
290            .update(self, |_, cx| {
291                f(cx.window
292                    .platform_window
293                    .as_any_mut()
294                    .downcast_mut::<TestWindow>()
295                    .unwrap())
296            })
297            .unwrap()
298    }
299
300    pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
301        let (tx, rx) = futures::channel::mpsc::unbounded();
302        self.update(|cx| {
303            cx.observe(entity, {
304                let tx = tx.clone();
305                move |_, _| {
306                    let _ = tx.unbounded_send(());
307                }
308            })
309            .detach();
310            cx.observe_release(entity, move |_, _| tx.close_channel())
311                .detach()
312        });
313        rx
314    }
315
316    pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
317        &mut self,
318        entity: &Model<T>,
319    ) -> futures::channel::mpsc::UnboundedReceiver<Evt>
320    where
321        Evt: 'static + Clone,
322    {
323        let (tx, rx) = futures::channel::mpsc::unbounded();
324        entity
325            .update(self, |_, cx: &mut ModelContext<T>| {
326                cx.subscribe(entity, move |_model, _handle, event, _cx| {
327                    let _ = tx.unbounded_send(event.clone());
328                })
329            })
330            .detach();
331        rx
332    }
333
334    pub async fn condition<T: 'static>(
335        &mut self,
336        model: &Model<T>,
337        mut predicate: impl FnMut(&mut T, &mut ModelContext<T>) -> bool,
338    ) {
339        let timer = self.executor().timer(Duration::from_secs(3));
340        let mut notifications = self.notifications(model);
341
342        use futures::FutureExt as _;
343        use smol::future::FutureExt as _;
344
345        async {
346            while notifications.next().await.is_some() {
347                if model.update(self, &mut predicate) {
348                    return Ok(());
349                }
350            }
351            bail!("model dropped")
352        }
353        .race(timer.map(|_| Err(anyhow!("condition timed out"))))
354        .await
355        .unwrap();
356    }
357}
358
359impl<T: Send> Model<T> {
360    pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
361    where
362        Evt: Send + Clone + 'static,
363        T: EventEmitter<Evt>,
364    {
365        let (tx, mut rx) = futures::channel::mpsc::unbounded();
366        let _subscription = self.update(cx, |_, cx| {
367            cx.subscribe(self, move |_, _, event, _| {
368                tx.unbounded_send(event.clone()).ok();
369            })
370        });
371
372        // Run other tasks until the event is emitted.
373        loop {
374            match rx.try_next() {
375                Ok(Some(event)) => return event,
376                Ok(None) => panic!("model was dropped"),
377                Err(_) => {
378                    if !cx.executor().tick() {
379                        break;
380                    }
381                }
382            }
383        }
384        panic!("no event received")
385    }
386}
387
388impl<V: 'static> View<V> {
389    pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
390        use postage::prelude::{Sink as _, Stream as _};
391
392        let (mut tx, mut rx) = postage::mpsc::channel(1);
393        let mut cx = cx.app.app.borrow_mut();
394        let subscription = cx.observe(self, move |_, _| {
395            tx.try_send(()).ok();
396        });
397
398        let duration = if std::env::var("CI").is_ok() {
399            Duration::from_secs(5)
400        } else {
401            Duration::from_secs(1)
402        };
403
404        async move {
405            let notification = crate::util::timeout(duration, rx.recv())
406                .await
407                .expect("next notification timed out");
408            drop(subscription);
409            notification.expect("model dropped while test was waiting for its next notification")
410        }
411    }
412}
413
414impl<V> View<V> {
415    pub fn condition<Evt>(
416        &self,
417        cx: &TestAppContext,
418        mut predicate: impl FnMut(&V, &AppContext) -> bool,
419    ) -> impl Future<Output = ()>
420    where
421        Evt: 'static,
422        V: EventEmitter<Evt>,
423    {
424        use postage::prelude::{Sink as _, Stream as _};
425
426        let (tx, mut rx) = postage::mpsc::channel(1024);
427        let timeout_duration = Duration::from_millis(100); //todo!() cx.condition_duration();
428
429        let mut cx = cx.app.borrow_mut();
430        let subscriptions = (
431            cx.observe(self, {
432                let mut tx = tx.clone();
433                move |_, _| {
434                    tx.blocking_send(()).ok();
435                }
436            }),
437            cx.subscribe(self, {
438                let mut tx = tx.clone();
439                move |_, _: &Evt, _| {
440                    tx.blocking_send(()).ok();
441                }
442            }),
443        );
444
445        let cx = cx.this.upgrade().unwrap();
446        let handle = self.downgrade();
447
448        async move {
449            crate::util::timeout(timeout_duration, async move {
450                loop {
451                    {
452                        let cx = cx.borrow();
453                        let cx = &*cx;
454                        if predicate(
455                            handle
456                                .upgrade()
457                                .expect("view dropped with pending condition")
458                                .read(cx),
459                            cx,
460                        ) {
461                            break;
462                        }
463                    }
464
465                    // todo!(start_waiting)
466                    // cx.borrow().foreground_executor().start_waiting();
467                    rx.recv()
468                        .await
469                        .expect("view dropped with pending condition");
470                    // cx.borrow().foreground_executor().finish_waiting();
471                }
472            })
473            .await
474            .expect("condition timed out");
475            drop(subscriptions);
476        }
477    }
478}
479
480use derive_more::{Deref, DerefMut};
481#[derive(Deref, DerefMut)]
482pub struct VisualTestContext<'a> {
483    #[deref]
484    #[deref_mut]
485    cx: &'a mut TestAppContext,
486    window: AnyWindowHandle,
487}
488
489impl<'a> VisualTestContext<'a> {
490    pub fn from_window(window: AnyWindowHandle, cx: &'a mut TestAppContext) -> Self {
491        Self { cx, window }
492    }
493
494    pub fn run_until_parked(&self) {
495        self.cx.background_executor.run_until_parked();
496    }
497
498    pub fn dispatch_action<A>(&mut self, action: A)
499    where
500        A: Action,
501    {
502        self.cx.dispatch_action(self.window, action)
503    }
504
505    pub fn window_title(&mut self) -> Option<String> {
506        self.cx
507            .update_window(self.window, |_, cx| {
508                cx.window
509                    .platform_window
510                    .as_test()
511                    .unwrap()
512                    .window_title
513                    .clone()
514            })
515            .unwrap()
516    }
517
518    pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
519        self.cx.simulate_keystrokes(self.window, keystrokes)
520    }
521
522    pub fn simulate_input(&mut self, input: &str) {
523        self.cx.simulate_input(self.window, input)
524    }
525
526    pub fn simulate_activation(&mut self) {
527        self.simulate_window_events(&mut |handlers| {
528            handlers
529                .active_status_change
530                .iter_mut()
531                .for_each(|f| f(true));
532        })
533    }
534
535    pub fn simulate_deactivation(&mut self) {
536        self.simulate_window_events(&mut |handlers| {
537            handlers
538                .active_status_change
539                .iter_mut()
540                .for_each(|f| f(false));
541        })
542    }
543
544    fn simulate_window_events(&mut self, f: &mut dyn FnMut(&mut TestWindowHandlers)) {
545        let handlers = self
546            .cx
547            .update_window(self.window, |_, cx| {
548                cx.window
549                    .platform_window
550                    .as_test()
551                    .unwrap()
552                    .handlers
553                    .clone()
554            })
555            .unwrap();
556        f(&mut *handlers.lock());
557    }
558}
559
560impl<'a> Context for VisualTestContext<'a> {
561    type Result<T> = <TestAppContext as Context>::Result<T>;
562
563    fn build_model<T: 'static>(
564        &mut self,
565        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
566    ) -> Self::Result<Model<T>> {
567        self.cx.build_model(build_model)
568    }
569
570    fn update_model<T, R>(
571        &mut self,
572        handle: &Model<T>,
573        update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
574    ) -> Self::Result<R>
575    where
576        T: 'static,
577    {
578        self.cx.update_model(handle, update)
579    }
580
581    fn read_model<T, R>(
582        &self,
583        handle: &Model<T>,
584        read: impl FnOnce(&T, &AppContext) -> R,
585    ) -> Self::Result<R>
586    where
587        T: 'static,
588    {
589        self.cx.read_model(handle, read)
590    }
591
592    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
593    where
594        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
595    {
596        self.cx.update_window(window, f)
597    }
598
599    fn read_window<T, R>(
600        &self,
601        window: &WindowHandle<T>,
602        read: impl FnOnce(View<T>, &AppContext) -> R,
603    ) -> Result<R>
604    where
605        T: 'static,
606    {
607        self.cx.read_window(window, read)
608    }
609}
610
611impl<'a> VisualContext for VisualTestContext<'a> {
612    fn build_view<V>(
613        &mut self,
614        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
615    ) -> Self::Result<View<V>>
616    where
617        V: 'static + Render,
618    {
619        self.window
620            .update(self.cx, |_, cx| cx.build_view(build_view))
621            .unwrap()
622    }
623
624    fn update_view<V: 'static, R>(
625        &mut self,
626        view: &View<V>,
627        update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
628    ) -> Self::Result<R> {
629        self.window
630            .update(self.cx, |_, cx| cx.update_view(view, update))
631            .unwrap()
632    }
633
634    fn replace_root_view<V>(
635        &mut self,
636        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
637    ) -> Self::Result<View<V>>
638    where
639        V: 'static + Render,
640    {
641        self.window
642            .update(self.cx, |_, cx| cx.replace_root_view(build_view))
643            .unwrap()
644    }
645
646    fn focus_view<V: crate::FocusableView>(&mut self, view: &View<V>) -> Self::Result<()> {
647        self.window
648            .update(self.cx, |_, cx| {
649                view.read(cx).focus_handle(cx).clone().focus(cx)
650            })
651            .unwrap()
652    }
653
654    fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
655    where
656        V: crate::ManagedView,
657    {
658        self.window
659            .update(self.cx, |_, cx| {
660                view.update(cx, |_, cx| cx.emit(crate::DismissEvent))
661            })
662            .unwrap()
663    }
664}
665
666impl AnyWindowHandle {
667    pub fn build_view<V: Render + 'static>(
668        &self,
669        cx: &mut TestAppContext,
670        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
671    ) -> View<V> {
672        self.update(cx, |_, cx| cx.build_view(build_view)).unwrap()
673    }
674}
675
676pub struct EmptyView {}
677
678impl Render for EmptyView {
679    type Element = Div;
680
681    fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> Self::Element {
682        div()
683    }
684}