1use crate::{
2 Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
3 AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem, Context, Empty, Entity,
4 EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Model, ModelContext,
5 Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
6 Pixels, Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow,
7 TextSystem, View, ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,
8};
9use anyhow::{anyhow, bail};
10use futures::{Stream, StreamExt};
11use std::{cell::RefCell, future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration};
12
13/// A TestAppContext is provided to tests created with `#[gpui::test]`, it provides
14/// an implementation of `Context` with additional methods that are useful in tests.
15#[derive(Clone)]
16pub struct TestAppContext {
17 #[doc(hidden)]
18 pub app: Rc<AppCell>,
19 #[doc(hidden)]
20 pub background_executor: BackgroundExecutor,
21 #[doc(hidden)]
22 pub foreground_executor: ForegroundExecutor,
23 #[doc(hidden)]
24 pub dispatcher: TestDispatcher,
25 test_platform: Rc<TestPlatform>,
26 text_system: Arc<TextSystem>,
27 fn_name: Option<&'static str>,
28 on_quit: Rc<RefCell<Vec<Box<dyn FnOnce() + 'static>>>>,
29}
30
31impl Context for TestAppContext {
32 type Result<T> = T;
33
34 fn new_model<T: 'static>(
35 &mut self,
36 build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
37 ) -> Self::Result<Model<T>>
38 where
39 T: 'static,
40 {
41 let mut app = self.app.borrow_mut();
42 app.new_model(build_model)
43 }
44
45 fn update_model<T: 'static, R>(
46 &mut self,
47 handle: &Model<T>,
48 update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
49 ) -> Self::Result<R> {
50 let mut app = self.app.borrow_mut();
51 app.update_model(handle, update)
52 }
53
54 fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
55 where
56 F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
57 {
58 let mut lock = self.app.borrow_mut();
59 lock.update_window(window, f)
60 }
61
62 fn read_model<T, R>(
63 &self,
64 handle: &Model<T>,
65 read: impl FnOnce(&T, &AppContext) -> R,
66 ) -> Self::Result<R>
67 where
68 T: 'static,
69 {
70 let app = self.app.borrow();
71 app.read_model(handle, read)
72 }
73
74 fn read_window<T, R>(
75 &self,
76 window: &WindowHandle<T>,
77 read: impl FnOnce(View<T>, &AppContext) -> R,
78 ) -> Result<R>
79 where
80 T: 'static,
81 {
82 let app = self.app.borrow();
83 app.read_window(window, read)
84 }
85}
86
87impl TestAppContext {
88 /// Creates a new `TestAppContext`. Usually you can rely on `#[gpui::test]` to do this for you.
89 pub fn new(dispatcher: TestDispatcher, fn_name: Option<&'static str>) -> Self {
90 let arc_dispatcher = Arc::new(dispatcher.clone());
91 let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
92 let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
93 let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
94 let asset_source = Arc::new(());
95 let http_client = util::http::FakeHttpClient::with_404_response();
96 let text_system = Arc::new(TextSystem::new(platform.text_system()));
97
98 Self {
99 app: AppContext::new(platform.clone(), asset_source, http_client),
100 background_executor,
101 foreground_executor,
102 dispatcher: dispatcher.clone(),
103 test_platform: platform,
104 text_system,
105 fn_name,
106 on_quit: Rc::new(RefCell::new(Vec::default())),
107 }
108 }
109
110 /// The name of the test function that created this `TestAppContext`
111 pub fn test_function_name(&self) -> Option<&'static str> {
112 self.fn_name
113 }
114
115 /// Checks whether there have been any new path prompts received by the platform.
116 pub fn did_prompt_for_new_path(&self) -> bool {
117 self.test_platform.did_prompt_for_new_path()
118 }
119
120 /// returns a new `TestAppContext` re-using the same executors to interleave tasks.
121 pub fn new_app(&self) -> TestAppContext {
122 Self::new(self.dispatcher.clone(), self.fn_name)
123 }
124
125 /// Called by the test helper to end the test.
126 /// public so the macro can call it.
127 pub fn quit(&self) {
128 self.on_quit.borrow_mut().drain(..).for_each(|f| f());
129 self.app.borrow_mut().shutdown();
130 }
131
132 /// Register cleanup to run when the test ends.
133 pub fn on_quit(&mut self, f: impl FnOnce() + 'static) {
134 self.on_quit.borrow_mut().push(Box::new(f));
135 }
136
137 /// Schedules all windows to be redrawn on the next effect cycle.
138 pub fn refresh(&mut self) -> Result<()> {
139 let mut app = self.app.borrow_mut();
140 app.refresh();
141 Ok(())
142 }
143
144 /// Returns an executor (for running tasks in the background)
145 pub fn executor(&self) -> BackgroundExecutor {
146 self.background_executor.clone()
147 }
148
149 /// Returns an executor (for running tasks on the main thread)
150 pub fn foreground_executor(&self) -> &ForegroundExecutor {
151 &self.foreground_executor
152 }
153
154 /// Gives you an `&mut AppContext` for the duration of the closure
155 pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {
156 let mut cx = self.app.borrow_mut();
157 cx.update(f)
158 }
159
160 /// Gives you an `&AppContext` for the duration of the closure
161 pub fn read<R>(&self, f: impl FnOnce(&AppContext) -> R) -> R {
162 let cx = self.app.borrow();
163 f(&cx)
164 }
165
166 /// Adds a new window. The Window will always be backed by a `TestWindow` which
167 /// can be retrieved with `self.test_window(handle)`
168 pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
169 where
170 F: FnOnce(&mut ViewContext<V>) -> V,
171 V: 'static + Render,
172 {
173 let mut cx = self.app.borrow_mut();
174
175 // Some tests rely on the window size matching the bounds of the test display
176 let bounds = Bounds::maximized(&mut cx);
177 cx.open_window(
178 WindowOptions {
179 bounds: Some(bounds),
180 ..Default::default()
181 },
182 |cx| cx.new_view(build_window),
183 )
184 }
185
186 /// Adds a new window with no content.
187 pub fn add_empty_window(&mut self) -> &mut VisualTestContext {
188 let mut cx = self.app.borrow_mut();
189 let bounds = Bounds::maximized(&mut cx);
190 let window = cx.open_window(
191 WindowOptions {
192 bounds: Some(bounds),
193 ..Default::default()
194 },
195 |cx| cx.new_view(|_| Empty),
196 );
197 drop(cx);
198 let cx = VisualTestContext::from_window(*window.deref(), self).as_mut();
199 cx.run_until_parked();
200 cx
201 }
202
203 /// Adds a new window, and returns its root view and a `VisualTestContext` which can be used
204 /// as a `WindowContext` for the rest of the test. Typically you would shadow this context with
205 /// the returned one. `let (view, cx) = cx.add_window_view(...);`
206 pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext)
207 where
208 F: FnOnce(&mut ViewContext<V>) -> V,
209 V: 'static + Render,
210 {
211 let mut cx = self.app.borrow_mut();
212 let bounds = Bounds::maximized(&mut cx);
213 let window = cx.open_window(
214 WindowOptions {
215 bounds: Some(bounds),
216 ..Default::default()
217 },
218 |cx| cx.new_view(build_window),
219 );
220 drop(cx);
221 let view = window.root_view(self).unwrap();
222 let cx = VisualTestContext::from_window(*window.deref(), self).as_mut();
223 cx.run_until_parked();
224
225 // it might be nice to try and cleanup these at the end of each test.
226 (view, cx)
227 }
228
229 /// returns the TextSystem
230 pub fn text_system(&self) -> &Arc<TextSystem> {
231 &self.text_system
232 }
233
234 /// Simulates writing to the platform clipboard
235 pub fn write_to_clipboard(&self, item: ClipboardItem) {
236 self.test_platform.write_to_clipboard(item)
237 }
238
239 /// Simulates reading from the platform clipboard.
240 /// This will return the most recent value from `write_to_clipboard`.
241 pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
242 self.test_platform.read_from_clipboard()
243 }
244
245 /// Simulates choosing a File in the platform's "Open" dialog.
246 pub fn simulate_new_path_selection(
247 &self,
248 select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
249 ) {
250 self.test_platform.simulate_new_path_selection(select_path);
251 }
252
253 /// Simulates clicking a button in an platform-level alert dialog.
254 pub fn simulate_prompt_answer(&self, button_ix: usize) {
255 self.test_platform.simulate_prompt_answer(button_ix);
256 }
257
258 /// Returns true if there's an alert dialog open.
259 pub fn has_pending_prompt(&self) -> bool {
260 self.test_platform.has_pending_prompt()
261 }
262
263 /// All the urls that have been opened with cx.open_url() during this test.
264 pub fn opened_url(&self) -> Option<String> {
265 self.test_platform.opened_url.borrow().clone()
266 }
267
268 /// Simulates the user resizing the window to the new size.
269 pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {
270 self.test_window(window_handle).simulate_resize(size);
271 }
272
273 /// Returns all windows open in the test.
274 pub fn windows(&self) -> Vec<AnyWindowHandle> {
275 self.app.borrow().windows().clone()
276 }
277
278 /// Run the given task on the main thread.
279 pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
280 where
281 Fut: Future<Output = R> + 'static,
282 R: 'static,
283 {
284 self.foreground_executor.spawn(f(self.to_async()))
285 }
286
287 /// true if the given global is defined
288 pub fn has_global<G: Global>(&self) -> bool {
289 let app = self.app.borrow();
290 app.has_global::<G>()
291 }
292
293 /// runs the given closure with a reference to the global
294 /// panics if `has_global` would return false.
295 pub fn read_global<G: Global, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
296 let app = self.app.borrow();
297 read(app.global(), &app)
298 }
299
300 /// runs the given closure with a reference to the global (if set)
301 pub fn try_read_global<G: Global, R>(
302 &self,
303 read: impl FnOnce(&G, &AppContext) -> R,
304 ) -> Option<R> {
305 let lock = self.app.borrow();
306 Some(read(lock.try_global()?, &lock))
307 }
308
309 /// sets the global in this context.
310 pub fn set_global<G: Global>(&mut self, global: G) {
311 let mut lock = self.app.borrow_mut();
312 lock.set_global(global);
313 }
314
315 /// updates the global in this context. (panics if `has_global` would return false)
316 pub fn update_global<G: Global, R>(
317 &mut self,
318 update: impl FnOnce(&mut G, &mut AppContext) -> R,
319 ) -> R {
320 let mut lock = self.app.borrow_mut();
321 lock.update_global(update)
322 }
323
324 /// Returns an `AsyncAppContext` which can be used to run tasks that expect to be on a background
325 /// thread on the current thread in tests.
326 pub fn to_async(&self) -> AsyncAppContext {
327 AsyncAppContext {
328 app: Rc::downgrade(&self.app),
329 background_executor: self.background_executor.clone(),
330 foreground_executor: self.foreground_executor.clone(),
331 }
332 }
333
334 /// Wait until there are no more pending tasks.
335 pub fn run_until_parked(&mut self) {
336 self.background_executor.run_until_parked()
337 }
338
339 /// Simulate dispatching an action to the currently focused node in the window.
340 pub fn dispatch_action<A>(&mut self, window: AnyWindowHandle, action: A)
341 where
342 A: Action,
343 {
344 window
345 .update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
346 .unwrap();
347
348 self.background_executor.run_until_parked()
349 }
350
351 /// simulate_keystrokes takes a space-separated list of keys to type.
352 /// cx.simulate_keystrokes("cmd-shift-p b k s p enter")
353 /// in Zed, this will run backspace on the current editor through the command palette.
354 /// This will also run the background executor until it's parked.
355 pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) {
356 for keystroke in keystrokes
357 .split(' ')
358 .map(Keystroke::parse)
359 .map(Result::unwrap)
360 {
361 self.dispatch_keystroke(window, keystroke);
362 }
363
364 self.background_executor.run_until_parked()
365 }
366
367 /// simulate_input takes a string of text to type.
368 /// cx.simulate_input("abc")
369 /// will type abc into your current editor
370 /// This will also run the background executor until it's parked.
371 pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) {
372 for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) {
373 self.dispatch_keystroke(window, keystroke);
374 }
375
376 self.background_executor.run_until_parked()
377 }
378
379 /// dispatches a single Keystroke (see also `simulate_keystrokes` and `simulate_input`)
380 pub fn dispatch_keystroke(&mut self, window: AnyWindowHandle, keystroke: Keystroke) {
381 self.update_window(window, |_, cx| cx.dispatch_keystroke(keystroke))
382 .unwrap();
383 }
384
385 /// Returns the `TestWindow` backing the given handle.
386 pub(crate) fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
387 self.app
388 .borrow_mut()
389 .windows
390 .get_mut(window.id)
391 .unwrap()
392 .as_mut()
393 .unwrap()
394 .platform_window
395 .as_test()
396 .unwrap()
397 .clone()
398 }
399
400 /// Returns a stream of notifications whenever the View or Model is updated.
401 pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
402 let (tx, rx) = futures::channel::mpsc::unbounded();
403 self.update(|cx| {
404 cx.observe(entity, {
405 let tx = tx.clone();
406 move |_, _| {
407 let _ = tx.unbounded_send(());
408 }
409 })
410 .detach();
411 cx.observe_release(entity, move |_, _| tx.close_channel())
412 .detach()
413 });
414 rx
415 }
416
417 /// Retuens a stream of events emitted by the given Model.
418 pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
419 &mut self,
420 entity: &Model<T>,
421 ) -> futures::channel::mpsc::UnboundedReceiver<Evt>
422 where
423 Evt: 'static + Clone,
424 {
425 let (tx, rx) = futures::channel::mpsc::unbounded();
426 entity
427 .update(self, |_, cx: &mut ModelContext<T>| {
428 cx.subscribe(entity, move |_model, _handle, event, _cx| {
429 let _ = tx.unbounded_send(event.clone());
430 })
431 })
432 .detach();
433 rx
434 }
435
436 /// Runs until the given condition becomes true. (Prefer `run_until_parked` if you
437 /// don't need to jump in at a specific time).
438 pub async fn condition<T: 'static>(
439 &mut self,
440 model: &Model<T>,
441 mut predicate: impl FnMut(&mut T, &mut ModelContext<T>) -> bool,
442 ) {
443 let timer = self.executor().timer(Duration::from_secs(3));
444 let mut notifications = self.notifications(model);
445
446 use futures::FutureExt as _;
447 use smol::future::FutureExt as _;
448
449 async {
450 loop {
451 if model.update(self, &mut predicate) {
452 return Ok(());
453 }
454
455 if notifications.next().await.is_none() {
456 bail!("model dropped")
457 }
458 }
459 }
460 .race(timer.map(|_| Err(anyhow!("condition timed out"))))
461 .await
462 .unwrap();
463 }
464}
465
466impl<T: Send> Model<T> {
467 /// Block until the next event is emitted by the model, then return it.
468 pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
469 where
470 Evt: Send + Clone + 'static,
471 T: EventEmitter<Evt>,
472 {
473 let (tx, mut rx) = futures::channel::mpsc::unbounded();
474 let _subscription = self.update(cx, |_, cx| {
475 cx.subscribe(self, move |_, _, event, _| {
476 tx.unbounded_send(event.clone()).ok();
477 })
478 });
479
480 // Run other tasks until the event is emitted.
481 loop {
482 match rx.try_next() {
483 Ok(Some(event)) => return event,
484 Ok(None) => panic!("model was dropped"),
485 Err(_) => {
486 if !cx.executor().tick() {
487 break;
488 }
489 }
490 }
491 }
492 panic!("no event received")
493 }
494}
495
496impl<V: 'static> View<V> {
497 /// Returns a future that resolves when the view is next updated.
498 pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
499 use postage::prelude::{Sink as _, Stream as _};
500
501 let (mut tx, mut rx) = postage::mpsc::channel(1);
502 let mut cx = cx.app.app.borrow_mut();
503 let subscription = cx.observe(self, move |_, _| {
504 tx.try_send(()).ok();
505 });
506
507 let duration = if std::env::var("CI").is_ok() {
508 Duration::from_secs(5)
509 } else {
510 Duration::from_secs(1)
511 };
512
513 async move {
514 let notification = crate::util::timeout(duration, rx.recv())
515 .await
516 .expect("next notification timed out");
517 drop(subscription);
518 notification.expect("model dropped while test was waiting for its next notification")
519 }
520 }
521}
522
523impl<V> View<V> {
524 /// Returns a future that resolves when the condition becomes true.
525 pub fn condition<Evt>(
526 &self,
527 cx: &TestAppContext,
528 mut predicate: impl FnMut(&V, &AppContext) -> bool,
529 ) -> impl Future<Output = ()>
530 where
531 Evt: 'static,
532 V: EventEmitter<Evt>,
533 {
534 use postage::prelude::{Sink as _, Stream as _};
535
536 let (tx, mut rx) = postage::mpsc::channel(1024);
537 let timeout_duration = Duration::from_millis(100);
538
539 let mut cx = cx.app.borrow_mut();
540 let subscriptions = (
541 cx.observe(self, {
542 let mut tx = tx.clone();
543 move |_, _| {
544 tx.blocking_send(()).ok();
545 }
546 }),
547 cx.subscribe(self, {
548 let mut tx = tx.clone();
549 move |_, _: &Evt, _| {
550 tx.blocking_send(()).ok();
551 }
552 }),
553 );
554
555 let cx = cx.this.upgrade().unwrap();
556 let handle = self.downgrade();
557
558 async move {
559 crate::util::timeout(timeout_duration, async move {
560 loop {
561 {
562 let cx = cx.borrow();
563 let cx = &*cx;
564 if predicate(
565 handle
566 .upgrade()
567 .expect("view dropped with pending condition")
568 .read(cx),
569 cx,
570 ) {
571 break;
572 }
573 }
574
575 cx.borrow().background_executor().start_waiting();
576 rx.recv()
577 .await
578 .expect("view dropped with pending condition");
579 cx.borrow().background_executor().finish_waiting();
580 }
581 })
582 .await
583 .expect("condition timed out");
584 drop(subscriptions);
585 }
586 }
587}
588
589use derive_more::{Deref, DerefMut};
590#[derive(Deref, DerefMut, Clone)]
591/// A VisualTestContext is the test-equivalent of a `WindowContext`. It allows you to
592/// run window-specific test code.
593pub struct VisualTestContext {
594 #[deref]
595 #[deref_mut]
596 /// cx is the original TestAppContext (you can more easily access this using Deref)
597 pub cx: TestAppContext,
598 window: AnyWindowHandle,
599}
600
601impl VisualTestContext {
602 /// Get the underlying window handle underlying this context.
603 pub fn handle(&self) -> AnyWindowHandle {
604 self.window
605 }
606
607 /// Provides the `WindowContext` for the duration of the closure.
608 pub fn update<R>(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R {
609 self.cx.update_window(self.window, |_, cx| f(cx)).unwrap()
610 }
611
612 /// Creates a new VisualTestContext. You would typically shadow the passed in
613 /// TestAppContext with this, as this is typically more useful.
614 /// `let cx = VisualTestContext::from_window(window, cx);`
615 pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self {
616 Self {
617 cx: cx.clone(),
618 window,
619 }
620 }
621
622 /// Wait until there are no more pending tasks.
623 pub fn run_until_parked(&self) {
624 self.cx.background_executor.run_until_parked();
625 }
626
627 /// Dispatch the action to the currently focused node.
628 pub fn dispatch_action<A>(&mut self, action: A)
629 where
630 A: Action,
631 {
632 self.cx.dispatch_action(self.window, action)
633 }
634
635 /// Read the title off the window (set by `WindowContext#set_window_title`)
636 pub fn window_title(&mut self) -> Option<String> {
637 self.cx.test_window(self.window).0.lock().title.clone()
638 }
639
640 /// Simulate a sequence of keystrokes `cx.simulate_keystrokes("cmd-p escape")`
641 /// Automatically runs until parked.
642 pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
643 self.cx.simulate_keystrokes(self.window, keystrokes)
644 }
645
646 /// Simulate typing text `cx.simulate_input("hello")`
647 /// Automatically runs until parked.
648 pub fn simulate_input(&mut self, input: &str) {
649 self.cx.simulate_input(self.window, input)
650 }
651
652 /// Simulate a mouse move event to the given point
653 pub fn simulate_mouse_move(&mut self, position: Point<Pixels>, modifiers: Modifiers) {
654 self.simulate_event(MouseMoveEvent {
655 position,
656 modifiers,
657 pressed_button: None,
658 })
659 }
660
661 /// Simulate a primary mouse click at the given point
662 pub fn simulate_click(&mut self, position: Point<Pixels>, modifiers: Modifiers) {
663 self.simulate_event(MouseDownEvent {
664 position,
665 modifiers,
666 button: MouseButton::Left,
667 click_count: 1,
668 });
669 self.simulate_event(MouseUpEvent {
670 position,
671 modifiers,
672 button: MouseButton::Left,
673 click_count: 1,
674 });
675 }
676
677 /// Simulate a modifiers changed event
678 pub fn simulate_modifiers_change(&mut self, modifiers: Modifiers) {
679 self.simulate_event(ModifiersChangedEvent { modifiers })
680 }
681
682 /// Simulates the user resizing the window to the new size.
683 pub fn simulate_resize(&self, size: Size<Pixels>) {
684 self.simulate_window_resize(self.window, size)
685 }
686
687 /// debug_bounds returns the bounds of the element with the given selector.
688 pub fn debug_bounds(&mut self, selector: &'static str) -> Option<Bounds<Pixels>> {
689 self.update(|cx| cx.window.rendered_frame.debug_bounds.get(selector).copied())
690 }
691
692 /// Draw an element to the window. Useful for simulating events or actions
693 pub fn draw(
694 &mut self,
695 origin: Point<Pixels>,
696 space: Size<AvailableSpace>,
697 f: impl FnOnce(&mut WindowContext) -> AnyElement,
698 ) {
699 self.update(|cx| {
700 cx.with_element_context(|cx| {
701 let mut element = f(cx);
702 element.layout(origin, space, cx);
703 element.paint(cx);
704 });
705
706 cx.refresh();
707 })
708 }
709
710 /// Simulate an event from the platform, e.g. a SrollWheelEvent
711 /// Make sure you've called [VisualTestContext::draw] first!
712 pub fn simulate_event<E: InputEvent>(&mut self, event: E) {
713 self.test_window(self.window)
714 .simulate_input(event.to_platform_input());
715 self.background_executor.run_until_parked();
716 }
717
718 /// Simulates the user blurring the window.
719 pub fn deactivate_window(&mut self) {
720 if Some(self.window) == self.test_platform.active_window() {
721 self.test_platform.set_active_window(None)
722 }
723 self.background_executor.run_until_parked();
724 }
725
726 /// Simulates the user closing the window.
727 /// Returns true if the window was closed.
728 pub fn simulate_close(&mut self) -> bool {
729 let handler = self
730 .cx
731 .update_window(self.window, |_, cx| {
732 cx.window
733 .platform_window
734 .as_test()
735 .unwrap()
736 .0
737 .lock()
738 .should_close_handler
739 .take()
740 })
741 .unwrap();
742 if let Some(mut handler) = handler {
743 let should_close = handler();
744 self.cx
745 .update_window(self.window, |_, cx| {
746 cx.window.platform_window.on_should_close(handler);
747 })
748 .unwrap();
749 should_close
750 } else {
751 false
752 }
753 }
754
755 /// Get an &mut VisualTestContext (which is mostly what you need to pass to other methods).
756 /// This method internally retains the VisualTestContext until the end of the test.
757 pub fn as_mut(self) -> &'static mut Self {
758 let ptr = Box::into_raw(Box::new(self));
759 // safety: on_quit will be called after the test has finished.
760 // the executor will ensure that all tasks related to the test have stopped.
761 // so there is no way for cx to be accessed after on_quit is called.
762 let cx = Box::leak(unsafe { Box::from_raw(ptr) });
763 cx.on_quit(move || unsafe {
764 drop(Box::from_raw(ptr));
765 });
766 cx
767 }
768}
769
770impl Context for VisualTestContext {
771 type Result<T> = <TestAppContext as Context>::Result<T>;
772
773 fn new_model<T: 'static>(
774 &mut self,
775 build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
776 ) -> Self::Result<Model<T>> {
777 self.cx.new_model(build_model)
778 }
779
780 fn update_model<T, R>(
781 &mut self,
782 handle: &Model<T>,
783 update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
784 ) -> Self::Result<R>
785 where
786 T: 'static,
787 {
788 self.cx.update_model(handle, update)
789 }
790
791 fn read_model<T, R>(
792 &self,
793 handle: &Model<T>,
794 read: impl FnOnce(&T, &AppContext) -> R,
795 ) -> Self::Result<R>
796 where
797 T: 'static,
798 {
799 self.cx.read_model(handle, read)
800 }
801
802 fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
803 where
804 F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
805 {
806 self.cx.update_window(window, f)
807 }
808
809 fn read_window<T, R>(
810 &self,
811 window: &WindowHandle<T>,
812 read: impl FnOnce(View<T>, &AppContext) -> R,
813 ) -> Result<R>
814 where
815 T: 'static,
816 {
817 self.cx.read_window(window, read)
818 }
819}
820
821impl VisualContext for VisualTestContext {
822 fn new_view<V>(
823 &mut self,
824 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
825 ) -> Self::Result<View<V>>
826 where
827 V: 'static + Render,
828 {
829 self.window
830 .update(&mut self.cx, |_, cx| cx.new_view(build_view))
831 .unwrap()
832 }
833
834 fn update_view<V: 'static, R>(
835 &mut self,
836 view: &View<V>,
837 update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
838 ) -> Self::Result<R> {
839 self.window
840 .update(&mut self.cx, |_, cx| cx.update_view(view, update))
841 .unwrap()
842 }
843
844 fn replace_root_view<V>(
845 &mut self,
846 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
847 ) -> Self::Result<View<V>>
848 where
849 V: 'static + Render,
850 {
851 self.window
852 .update(&mut self.cx, |_, cx| cx.replace_root_view(build_view))
853 .unwrap()
854 }
855
856 fn focus_view<V: crate::FocusableView>(&mut self, view: &View<V>) -> Self::Result<()> {
857 self.window
858 .update(&mut self.cx, |_, cx| {
859 view.read(cx).focus_handle(cx).clone().focus(cx)
860 })
861 .unwrap()
862 }
863
864 fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
865 where
866 V: crate::ManagedView,
867 {
868 self.window
869 .update(&mut self.cx, |_, cx| {
870 view.update(cx, |_, cx| cx.emit(crate::DismissEvent))
871 })
872 .unwrap()
873 }
874}
875
876impl AnyWindowHandle {
877 /// Creates the given view in this window.
878 pub fn build_view<V: Render + 'static>(
879 &self,
880 cx: &mut TestAppContext,
881 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
882 ) -> View<V> {
883 self.update(cx, |_, cx| cx.new_view(build_view)).unwrap()
884 }
885}