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