1use crate::{
2 AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, Context,
3 EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model, ModelContext,
4 Render, Result, Task, TestDispatcher, TestPlatform, View, ViewContext, VisualContext,
5 WindowContext, WindowHandle, WindowOptions,
6};
7use anyhow::{anyhow, bail};
8use futures::{Stream, StreamExt};
9use std::{future::Future, ops::Deref, 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 fn read_window<T, R>(
63 &self,
64 window: &WindowHandle<T>,
65 read: impl FnOnce(View<T>, &AppContext) -> R,
66 ) -> Result<R>
67 where
68 T: 'static,
69 {
70 let app = self.app.borrow();
71 app.read_window(window, read)
72 }
73}
74
75impl TestAppContext {
76 pub fn new(dispatcher: TestDispatcher) -> Self {
77 let arc_dispatcher = Arc::new(dispatcher.clone());
78 let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
79 let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
80 let platform = Rc::new(TestPlatform::new(
81 background_executor.clone(),
82 foreground_executor.clone(),
83 ));
84 let asset_source = Arc::new(());
85 let http_client = util::http::FakeHttpClient::with_404_response();
86 Self {
87 app: AppContext::new(platform, asset_source, http_client),
88 background_executor,
89 foreground_executor,
90 dispatcher: dispatcher.clone(),
91 }
92 }
93
94 pub fn new_app(&self) -> TestAppContext {
95 Self::new(self.dispatcher.clone())
96 }
97
98 pub fn quit(&self) {
99 self.app.borrow_mut().quit();
100 }
101
102 pub fn refresh(&mut self) -> Result<()> {
103 let mut app = self.app.borrow_mut();
104 app.refresh();
105 Ok(())
106 }
107
108 pub fn executor(&self) -> BackgroundExecutor {
109 self.background_executor.clone()
110 }
111
112 pub fn foreground_executor(&self) -> &ForegroundExecutor {
113 &self.foreground_executor
114 }
115
116 pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {
117 let mut cx = self.app.borrow_mut();
118 cx.update(f)
119 }
120
121 pub fn read<R>(&self, f: impl FnOnce(&AppContext) -> R) -> R {
122 let cx = self.app.borrow();
123 f(&*cx)
124 }
125
126 pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
127 where
128 F: FnOnce(&mut ViewContext<V>) -> V,
129 V: Render,
130 {
131 let mut cx = self.app.borrow_mut();
132 cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window))
133 }
134
135 pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, VisualTestContext)
136 where
137 F: FnOnce(&mut ViewContext<V>) -> V,
138 V: Render,
139 {
140 let mut cx = self.app.borrow_mut();
141 let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window));
142 drop(cx);
143 let view = window.root_view(self).unwrap();
144 (view, VisualTestContext::from_window(*window.deref(), self))
145 }
146
147 pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
148 where
149 Fut: Future<Output = R> + 'static,
150 R: 'static,
151 {
152 self.foreground_executor.spawn(f(self.to_async()))
153 }
154
155 pub fn has_global<G: 'static>(&self) -> bool {
156 let app = self.app.borrow();
157 app.has_global::<G>()
158 }
159
160 pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
161 let app = self.app.borrow();
162 read(app.global(), &app)
163 }
164
165 pub fn try_read_global<G: 'static, R>(
166 &self,
167 read: impl FnOnce(&G, &AppContext) -> R,
168 ) -> Option<R> {
169 let lock = self.app.borrow();
170 Some(read(lock.try_global()?, &lock))
171 }
172
173 pub fn set_global<G: 'static>(&mut self, global: G) {
174 let mut lock = self.app.borrow_mut();
175 lock.set_global(global);
176 }
177
178 pub fn update_global<G: 'static, R>(
179 &mut self,
180 update: impl FnOnce(&mut G, &mut AppContext) -> R,
181 ) -> R {
182 let mut lock = self.app.borrow_mut();
183 lock.update_global(update)
184 }
185
186 pub fn to_async(&self) -> AsyncAppContext {
187 AsyncAppContext {
188 app: Rc::downgrade(&self.app),
189 background_executor: self.background_executor.clone(),
190 foreground_executor: self.foreground_executor.clone(),
191 }
192 }
193
194 pub fn dispatch_keystroke(
195 &mut self,
196 window: AnyWindowHandle,
197 keystroke: Keystroke,
198 is_held: bool,
199 ) {
200 let handled = window
201 .update(self, |_, cx| {
202 cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held }))
203 })
204 .is_ok_and(|handled| handled);
205
206 if !handled {
207 // todo!() simluate input here
208 }
209 }
210
211 pub fn notifications<T: 'static>(&mut self, entity: &Model<T>) -> impl Stream<Item = ()> {
212 let (tx, rx) = futures::channel::mpsc::unbounded();
213
214 entity.update(self, move |_, cx: &mut ModelContext<T>| {
215 cx.observe(entity, {
216 let tx = tx.clone();
217 move |_, _, _| {
218 let _ = tx.unbounded_send(());
219 }
220 })
221 .detach();
222
223 cx.on_release(move |_, _| tx.close_channel()).detach();
224 });
225
226 rx
227 }
228
229 pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
230 &mut self,
231 entity: &Model<T>,
232 ) -> futures::channel::mpsc::UnboundedReceiver<Evt>
233 where
234 Evt: 'static + Clone,
235 {
236 let (tx, rx) = futures::channel::mpsc::unbounded();
237 entity
238 .update(self, |_, cx: &mut ModelContext<T>| {
239 cx.subscribe(entity, move |_model, _handle, event, _cx| {
240 let _ = tx.unbounded_send(event.clone());
241 })
242 })
243 .detach();
244 rx
245 }
246
247 pub async fn condition<T: 'static>(
248 &mut self,
249 model: &Model<T>,
250 mut predicate: impl FnMut(&mut T, &mut ModelContext<T>) -> bool,
251 ) {
252 let timer = self.executor().timer(Duration::from_secs(3));
253 let mut notifications = self.notifications(model);
254
255 use futures::FutureExt as _;
256 use smol::future::FutureExt as _;
257
258 async {
259 while notifications.next().await.is_some() {
260 if model.update(self, &mut predicate) {
261 return Ok(());
262 }
263 }
264 bail!("model dropped")
265 }
266 .race(timer.map(|_| Err(anyhow!("condition timed out"))))
267 .await
268 .unwrap();
269 }
270}
271
272impl<T: Send> Model<T> {
273 pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
274 where
275 Evt: Send + Clone + 'static,
276 T: EventEmitter<Evt>,
277 {
278 let (tx, mut rx) = futures::channel::mpsc::unbounded();
279 let _subscription = self.update(cx, |_, cx| {
280 cx.subscribe(self, move |_, _, event, _| {
281 tx.unbounded_send(event.clone()).ok();
282 })
283 });
284
285 cx.executor().run_until_parked();
286 rx.try_next()
287 .expect("no event received")
288 .expect("model was dropped")
289 }
290}
291
292impl<V> View<V> {
293 pub fn condition<Evt>(
294 &self,
295 cx: &TestAppContext,
296 mut predicate: impl FnMut(&V, &AppContext) -> bool,
297 ) -> impl Future<Output = ()>
298 where
299 Evt: 'static,
300 V: EventEmitter<Evt>,
301 {
302 use postage::prelude::{Sink as _, Stream as _};
303
304 let (tx, mut rx) = postage::mpsc::channel(1024);
305 let timeout_duration = Duration::from_millis(100); //todo!() cx.condition_duration();
306
307 let mut cx = cx.app.borrow_mut();
308 let subscriptions = (
309 cx.observe(self, {
310 let mut tx = tx.clone();
311 move |_, _| {
312 tx.blocking_send(()).ok();
313 }
314 }),
315 cx.subscribe(self, {
316 let mut tx = tx.clone();
317 move |_, _: &Evt, _| {
318 tx.blocking_send(()).ok();
319 }
320 }),
321 );
322
323 let cx = cx.this.upgrade().unwrap();
324 let handle = self.downgrade();
325
326 async move {
327 crate::util::timeout(timeout_duration, async move {
328 loop {
329 {
330 let cx = cx.borrow();
331 let cx = &*cx;
332 if predicate(
333 handle
334 .upgrade()
335 .expect("view dropped with pending condition")
336 .read(cx),
337 cx,
338 ) {
339 break;
340 }
341 }
342
343 // todo!(start_waiting)
344 // cx.borrow().foreground_executor().start_waiting();
345 rx.recv()
346 .await
347 .expect("view dropped with pending condition");
348 // cx.borrow().foreground_executor().finish_waiting();
349 }
350 })
351 .await
352 .expect("condition timed out");
353 drop(subscriptions);
354 }
355 }
356}
357
358use derive_more::{Deref, DerefMut};
359#[derive(Deref, DerefMut)]
360pub struct VisualTestContext<'a> {
361 #[deref]
362 #[deref_mut]
363 cx: &'a mut TestAppContext,
364 window: AnyWindowHandle,
365}
366
367impl<'a> VisualTestContext<'a> {
368 pub fn from_window(window: AnyWindowHandle, cx: &'a mut TestAppContext) -> Self {
369 Self { cx, window }
370 }
371}
372
373impl<'a> Context for VisualTestContext<'a> {
374 type Result<T> = <TestAppContext as Context>::Result<T>;
375
376 fn build_model<T: 'static>(
377 &mut self,
378 build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
379 ) -> Self::Result<Model<T>> {
380 self.cx.build_model(build_model)
381 }
382
383 fn update_model<T, R>(
384 &mut self,
385 handle: &Model<T>,
386 update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
387 ) -> Self::Result<R>
388 where
389 T: 'static,
390 {
391 self.cx.update_model(handle, update)
392 }
393
394 fn read_model<T, R>(
395 &self,
396 handle: &Model<T>,
397 read: impl FnOnce(&T, &AppContext) -> R,
398 ) -> Self::Result<R>
399 where
400 T: 'static,
401 {
402 self.cx.read_model(handle, read)
403 }
404
405 fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
406 where
407 F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
408 {
409 self.cx.update_window(window, f)
410 }
411
412 fn read_window<T, R>(
413 &self,
414 window: &WindowHandle<T>,
415 read: impl FnOnce(View<T>, &AppContext) -> R,
416 ) -> Result<R>
417 where
418 T: 'static,
419 {
420 self.cx.read_window(window, read)
421 }
422}
423
424impl<'a> VisualContext for VisualTestContext<'a> {
425 fn build_view<V>(
426 &mut self,
427 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
428 ) -> Self::Result<View<V>>
429 where
430 V: 'static + Render,
431 {
432 self.window
433 .update(self.cx, |_, cx| cx.build_view(build_view))
434 .unwrap()
435 }
436
437 fn update_view<V: 'static, R>(
438 &mut self,
439 view: &View<V>,
440 update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
441 ) -> Self::Result<R> {
442 self.window
443 .update(self.cx, |_, cx| cx.update_view(view, update))
444 .unwrap()
445 }
446
447 fn replace_root_view<V>(
448 &mut self,
449 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
450 ) -> Self::Result<View<V>>
451 where
452 V: Render,
453 {
454 self.window
455 .update(self.cx, |_, cx| cx.replace_root_view(build_view))
456 .unwrap()
457 }
458}