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}