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