Skip to main content

gpui/app/
test_app.rs

1//! A headless app for tests of views, entities and input.
2//!
3//! [`TestApp`] runs a real [`App`] on a deterministic scheduler, with a test
4//! platform in place of the operating system. Open windows with
5//! [`TestApp::open_window`], send them input with the `simulate_*` methods on
6//! [`TestAppWindow`], and read the results back. Tasks spawned in the app don't
7//! run until you call [`TestApp::run_until_parked`] or an `update` method.
8//!
9//! Use [`TestAppContext`](crate::TestAppContext) instead when a test needs an
10//! async function, several apps or randomized scheduling. The
11//! [testing guide](crate::_documentation::guides::testing) covers both.
12//!
13//! # Example
14//!
15//! ```
16//! use gpui::{Context, MouseButton, TestApp, Window, div, point, prelude::*, px};
17//!
18//! struct Counter {
19//!     count: usize,
20//! }
21//!
22//! impl Render for Counter {
23//!     fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24//!         div()
25//!             .id("increment")
26//!             .size(px(100.))
27//!             .on_click(cx.listener(|this, _event, _window, cx| {
28//!                 this.count += 1;
29//!                 cx.notify();
30//!             }))
31//!             .child(format!("{}", self.count))
32//!     }
33//! }
34//!
35//! let mut app = TestApp::new();
36//! let mut window = app.open_window(|_, _| Counter { count: 0 });
37//!
38//! // Paint once, so the click has bounds to hit.
39//! window.draw();
40//! window.simulate_click(point(px(10.), px(10.)), MouseButton::Left);
41//!
42//! window.read(|counter, _| assert_eq!(counter.count, 1));
43//! ```
44
45use crate::{
46    AnyWindowHandle, App, AppCell, AppContext, AssetRegistry, AsyncApp, BackgroundExecutor,
47    BorrowAppContext, Bounds, ClipboardItem, Context, Entity, ForegroundExecutor, Global,
48    InputEvent, Keystroke, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
49    Platform, PlatformTextSystem, Point, Render, Size, Task, TestDispatcher, TestPlatform,
50    TextSystem, Window, WindowBounds, WindowHandle, WindowOptions, app::GpuiMode,
51};
52use std::{future::Future, rc::Rc, sync::Arc, time::Duration};
53
54/// A headless app for tests.
55///
56/// `TestApp` runs a real [`App`] on a deterministic scheduler. Its platform keeps
57/// the clipboard, opened URLs and prompts in memory, so you can assert on them
58/// instead of seeing them on screen. Each `TestApp` is independent: state set in
59/// one doesn't appear in another.
60///
61/// See the [testing guide](crate::_documentation::guides::testing).
62pub struct TestApp {
63    app: Rc<AppCell>,
64    platform: Rc<TestPlatform>,
65    background_executor: BackgroundExecutor,
66    foreground_executor: ForegroundExecutor,
67    #[allow(dead_code)]
68    dispatcher: TestDispatcher,
69    text_system: Arc<TextSystem>,
70}
71
72impl TestApp {
73    /// Creates a test app with scheduler seed `0`.
74    ///
75    /// Text is shaped by the test text system, not by real fonts. Use
76    /// [`TestApp::with_text_system`] when a test measures real glyphs.
77    pub fn new() -> Self {
78        Self::with_seed(0)
79    }
80
81    /// Creates a test app whose scheduler orders ready tasks by `seed`.
82    ///
83    /// The seed decides which ready task runs first. The same seed gives the same
84    /// order every time, so a failing seed can be replayed.
85    pub fn with_seed(seed: u64) -> Self {
86        Self::build(seed, None, Arc::new(()))
87    }
88
89    /// Creates a test app that shapes text with `text_system`. Uses seed `0`.
90    ///
91    /// Pass a real text system to measure glyphs as the app would on a platform.
92    pub fn with_text_system(text_system: Arc<dyn PlatformTextSystem>) -> Self {
93        Self::build(0, Some(text_system), Arc::new(()))
94    }
95
96    /// Creates a test app with the given text system and asset source.
97    ///
98    /// This is [`TestApp::with_text_system`] plus an [`AssetSource`](crate::AssetSource),
99    /// so views can load icons and other assets. Uses seed `0`.
100    pub fn with_text_system_and_assets(
101        text_system: Arc<dyn PlatformTextSystem>,
102        asset_source: Arc<dyn crate::AssetSource>,
103    ) -> Self {
104        Self::build(0, Some(text_system), asset_source)
105    }
106
107    fn build(
108        seed: u64,
109        platform_text_system: Option<Arc<dyn PlatformTextSystem>>,
110        asset_source: Arc<dyn crate::AssetSource>,
111    ) -> Self {
112        let dispatcher = TestDispatcher::new(seed);
113        let arc_dispatcher = Arc::new(dispatcher.clone());
114        let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
115        let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
116        let platform = match platform_text_system.clone() {
117            Some(ts) => TestPlatform::with_text_system(
118                background_executor.clone(),
119                foreground_executor.clone(),
120                ts,
121            ),
122            None => TestPlatform::new(background_executor.clone(), foreground_executor.clone()),
123        };
124        let http_client = crate::http_client::FakeHttpClient::with_404_response();
125        let text_system = Arc::new(TextSystem::new(
126            platform_text_system.unwrap_or_else(|| platform.text_system.clone()),
127        ));
128
129        let app = App::new_app(
130            platform.clone(),
131            AssetRegistry::from(asset_source).into(),
132            http_client,
133        );
134        app.borrow_mut().mode = GpuiMode::test();
135
136        Self {
137            app,
138            platform,
139            background_executor,
140            foreground_executor,
141            dispatcher,
142            text_system,
143        }
144    }
145
146    /// Runs `f` with mutable access to the [`App`], then runs pending tasks until none can make progress.
147    ///
148    /// Effects such as observers and subscriptions run when `f` returns, before the
149    /// pending tasks run. Timers that haven't expired stay pending; use
150    /// [`TestApp::advance_clock`] to fire them.
151    ///
152    /// # Example
153    ///
154    /// ```
155    /// # use gpui::TestApp;
156    /// # use std::{cell::Cell, rc::Rc};
157    /// let mut app = TestApp::new();
158    /// let finished = Rc::new(Cell::new(false));
159    ///
160    /// let flag = finished.clone();
161    /// app.spawn(|_| async move { flag.set(true) }).detach();
162    ///
163    /// // Any update runs the task that was spawned above.
164    /// app.update(|_| ());
165    /// assert!(finished.get());
166    /// ```
167    pub fn update<R>(&mut self, f: impl FnOnce(&mut App) -> R) -> R {
168        let result = {
169            let mut app = self.app.borrow_mut();
170            app.update(f)
171        };
172        self.run_until_parked();
173        result
174    }
175
176    /// Runs `f` with read-only access to the [`App`].
177    ///
178    /// This doesn't run pending tasks. A task that is still waiting to run doesn't
179    /// run before `f` is called.
180    pub fn read<R>(&self, f: impl FnOnce(&App) -> R) -> R {
181        let app = self.app.borrow();
182        f(&app)
183    }
184
185    /// Creates an entity from `build` and runs pending tasks until parked.
186    ///
187    /// This is `cx.new(build)` inside [`TestApp::update`].
188    pub fn new_entity<T: 'static>(
189        &mut self,
190        build: impl FnOnce(&mut Context<T>) -> T,
191    ) -> Entity<T> {
192        self.update(|cx| cx.new(build))
193    }
194
195    /// Updates an entity and runs pending tasks until parked.
196    pub fn update_entity<T: 'static, R>(
197        &mut self,
198        entity: &Entity<T>,
199        f: impl FnOnce(&mut T, &mut Context<T>) -> R,
200    ) -> R {
201        self.update(|cx| entity.update(cx, f))
202    }
203
204    /// Reads an entity without running pending tasks.
205    pub fn read_entity<T: 'static, R>(
206        &self,
207        entity: &Entity<T>,
208        f: impl FnOnce(&T, &App) -> R,
209    ) -> R {
210        self.read(|cx| f(entity.read(cx), cx))
211    }
212
213    /// Opens a window sized to the test display and returns a [`TestAppWindow`] for its root view.
214    ///
215    /// The window's bounds are the maximized bounds of the test display. Use
216    /// [`TestApp::open_window_with_options`] to choose other bounds.
217    ///
218    /// # Panics
219    ///
220    /// Panics if the window can't be opened.
221    pub fn open_window<V: Render + 'static>(
222        &mut self,
223        build_view: impl FnOnce(&mut Window, &mut Context<V>) -> V,
224    ) -> TestAppWindow<V> {
225        let bounds = self.read(|cx| Bounds::maximized(None, cx));
226        let handle = self.update(|cx| {
227            cx.open_window(
228                WindowOptions::new().window_bounds(Some(WindowBounds::Windowed(bounds))),
229                |window, cx| cx.new(|cx| build_view(window, cx)),
230            )
231            .unwrap()
232        });
233
234        TestAppWindow {
235            handle,
236            app: self.app.clone(),
237            platform: self.platform.clone(),
238            background_executor: self.background_executor.clone(),
239        }
240    }
241
242    /// Opens a window with `options` and returns a [`TestAppWindow`] for its root view.
243    ///
244    /// # Panics
245    ///
246    /// Panics if the window can't be opened.
247    pub fn open_window_with_options<V: Render + 'static>(
248        &mut self,
249        options: WindowOptions,
250        build_view: impl FnOnce(&mut Window, &mut Context<V>) -> V,
251    ) -> TestAppWindow<V> {
252        let handle = self.update(|cx| {
253            cx.open_window(options, |window, cx| cx.new(|cx| build_view(window, cx)))
254                .unwrap()
255        });
256
257        TestAppWindow {
258            handle,
259            app: self.app.clone(),
260            platform: self.platform.clone(),
261            background_executor: self.background_executor.clone(),
262        }
263    }
264
265    /// Runs tasks until none can make progress.
266    ///
267    /// This doesn't move the simulated clock. A timer that hasn't expired stays
268    /// pending until [`TestApp::advance_clock`] moves time past it.
269    ///
270    /// # Example
271    ///
272    /// ```
273    /// # use gpui::TestApp;
274    /// # use std::{cell::Cell, rc::Rc, time::Duration};
275    /// let mut app = TestApp::new();
276    /// let fired = Rc::new(Cell::new(false));
277    ///
278    /// let flag = fired.clone();
279    /// app.spawn(|cx| async move {
280    ///     cx.background_executor().timer(Duration::from_secs(1)).await;
281    ///     flag.set(true);
282    /// })
283    /// .detach();
284    ///
285    /// app.run_until_parked();
286    /// assert!(!fired.get(), "the timer hasn't expired yet");
287    ///
288    /// app.advance_clock(Duration::from_secs(1));
289    /// app.run_until_parked();
290    /// assert!(fired.get());
291    /// ```
292    pub fn run_until_parked(&self) {
293        self.background_executor.run_until_parked();
294    }
295
296    /// Moves the simulated clock forward by `duration`.
297    ///
298    /// Tasks that become ready run during the call, including timers that expire
299    /// within the window. Use this to test timeouts and delays, as in the example for
300    /// [`TestApp::run_until_parked`].
301    pub fn advance_clock(&self, duration: Duration) {
302        self.background_executor.advance_clock(duration);
303    }
304
305    /// Spawns a future on the foreground executor, given an [`AsyncApp`] for this app.
306    ///
307    /// The future runs when the app runs tasks: during [`TestApp::run_until_parked`],
308    /// [`TestApp::advance_clock`] or the next `update`. Await, store or detach the
309    /// returned [`Task`], as in a real app. See [async work](crate::_documentation::guides::async_work).
310    pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncApp) -> Fut) -> Task<R>
311    where
312        Fut: Future<Output = R> + 'static,
313        R: 'static,
314    {
315        self.foreground_executor.spawn(f(self.to_async()))
316    }
317
318    /// Spawns a `Send` future on the background executor.
319    ///
320    /// Like [`TestApp::spawn`], the future runs when the app runs tasks.
321    pub fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
322    where
323        R: Send + 'static,
324    {
325        self.background_executor.spawn(future)
326    }
327
328    /// Returns an [`AsyncApp`] for this app.
329    ///
330    /// The handle holds a weak reference, so it can't keep the app alive.
331    pub fn to_async(&self) -> AsyncApp {
332        AsyncApp {
333            app: Rc::downgrade(&self.app),
334            background_executor: self.background_executor.clone(),
335            foreground_executor: self.foreground_executor.clone(),
336        }
337    }
338
339    /// Returns the test background executor.
340    pub fn background_executor(&self) -> &BackgroundExecutor {
341        &self.background_executor
342    }
343
344    /// Returns the test foreground executor.
345    pub fn foreground_executor(&self) -> &ForegroundExecutor {
346        &self.foreground_executor
347    }
348
349    /// Returns the text system the app uses to shape and measure text.
350    pub fn text_system(&self) -> &Arc<TextSystem> {
351        &self.text_system
352    }
353
354    /// Returns whether a global of type `G` is set.
355    pub fn has_global<G: Global>(&self) -> bool {
356        self.read(|cx| cx.has_global::<G>())
357    }
358
359    /// Sets the global of type `G`, replacing any previous value, then runs pending tasks.
360    pub fn set_global<G: Global>(&mut self, global: G) {
361        self.update(|cx| cx.set_global(global));
362    }
363
364    /// Runs `f` with the global of type `G`.
365    ///
366    /// # Panics
367    ///
368    /// Panics if no global of type `G` is set. Check with [`TestApp::has_global`] first.
369    pub fn read_global<G: Global, R>(&self, f: impl FnOnce(&G, &App) -> R) -> R {
370        self.read(|cx| f(cx.global(), cx))
371    }
372
373    /// Runs `f` with mutable access to the global of type `G`, then runs pending tasks.
374    ///
375    /// # Panics
376    ///
377    /// Panics if no global of type `G` is set.
378    pub fn update_global<G: Global, R>(&mut self, f: impl FnOnce(&mut G, &mut App) -> R) -> R {
379        self.update(|cx| cx.update_global(f))
380    }
381
382    // Platform simulation methods
383
384    /// Writes `item` to the test clipboard.
385    ///
386    /// [`TestApp::read_from_clipboard`] returns the item until something else is written.
387    pub fn write_to_clipboard(&self, item: ClipboardItem) {
388        self.platform.write_to_clipboard(item);
389    }
390
391    /// Returns the item most recently written to the test clipboard, or `None` if nothing was written.
392    pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
393        self.platform.read_from_clipboard()
394    }
395
396    /// Returns the URL most recently passed to [`App::open_url`], or `None`.
397    ///
398    /// Nothing is launched. Only the last URL is kept, so open a second URL and the
399    /// first is no longer reported.
400    ///
401    /// # Example
402    ///
403    /// ```
404    /// # use gpui::TestApp;
405    /// let mut app = TestApp::new();
406    /// app.update(|cx| cx.open_url("https://example.com"));
407    /// assert_eq!(app.opened_url().as_deref(), Some("https://example.com"));
408    /// ```
409    pub fn opened_url(&self) -> Option<String> {
410        self.platform.opened_url.borrow().clone()
411    }
412
413    /// Returns whether a [`App::prompt_for_new_path`] request is waiting for an answer.
414    ///
415    /// Answer it with [`TestApp::simulate_new_path_selection`].
416    pub fn did_prompt_for_new_path(&self) -> bool {
417        self.platform.did_prompt_for_new_path()
418    }
419
420    /// Answers the oldest pending [`App::prompt_for_new_path`] request.
421    ///
422    /// `select` receives the directory the app asked about and returns the chosen
423    /// path, or `None` to cancel the request.
424    ///
425    /// # Panics
426    ///
427    /// Panics if no new-path request is pending.
428    pub fn simulate_new_path_selection(
429        &self,
430        select: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
431    ) {
432        self.platform.simulate_new_path_selection(select);
433    }
434
435    /// Returns whether a prompt from [`Window::prompt`] is waiting for an answer.
436    pub fn has_pending_prompt(&self) -> bool {
437        self.platform.has_pending_prompt()
438    }
439
440    /// Answers the oldest pending prompt by pressing the button labelled `button`.
441    ///
442    /// The prompt is removed before the label is checked, so a wrong label still
443    /// consumes it.
444    ///
445    /// # Panics
446    ///
447    /// Panics if no prompt is pending, or if no button has the label `button`.
448    pub fn simulate_prompt_answer(&self, button: &str) {
449        self.platform.simulate_prompt_answer(button);
450    }
451
452    /// Returns the handles of all open windows.
453    pub fn windows(&self) -> Vec<AnyWindowHandle> {
454        self.read(|cx| cx.windows())
455    }
456}
457
458impl Default for TestApp {
459    fn default() -> Self {
460        Self::new()
461    }
462}
463
464/// A window opened by [`TestApp::open_window`].
465///
466/// Use it to read and update the root view and to send input to the window.
467/// Clones refer to the same window.
468///
469/// Each method that sends input or updates the view runs pending tasks before it
470/// returns, so the next line can assert on the result. The simulated input
471/// methods use the window's last painted frame to find elements, so call
472/// [`TestAppWindow::draw`] after building or changing the view.
473pub struct TestAppWindow<V> {
474    handle: WindowHandle<V>,
475    app: Rc<AppCell>,
476    platform: Rc<TestPlatform>,
477    background_executor: BackgroundExecutor,
478}
479
480impl<V: 'static + Render> TestAppWindow<V> {
481    /// Returns the window's handle, for APIs that take a [`WindowHandle`].
482    pub fn handle(&self) -> WindowHandle<V> {
483        self.handle
484    }
485
486    /// Returns the root view's entity.
487    ///
488    /// # Panics
489    ///
490    /// Panics if the window has been closed.
491    pub fn root(&self) -> Entity<V> {
492        let mut app = self.app.borrow_mut();
493        let any_handle: AnyWindowHandle = self.handle.into();
494        app.update_window(any_handle, |root_view, _, _| {
495            root_view.downcast::<V>().expect("root view type mismatch")
496        })
497        .expect("window not found")
498    }
499
500    /// Runs `f` with the root view, its [`Window`] and a [`Context`], then runs pending tasks until parked.
501    ///
502    /// # Panics
503    ///
504    /// Panics if the window has been closed.
505    pub fn update<R>(&mut self, f: impl FnOnce(&mut V, &mut Window, &mut Context<V>) -> R) -> R {
506        let result = {
507            let mut app = self.app.borrow_mut();
508            let any_handle: AnyWindowHandle = self.handle.into();
509            app.update_window(any_handle, |root_view, window, cx| {
510                let view = root_view.downcast::<V>().expect("root view type mismatch");
511                view.update(cx, |view, cx| f(view, window, cx))
512            })
513            .expect("window not found")
514        };
515        self.background_executor.run_until_parked();
516        result
517    }
518
519    /// Runs `f` with the root view and the [`App`], without running pending tasks.
520    ///
521    /// # Panics
522    ///
523    /// Panics if the window has been closed.
524    pub fn read<R>(&self, f: impl FnOnce(&V, &App) -> R) -> R {
525        let app = self.app.borrow();
526        let view = self
527            .app
528            .borrow()
529            .windows
530            .get(self.handle.window_id())
531            .and_then(|w| w.as_ref())
532            .and_then(|w| w.root.clone())
533            .and_then(|r| r.downcast::<V>().ok())
534            .expect("window or root view not found");
535        f(view.read(&app), &app)
536    }
537
538    /// Always returns `None`. The window title isn't available through this type yet.
539    ///
540    /// Don't use this to check a title. Tests that open windows with
541    /// [`TestAppContext::add_window_view`](crate::TestAppContext::add_window_view) can read the title with
542    /// [`VisualTestContext::window_title`](crate::VisualTestContext::window_title).
543    ///
544    /// # Panics
545    ///
546    /// Panics if the window has been closed.
547    ///
548    /// # Example
549    ///
550    /// ```
551    /// # use gpui::{Empty, TestApp};
552    /// let mut app = TestApp::new();
553    /// let window = app.open_window(|_, _| Empty);
554    /// assert_eq!(window.title(), None);
555    /// ```
556    pub fn title(&self) -> Option<String> {
557        let app = self.app.borrow();
558        app.read_window(&self.handle, |_, _cx| {
559            // TODO: expose title through Window API
560            None
561        })
562        .unwrap()
563    }
564
565    /// Sends one keystroke, such as `"ctrl-s"` or `"space"`, to the focused element, then runs pending tasks.
566    ///
567    /// Use the key name `"space"` for the space bar. A space character in the string
568    /// isn't a separator here; [`TestAppWindow::simulate_keystrokes`] splits on spaces.
569    ///
570    /// # Panics
571    ///
572    /// Panics if `keystroke` isn't a valid keystroke. See [keyboard input](crate::_documentation::guides::keyboard)
573    /// for the syntax.
574    ///
575    /// # Example
576    ///
577    /// ```
578    /// # use gpui::{Context, FocusHandle, KeyDownEvent, Render, TestApp, Window, div, prelude::*};
579    /// # struct Keys { focus: FocusHandle, last: Option<String> }
580    /// # impl Render for Keys {
581    /// #     fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
582    /// #         div().track_focus(&self.focus).on_key_down(cx.listener(|this, event: &KeyDownEvent, _, cx| {
583    /// #             this.last = Some(event.keystroke.key.clone());
584    /// #             cx.notify();
585    /// #         }))
586    /// #     }
587    /// # }
588    /// # let mut app = TestApp::new();
589    /// # let mut window = app.open_window(|window, cx| {
590    /// #     let focus = cx.focus_handle();
591    /// #     window.focus(&focus, cx);
592    /// #     Keys { focus, last: None }
593    /// # });
594    /// window.simulate_keystroke("space");
595    /// window.read(|keys, _| assert_eq!(keys.last.as_deref(), Some("space")));
596    /// ```
597    pub fn simulate_keystroke(&mut self, keystroke: &str) {
598        let keystroke = Keystroke::parse(keystroke).unwrap();
599        {
600            let mut app = self.app.borrow_mut();
601            let any_handle: AnyWindowHandle = self.handle.into();
602            app.update_window(any_handle, |_, window, cx| {
603                window.dispatch_keystroke(keystroke, cx);
604            })
605            .unwrap();
606        }
607        self.background_executor.run_until_parked();
608    }
609
610    /// Sends each keystroke in a space-separated list, in order, then runs pending tasks after each one.
611    ///
612    /// Each part is parsed as in [`TestAppWindow::simulate_keystroke`]. A space
613    /// character in the list is a separator, so use `simulate_keystroke("space")` to
614    /// press the space bar. Two spaces in a row send an empty keystroke.
615    ///
616    /// # Panics
617    ///
618    /// Panics if any part isn't a valid keystroke.
619    ///
620    /// # Example
621    ///
622    /// ```
623    /// # use gpui::{Context, FocusHandle, KeyDownEvent, Render, TestApp, Window, div, prelude::*};
624    /// # struct Keys { focus: FocusHandle, last: Option<String> }
625    /// # impl Render for Keys {
626    /// #     fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
627    /// #         div().track_focus(&self.focus).on_key_down(cx.listener(|this, event: &KeyDownEvent, _, cx| {
628    /// #             this.last = Some(event.keystroke.key.clone());
629    /// #             cx.notify();
630    /// #         }))
631    /// #     }
632    /// # }
633    /// # let mut app = TestApp::new();
634    /// # let mut window = app.open_window(|window, cx| {
635    /// #     let focus = cx.focus_handle();
636    /// #     window.focus(&focus, cx);
637    /// #     Keys { focus, last: None }
638    /// # });
639    /// window.simulate_keystrokes("a b");
640    /// window.read(|keys, _| assert_eq!(keys.last.as_deref(), Some("b")));
641    /// ```
642    pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
643        for keystroke in keystrokes.split(' ') {
644            self.simulate_keystroke(keystroke);
645        }
646    }
647
648    /// Types `input` by sending one keystroke per character, then runs pending tasks.
649    ///
650    /// Each character is sent as a keystroke whose key is that character, so text
651    /// inputs receive the text. Uppercase letters are sent with shift held. A space
652    /// is sent with the key `" "`, not `"space"`, so a binding for `space` doesn't
653    /// match it. Use [`TestAppWindow::simulate_keystroke`] for named keys.
654    ///
655    /// # Example
656    ///
657    /// ```
658    /// # use gpui::{Context, FocusHandle, KeyDownEvent, Render, TestApp, Window, div, prelude::*};
659    /// # struct Keys { focus: FocusHandle, last: Option<String> }
660    /// # impl Render for Keys {
661    /// #     fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
662    /// #         div().track_focus(&self.focus).on_key_down(cx.listener(|this, event: &KeyDownEvent, _, cx| {
663    /// #             this.last = Some(event.keystroke.key.clone());
664    /// #             cx.notify();
665    /// #         }))
666    /// #     }
667    /// # }
668    /// # let mut app = TestApp::new();
669    /// # let mut window = app.open_window(|window, cx| {
670    /// #     let focus = cx.focus_handle();
671    /// #     window.focus(&focus, cx);
672    /// #     Keys { focus, last: None }
673    /// # });
674    /// window.simulate_input("xy");
675    /// window.read(|keys, _| assert_eq!(keys.last.as_deref(), Some("y")));
676    /// ```
677    pub fn simulate_input(&mut self, input: &str) {
678        for char in input.chars() {
679            self.simulate_keystroke(&char.to_string());
680        }
681    }
682
683    /// Moves the pointer to `position` with no button held, then runs pending tasks.
684    pub fn simulate_mouse_move(&mut self, position: Point<Pixels>) {
685        self.simulate_event(MouseMoveEvent {
686            position,
687            modifiers: Default::default(),
688            pressed_button: None,
689        });
690    }
691
692    /// Presses `button` at `position`, then runs pending tasks.
693    ///
694    /// The click count is always `1`, so this never sends a double click.
695    pub fn simulate_mouse_down(&mut self, position: Point<Pixels>, button: MouseButton) {
696        self.simulate_event(MouseDownEvent {
697            position,
698            button,
699            modifiers: Default::default(),
700            click_count: 1,
701            first_mouse: false,
702        });
703    }
704
705    /// Releases `button` at `position`, then runs pending tasks.
706    ///
707    /// The click count is always `1`.
708    pub fn simulate_mouse_up(&mut self, position: Point<Pixels>, button: MouseButton) {
709        self.simulate_event(MouseUpEvent {
710            position,
711            button,
712            modifiers: Default::default(),
713            click_count: 1,
714        });
715    }
716
717    /// Presses and releases `button` at `position`, then runs pending tasks.
718    ///
719    /// Both events have a click count of `1`, so calling this twice in a row sends two
720    /// single clicks, never a double click. Call [`TestAppWindow::draw`] first, so
721    /// the click hits the element you expect. See the example on [`TestApp`].
722    pub fn simulate_click(&mut self, position: Point<Pixels>, button: MouseButton) {
723        self.simulate_mouse_down(position, button);
724        self.simulate_mouse_up(position, button);
725    }
726
727    /// Scrolls by `delta`, in pixels, with the pointer at `position`, then runs pending tasks.
728    ///
729    /// No modifier keys are held.
730    pub fn simulate_scroll(&mut self, position: Point<Pixels>, delta: Point<Pixels>) {
731        self.simulate_event(crate::ScrollWheelEvent {
732            position,
733            delta: crate::ScrollDelta::Pixels(delta),
734            modifiers: Default::default(),
735            touch_phase: crate::TouchPhase::Moved,
736        });
737    }
738
739    /// Dispatches `event` to the window as platform input, then runs pending tasks.
740    ///
741    /// Use this for input events that have no dedicated method on [`TestAppWindow`].
742    pub fn simulate_event<E: InputEvent>(&mut self, event: E) {
743        let platform_input = event.to_platform_input();
744        {
745            let mut app = self.app.borrow_mut();
746            let any_handle: AnyWindowHandle = self.handle.into();
747            app.update_window(any_handle, |_, window, cx| {
748                window.dispatch_event(platform_input, cx);
749            })
750            .unwrap();
751        }
752        self.background_executor.run_until_parked();
753    }
754
755    /// Sets the window's size to `size`, then runs pending tasks.
756    ///
757    /// This runs the window's resize callback, if it has one, and updates the viewport that
758    /// `Window::viewport_size` reports. It doesn't paint the window: call
759    /// [`TestAppWindow::draw`] to paint at the new size.
760    pub fn simulate_resize(&mut self, size: Size<Pixels>) {
761        let window_id = self.handle.window_id();
762        // The window's resize callback updates the app, so it can't run while the app is borrowed.
763        let test_window = {
764            let mut app = self.app.borrow_mut();
765            match app.windows.get_mut(window_id) {
766                Some(Some(window)) => window.platform_window.as_test().cloned(),
767                _ => None,
768            }
769        };
770        if let Some(mut test_window) = test_window {
771            test_window.simulate_resize(size);
772        }
773        self.background_executor.run_until_parked();
774    }
775
776    /// Makes this the active window, as if the user had clicked it, then runs pending tasks.
777    ///
778    /// Windows that [`TestApp`] opens start inactive. Focus listeners such as
779    /// [`Context::on_focus`] only run in the active window, so activate it before testing them.
780    pub fn activate(&mut self) {
781        let window_id = self.handle.window_id();
782        let test_window = match self.app.borrow_mut().windows.get_mut(window_id) {
783            Some(Some(window)) => window.platform_window.as_test().cloned(),
784            _ => None,
785        };
786        self.platform.set_active_window(test_window);
787        self.background_executor.run_until_parked();
788    }
789
790    /// Simulate the window moving to a display with a different scale factor.
791    pub fn simulate_scale_factor_change(&mut self, scale_factor: f32) {
792        let window_id = self.handle.window_id();
793        let test_window = {
794            let mut app = self.app.borrow_mut();
795            app.windows
796                .get_mut(window_id)
797                .and_then(|window| window.as_mut())
798                .and_then(|window| window.platform_window.as_test())
799                .cloned()
800        };
801        // The resize callback needs to borrow the app again synchronously.
802        if let Some(mut test_window) = test_window {
803            test_window.simulate_scale_factor_change(scale_factor);
804        }
805        self.background_executor.run_until_parked();
806    }
807
808    /// Paints the window now, so that later input and hit tests use the current layout.
809    ///
810    /// Call this after building the view and after anything changes its layout, before
811    /// simulating a click at a position. It doesn't run pending tasks. See the example
812    /// on [`TestApp`].
813    pub fn draw(&mut self) {
814        let mut app = self.app.borrow_mut();
815        let any_handle: AnyWindowHandle = self.handle.into();
816        app.update_window(any_handle, |_, window, cx| {
817            window.draw(cx).clear(cx);
818        })
819        .unwrap();
820    }
821}
822
823impl<V> Clone for TestAppWindow<V> {
824    fn clone(&self) -> Self {
825        Self {
826            handle: self.handle,
827            app: self.app.clone(),
828            platform: self.platform.clone(),
829            background_executor: self.background_executor.clone(),
830        }
831    }
832}
833
834#[cfg(test)]
835mod tests {
836    use super::*;
837    use crate::{FocusHandle, Focusable, div, prelude::*, px, size};
838
839    struct Counter {
840        count: usize,
841        focus_handle: FocusHandle,
842    }
843
844    impl Counter {
845        fn new(_window: &mut Window, cx: &mut Context<Self>) -> Self {
846            let focus_handle = cx.focus_handle();
847            Self {
848                count: 0,
849                focus_handle,
850            }
851        }
852
853        fn increment(&mut self, _cx: &mut Context<Self>) {
854            self.count += 1;
855        }
856    }
857
858    impl Focusable for Counter {
859        fn focus_handle(&self, _cx: &App) -> FocusHandle {
860            self.focus_handle.clone()
861        }
862    }
863
864    impl Render for Counter {
865        fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
866            div().child(format!("Count: {}", self.count))
867        }
868    }
869
870    #[test]
871    fn test_basic_usage() {
872        let mut app = TestApp::new();
873
874        let mut window = app.open_window(Counter::new);
875
876        window.update(|counter, _window, cx| {
877            counter.increment(cx);
878        });
879
880        window.read(|counter, _| {
881            assert_eq!(counter.count, 1);
882        });
883
884        drop(window);
885        app.update(|cx| cx.shutdown());
886    }
887
888    #[test]
889    fn test_resize_updates_gpui_viewport() {
890        let mut app = TestApp::new();
891        let mut window = app.open_window(Counter::new);
892
893        window.simulate_resize(size(px(321.), px(123.)));
894
895        window.update(|_, window, _| {
896            assert_eq!(window.viewport_size(), size(px(321.), px(123.)));
897        });
898    }
899
900    #[test]
901    fn test_simulate_scale_factor_change() {
902        let mut app = TestApp::new();
903        let mut window = app.open_window(|_, _| crate::EmptyView);
904        let viewport_size = window.update(|_, window, _| {
905            assert_eq!(window.scale_factor(), 2.0);
906            window.viewport_size()
907        });
908
909        window.simulate_scale_factor_change(1.0);
910
911        window.update(|_, window, _| {
912            assert_eq!(window.scale_factor(), 1.0);
913            assert_eq!(window.viewport_size(), viewport_size);
914        });
915
916        drop(window);
917        app.update(|cx| cx.shutdown());
918    }
919
920    #[test]
921    fn test_entity_creation() {
922        let mut app = TestApp::new();
923
924        let entity = app.new_entity(|cx| Counter {
925            count: 42,
926            focus_handle: cx.focus_handle(),
927        });
928
929        app.read_entity(&entity, |counter, _| {
930            assert_eq!(counter.count, 42);
931        });
932
933        app.update_entity(&entity, |counter, _cx| {
934            counter.count += 1;
935        });
936
937        app.read_entity(&entity, |counter, _| {
938            assert_eq!(counter.count, 43);
939        });
940    }
941
942    #[test]
943    fn test_globals() {
944        let mut app = TestApp::new();
945
946        struct MyGlobal(String);
947        impl Global for MyGlobal {}
948
949        assert!(!app.has_global::<MyGlobal>());
950
951        app.set_global(MyGlobal("hello".into()));
952
953        assert!(app.has_global::<MyGlobal>());
954
955        app.read_global::<MyGlobal, _>(|global, _| {
956            assert_eq!(global.0, "hello");
957        });
958
959        app.update_global::<MyGlobal, _>(|global, _| {
960            global.0 = "world".into();
961        });
962
963        app.read_global::<MyGlobal, _>(|global, _| {
964            assert_eq!(global.0, "world");
965        });
966    }
967}