test_context.rs

  1use crate::{
  2    AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, Context,
  3    EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model, ModelContext,
  4    Result, Task, TestDispatcher, TestPlatform, WindowContext,
  5};
  6use anyhow::{anyhow, bail};
  7use futures::{Stream, StreamExt};
  8use std::{future::Future, rc::Rc, sync::Arc, time::Duration};
  9
 10#[derive(Clone)]
 11pub struct TestAppContext {
 12    pub app: Rc<AppCell>,
 13    pub background_executor: BackgroundExecutor,
 14    pub foreground_executor: ForegroundExecutor,
 15}
 16
 17impl Context for TestAppContext {
 18    type Result<T> = T;
 19
 20    fn build_model<T: 'static>(
 21        &mut self,
 22        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
 23    ) -> Self::Result<Model<T>>
 24    where
 25        T: 'static,
 26    {
 27        let mut app = self.app.borrow_mut();
 28        app.build_model(build_model)
 29    }
 30
 31    fn update_model<T: 'static, R>(
 32        &mut self,
 33        handle: &Model<T>,
 34        update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
 35    ) -> Self::Result<R> {
 36        let mut app = self.app.borrow_mut();
 37        app.update_model(handle, update)
 38    }
 39
 40    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
 41    where
 42        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
 43    {
 44        let mut lock = self.app.borrow_mut();
 45        lock.update_window(window, f)
 46    }
 47}
 48
 49impl TestAppContext {
 50    pub fn new(dispatcher: TestDispatcher) -> Self {
 51        let dispatcher = Arc::new(dispatcher);
 52        let background_executor = BackgroundExecutor::new(dispatcher.clone());
 53        let foreground_executor = ForegroundExecutor::new(dispatcher);
 54        let platform = Rc::new(TestPlatform::new(
 55            background_executor.clone(),
 56            foreground_executor.clone(),
 57        ));
 58        let asset_source = Arc::new(());
 59        let http_client = util::http::FakeHttpClient::with_404_response();
 60        Self {
 61            app: AppContext::new(platform, asset_source, http_client),
 62            background_executor,
 63            foreground_executor,
 64        }
 65    }
 66
 67    pub fn quit(&self) {
 68        self.app.borrow_mut().quit();
 69    }
 70
 71    pub fn refresh(&mut self) -> Result<()> {
 72        let mut app = self.app.borrow_mut();
 73        app.refresh();
 74        Ok(())
 75    }
 76
 77    pub fn executor(&self) -> &BackgroundExecutor {
 78        &self.background_executor
 79    }
 80
 81    pub fn foreground_executor(&self) -> &ForegroundExecutor {
 82        &self.foreground_executor
 83    }
 84
 85    pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {
 86        let mut cx = self.app.borrow_mut();
 87        cx.update(f)
 88    }
 89
 90    pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
 91    where
 92        Fut: Future<Output = R> + 'static,
 93        R: 'static,
 94    {
 95        self.foreground_executor.spawn(f(self.to_async()))
 96    }
 97
 98    pub fn has_global<G: 'static>(&self) -> bool {
 99        let app = self.app.borrow();
100        app.has_global::<G>()
101    }
102
103    pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
104        let app = self.app.borrow();
105        read(app.global(), &app)
106    }
107
108    pub fn try_read_global<G: 'static, R>(
109        &self,
110        read: impl FnOnce(&G, &AppContext) -> R,
111    ) -> Option<R> {
112        let lock = self.app.borrow();
113        Some(read(lock.try_global()?, &lock))
114    }
115
116    pub fn update_global<G: 'static, R>(
117        &mut self,
118        update: impl FnOnce(&mut G, &mut AppContext) -> R,
119    ) -> R {
120        let mut lock = self.app.borrow_mut();
121        lock.update_global(update)
122    }
123
124    pub fn to_async(&self) -> AsyncAppContext {
125        AsyncAppContext {
126            app: Rc::downgrade(&self.app),
127            background_executor: self.background_executor.clone(),
128            foreground_executor: self.foreground_executor.clone(),
129        }
130    }
131
132    pub fn dispatch_keystroke(
133        &mut self,
134        window: AnyWindowHandle,
135        keystroke: Keystroke,
136        is_held: bool,
137    ) {
138        let handled = window
139            .update(self, |_, cx| {
140                cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held }))
141            })
142            .is_ok_and(|handled| handled);
143
144        if !handled {
145            // todo!() simluate input here
146        }
147    }
148
149    pub fn notifications<T: 'static>(&mut self, entity: &Model<T>) -> impl Stream<Item = ()> {
150        let (tx, rx) = futures::channel::mpsc::unbounded();
151
152        entity.update(self, move |_, cx: &mut ModelContext<T>| {
153            cx.observe(entity, {
154                let tx = tx.clone();
155                move |_, _, _| {
156                    let _ = tx.unbounded_send(());
157                }
158            })
159            .detach();
160
161            cx.on_release(move |_, _| tx.close_channel()).detach();
162        });
163
164        rx
165    }
166
167    pub fn events<T: 'static + EventEmitter>(
168        &mut self,
169        entity: &Model<T>,
170    ) -> futures::channel::mpsc::UnboundedReceiver<T::Event>
171    where
172        T::Event: 'static + Clone,
173    {
174        let (tx, rx) = futures::channel::mpsc::unbounded();
175        entity
176            .update(self, |_, cx: &mut ModelContext<T>| {
177                cx.subscribe(entity, move |_model, _handle, event, _cx| {
178                    let _ = tx.unbounded_send(event.clone());
179                })
180            })
181            .detach();
182        rx
183    }
184
185    pub async fn condition<T: 'static>(
186        &mut self,
187        model: &Model<T>,
188        mut predicate: impl FnMut(&mut T, &mut ModelContext<T>) -> bool,
189    ) {
190        let timer = self.executor().timer(Duration::from_secs(3));
191        let mut notifications = self.notifications(model);
192
193        use futures::FutureExt as _;
194        use smol::future::FutureExt as _;
195
196        async {
197            while notifications.next().await.is_some() {
198                if model.update(self, &mut predicate) {
199                    return Ok(());
200                }
201            }
202            bail!("model dropped")
203        }
204        .race(timer.map(|_| Err(anyhow!("condition timed out"))))
205        .await
206        .unwrap();
207    }
208}
209
210impl<T: Send + EventEmitter> Model<T> {
211    pub fn next_event(&self, cx: &mut TestAppContext) -> T::Event
212    where
213        T::Event: Send + Clone,
214    {
215        let (tx, mut rx) = futures::channel::mpsc::unbounded();
216        let _subscription = self.update(cx, |_, cx| {
217            cx.subscribe(self, move |_, _, event, _| {
218                tx.unbounded_send(event.clone()).ok();
219            })
220        });
221
222        cx.executor().run_until_parked();
223        rx.try_next()
224            .expect("no event received")
225            .expect("model was dropped")
226    }
227}