use crate::{ AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter, ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher, TestPlatform, WindowContext, }; use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; use std::{future::Future, rc::Rc, sync::Arc, time::Duration}; #[derive(Clone)] pub struct TestAppContext { pub app: Rc, pub background_executor: BackgroundExecutor, pub foreground_executor: ForegroundExecutor, } impl Context for TestAppContext { type Result = T; fn build_model( &mut self, build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T, ) -> Self::Result> where T: 'static, { let mut app = self.app.borrow_mut(); app.build_model(build_model) } fn update_model( &mut self, handle: &Model, update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R, ) -> Self::Result { let mut app = self.app.borrow_mut(); app.update_model(handle, update) } fn update_window(&mut self, window: AnyWindowHandle, f: F) -> Result where F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, { let mut lock = self.app.borrow_mut(); lock.update_window(window, f) } } impl TestAppContext { pub fn new(dispatcher: TestDispatcher) -> Self { let dispatcher = Arc::new(dispatcher); let background_executor = BackgroundExecutor::new(dispatcher.clone()); let foreground_executor = ForegroundExecutor::new(dispatcher); let platform = Rc::new(TestPlatform::new( background_executor.clone(), foreground_executor.clone(), )); let asset_source = Arc::new(()); let http_client = util::http::FakeHttpClient::with_404_response(); Self { app: AppContext::new(platform, asset_source, http_client), background_executor, foreground_executor, } } pub fn quit(&self) { self.app.borrow_mut().quit(); } pub fn refresh(&mut self) -> Result<()> { let mut app = self.app.borrow_mut(); app.refresh(); Ok(()) } pub fn executor(&self) -> &BackgroundExecutor { &self.background_executor } pub fn foreground_executor(&self) -> &ForegroundExecutor { &self.foreground_executor } pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> R { let mut cx = self.app.borrow_mut(); cx.update(f) } pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where Fut: Future + 'static, R: 'static, { self.foreground_executor.spawn(f(self.to_async())) } pub fn has_global(&self) -> bool { let app = self.app.borrow(); app.has_global::() } pub fn read_global(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R { let app = self.app.borrow(); read(app.global(), &app) } pub fn try_read_global( &self, read: impl FnOnce(&G, &AppContext) -> R, ) -> Option { let lock = self.app.borrow(); Some(read(lock.try_global()?, &lock)) } pub fn update_global( &mut self, update: impl FnOnce(&mut G, &mut AppContext) -> R, ) -> R { let mut lock = self.app.borrow_mut(); lock.update_global(update) } pub fn to_async(&self) -> AsyncAppContext { AsyncAppContext { app: Rc::downgrade(&self.app), background_executor: self.background_executor.clone(), foreground_executor: self.foreground_executor.clone(), } } pub fn notifications(&mut self, entity: &Model) -> impl Stream { let (tx, rx) = futures::channel::mpsc::unbounded(); entity.update(self, move |_, cx: &mut ModelContext| { cx.observe(entity, { let tx = tx.clone(); move |_, _, _| { let _ = tx.unbounded_send(()); } }) .detach(); cx.on_release(move |_, _| tx.close_channel()).detach(); }); rx } pub fn events( &mut self, entity: &Model, ) -> futures::channel::mpsc::UnboundedReceiver where T::Event: 'static + Clone, { let (tx, rx) = futures::channel::mpsc::unbounded(); entity .update(self, |_, cx: &mut ModelContext| { cx.subscribe(entity, move |_model, _handle, event, _cx| { let _ = tx.unbounded_send(event.clone()); }) }) .detach(); rx } pub async fn condition( &mut self, model: &Model, mut predicate: impl FnMut(&mut T, &mut ModelContext) -> bool, ) { let timer = self.executor().timer(Duration::from_secs(3)); let mut notifications = self.notifications(model); use futures::FutureExt as _; use smol::future::FutureExt as _; async { while notifications.next().await.is_some() { if model.update(self, &mut predicate) { return Ok(()); } } bail!("model dropped") } .race(timer.map(|_| Err(anyhow!("condition timed out")))) .await .unwrap(); } } impl Model { pub fn next_event(&self, cx: &mut TestAppContext) -> T::Event where T::Event: Send + Clone, { let (tx, mut rx) = futures::channel::mpsc::unbounded(); let _subscription = self.update(cx, |_, cx| { cx.subscribe(self, move |_, _, event, _| { tx.unbounded_send(event.clone()).ok(); }) }); cx.executor().run_until_parked(); rx.try_next() .expect("no event received") .expect("model was dropped") } }