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