1use crate::{
2 div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
3 BackgroundExecutor, Bounds, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent,
4 KeyDownEvent, Keystroke, Model, ModelContext, Pixels, PlatformWindow, Point, Render, Result,
5 Size, Task, TestDispatcher, TestPlatform, TestWindow, TestWindowHandlers, View, ViewContext,
6 VisualContext, WindowBounds, WindowContext, WindowHandle, WindowOptions,
7};
8use anyhow::{anyhow, bail};
9use futures::{Stream, StreamExt};
10use std::{future::Future, mem, ops::Deref, rc::Rc, sync::Arc, time::Duration};
11
12#[derive(Clone)]
13pub struct TestAppContext {
14 pub app: Rc<AppCell>,
15 pub background_executor: BackgroundExecutor,
16 pub foreground_executor: ForegroundExecutor,
17 pub dispatcher: TestDispatcher,
18 pub test_platform: Rc<TestPlatform>,
19}
20
21impl Context for TestAppContext {
22 type Result<T> = T;
23
24 fn build_model<T: 'static>(
25 &mut self,
26 build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
27 ) -> Self::Result<Model<T>>
28 where
29 T: 'static,
30 {
31 let mut app = self.app.borrow_mut();
32 app.build_model(build_model)
33 }
34
35 fn update_model<T: 'static, R>(
36 &mut self,
37 handle: &Model<T>,
38 update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
39 ) -> Self::Result<R> {
40 let mut app = self.app.borrow_mut();
41 app.update_model(handle, update)
42 }
43
44 fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
45 where
46 F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
47 {
48 let mut lock = self.app.borrow_mut();
49 lock.update_window(window, f)
50 }
51
52 fn read_model<T, R>(
53 &self,
54 handle: &Model<T>,
55 read: impl FnOnce(&T, &AppContext) -> R,
56 ) -> Self::Result<R>
57 where
58 T: 'static,
59 {
60 let app = self.app.borrow();
61 app.read_model(handle, read)
62 }
63
64 fn read_window<T, R>(
65 &self,
66 window: &WindowHandle<T>,
67 read: impl FnOnce(View<T>, &AppContext) -> R,
68 ) -> Result<R>
69 where
70 T: 'static,
71 {
72 let app = self.app.borrow();
73 app.read_window(window, read)
74 }
75}
76
77impl TestAppContext {
78 pub fn new(dispatcher: TestDispatcher) -> Self {
79 let arc_dispatcher = Arc::new(dispatcher.clone());
80 let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
81 let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
82 let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
83 let asset_source = Arc::new(());
84 let http_client = util::http::FakeHttpClient::with_404_response();
85
86 Self {
87 app: AppContext::new(platform.clone(), asset_source, http_client),
88 background_executor,
89 foreground_executor,
90 dispatcher: dispatcher.clone(),
91 test_platform: platform,
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().shutdown();
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: 'static + 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>, &mut VisualTestContext)
145 where
146 F: FnOnce(&mut ViewContext<V>) -> V,
147 V: 'static + 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 let cx = Box::new(VisualTestContext::from_window(*window.deref(), self));
154 // it might be nice to try and cleanup these at the end of each test.
155 (view, Box::leak(cx))
156 }
157
158 pub fn simulate_new_path_selection(
159 &self,
160 select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
161 ) {
162 self.test_platform.simulate_new_path_selection(select_path);
163 }
164
165 pub fn simulate_prompt_answer(&self, button_ix: usize) {
166 self.test_platform.simulate_prompt_answer(button_ix);
167 }
168
169 pub fn has_pending_prompt(&self) -> bool {
170 self.test_platform.has_pending_prompt()
171 }
172
173 pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {
174 let (mut handlers, scale_factor) = self
175 .app
176 .borrow_mut()
177 .update_window(window_handle, |_, cx| {
178 let platform_window = cx.window.platform_window.as_test().unwrap();
179 let scale_factor = platform_window.scale_factor();
180 match &mut platform_window.bounds {
181 WindowBounds::Fullscreen | WindowBounds::Maximized => {
182 platform_window.bounds = WindowBounds::Fixed(Bounds {
183 origin: Point::default(),
184 size: size.map(|pixels| f64::from(pixels).into()),
185 });
186 }
187 WindowBounds::Fixed(bounds) => {
188 bounds.size = size.map(|pixels| f64::from(pixels).into());
189 }
190 }
191
192 (
193 mem::take(&mut platform_window.handlers.lock().resize),
194 scale_factor,
195 )
196 })
197 .unwrap();
198
199 for handler in &mut handlers {
200 handler(size, scale_factor);
201 }
202
203 self.app
204 .borrow_mut()
205 .update_window(window_handle, |_, cx| {
206 let platform_window = cx.window.platform_window.as_test().unwrap();
207 platform_window.handlers.lock().resize = handlers;
208 })
209 .unwrap();
210 }
211
212 pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
213 where
214 Fut: Future<Output = R> + 'static,
215 R: 'static,
216 {
217 self.foreground_executor.spawn(f(self.to_async()))
218 }
219
220 pub fn has_global<G: 'static>(&self) -> bool {
221 let app = self.app.borrow();
222 app.has_global::<G>()
223 }
224
225 pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
226 let app = self.app.borrow();
227 read(app.global(), &app)
228 }
229
230 pub fn try_read_global<G: 'static, R>(
231 &self,
232 read: impl FnOnce(&G, &AppContext) -> R,
233 ) -> Option<R> {
234 let lock = self.app.borrow();
235 Some(read(lock.try_global()?, &lock))
236 }
237
238 pub fn set_global<G: 'static>(&mut self, global: G) {
239 let mut lock = self.app.borrow_mut();
240 lock.set_global(global);
241 }
242
243 pub fn update_global<G: 'static, R>(
244 &mut self,
245 update: impl FnOnce(&mut G, &mut AppContext) -> R,
246 ) -> R {
247 let mut lock = self.app.borrow_mut();
248 lock.update_global(update)
249 }
250
251 pub fn to_async(&self) -> AsyncAppContext {
252 AsyncAppContext {
253 app: Rc::downgrade(&self.app),
254 background_executor: self.background_executor.clone(),
255 foreground_executor: self.foreground_executor.clone(),
256 }
257 }
258
259 pub fn dispatch_action<A>(&mut self, window: AnyWindowHandle, action: A)
260 where
261 A: Action,
262 {
263 window
264 .update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
265 .unwrap();
266
267 self.background_executor.run_until_parked()
268 }
269
270 /// simulate_keystrokes takes a space-separated list of keys to type.
271 /// cx.simulate_keystrokes("cmd-shift-p b k s p enter")
272 /// will run backspace on the current editor through the command palette.
273 pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) {
274 for keystroke in keystrokes
275 .split(" ")
276 .map(Keystroke::parse)
277 .map(Result::unwrap)
278 {
279 self.dispatch_keystroke(window, keystroke.into(), false);
280 }
281
282 self.background_executor.run_until_parked()
283 }
284
285 /// simulate_input takes a string of text to type.
286 /// cx.simulate_input("abc")
287 /// will type abc into your current editor.
288 pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) {
289 for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) {
290 self.dispatch_keystroke(window, keystroke.into(), false);
291 }
292
293 self.background_executor.run_until_parked()
294 }
295
296 pub fn dispatch_keystroke(
297 &mut self,
298 window: AnyWindowHandle,
299 keystroke: Keystroke,
300 is_held: bool,
301 ) {
302 let keystroke2 = keystroke.clone();
303 let handled = window
304 .update(self, |_, cx| {
305 cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held }))
306 })
307 .is_ok_and(|handled| handled);
308 if handled {
309 return;
310 }
311
312 let input_handler = self.update_test_window(window, |window| window.input_handler.clone());
313 let Some(input_handler) = input_handler else {
314 panic!(
315 "dispatch_keystroke {:?} failed to dispatch action or input",
316 &keystroke2
317 );
318 };
319 let text = keystroke2.ime_key.unwrap_or(keystroke2.key);
320 input_handler.lock().replace_text_in_range(None, &text);
321 }
322
323 pub fn update_test_window<R>(
324 &mut self,
325 window: AnyWindowHandle,
326 f: impl FnOnce(&mut TestWindow) -> R,
327 ) -> R {
328 window
329 .update(self, |_, cx| {
330 f(cx.window
331 .platform_window
332 .as_any_mut()
333 .downcast_mut::<TestWindow>()
334 .unwrap())
335 })
336 .unwrap()
337 }
338
339 pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
340 let (tx, rx) = futures::channel::mpsc::unbounded();
341 self.update(|cx| {
342 cx.observe(entity, {
343 let tx = tx.clone();
344 move |_, _| {
345 let _ = tx.unbounded_send(());
346 }
347 })
348 .detach();
349 cx.observe_release(entity, move |_, _| tx.close_channel())
350 .detach()
351 });
352 rx
353 }
354
355 pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
356 &mut self,
357 entity: &Model<T>,
358 ) -> futures::channel::mpsc::UnboundedReceiver<Evt>
359 where
360 Evt: 'static + Clone,
361 {
362 let (tx, rx) = futures::channel::mpsc::unbounded();
363 entity
364 .update(self, |_, cx: &mut ModelContext<T>| {
365 cx.subscribe(entity, move |_model, _handle, event, _cx| {
366 let _ = tx.unbounded_send(event.clone());
367 })
368 })
369 .detach();
370 rx
371 }
372
373 pub async fn condition<T: 'static>(
374 &mut self,
375 model: &Model<T>,
376 mut predicate: impl FnMut(&mut T, &mut ModelContext<T>) -> bool,
377 ) {
378 let timer = self.executor().timer(Duration::from_secs(3));
379 let mut notifications = self.notifications(model);
380
381 use futures::FutureExt as _;
382 use smol::future::FutureExt as _;
383
384 async {
385 loop {
386 if model.update(self, &mut predicate) {
387 return Ok(());
388 }
389
390 if notifications.next().await.is_none() {
391 bail!("model dropped")
392 }
393 }
394 }
395 .race(timer.map(|_| Err(anyhow!("condition timed out"))))
396 .await
397 .unwrap();
398 }
399}
400
401impl<T: Send> Model<T> {
402 pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
403 where
404 Evt: Send + Clone + 'static,
405 T: EventEmitter<Evt>,
406 {
407 let (tx, mut rx) = futures::channel::mpsc::unbounded();
408 let _subscription = self.update(cx, |_, cx| {
409 cx.subscribe(self, move |_, _, event, _| {
410 tx.unbounded_send(event.clone()).ok();
411 })
412 });
413
414 // Run other tasks until the event is emitted.
415 loop {
416 match rx.try_next() {
417 Ok(Some(event)) => return event,
418 Ok(None) => panic!("model was dropped"),
419 Err(_) => {
420 if !cx.executor().tick() {
421 break;
422 }
423 }
424 }
425 }
426 panic!("no event received")
427 }
428}
429
430impl<V: 'static> View<V> {
431 pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
432 use postage::prelude::{Sink as _, Stream as _};
433
434 let (mut tx, mut rx) = postage::mpsc::channel(1);
435 let mut cx = cx.app.app.borrow_mut();
436 let subscription = cx.observe(self, move |_, _| {
437 tx.try_send(()).ok();
438 });
439
440 let duration = if std::env::var("CI").is_ok() {
441 Duration::from_secs(5)
442 } else {
443 Duration::from_secs(1)
444 };
445
446 async move {
447 let notification = crate::util::timeout(duration, rx.recv())
448 .await
449 .expect("next notification timed out");
450 drop(subscription);
451 notification.expect("model dropped while test was waiting for its next notification")
452 }
453 }
454}
455
456impl<V> View<V> {
457 pub fn condition<Evt>(
458 &self,
459 cx: &TestAppContext,
460 mut predicate: impl FnMut(&V, &AppContext) -> bool,
461 ) -> impl Future<Output = ()>
462 where
463 Evt: 'static,
464 V: EventEmitter<Evt>,
465 {
466 use postage::prelude::{Sink as _, Stream as _};
467
468 let (tx, mut rx) = postage::mpsc::channel(1024);
469 let timeout_duration = Duration::from_millis(100); //todo!() cx.condition_duration();
470
471 let mut cx = cx.app.borrow_mut();
472 let subscriptions = (
473 cx.observe(self, {
474 let mut tx = tx.clone();
475 move |_, _| {
476 tx.blocking_send(()).ok();
477 }
478 }),
479 cx.subscribe(self, {
480 let mut tx = tx.clone();
481 move |_, _: &Evt, _| {
482 tx.blocking_send(()).ok();
483 }
484 }),
485 );
486
487 let cx = cx.this.upgrade().unwrap();
488 let handle = self.downgrade();
489
490 async move {
491 crate::util::timeout(timeout_duration, async move {
492 loop {
493 {
494 let cx = cx.borrow();
495 let cx = &*cx;
496 if predicate(
497 handle
498 .upgrade()
499 .expect("view dropped with pending condition")
500 .read(cx),
501 cx,
502 ) {
503 break;
504 }
505 }
506
507 // todo!(start_waiting)
508 // cx.borrow().foreground_executor().start_waiting();
509 rx.recv()
510 .await
511 .expect("view dropped with pending condition");
512 // cx.borrow().foreground_executor().finish_waiting();
513 }
514 })
515 .await
516 .expect("condition timed out");
517 drop(subscriptions);
518 }
519 }
520}
521
522use derive_more::{Deref, DerefMut};
523#[derive(Deref, DerefMut)]
524pub struct VisualTestContext<'a> {
525 #[deref]
526 #[deref_mut]
527 cx: &'a mut TestAppContext,
528 window: AnyWindowHandle,
529}
530
531impl<'a> VisualTestContext<'a> {
532 pub fn from_window(window: AnyWindowHandle, cx: &'a mut TestAppContext) -> Self {
533 Self { cx, window }
534 }
535
536 pub fn run_until_parked(&self) {
537 self.cx.background_executor.run_until_parked();
538 }
539
540 pub fn dispatch_action<A>(&mut self, action: A)
541 where
542 A: Action,
543 {
544 self.cx.dispatch_action(self.window, action)
545 }
546
547 pub fn window_title(&mut self) -> Option<String> {
548 self.cx
549 .update_window(self.window, |_, cx| {
550 cx.window
551 .platform_window
552 .as_test()
553 .unwrap()
554 .window_title
555 .clone()
556 })
557 .unwrap()
558 }
559
560 pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
561 self.cx.simulate_keystrokes(self.window, keystrokes)
562 }
563
564 pub fn simulate_input(&mut self, input: &str) {
565 self.cx.simulate_input(self.window, input)
566 }
567
568 pub fn simulate_activation(&mut self) {
569 self.simulate_window_events(&mut |handlers| {
570 handlers
571 .active_status_change
572 .iter_mut()
573 .for_each(|f| f(true));
574 })
575 }
576
577 pub fn simulate_deactivation(&mut self) {
578 self.simulate_window_events(&mut |handlers| {
579 handlers
580 .active_status_change
581 .iter_mut()
582 .for_each(|f| f(false));
583 })
584 }
585
586 fn simulate_window_events(&mut self, f: &mut dyn FnMut(&mut TestWindowHandlers)) {
587 let handlers = self
588 .cx
589 .update_window(self.window, |_, cx| {
590 cx.window
591 .platform_window
592 .as_test()
593 .unwrap()
594 .handlers
595 .clone()
596 })
597 .unwrap();
598 f(&mut *handlers.lock());
599 }
600}
601
602impl<'a> Context for VisualTestContext<'a> {
603 type Result<T> = <TestAppContext as Context>::Result<T>;
604
605 fn build_model<T: 'static>(
606 &mut self,
607 build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
608 ) -> Self::Result<Model<T>> {
609 self.cx.build_model(build_model)
610 }
611
612 fn update_model<T, R>(
613 &mut self,
614 handle: &Model<T>,
615 update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
616 ) -> Self::Result<R>
617 where
618 T: 'static,
619 {
620 self.cx.update_model(handle, update)
621 }
622
623 fn read_model<T, R>(
624 &self,
625 handle: &Model<T>,
626 read: impl FnOnce(&T, &AppContext) -> R,
627 ) -> Self::Result<R>
628 where
629 T: 'static,
630 {
631 self.cx.read_model(handle, read)
632 }
633
634 fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
635 where
636 F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
637 {
638 self.cx.update_window(window, f)
639 }
640
641 fn read_window<T, R>(
642 &self,
643 window: &WindowHandle<T>,
644 read: impl FnOnce(View<T>, &AppContext) -> R,
645 ) -> Result<R>
646 where
647 T: 'static,
648 {
649 self.cx.read_window(window, read)
650 }
651}
652
653impl<'a> VisualContext for VisualTestContext<'a> {
654 fn build_view<V>(
655 &mut self,
656 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
657 ) -> Self::Result<View<V>>
658 where
659 V: 'static + Render,
660 {
661 self.window
662 .update(self.cx, |_, cx| cx.build_view(build_view))
663 .unwrap()
664 }
665
666 fn update_view<V: 'static, R>(
667 &mut self,
668 view: &View<V>,
669 update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
670 ) -> Self::Result<R> {
671 self.window
672 .update(self.cx, |_, cx| cx.update_view(view, update))
673 .unwrap()
674 }
675
676 fn replace_root_view<V>(
677 &mut self,
678 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
679 ) -> Self::Result<View<V>>
680 where
681 V: 'static + Render,
682 {
683 self.window
684 .update(self.cx, |_, cx| cx.replace_root_view(build_view))
685 .unwrap()
686 }
687
688 fn focus_view<V: crate::FocusableView>(&mut self, view: &View<V>) -> Self::Result<()> {
689 self.window
690 .update(self.cx, |_, cx| {
691 view.read(cx).focus_handle(cx).clone().focus(cx)
692 })
693 .unwrap()
694 }
695
696 fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
697 where
698 V: crate::ManagedView,
699 {
700 self.window
701 .update(self.cx, |_, cx| {
702 view.update(cx, |_, cx| cx.emit(crate::DismissEvent))
703 })
704 .unwrap()
705 }
706}
707
708impl AnyWindowHandle {
709 pub fn build_view<V: Render + 'static>(
710 &self,
711 cx: &mut TestAppContext,
712 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
713 ) -> View<V> {
714 self.update(cx, |_, cx| cx.build_view(build_view)).unwrap()
715 }
716}
717
718pub struct EmptyView {}
719
720impl Render for EmptyView {
721 type Element = Div;
722
723 fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> Self::Element {
724 div()
725 }
726}