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