test_context.rs

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