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                    // todo!(start_waiting)
471                    // cx.borrow().foreground_executor().start_waiting();
472                    rx.recv()
473                        .await
474                        .expect("view dropped with pending condition");
475                    // cx.borrow().foreground_executor().finish_waiting();
476                }
477            })
478            .await
479            .expect("condition timed out");
480            drop(subscriptions);
481        }
482    }
483}
484
485use derive_more::{Deref, DerefMut};
486#[derive(Deref, DerefMut, Clone)]
487pub struct VisualTestContext {
488    #[deref]
489    #[deref_mut]
490    cx: TestAppContext,
491    window: AnyWindowHandle,
492}
493
494impl<'a> VisualTestContext {
495    pub fn update<R>(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R {
496        self.cx.update_window(self.window, |_, cx| f(cx)).unwrap()
497    }
498
499    pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self {
500        Self {
501            cx: cx.clone(),
502            window,
503        }
504    }
505
506    pub fn run_until_parked(&self) {
507        self.cx.background_executor.run_until_parked();
508    }
509
510    pub fn dispatch_action<A>(&mut self, action: A)
511    where
512        A: Action,
513    {
514        self.cx.dispatch_action(self.window, action)
515    }
516
517    pub fn window_title(&mut self) -> Option<String> {
518        self.cx.test_window(self.window).0.lock().title.clone()
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 deactivate_window(&mut self) {
530        if Some(self.window) == self.test_platform.active_window() {
531            self.test_platform.set_active_window(None)
532        }
533        self.background_executor.run_until_parked();
534    }
535    /// Returns true if the window was closed.
536    pub fn simulate_close(&mut self) -> bool {
537        let handler = self
538            .cx
539            .update_window(self.window, |_, cx| {
540                cx.window
541                    .platform_window
542                    .as_test()
543                    .unwrap()
544                    .0
545                    .lock()
546                    .should_close_handler
547                    .take()
548            })
549            .unwrap();
550        if let Some(mut handler) = handler {
551            let should_close = handler();
552            self.cx
553                .update_window(self.window, |_, cx| {
554                    cx.window.platform_window.on_should_close(handler);
555                })
556                .unwrap();
557            should_close
558        } else {
559            false
560        }
561    }
562}
563
564impl Context for VisualTestContext {
565    type Result<T> = <TestAppContext as Context>::Result<T>;
566
567    fn new_model<T: 'static>(
568        &mut self,
569        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
570    ) -> Self::Result<Model<T>> {
571        self.cx.new_model(build_model)
572    }
573
574    fn update_model<T, R>(
575        &mut self,
576        handle: &Model<T>,
577        update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
578    ) -> Self::Result<R>
579    where
580        T: 'static,
581    {
582        self.cx.update_model(handle, update)
583    }
584
585    fn read_model<T, R>(
586        &self,
587        handle: &Model<T>,
588        read: impl FnOnce(&T, &AppContext) -> R,
589    ) -> Self::Result<R>
590    where
591        T: 'static,
592    {
593        self.cx.read_model(handle, read)
594    }
595
596    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
597    where
598        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
599    {
600        self.cx.update_window(window, f)
601    }
602
603    fn read_window<T, R>(
604        &self,
605        window: &WindowHandle<T>,
606        read: impl FnOnce(View<T>, &AppContext) -> R,
607    ) -> Result<R>
608    where
609        T: 'static,
610    {
611        self.cx.read_window(window, read)
612    }
613}
614
615impl VisualContext for VisualTestContext {
616    fn new_view<V>(
617        &mut self,
618        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
619    ) -> Self::Result<View<V>>
620    where
621        V: 'static + Render,
622    {
623        self.window
624            .update(&mut self.cx, |_, cx| cx.new_view(build_view))
625            .unwrap()
626    }
627
628    fn update_view<V: 'static, R>(
629        &mut self,
630        view: &View<V>,
631        update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
632    ) -> Self::Result<R> {
633        self.window
634            .update(&mut self.cx, |_, cx| cx.update_view(view, update))
635            .unwrap()
636    }
637
638    fn replace_root_view<V>(
639        &mut self,
640        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
641    ) -> Self::Result<View<V>>
642    where
643        V: 'static + Render,
644    {
645        self.window
646            .update(&mut self.cx, |_, cx| cx.replace_root_view(build_view))
647            .unwrap()
648    }
649
650    fn focus_view<V: crate::FocusableView>(&mut self, view: &View<V>) -> Self::Result<()> {
651        self.window
652            .update(&mut self.cx, |_, cx| {
653                view.read(cx).focus_handle(cx).clone().focus(cx)
654            })
655            .unwrap()
656    }
657
658    fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
659    where
660        V: crate::ManagedView,
661    {
662        self.window
663            .update(&mut self.cx, |_, cx| {
664                view.update(cx, |_, cx| cx.emit(crate::DismissEvent))
665            })
666            .unwrap()
667    }
668}
669
670impl AnyWindowHandle {
671    pub fn build_view<V: Render + 'static>(
672        &self,
673        cx: &mut TestAppContext,
674        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
675    ) -> View<V> {
676        self.update(cx, |_, cx| cx.new_view(build_view)).unwrap()
677    }
678}
679
680pub struct EmptyView {}
681
682impl Render for EmptyView {
683    fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> impl IntoElement {
684        div()
685    }
686}