1use crate::{
2 Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
3 AvailableSpace, BackgroundExecutor, BorrowAppContext, Bounds, ClipboardItem, Context, Empty,
4 Entity, 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 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 update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
67 where
68 F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
69 {
70 let mut lock = self.app.borrow_mut();
71 lock.update_window(window, f)
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(None, &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(None, &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(None, &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.update(|cx| cx.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(|cx| cx.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: 'static> 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 /// Returns a future that resolves when the model notifies.
496 pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
497 use postage::prelude::{Sink as _, Stream as _};
498
499 let (mut tx, mut rx) = postage::mpsc::channel(1);
500 let mut cx = cx.app.app.borrow_mut();
501 let subscription = cx.observe(self, move |_, _| {
502 tx.try_send(()).ok();
503 });
504
505 let duration = if std::env::var("CI").is_ok() {
506 Duration::from_secs(5)
507 } else {
508 Duration::from_secs(1)
509 };
510
511 async move {
512 let notification = crate::util::timeout(duration, rx.recv())
513 .await
514 .expect("next notification timed out");
515 drop(subscription);
516 notification.expect("model dropped while test was waiting for its next notification")
517 }
518 }
519}
520
521impl<V: 'static> View<V> {
522 /// Returns a future that resolves when the view is next updated.
523 pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
524 use postage::prelude::{Sink as _, Stream as _};
525
526 let (mut tx, mut rx) = postage::mpsc::channel(1);
527 let mut cx = cx.app.app.borrow_mut();
528 let subscription = cx.observe(self, move |_, _| {
529 tx.try_send(()).ok();
530 });
531
532 let duration = if std::env::var("CI").is_ok() {
533 Duration::from_secs(5)
534 } else {
535 Duration::from_secs(1)
536 };
537
538 async move {
539 let notification = crate::util::timeout(duration, rx.recv())
540 .await
541 .expect("next notification timed out");
542 drop(subscription);
543 notification.expect("model dropped while test was waiting for its next notification")
544 }
545 }
546}
547
548impl<V> View<V> {
549 /// Returns a future that resolves when the condition becomes true.
550 pub fn condition<Evt>(
551 &self,
552 cx: &TestAppContext,
553 mut predicate: impl FnMut(&V, &AppContext) -> bool,
554 ) -> impl Future<Output = ()>
555 where
556 Evt: 'static,
557 V: EventEmitter<Evt>,
558 {
559 use postage::prelude::{Sink as _, Stream as _};
560
561 let (tx, mut rx) = postage::mpsc::channel(1024);
562 let timeout_duration = Duration::from_millis(100);
563
564 let mut cx = cx.app.borrow_mut();
565 let subscriptions = (
566 cx.observe(self, {
567 let mut tx = tx.clone();
568 move |_, _| {
569 tx.blocking_send(()).ok();
570 }
571 }),
572 cx.subscribe(self, {
573 let mut tx = tx.clone();
574 move |_, _: &Evt, _| {
575 tx.blocking_send(()).ok();
576 }
577 }),
578 );
579
580 let cx = cx.this.upgrade().unwrap();
581 let handle = self.downgrade();
582
583 async move {
584 crate::util::timeout(timeout_duration, async move {
585 loop {
586 {
587 let cx = cx.borrow();
588 let cx = &*cx;
589 if predicate(
590 handle
591 .upgrade()
592 .expect("view dropped with pending condition")
593 .read(cx),
594 cx,
595 ) {
596 break;
597 }
598 }
599
600 cx.borrow().background_executor().start_waiting();
601 rx.recv()
602 .await
603 .expect("view dropped with pending condition");
604 cx.borrow().background_executor().finish_waiting();
605 }
606 })
607 .await
608 .expect("condition timed out");
609 drop(subscriptions);
610 }
611 }
612}
613
614use derive_more::{Deref, DerefMut};
615#[derive(Deref, DerefMut, Clone)]
616/// A VisualTestContext is the test-equivalent of a `WindowContext`. It allows you to
617/// run window-specific test code.
618pub struct VisualTestContext {
619 #[deref]
620 #[deref_mut]
621 /// cx is the original TestAppContext (you can more easily access this using Deref)
622 pub cx: TestAppContext,
623 window: AnyWindowHandle,
624}
625
626impl VisualTestContext {
627 /// Get the underlying window handle underlying this context.
628 pub fn handle(&self) -> AnyWindowHandle {
629 self.window
630 }
631
632 /// Provides the `WindowContext` for the duration of the closure.
633 pub fn update<R>(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R {
634 self.cx.update_window(self.window, |_, cx| f(cx)).unwrap()
635 }
636
637 /// Creates a new VisualTestContext. You would typically shadow the passed in
638 /// TestAppContext with this, as this is typically more useful.
639 /// `let cx = VisualTestContext::from_window(window, cx);`
640 pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self {
641 Self {
642 cx: cx.clone(),
643 window,
644 }
645 }
646
647 /// Wait until there are no more pending tasks.
648 pub fn run_until_parked(&self) {
649 self.cx.background_executor.run_until_parked();
650 }
651
652 /// Dispatch the action to the currently focused node.
653 pub fn dispatch_action<A>(&mut self, action: A)
654 where
655 A: Action,
656 {
657 self.cx.dispatch_action(self.window, action)
658 }
659
660 /// Read the title off the window (set by `WindowContext#set_window_title`)
661 pub fn window_title(&mut self) -> Option<String> {
662 self.cx.test_window(self.window).0.lock().title.clone()
663 }
664
665 /// Simulate a sequence of keystrokes `cx.simulate_keystrokes("cmd-p escape")`
666 /// Automatically runs until parked.
667 pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
668 self.cx.simulate_keystrokes(self.window, keystrokes)
669 }
670
671 /// Simulate typing text `cx.simulate_input("hello")`
672 /// Automatically runs until parked.
673 pub fn simulate_input(&mut self, input: &str) {
674 self.cx.simulate_input(self.window, input)
675 }
676
677 /// Simulate a mouse move event to the given point
678 pub fn simulate_mouse_move(&mut self, position: Point<Pixels>, modifiers: Modifiers) {
679 self.simulate_event(MouseMoveEvent {
680 position,
681 modifiers,
682 pressed_button: None,
683 })
684 }
685
686 /// Simulate a primary mouse click at the given point
687 pub fn simulate_click(&mut self, position: Point<Pixels>, modifiers: Modifiers) {
688 self.simulate_event(MouseDownEvent {
689 position,
690 modifiers,
691 button: MouseButton::Left,
692 click_count: 1,
693 first_mouse: false,
694 });
695 self.simulate_event(MouseUpEvent {
696 position,
697 modifiers,
698 button: MouseButton::Left,
699 click_count: 1,
700 });
701 }
702
703 /// Simulate a modifiers changed event
704 pub fn simulate_modifiers_change(&mut self, modifiers: Modifiers) {
705 self.simulate_event(ModifiersChangedEvent { modifiers })
706 }
707
708 /// Simulates the user resizing the window to the new size.
709 pub fn simulate_resize(&self, size: Size<Pixels>) {
710 self.simulate_window_resize(self.window, size)
711 }
712
713 /// debug_bounds returns the bounds of the element with the given selector.
714 pub fn debug_bounds(&mut self, selector: &'static str) -> Option<Bounds<Pixels>> {
715 self.update(|cx| cx.window.rendered_frame.debug_bounds.get(selector).copied())
716 }
717
718 /// Draw an element to the window. Useful for simulating events or actions
719 pub fn draw(
720 &mut self,
721 origin: Point<Pixels>,
722 space: Size<AvailableSpace>,
723 f: impl FnOnce(&mut WindowContext) -> AnyElement,
724 ) {
725 self.update(|cx| {
726 cx.with_element_context(|cx| {
727 let mut element = f(cx);
728 element.layout(origin, space, cx);
729 element.paint(cx);
730 });
731
732 cx.refresh();
733 })
734 }
735
736 /// Simulate an event from the platform, e.g. a SrollWheelEvent
737 /// Make sure you've called [VisualTestContext::draw] first!
738 pub fn simulate_event<E: InputEvent>(&mut self, event: E) {
739 self.test_window(self.window)
740 .simulate_input(event.to_platform_input());
741 self.background_executor.run_until_parked();
742 }
743
744 /// Simulates the user blurring the window.
745 pub fn deactivate_window(&mut self) {
746 if Some(self.window) == self.test_platform.active_window() {
747 self.test_platform.set_active_window(None)
748 }
749 self.background_executor.run_until_parked();
750 }
751
752 /// Simulates the user closing the window.
753 /// Returns true if the window was closed.
754 pub fn simulate_close(&mut self) -> bool {
755 let handler = self
756 .cx
757 .update_window(self.window, |_, cx| {
758 cx.window
759 .platform_window
760 .as_test()
761 .unwrap()
762 .0
763 .lock()
764 .should_close_handler
765 .take()
766 })
767 .unwrap();
768 if let Some(mut handler) = handler {
769 let should_close = handler();
770 self.cx
771 .update_window(self.window, |_, cx| {
772 cx.window.platform_window.on_should_close(handler);
773 })
774 .unwrap();
775 should_close
776 } else {
777 false
778 }
779 }
780
781 /// Get an &mut VisualTestContext (which is mostly what you need to pass to other methods).
782 /// This method internally retains the VisualTestContext until the end of the test.
783 pub fn as_mut(self) -> &'static mut Self {
784 let ptr = Box::into_raw(Box::new(self));
785 // safety: on_quit will be called after the test has finished.
786 // the executor will ensure that all tasks related to the test have stopped.
787 // so there is no way for cx to be accessed after on_quit is called.
788 let cx = Box::leak(unsafe { Box::from_raw(ptr) });
789 cx.on_quit(move || unsafe {
790 drop(Box::from_raw(ptr));
791 });
792 cx
793 }
794}
795
796impl Context for VisualTestContext {
797 type Result<T> = <TestAppContext as Context>::Result<T>;
798
799 fn new_model<T: 'static>(
800 &mut self,
801 build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
802 ) -> Self::Result<Model<T>> {
803 self.cx.new_model(build_model)
804 }
805
806 fn update_model<T, R>(
807 &mut self,
808 handle: &Model<T>,
809 update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
810 ) -> Self::Result<R>
811 where
812 T: 'static,
813 {
814 self.cx.update_model(handle, update)
815 }
816
817 fn read_model<T, R>(
818 &self,
819 handle: &Model<T>,
820 read: impl FnOnce(&T, &AppContext) -> R,
821 ) -> Self::Result<R>
822 where
823 T: 'static,
824 {
825 self.cx.read_model(handle, read)
826 }
827
828 fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
829 where
830 F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
831 {
832 self.cx.update_window(window, f)
833 }
834
835 fn read_window<T, R>(
836 &self,
837 window: &WindowHandle<T>,
838 read: impl FnOnce(View<T>, &AppContext) -> R,
839 ) -> Result<R>
840 where
841 T: 'static,
842 {
843 self.cx.read_window(window, read)
844 }
845}
846
847impl VisualContext for VisualTestContext {
848 fn new_view<V>(
849 &mut self,
850 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
851 ) -> Self::Result<View<V>>
852 where
853 V: 'static + Render,
854 {
855 self.window
856 .update(&mut self.cx, |_, cx| cx.new_view(build_view))
857 .unwrap()
858 }
859
860 fn update_view<V: 'static, R>(
861 &mut self,
862 view: &View<V>,
863 update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
864 ) -> Self::Result<R> {
865 self.window
866 .update(&mut self.cx, |_, cx| cx.update_view(view, update))
867 .unwrap()
868 }
869
870 fn replace_root_view<V>(
871 &mut self,
872 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
873 ) -> Self::Result<View<V>>
874 where
875 V: 'static + Render,
876 {
877 self.window
878 .update(&mut self.cx, |_, cx| cx.replace_root_view(build_view))
879 .unwrap()
880 }
881
882 fn focus_view<V: crate::FocusableView>(&mut self, view: &View<V>) -> Self::Result<()> {
883 self.window
884 .update(&mut self.cx, |_, cx| {
885 view.read(cx).focus_handle(cx).clone().focus(cx)
886 })
887 .unwrap()
888 }
889
890 fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
891 where
892 V: crate::ManagedView,
893 {
894 self.window
895 .update(&mut self.cx, |_, cx| {
896 view.update(cx, |_, cx| cx.emit(crate::DismissEvent))
897 })
898 .unwrap()
899 }
900}
901
902impl AnyWindowHandle {
903 /// Creates the given view in this window.
904 pub fn build_view<V: Render + 'static>(
905 &self,
906 cx: &mut TestAppContext,
907 build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
908 ) -> View<V> {
909 self.update(cx, |_, cx| cx.new_view(build_view)).unwrap()
910 }
911}