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, View, ViewContext, VisualContext, WindowContext, WindowHandle,
  6    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: 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: 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> View<V> {
389    pub fn condition<Evt>(
390        &self,
391        cx: &TestAppContext,
392        mut predicate: impl FnMut(&V, &AppContext) -> bool,
393    ) -> impl Future<Output = ()>
394    where
395        Evt: 'static,
396        V: EventEmitter<Evt>,
397    {
398        use postage::prelude::{Sink as _, Stream as _};
399
400        let (tx, mut rx) = postage::mpsc::channel(1024);
401        let timeout_duration = Duration::from_millis(100); //todo!() cx.condition_duration();
402
403        let mut cx = cx.app.borrow_mut();
404        let subscriptions = (
405            cx.observe(self, {
406                let mut tx = tx.clone();
407                move |_, _| {
408                    tx.blocking_send(()).ok();
409                }
410            }),
411            cx.subscribe(self, {
412                let mut tx = tx.clone();
413                move |_, _: &Evt, _| {
414                    tx.blocking_send(()).ok();
415                }
416            }),
417        );
418
419        let cx = cx.this.upgrade().unwrap();
420        let handle = self.downgrade();
421
422        async move {
423            crate::util::timeout(timeout_duration, async move {
424                loop {
425                    {
426                        let cx = cx.borrow();
427                        let cx = &*cx;
428                        if predicate(
429                            handle
430                                .upgrade()
431                                .expect("view dropped with pending condition")
432                                .read(cx),
433                            cx,
434                        ) {
435                            break;
436                        }
437                    }
438
439                    // todo!(start_waiting)
440                    // cx.borrow().foreground_executor().start_waiting();
441                    rx.recv()
442                        .await
443                        .expect("view dropped with pending condition");
444                    // cx.borrow().foreground_executor().finish_waiting();
445                }
446            })
447            .await
448            .expect("condition timed out");
449            drop(subscriptions);
450        }
451    }
452}
453
454use derive_more::{Deref, DerefMut};
455#[derive(Deref, DerefMut)]
456pub struct VisualTestContext<'a> {
457    #[deref]
458    #[deref_mut]
459    cx: &'a mut TestAppContext,
460    window: AnyWindowHandle,
461}
462
463impl<'a> VisualTestContext<'a> {
464    pub fn from_window(window: AnyWindowHandle, cx: &'a mut TestAppContext) -> Self {
465        Self { cx, window }
466    }
467
468    pub fn run_until_parked(&self) {
469        self.cx.background_executor.run_until_parked();
470    }
471
472    pub fn dispatch_action<A>(&mut self, action: A)
473    where
474        A: Action,
475    {
476        self.cx.dispatch_action(self.window, action)
477    }
478
479    pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
480        self.cx.simulate_keystrokes(self.window, keystrokes)
481    }
482
483    pub fn simulate_input(&mut self, input: &str) {
484        self.cx.simulate_input(self.window, input)
485    }
486}
487
488impl<'a> Context for VisualTestContext<'a> {
489    type Result<T> = <TestAppContext as Context>::Result<T>;
490
491    fn build_model<T: 'static>(
492        &mut self,
493        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
494    ) -> Self::Result<Model<T>> {
495        self.cx.build_model(build_model)
496    }
497
498    fn update_model<T, R>(
499        &mut self,
500        handle: &Model<T>,
501        update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
502    ) -> Self::Result<R>
503    where
504        T: 'static,
505    {
506        self.cx.update_model(handle, update)
507    }
508
509    fn read_model<T, R>(
510        &self,
511        handle: &Model<T>,
512        read: impl FnOnce(&T, &AppContext) -> R,
513    ) -> Self::Result<R>
514    where
515        T: 'static,
516    {
517        self.cx.read_model(handle, read)
518    }
519
520    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
521    where
522        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
523    {
524        self.cx.update_window(window, f)
525    }
526
527    fn read_window<T, R>(
528        &self,
529        window: &WindowHandle<T>,
530        read: impl FnOnce(View<T>, &AppContext) -> R,
531    ) -> Result<R>
532    where
533        T: 'static,
534    {
535        self.cx.read_window(window, read)
536    }
537}
538
539impl<'a> VisualContext for VisualTestContext<'a> {
540    fn build_view<V>(
541        &mut self,
542        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
543    ) -> Self::Result<View<V>>
544    where
545        V: 'static + Render,
546    {
547        self.window
548            .update(self.cx, |_, cx| cx.build_view(build_view))
549            .unwrap()
550    }
551
552    fn update_view<V: 'static, R>(
553        &mut self,
554        view: &View<V>,
555        update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
556    ) -> Self::Result<R> {
557        self.window
558            .update(self.cx, |_, cx| cx.update_view(view, update))
559            .unwrap()
560    }
561
562    fn replace_root_view<V>(
563        &mut self,
564        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
565    ) -> Self::Result<View<V>>
566    where
567        V: Render,
568    {
569        self.window
570            .update(self.cx, |_, cx| cx.replace_root_view(build_view))
571            .unwrap()
572    }
573
574    fn focus_view<V: crate::FocusableView>(&mut self, view: &View<V>) -> Self::Result<()> {
575        self.window
576            .update(self.cx, |_, cx| {
577                view.read(cx).focus_handle(cx).clone().focus(cx)
578            })
579            .unwrap()
580    }
581}
582
583impl AnyWindowHandle {
584    pub fn build_view<V: Render + 'static>(
585        &self,
586        cx: &mut TestAppContext,
587        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
588    ) -> View<V> {
589        self.update(cx, |_, cx| cx.build_view(build_view)).unwrap()
590    }
591}
592
593pub struct EmptyView {}
594
595impl Render for EmptyView {
596    type Element = Div<Self>;
597
598    fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> Self::Element {
599        div()
600    }
601}