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