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 pub test_platform: Rc<TestPlatform>,
18}
19
20impl Context for TestAppContext {
21 type Result<T> = T;
22
23 fn build_model<T: 'static>(
24 &mut self,
25 build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
26 ) -> Self::Result<Model<T>>
27 where
28 T: 'static,
29 {
30 let mut app = self.app.borrow_mut();
31 app.build_model(build_model)
32 }
33
34 fn update_model<T: 'static, R>(
35 &mut self,
36 handle: &Model<T>,
37 update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
38 ) -> Self::Result<R> {
39 let mut app = self.app.borrow_mut();
40 app.update_model(handle, update)
41 }
42
43 fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
44 where
45 F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
46 {
47 let mut lock = self.app.borrow_mut();
48 lock.update_window(window, f)
49 }
50
51 fn read_model<T, R>(
52 &self,
53 handle: &Model<T>,
54 read: impl FnOnce(&T, &AppContext) -> R,
55 ) -> Self::Result<R>
56 where
57 T: 'static,
58 {
59 let app = self.app.borrow();
60 app.read_model(handle, read)
61 }
62
63 fn read_window<T, R>(
64 &self,
65 window: &WindowHandle<T>,
66 read: impl FnOnce(View<T>, &AppContext) -> R,
67 ) -> Result<R>
68 where
69 T: 'static,
70 {
71 let app = self.app.borrow();
72 app.read_window(window, read)
73 }
74}
75
76impl TestAppContext {
77 pub fn new(dispatcher: TestDispatcher) -> Self {
78 let arc_dispatcher = Arc::new(dispatcher.clone());
79 let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
80 let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
81 let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
82 let asset_source = Arc::new(());
83 let http_client = util::http::FakeHttpClient::with_404_response();
84
85 Self {
86 app: AppContext::new(platform.clone(), asset_source, http_client),
87 background_executor,
88 foreground_executor,
89 dispatcher: dispatcher.clone(),
90 test_platform: platform,
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().shutdown();
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_empty_window(&mut self) -> AnyWindowHandle {
136 let mut cx = self.app.borrow_mut();
137 cx.open_window(WindowOptions::default(), |cx| {
138 cx.build_view(|_| EmptyView {})
139 })
140 .any_handle
141 }
142
143 pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, VisualTestContext)
144 where
145 F: FnOnce(&mut ViewContext<V>) -> V,
146 V: Render,
147 {
148 let mut cx = self.app.borrow_mut();
149 let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window));
150 drop(cx);
151 let view = window.root_view(self).unwrap();
152 (view, VisualTestContext::from_window(*window.deref(), self))
153 }
154
155 pub fn simulate_new_path_selection(
156 &self,
157 select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
158 ) {
159 self.test_platform.simulate_new_path_selection(select_path);
160 }
161
162 pub fn simulate_prompt_answer(&self, button_ix: usize) {
163 self.test_platform.simulate_prompt_answer(button_ix);
164 }
165
166 pub fn has_pending_prompt(&self) -> bool {
167 self.test_platform.has_pending_prompt()
168 }
169
170 pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
171 where
172 Fut: Future<Output = R> + 'static,
173 R: 'static,
174 {
175 self.foreground_executor.spawn(f(self.to_async()))
176 }
177
178 pub fn has_global<G: 'static>(&self) -> bool {
179 let app = self.app.borrow();
180 app.has_global::<G>()
181 }
182
183 pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
184 let app = self.app.borrow();
185 read(app.global(), &app)
186 }
187
188 pub fn try_read_global<G: 'static, R>(
189 &self,
190 read: impl FnOnce(&G, &AppContext) -> R,
191 ) -> Option<R> {
192 let lock = self.app.borrow();
193 Some(read(lock.try_global()?, &lock))
194 }
195
196 pub fn set_global<G: 'static>(&mut self, global: G) {
197 let mut lock = self.app.borrow_mut();
198 lock.set_global(global);
199 }
200
201 pub fn update_global<G: 'static, R>(
202 &mut self,
203 update: impl FnOnce(&mut G, &mut AppContext) -> R,
204 ) -> R {
205 let mut lock = self.app.borrow_mut();
206 lock.update_global(update)
207 }
208
209 pub fn to_async(&self) -> AsyncAppContext {
210 AsyncAppContext {
211 app: Rc::downgrade(&self.app),
212 background_executor: self.background_executor.clone(),
213 foreground_executor: self.foreground_executor.clone(),
214 }
215 }
216
217 pub fn dispatch_action<A>(&mut self, window: AnyWindowHandle, action: A)
218 where
219 A: Action,
220 {
221 window
222 .update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
223 .unwrap()
224 }
225
226 pub fn dispatch_keystroke(
227 &mut self,
228 window: AnyWindowHandle,
229 keystroke: Keystroke,
230 is_held: bool,
231 ) {
232 let handled = window
233 .update(self, |_, cx| {
234 cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held }))
235 })
236 .is_ok_and(|handled| handled);
237
238 if !handled {
239 // todo!() simluate input here
240 }
241 }
242
243 pub fn notifications<T: 'static>(&mut self, entity: &Model<T>) -> impl Stream<Item = ()> {
244 let (tx, rx) = futures::channel::mpsc::unbounded();
245
246 entity.update(self, move |_, cx: &mut ModelContext<T>| {
247 cx.observe(entity, {
248 let tx = tx.clone();
249 move |_, _, _| {
250 let _ = tx.unbounded_send(());
251 }
252 })
253 .detach();
254
255 cx.on_release(move |_, _| tx.close_channel()).detach();
256 });
257
258 rx
259 }
260
261 pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
262 &mut self,
263 entity: &Model<T>,
264 ) -> futures::channel::mpsc::UnboundedReceiver<Evt>
265 where
266 Evt: 'static + Clone,
267 {
268 let (tx, rx) = futures::channel::mpsc::unbounded();
269 entity
270 .update(self, |_, cx: &mut ModelContext<T>| {
271 cx.subscribe(entity, move |_model, _handle, event, _cx| {
272 let _ = tx.unbounded_send(event.clone());
273 })
274 })
275 .detach();
276 rx
277 }
278
279 pub async fn condition<T: 'static>(
280 &mut self,
281 model: &Model<T>,
282 mut predicate: impl FnMut(&mut T, &mut ModelContext<T>) -> bool,
283 ) {
284 let timer = self.executor().timer(Duration::from_secs(3));
285 let mut notifications = self.notifications(model);
286
287 use futures::FutureExt as _;
288 use smol::future::FutureExt as _;
289
290 async {
291 while notifications.next().await.is_some() {
292 if model.update(self, &mut predicate) {
293 return Ok(());
294 }
295 }
296 bail!("model dropped")
297 }
298 .race(timer.map(|_| Err(anyhow!("condition timed out"))))
299 .await
300 .unwrap();
301 }
302}
303
304impl<T: Send> Model<T> {
305 pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
306 where
307 Evt: Send + Clone + 'static,
308 T: EventEmitter<Evt>,
309 {
310 let (tx, mut rx) = futures::channel::mpsc::unbounded();
311 let _subscription = self.update(cx, |_, cx| {
312 cx.subscribe(self, move |_, _, event, _| {
313 tx.unbounded_send(event.clone()).ok();
314 })
315 });
316
317 cx.executor().run_until_parked();
318 rx.try_next()
319 .expect("no event received")
320 .expect("model was dropped")
321 }
322}
323
324impl<V> View<V> {
325 pub fn condition<Evt>(
326 &self,
327 cx: &TestAppContext,
328 mut predicate: impl FnMut(&V, &AppContext) -> bool,
329 ) -> impl Future<Output = ()>
330 where
331 Evt: 'static,
332 V: EventEmitter<Evt>,
333 {
334 use postage::prelude::{Sink as _, Stream as _};
335
336 let (tx, mut rx) = postage::mpsc::channel(1024);
337 let timeout_duration = Duration::from_millis(100); //todo!() cx.condition_duration();
338
339 let mut cx = cx.app.borrow_mut();
340 let subscriptions = (
341 cx.observe(self, {
342 let mut tx = tx.clone();
343 move |_, _| {
344 tx.blocking_send(()).ok();
345 }
346 }),
347 cx.subscribe(self, {
348 let mut tx = tx.clone();
349 move |_, _: &Evt, _| {
350 tx.blocking_send(()).ok();
351 }
352 }),
353 );
354
355 let cx = cx.this.upgrade().unwrap();
356 let handle = self.downgrade();
357
358 async move {
359 crate::util::timeout(timeout_duration, async move {
360 loop {
361 {
362 let cx = cx.borrow();
363 let cx = &*cx;
364 if predicate(
365 handle
366 .upgrade()
367 .expect("view dropped with pending condition")
368 .read(cx),
369 cx,
370 ) {
371 break;
372 }
373 }
374
375 // todo!(start_waiting)
376 // cx.borrow().foreground_executor().start_waiting();
377 rx.recv()
378 .await
379 .expect("view dropped with pending condition");
380 // cx.borrow().foreground_executor().finish_waiting();
381 }
382 })
383 .await
384 .expect("condition timed out");
385 drop(subscriptions);
386 }
387 }
388}
389
390use derive_more::{Deref, DerefMut};
391#[derive(Deref, DerefMut)]
392pub struct VisualTestContext<'a> {
393 #[deref]
394 #[deref_mut]
395 cx: &'a mut TestAppContext,
396 window: AnyWindowHandle,
397}
398
399impl<'a> VisualTestContext<'a> {
400 pub fn from_window(window: AnyWindowHandle, cx: &'a mut TestAppContext) -> Self {
401 Self { cx, window }
402 }
403
404 pub fn dispatch_action<A>(&mut self, action: A)
405 where
406 A: Action,
407 {
408 self.cx.dispatch_action(self.window, action)
409 }
410}
411
412impl<'a> Context for VisualTestContext<'a> {
413 type Result<T> = <TestAppContext as Context>::Result<T>;
414
415 fn build_model<T: 'static>(
416 &mut self,
417 build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
418 ) -> Self::Result<Model<T>> {
419 self.cx.build_model(build_model)
420 }
421
422 fn update_model<T, R>(
423 &mut self,
424 handle: &Model<T>,
425 update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
426 ) -> Self::Result<R>
427 where
428 T: 'static,
429 {
430 self.cx.update_model(handle, update)
431 }
432
433 fn read_model<T, R>(
434 &self,
435 handle: &Model<T>,
436 read: impl FnOnce(&T, &AppContext) -> R,
437 ) -> Self::Result<R>
438 where
439 T: 'static,
440 {
441 self.cx.read_model(handle, read)
442 }
443
444 fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
445 where
446 F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
447 {
448 self.cx.update_window(window, f)
449 }
450
451 fn read_window<T, R>(
452 &self,
453 window: &WindowHandle<T>,
454 read: impl FnOnce(View<T>, &AppContext) -> R,
455 ) -> Result<R>
456 where
457 T: 'static,
458 {
459 self.cx.read_window(window, read)
460 }
461}
462
463impl<'a> VisualContext for VisualTestContext<'a> {
464 fn build_view<V>(
465 &mut self,
466 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
467 ) -> Self::Result<View<V>>
468 where
469 V: 'static + Render,
470 {
471 self.window
472 .update(self.cx, |_, cx| cx.build_view(build_view))
473 .unwrap()
474 }
475
476 fn update_view<V: 'static, R>(
477 &mut self,
478 view: &View<V>,
479 update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
480 ) -> Self::Result<R> {
481 self.window
482 .update(self.cx, |_, cx| cx.update_view(view, update))
483 .unwrap()
484 }
485
486 fn replace_root_view<V>(
487 &mut self,
488 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
489 ) -> Self::Result<View<V>>
490 where
491 V: Render,
492 {
493 self.window
494 .update(self.cx, |_, cx| cx.replace_root_view(build_view))
495 .unwrap()
496 }
497}
498
499impl AnyWindowHandle {
500 pub fn build_view<V: Render + 'static>(
501 &self,
502 cx: &mut TestAppContext,
503 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
504 ) -> View<V> {
505 self.update(cx, |_, cx| cx.build_view(build_view)).unwrap()
506 }
507}
508
509pub struct EmptyView {}
510
511impl Render for EmptyView {
512 type Element = Div<Self>;
513
514 fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> Self::Element {
515 div()
516 }
517}