test_context.rs

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