test_context.rs

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