test_context.rs

  1use crate::{
  2    div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
  3    BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor,
  4    IntoElement, Keystroke, Model, ModelContext, Pixels, Platform, Render, Result, Size, Task,
  5    TestDispatcher, TestPlatform, TestWindow, TextSystem, View, ViewContext, VisualContext,
  6    WindowContext, 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    text_system: Arc<TextSystem>,
 20}
 21
 22impl Context for TestAppContext {
 23    type Result<T> = T;
 24
 25    fn new_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.new_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        let text_system = Arc::new(TextSystem::new(platform.text_system()));
 87
 88        Self {
 89            app: AppContext::new(platform.clone(), asset_source, http_client),
 90            background_executor,
 91            foreground_executor,
 92            dispatcher: dispatcher.clone(),
 93            test_platform: platform,
 94            text_system,
 95        }
 96    }
 97
 98    pub fn new_app(&self) -> TestAppContext {
 99        Self::new(self.dispatcher.clone())
100    }
101
102    pub fn quit(&self) {
103        self.app.borrow_mut().shutdown();
104    }
105
106    pub fn refresh(&mut self) -> Result<()> {
107        let mut app = self.app.borrow_mut();
108        app.refresh();
109        Ok(())
110    }
111
112    pub fn executor(&self) -> BackgroundExecutor {
113        self.background_executor.clone()
114    }
115
116    pub fn foreground_executor(&self) -> &ForegroundExecutor {
117        &self.foreground_executor
118    }
119
120    pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {
121        let mut cx = self.app.borrow_mut();
122        cx.update(f)
123    }
124
125    pub fn read<R>(&self, f: impl FnOnce(&AppContext) -> R) -> R {
126        let cx = self.app.borrow();
127        f(&*cx)
128    }
129
130    pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
131    where
132        F: FnOnce(&mut ViewContext<V>) -> V,
133        V: 'static + Render,
134    {
135        let mut cx = self.app.borrow_mut();
136        cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window))
137    }
138
139    pub fn add_empty_window(&mut self) -> AnyWindowHandle {
140        let mut cx = self.app.borrow_mut();
141        cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {}))
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.new_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 text_system(&self) -> &Arc<TextSystem> {
160        &self.text_system
161    }
162
163    pub fn write_to_clipboard(&self, item: ClipboardItem) {
164        self.test_platform.write_to_clipboard(item)
165    }
166
167    pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
168        self.test_platform.read_from_clipboard()
169    }
170
171    pub fn simulate_new_path_selection(
172        &self,
173        select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
174    ) {
175        self.test_platform.simulate_new_path_selection(select_path);
176    }
177
178    pub fn simulate_prompt_answer(&self, button_ix: usize) {
179        self.test_platform.simulate_prompt_answer(button_ix);
180    }
181
182    pub fn has_pending_prompt(&self) -> bool {
183        self.test_platform.has_pending_prompt()
184    }
185
186    pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {
187        self.test_window(window_handle).simulate_resize(size);
188    }
189
190    pub fn windows(&self) -> Vec<AnyWindowHandle> {
191        self.app.borrow().windows().clone()
192    }
193
194    pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
195    where
196        Fut: Future<Output = R> + 'static,
197        R: 'static,
198    {
199        self.foreground_executor.spawn(f(self.to_async()))
200    }
201
202    pub fn has_global<G: 'static>(&self) -> bool {
203        let app = self.app.borrow();
204        app.has_global::<G>()
205    }
206
207    pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
208        let app = self.app.borrow();
209        read(app.global(), &app)
210    }
211
212    pub fn try_read_global<G: 'static, R>(
213        &self,
214        read: impl FnOnce(&G, &AppContext) -> R,
215    ) -> Option<R> {
216        let lock = self.app.borrow();
217        Some(read(lock.try_global()?, &lock))
218    }
219
220    pub fn set_global<G: 'static>(&mut self, global: G) {
221        let mut lock = self.app.borrow_mut();
222        lock.set_global(global);
223    }
224
225    pub fn update_global<G: 'static, R>(
226        &mut self,
227        update: impl FnOnce(&mut G, &mut AppContext) -> R,
228    ) -> R {
229        let mut lock = self.app.borrow_mut();
230        lock.update_global(update)
231    }
232
233    pub fn to_async(&self) -> AsyncAppContext {
234        AsyncAppContext {
235            app: Rc::downgrade(&self.app),
236            background_executor: self.background_executor.clone(),
237            foreground_executor: self.foreground_executor.clone(),
238        }
239    }
240
241    pub fn dispatch_action<A>(&mut self, window: AnyWindowHandle, action: A)
242    where
243        A: Action,
244    {
245        window
246            .update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
247            .unwrap();
248
249        self.background_executor.run_until_parked()
250    }
251
252    /// simulate_keystrokes takes a space-separated list of keys to type.
253    /// cx.simulate_keystrokes("cmd-shift-p b k s p enter")
254    /// will run backspace on the current editor through the command palette.
255    pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) {
256        for keystroke in keystrokes
257            .split(" ")
258            .map(Keystroke::parse)
259            .map(Result::unwrap)
260        {
261            self.dispatch_keystroke(window, keystroke.into(), false);
262        }
263
264        self.background_executor.run_until_parked()
265    }
266
267    /// simulate_input takes a string of text to type.
268    /// cx.simulate_input("abc")
269    /// will type abc into your current editor.
270    pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) {
271        for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) {
272            self.dispatch_keystroke(window, keystroke.into(), false);
273        }
274
275        self.background_executor.run_until_parked()
276    }
277
278    pub fn dispatch_keystroke(
279        &mut self,
280        window: AnyWindowHandle,
281        keystroke: Keystroke,
282        is_held: bool,
283    ) {
284        self.test_window(window)
285            .simulate_keystroke(keystroke, is_held)
286    }
287
288    pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
289        self.app
290            .borrow_mut()
291            .windows
292            .get_mut(window.id)
293            .unwrap()
294            .as_mut()
295            .unwrap()
296            .platform_window
297            .as_test()
298            .unwrap()
299            .clone()
300    }
301
302    pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
303        let (tx, rx) = futures::channel::mpsc::unbounded();
304        self.update(|cx| {
305            cx.observe(entity, {
306                let tx = tx.clone();
307                move |_, _| {
308                    let _ = tx.unbounded_send(());
309                }
310            })
311            .detach();
312            cx.observe_release(entity, move |_, _| tx.close_channel())
313                .detach()
314        });
315        rx
316    }
317
318    pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
319        &mut self,
320        entity: &Model<T>,
321    ) -> futures::channel::mpsc::UnboundedReceiver<Evt>
322    where
323        Evt: 'static + Clone,
324    {
325        let (tx, rx) = futures::channel::mpsc::unbounded();
326        entity
327            .update(self, |_, cx: &mut ModelContext<T>| {
328                cx.subscribe(entity, move |_model, _handle, event, _cx| {
329                    let _ = tx.unbounded_send(event.clone());
330                })
331            })
332            .detach();
333        rx
334    }
335
336    pub async fn condition<T: 'static>(
337        &mut self,
338        model: &Model<T>,
339        mut predicate: impl FnMut(&mut T, &mut ModelContext<T>) -> bool,
340    ) {
341        let timer = self.executor().timer(Duration::from_secs(3));
342        let mut notifications = self.notifications(model);
343
344        use futures::FutureExt as _;
345        use smol::future::FutureExt as _;
346
347        async {
348            loop {
349                if model.update(self, &mut predicate) {
350                    return Ok(());
351                }
352
353                if notifications.next().await.is_none() {
354                    bail!("model dropped")
355                }
356            }
357        }
358        .race(timer.map(|_| Err(anyhow!("condition timed out"))))
359        .await
360        .unwrap();
361    }
362}
363
364impl<T: Send> Model<T> {
365    pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
366    where
367        Evt: Send + Clone + 'static,
368        T: EventEmitter<Evt>,
369    {
370        let (tx, mut rx) = futures::channel::mpsc::unbounded();
371        let _subscription = self.update(cx, |_, cx| {
372            cx.subscribe(self, move |_, _, event, _| {
373                tx.unbounded_send(event.clone()).ok();
374            })
375        });
376
377        // Run other tasks until the event is emitted.
378        loop {
379            match rx.try_next() {
380                Ok(Some(event)) => return event,
381                Ok(None) => panic!("model was dropped"),
382                Err(_) => {
383                    if !cx.executor().tick() {
384                        break;
385                    }
386                }
387            }
388        }
389        panic!("no event received")
390    }
391}
392
393impl<V: 'static> View<V> {
394    pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
395        use postage::prelude::{Sink as _, Stream as _};
396
397        let (mut tx, mut rx) = postage::mpsc::channel(1);
398        let mut cx = cx.app.app.borrow_mut();
399        let subscription = cx.observe(self, move |_, _| {
400            tx.try_send(()).ok();
401        });
402
403        let duration = if std::env::var("CI").is_ok() {
404            Duration::from_secs(5)
405        } else {
406            Duration::from_secs(1)
407        };
408
409        async move {
410            let notification = crate::util::timeout(duration, rx.recv())
411                .await
412                .expect("next notification timed out");
413            drop(subscription);
414            notification.expect("model dropped while test was waiting for its next notification")
415        }
416    }
417}
418
419impl<V> View<V> {
420    pub fn condition<Evt>(
421        &self,
422        cx: &TestAppContext,
423        mut predicate: impl FnMut(&V, &AppContext) -> bool,
424    ) -> impl Future<Output = ()>
425    where
426        Evt: 'static,
427        V: EventEmitter<Evt>,
428    {
429        use postage::prelude::{Sink as _, Stream as _};
430
431        let (tx, mut rx) = postage::mpsc::channel(1024);
432        let timeout_duration = Duration::from_millis(100); //todo!() cx.condition_duration();
433
434        let mut cx = cx.app.borrow_mut();
435        let subscriptions = (
436            cx.observe(self, {
437                let mut tx = tx.clone();
438                move |_, _| {
439                    tx.blocking_send(()).ok();
440                }
441            }),
442            cx.subscribe(self, {
443                let mut tx = tx.clone();
444                move |_, _: &Evt, _| {
445                    tx.blocking_send(()).ok();
446                }
447            }),
448        );
449
450        let cx = cx.this.upgrade().unwrap();
451        let handle = self.downgrade();
452
453        async move {
454            crate::util::timeout(timeout_duration, async move {
455                loop {
456                    {
457                        let cx = cx.borrow();
458                        let cx = &*cx;
459                        if predicate(
460                            handle
461                                .upgrade()
462                                .expect("view dropped with pending condition")
463                                .read(cx),
464                            cx,
465                        ) {
466                            break;
467                        }
468                    }
469
470                    cx.borrow().background_executor().start_waiting();
471                    rx.recv()
472                        .await
473                        .expect("view dropped with pending condition");
474                    cx.borrow().background_executor().finish_waiting();
475                }
476            })
477            .await
478            .expect("condition timed out");
479            drop(subscriptions);
480        }
481    }
482}
483
484use derive_more::{Deref, DerefMut};
485#[derive(Deref, DerefMut, Clone)]
486pub struct VisualTestContext {
487    #[deref]
488    #[deref_mut]
489    cx: TestAppContext,
490    window: AnyWindowHandle,
491}
492
493impl<'a> VisualTestContext {
494    pub fn update<R>(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R {
495        self.cx.update_window(self.window, |_, cx| f(cx)).unwrap()
496    }
497
498    pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self {
499        Self {
500            cx: cx.clone(),
501            window,
502        }
503    }
504
505    pub fn run_until_parked(&self) {
506        self.cx.background_executor.run_until_parked();
507    }
508
509    pub fn dispatch_action<A>(&mut self, action: A)
510    where
511        A: Action,
512    {
513        self.cx.dispatch_action(self.window, action)
514    }
515
516    pub fn window_title(&mut self) -> Option<String> {
517        self.cx.test_window(self.window).0.lock().title.clone()
518    }
519
520    pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
521        self.cx.simulate_keystrokes(self.window, keystrokes)
522    }
523
524    pub fn simulate_input(&mut self, input: &str) {
525        self.cx.simulate_input(self.window, input)
526    }
527
528    pub fn deactivate_window(&mut self) {
529        if Some(self.window) == self.test_platform.active_window() {
530            self.test_platform.set_active_window(None)
531        }
532        self.background_executor.run_until_parked();
533    }
534    /// Returns true if the window was closed.
535    pub fn simulate_close(&mut self) -> bool {
536        let handler = self
537            .cx
538            .update_window(self.window, |_, cx| {
539                cx.window
540                    .platform_window
541                    .as_test()
542                    .unwrap()
543                    .0
544                    .lock()
545                    .should_close_handler
546                    .take()
547            })
548            .unwrap();
549        if let Some(mut handler) = handler {
550            let should_close = handler();
551            self.cx
552                .update_window(self.window, |_, cx| {
553                    cx.window.platform_window.on_should_close(handler);
554                })
555                .unwrap();
556            should_close
557        } else {
558            false
559        }
560    }
561}
562
563impl Context for VisualTestContext {
564    type Result<T> = <TestAppContext as Context>::Result<T>;
565
566    fn new_model<T: 'static>(
567        &mut self,
568        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
569    ) -> Self::Result<Model<T>> {
570        self.cx.new_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 VisualContext for VisualTestContext {
615    fn new_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(&mut self.cx, |_, cx| cx.new_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(&mut 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(&mut 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(&mut 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(&mut 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.new_view(build_view)).unwrap()
676    }
677}
678
679pub struct EmptyView {}
680
681impl Render for EmptyView {
682    fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> impl IntoElement {
683        div()
684    }
685}