Skip to main content

gpui/app/
test_context.rs

1use crate::{
2    Action, AnyView, AnyWindowHandle, App, AppCell, AppContext, AssetRegistry, AsyncApp,
3    AvailableSpace, BackgroundExecutor, BorrowAppContext, Bounds, Capslock, ClipboardItem,
4    DisplayId, DrawPhase, Drawable, Element, Empty, EntityId, EventEmitter, ForegroundExecutor,
5    Global, InputEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
6    MouseMoveEvent, MouseUpEvent, Pixels, Platform, PlatformTextSystem, Point, Render, Result,
7    SharedString, Size, SystemNotification, SystemNotificationResponse, Task, TestDispatcher,
8    TestPlatform, TestScreenCaptureSource, TestWindow, TextSystem, VisualContext, Window,
9    WindowBounds, WindowHandle, WindowOptions, WindowVisibility, app::GpuiMode,
10    window::ElementArenaScope,
11};
12use anyhow::{anyhow, bail};
13use futures::{Stream, StreamExt, channel::oneshot};
14
15use std::{
16    cell::RefCell, future::Future, ops::Deref, path::PathBuf, rc::Rc, sync::Arc, time::Duration,
17};
18
19/// The test version of [`App`], passed to `#[gpui::test]` functions.
20///
21/// `TestAppContext` implements [`AppContext`] and adds helpers for windows, input,
22/// prompts and executors. Unlike [`TestApp`](crate::TestApp), its methods don't
23/// run pending tasks unless the method says so. Call [`TestAppContext::run_until_parked`]
24/// to run them. See the [testing guide](crate::_documentation::guides::testing).
25///
26/// Create one directly with [`TestAppContext::single`] when you don't need the
27/// macro.
28#[derive(Clone)]
29pub struct TestAppContext {
30    #[doc(hidden)]
31    pub background_executor: BackgroundExecutor,
32    #[doc(hidden)]
33    pub foreground_executor: ForegroundExecutor,
34    #[doc(hidden)]
35    pub dispatcher: TestDispatcher,
36    test_platform: Rc<TestPlatform>,
37    text_system: Arc<TextSystem>,
38    fn_name: Option<&'static str>,
39    on_quit: Rc<RefCell<Vec<Box<dyn FnOnce() + 'static>>>>,
40    #[doc(hidden)]
41    pub app: Rc<AppCell>,
42}
43
44impl AppContext for TestAppContext {
45    fn new<T: 'static>(&mut self, build_entity: impl FnOnce(&mut Context<T>) -> T) -> Entity<T> {
46        let mut app = self.app.borrow_mut();
47        app.new(build_entity)
48    }
49
50    fn reserve_entity<T: 'static>(&mut self) -> crate::Reservation<T> {
51        let mut app = self.app.borrow_mut();
52        app.reserve_entity()
53    }
54
55    fn insert_entity<T: 'static>(
56        &mut self,
57        reservation: crate::Reservation<T>,
58        build_entity: impl FnOnce(&mut Context<T>) -> T,
59    ) -> Entity<T> {
60        let mut app = self.app.borrow_mut();
61        app.insert_entity(reservation, build_entity)
62    }
63
64    fn update_entity<T: 'static, R>(
65        &mut self,
66        handle: &Entity<T>,
67        update: impl FnOnce(&mut T, &mut Context<T>) -> R,
68    ) -> R {
69        let mut app = self.app.borrow_mut();
70        app.update_entity(handle, update)
71    }
72
73    fn as_mut<'a, T>(&'a mut self, _: &Entity<T>) -> super::GpuiBorrow<'a, T>
74    where
75        T: 'static,
76    {
77        panic!("Cannot use as_mut with a test app context. Try calling update() first")
78    }
79
80    fn read_entity<T, R>(&self, handle: &Entity<T>, read: impl FnOnce(&T, &App) -> R) -> R
81    where
82        T: 'static,
83    {
84        let app = self.app.borrow();
85        app.read_entity(handle, read)
86    }
87
88    fn notify(&mut self, entity_id: EntityId) {
89        let mut lock = self.app.borrow_mut();
90        lock.notify(entity_id)
91    }
92
93    fn emit<EntityType, EventType>(&mut self, entity: &Entity<EntityType>, event: EventType)
94    where
95        EntityType: EventEmitter<EventType>,
96        EventType: 'static,
97    {
98        let mut lock = self.app.borrow_mut();
99        lock.emit(entity, event)
100    }
101
102    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
103    where
104        F: FnOnce(AnyView, &mut Window, &mut App) -> T,
105    {
106        let mut lock = self.app.borrow_mut();
107        lock.update_window(window, f)
108    }
109
110    fn with_window<R>(
111        &mut self,
112        entity_id: EntityId,
113        f: impl FnOnce(&mut Window, &mut App) -> R,
114    ) -> Option<R> {
115        let mut lock = self.app.borrow_mut();
116        lock.with_window(entity_id, f)
117    }
118
119    fn read_window<T, R>(
120        &self,
121        window: &WindowHandle<T>,
122        read: impl FnOnce(Entity<T>, &App) -> R,
123    ) -> Result<R>
124    where
125        T: 'static,
126    {
127        let app = self.app.borrow();
128        app.read_window(window, read)
129    }
130
131    fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
132    where
133        R: Send + 'static,
134    {
135        self.background_executor.spawn(future)
136    }
137
138    fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> R
139    where
140        G: Global,
141    {
142        let app = self.app.borrow();
143        app.read_global(callback)
144    }
145
146    fn insert_global_entity<T: 'static>(&mut self, entity: Entity<T>) {
147        let mut lock = self.app.borrow_mut();
148        lock.insert_global_entity(entity)
149    }
150
151    fn remove_global_entity<T: 'static>(&mut self, entity: &Entity<T>) {
152        let mut lock = self.app.borrow_mut();
153        lock.remove_global_entity(entity)
154    }
155
156    fn global_entities<T: 'static>(&self) -> impl Iterator<Item = Entity<T>> {
157        let lock = self.app.borrow();
158        let iter = lock.global_entities();
159        // Cloning the iterator here so that lock can be released when function is going out of scope
160        iter.collect::<Vec<_>>().into_iter()
161    }
162}
163
164impl TestAppContext {
165    /// Creates a context that runs on `dispatcher`.
166    ///
167    /// `#[gpui::test]` calls this for you.
168    pub fn build(dispatcher: TestDispatcher, fn_name: Option<&'static str>) -> Self {
169        let arc_dispatcher = Arc::new(dispatcher.clone());
170        let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
171        let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
172        let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
173        Self::build_with_platform(
174            dispatcher,
175            fn_name,
176            background_executor,
177            foreground_executor,
178            platform,
179        )
180    }
181
182    /// Creates a test context backed by the provided platform text system.
183    pub fn build_with_text_system(
184        dispatcher: TestDispatcher,
185        fn_name: Option<&'static str>,
186        platform_text_system: Arc<dyn PlatformTextSystem>,
187    ) -> Self {
188        let arc_dispatcher = Arc::new(dispatcher.clone());
189        let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
190        let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
191        let platform = TestPlatform::with_text_system(
192            background_executor.clone(),
193            foreground_executor.clone(),
194            platform_text_system,
195        );
196        Self::build_with_platform(
197            dispatcher,
198            fn_name,
199            background_executor,
200            foreground_executor,
201            platform,
202        )
203    }
204
205    fn build_with_platform(
206        dispatcher: TestDispatcher,
207        fn_name: Option<&'static str>,
208        background_executor: BackgroundExecutor,
209        foreground_executor: ForegroundExecutor,
210        platform: Rc<TestPlatform>,
211    ) -> Self {
212        let http_client = crate::http_client::FakeHttpClient::with_404_response();
213        let text_system = Arc::new(TextSystem::new(platform.text_system()));
214
215        let app = App::new_app(
216            platform.clone(),
217            AssetRegistry::default().into(),
218            http_client,
219        );
220        app.borrow_mut().mode = GpuiMode::test();
221
222        Self {
223            app,
224            background_executor,
225            foreground_executor,
226            dispatcher,
227            test_platform: platform,
228            text_system,
229            fn_name,
230            on_quit: Rc::new(RefCell::new(Vec::default())),
231        }
232    }
233
234    /// Disables accessibility for subsequently created test windows.
235    pub fn disable_accessibility(&mut self) {
236        self.update(|cx| cx.accessibility_force_disabled = true);
237    }
238
239    /// Turns off drawing for this app for the rest of the test.
240    pub fn skip_drawing(&mut self) {
241        self.app.borrow_mut().mode = GpuiMode::Test { skip_drawing: true };
242    }
243
244    /// Creates a context with seed `0` that isn't tied to a test function.
245    ///
246    /// Use this in doctests and in code that runs outside `#[gpui::test]`.
247    pub fn single() -> Self {
248        let dispatcher = TestDispatcher::new(0);
249        Self::build(dispatcher, None)
250    }
251
252    /// Returns the name of the test function that created this context, if known.
253    pub fn test_function_name(&self) -> Option<&'static str> {
254        self.fn_name
255    }
256
257    /// Returns whether a new-path prompt is waiting for [`TestAppContext::simulate_new_path_selection`].
258    pub fn did_prompt_for_new_path(&self) -> bool {
259        self.test_platform.did_prompt_for_new_path()
260    }
261
262    /// Returns the number of active idle sleep prevention tokens.
263    pub fn active_idle_sleep_preventions(&self) -> usize {
264        self.test_platform.active_idle_sleep_preventions()
265    }
266
267    /// Sets the delay for subsequent idle sleep prevention acquisitions.
268    pub fn set_idle_sleep_prevention_delay(&self, delay: Duration) {
269        self.test_platform.set_idle_sleep_prevention_delay(delay);
270    }
271
272    /// Makes subsequent idle sleep prevention acquisitions fail or succeed.
273    pub fn set_idle_sleep_prevention_fails(&self, fails: bool) {
274        self.test_platform.set_idle_sleep_prevention_fails(fails);
275    }
276
277    /// Creates another app that shares this context's scheduler, so tasks from both apps interleave deterministically.
278    ///
279    /// The new app has its own state, windows and globals.
280    pub fn new_app(&self) -> TestAppContext {
281        Self::build(self.dispatcher.clone(), self.fn_name)
282    }
283
284    /// Runs the cleanup registered with [`TestAppContext::on_quit`], then shuts the app down.
285    ///
286    /// `#[gpui::test]` calls this when the test ends.
287    pub fn quit(&self) {
288        self.on_quit.borrow_mut().drain(..).for_each(|f| f());
289        self.app.borrow_mut().shutdown();
290    }
291
292    /// Registers `f` to run when the test ends, before the app shuts down.
293    pub fn on_quit(&mut self, f: impl FnOnce() + 'static) {
294        self.on_quit.borrow_mut().push(Box::new(f));
295    }
296
297    /// Marks every window for redraw on the next effect cycle. Always returns `Ok(())`.
298    pub fn refresh(&mut self) -> Result<()> {
299        let mut app = self.app.borrow_mut();
300        app.refresh_windows();
301        Ok(())
302    }
303
304    /// Returns a clone of the background executor.
305    pub fn executor(&self) -> BackgroundExecutor {
306        self.background_executor.clone()
307    }
308
309    /// Returns the foreground executor.
310    pub fn foreground_executor(&self) -> &ForegroundExecutor {
311        &self.foreground_executor
312    }
313
314    /// Runs `f` with mutable access to the [`App`].
315    ///
316    /// Unlike [`TestApp::update`](crate::TestApp::update), this doesn't run pending
317    /// tasks. Call [`TestAppContext::run_until_parked`] afterward if you need them.
318    ///
319    /// # Example
320    ///
321    /// ```
322    /// # use gpui::TestAppContext;
323    /// # use std::{cell::Cell, rc::Rc};
324    /// let cx = TestAppContext::single();
325    /// let finished = Rc::new(Cell::new(false));
326    ///
327    /// let flag = finished.clone();
328    /// cx.foreground_executor().spawn(async move { flag.set(true) }).detach();
329    ///
330    /// cx.update(|_| ());
331    /// assert!(!finished.get(), "update doesn't run pending tasks");
332    ///
333    /// cx.run_until_parked();
334    /// assert!(finished.get());
335    /// ```
336    pub fn update<R>(&self, f: impl FnOnce(&mut App) -> R) -> R {
337        let mut cx = self.app.borrow_mut();
338        cx.update(f)
339    }
340
341    /// Runs `f` with read-only access to the [`App`].
342    pub fn read<R>(&self, f: impl FnOnce(&App) -> R) -> R {
343        let cx = self.app.borrow();
344        f(&cx)
345    }
346
347    /// Opens a window with maximized bounds and returns its handle.
348    ///
349    /// The window is backed by a test platform window, so input and resize helpers
350    /// work on it. This doesn't run pending tasks.
351    ///
352    /// # Panics
353    ///
354    /// Panics if the window can't be opened.
355    pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
356    where
357        F: FnOnce(&mut Window, &mut Context<V>) -> V,
358        V: 'static + Render,
359    {
360        let mut cx = self.app.borrow_mut();
361
362        // Some tests rely on the window size matching the bounds of the test display
363        let bounds = Bounds::maximized(None, &cx);
364        cx.open_window(
365            WindowOptions::new().window_bounds(Some(WindowBounds::Windowed(bounds))),
366            |window, cx| cx.new(|cx| build_window(window, cx)),
367        )
368        .unwrap()
369    }
370
371    /// Opens a window of `window_size` at the origin and returns its handle.
372    ///
373    /// Use this when a layout test needs a known size. [`TestAppContext::add_window`]
374    /// uses the size of the test display instead. This doesn't run pending tasks.
375    ///
376    /// # Panics
377    ///
378    /// Panics if the window can't be opened.
379    pub fn open_window<F, V>(
380        &mut self,
381        window_size: Size<Pixels>,
382        build_window: F,
383    ) -> WindowHandle<V>
384    where
385        F: FnOnce(&mut Window, &mut Context<V>) -> V,
386        V: 'static + Render,
387    {
388        let mut cx = self.app.borrow_mut();
389        cx.open_window(
390            WindowOptions::new().window_bounds(Some(WindowBounds::Windowed(Bounds {
391                origin: Point::default(),
392                size: window_size,
393            }))),
394            |window, cx| cx.new(|cx| build_window(window, cx)),
395        )
396        .unwrap()
397    }
398
399    /// Opens a window with an empty root view and returns a [`VisualTestContext`] for it.
400    ///
401    /// Runs pending tasks before it returns.
402    pub fn add_empty_window(&mut self) -> &mut VisualTestContext {
403        let mut cx = self.app.borrow_mut();
404        let bounds = Bounds::maximized(None, &cx);
405        let window = cx
406            .open_window(
407                WindowOptions::new().window_bounds(Some(WindowBounds::Windowed(bounds))),
408                |_, cx| cx.new(|_| Empty),
409            )
410            .unwrap();
411        drop(cx);
412        let cx = VisualTestContext::from_window(*window.deref(), self).into_mut();
413        cx.run_until_parked();
414        cx
415    }
416
417    /// Opens a window whose root view `build_root_view` builds.
418    ///
419    /// Returns the root view's entity and a [`VisualTestContext`] for the window. Use
420    /// the returned context from then on, which is the usual pattern:
421    /// `let (view, cx) = cx.add_window_view(...);`. Runs pending tasks before it returns.
422    /// See [testing a window](crate::_documentation::guides::testing) for an example.
423    pub fn add_window_view<F, V>(
424        &mut self,
425        build_root_view: F,
426    ) -> (Entity<V>, &mut VisualTestContext)
427    where
428        F: FnOnce(&mut Window, &mut Context<V>) -> V,
429        V: 'static + Render,
430    {
431        let mut cx = self.app.borrow_mut();
432        let bounds = Bounds::maximized(None, &cx);
433        let window = cx
434            .open_window(
435                WindowOptions::new().window_bounds(Some(WindowBounds::Windowed(bounds))),
436                |window, cx| cx.new(|cx| build_root_view(window, cx)),
437            )
438            .unwrap();
439        drop(cx);
440        let view = window.root(self).unwrap();
441        let cx = VisualTestContext::from_window(*window.deref(), self).into_mut();
442        cx.run_until_parked();
443
444        // it might be nice to try and cleanup these at the end of each test.
445        (view, cx)
446    }
447
448    /// Returns the text system.
449    pub fn text_system(&self) -> &Arc<TextSystem> {
450        &self.text_system
451    }
452
453    /// Returns the cursor most recently applied to the test platform.
454    pub fn cursor_style(&self) -> crate::CursorStyle {
455        self.test_platform.cursor_style()
456    }
457
458    /// Writes `item` to the test clipboard.
459    pub fn write_to_clipboard(&self, item: ClipboardItem) {
460        self.test_platform.write_to_clipboard(item)
461    }
462
463    /// Returns the item most recently written to the test clipboard, or `None`.
464    pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
465        self.test_platform.read_from_clipboard()
466    }
467
468    /// Answers the oldest pending new-path prompt.
469    ///
470    /// `select_path` receives the directory the app asked about and returns the chosen
471    /// path, or `None` to cancel.
472    ///
473    /// # Panics
474    ///
475    /// Panics if no new-path prompt is pending.
476    pub fn simulate_new_path_selection(
477        &self,
478        select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
479    ) {
480        self.test_platform.simulate_new_path_selection(select_path);
481    }
482
483    /// Answers the oldest pending `prompt_for_paths` request.
484    ///
485    /// `select_paths` receives the prompt's options and returns the chosen paths, or
486    /// `None` to cancel.
487    ///
488    /// # Panics
489    ///
490    /// Panics if no paths prompt is pending. Also panics if `select_paths` returns more
491    /// than one path for a prompt that doesn't allow multiple selection.
492    pub fn simulate_path_prompt_response(
493        &self,
494        select_paths: impl FnOnce(&crate::PathPromptOptions) -> Option<Vec<std::path::PathBuf>>,
495    ) {
496        self.test_platform
497            .simulate_path_prompt_response(select_paths);
498    }
499
500    /// Returns whether a `prompt_for_paths` request is waiting for [`TestAppContext::simulate_path_prompt_response`].
501    pub fn did_prompt_for_paths(&self) -> bool {
502        self.test_platform.did_prompt_for_paths()
503    }
504
505    /// Presses the button labelled `button` on the oldest pending prompt.
506    ///
507    /// # Panics
508    ///
509    /// Panics if no prompt is pending, or if no button on the prompt has the label `button`.
510    #[track_caller]
511    pub fn simulate_prompt_answer(&self, button: &str) {
512        self.test_platform.simulate_prompt_answer(button);
513    }
514
515    /// Returns whether a prompt is waiting for an answer.
516    pub fn has_pending_prompt(&self) -> bool {
517        self.test_platform.has_pending_prompt()
518    }
519
520    /// Returns the message and detail of the oldest pending prompt, or `None`.
521    ///
522    /// The detail is an empty string when the prompt has none.
523    pub fn pending_prompt(&self) -> Option<(String, String)> {
524        self.test_platform.pending_prompt()
525    }
526
527    /// Returns the URL most recently passed to `open_url`, or `None`.
528    ///
529    /// Only the last URL is kept.
530    pub fn opened_url(&self) -> Option<String> {
531        self.test_platform.opened_url.borrow().clone()
532    }
533
534    /// Returns the identifier and name set with `set_app_identity`, or `None` if none was set.
535    pub fn app_identity(&self) -> Option<(SharedString, SharedString)> {
536        self.test_platform.app_identity()
537    }
538
539    /// Returns every system notification shown during the test, in order.
540    ///
541    /// A notification shown before the app has an identity is dropped and doesn't
542    /// appear here.
543    pub fn shown_system_notifications(&self) -> Vec<SystemNotification> {
544        self.test_platform.shown_system_notifications()
545    }
546
547    /// Returns the notifications that are shown and not yet dismissed.
548    ///
549    /// A notification with the same tag as an earlier one replaces it.
550    pub fn delivered_system_notifications(&self) -> Vec<SystemNotification> {
551        self.test_platform.delivered_system_notifications()
552    }
553
554    /// Returns the tags of the notifications dismissed during the test, in order.
555    pub fn dismissed_system_notifications(&self) -> Vec<SharedString> {
556        self.test_platform.dismissed_system_notifications()
557    }
558
559    /// Delivers `response` to the response handlers, as if the user had activated the notification.
560    pub fn simulate_system_notification_response(&self, response: SystemNotificationResponse) {
561        self.test_platform
562            .simulate_system_notification_response(response);
563    }
564
565    /// Resizes `window_handle` to `size`. See [`VisualTestContext::simulate_resize`].
566    pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {
567        self.test_window(window_handle).simulate_resize(size);
568    }
569
570    /// Simulates a change in whether the platform is presenting the window.
571    pub fn simulate_window_visibility_change(
572        &self,
573        window_handle: AnyWindowHandle,
574        visibility: WindowVisibility,
575    ) {
576        self.test_window(window_handle)
577            .simulate_visibility_change(visibility);
578    }
579
580    /// Simulates connecting a display.
581    pub fn simulate_display_added(
582        &self,
583        display_id: DisplayId,
584        refresh_interval: Option<std::time::Duration>,
585    ) {
586        self.test_platform
587            .simulate_display_added(display_id, refresh_interval);
588    }
589
590    #[cfg(test)]
591    pub(crate) fn test_platform(&self) -> &TestPlatform {
592        &self.test_platform
593    }
594
595    /// Simulates disconnecting a display.
596    pub fn simulate_display_removed(&self, display_id: DisplayId) {
597        self.test_platform.simulate_display_removed(display_id);
598    }
599
600    /// Simulates a connected display changing its refresh interval.
601    pub fn simulate_display_refresh_interval_change(
602        &self,
603        display_id: DisplayId,
604        refresh_interval: Option<std::time::Duration>,
605    ) {
606        self.test_platform
607            .simulate_display_refresh_interval_change(display_id, refresh_interval);
608    }
609
610    /// Simulates visible viewport changes without resizing the window's layout area.
611    pub fn simulate_window_visual_viewport_change(
612        &self,
613        window_handle: AnyWindowHandle,
614        bounds: Bounds<Pixels>,
615    ) {
616        self.test_window(window_handle)
617            .simulate_visual_viewport_change(bounds);
618    }
619
620    /// Simulates the window moving to a display with a different scale factor.
621    pub fn simulate_window_scale_factor_change(
622        &self,
623        window_handle: AnyWindowHandle,
624        scale_factor: f32,
625    ) {
626        self.test_window(window_handle)
627            .simulate_scale_factor_change(scale_factor);
628    }
629
630    /// Returns a receiver that gets the path and arguments of the next restart request.
631    ///
632    /// The app doesn't restart in a test. Calling this again replaces the previous
633    /// sender, so the previous receiver is cancelled.
634    pub fn expect_restart(&self) -> oneshot::Receiver<(Option<PathBuf>, Vec<std::ffi::OsString>)> {
635        let (tx, rx) = futures::channel::oneshot::channel();
636        self.test_platform.expect_restart.borrow_mut().replace(tx);
637        rx
638    }
639
640    /// Sets the sources that screen capture queries return.
641    pub fn set_screen_capture_sources(&self, sources: Vec<TestScreenCaptureSource>) {
642        self.test_platform.set_screen_capture_sources(sources);
643    }
644
645    /// Returns the handles of all open windows.
646    pub fn windows(&self) -> Vec<AnyWindowHandle> {
647        self.app.borrow().windows()
648    }
649
650    /// Spawns a future on the foreground executor, with an [`AsyncApp`] for this app.
651    ///
652    /// The future runs when tasks run, such as during [`TestAppContext::run_until_parked`]
653    /// or a `simulate_*` method. A plain `update` doesn't run it.
654    #[track_caller]
655    pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncApp) -> Fut) -> Task<R>
656    where
657        Fut: Future<Output = R> + 'static,
658        R: 'static,
659    {
660        self.foreground_executor.spawn(f(self.to_async()))
661    }
662
663    /// Returns whether a global of type `G` is set.
664    pub fn has_global<G: Global>(&self) -> bool {
665        let app = self.app.borrow();
666        app.has_global::<G>()
667    }
668
669    /// Runs `read` with the global of type `G`.
670    ///
671    /// # Panics
672    ///
673    /// Panics if no global of type `G` is set. Use [`TestAppContext::try_read_global`] when it might not be.
674    pub fn read_global<G: Global, R>(&self, read: impl FnOnce(&G, &App) -> R) -> R {
675        let app = self.app.borrow();
676        read(app.global(), &app)
677    }
678
679    /// Runs `read` with the global of type `G` if it is set, and returns `None` otherwise.
680    pub fn try_read_global<G: Global, R>(&self, read: impl FnOnce(&G, &App) -> R) -> Option<R> {
681        let lock = self.app.borrow();
682        Some(read(lock.try_global()?, &lock))
683    }
684
685    /// Sets the global of type `G`, replacing any previous value.
686    pub fn set_global<G: Global>(&mut self, global: G) {
687        let mut lock = self.app.borrow_mut();
688        lock.update(|cx| cx.set_global(global))
689    }
690
691    /// Runs `update` with mutable access to the global of type `G`.
692    ///
693    /// # Panics
694    ///
695    /// Panics if no global of type `G` is set.
696    pub fn update_global<G: Global, R>(&mut self, update: impl FnOnce(&mut G, &mut App) -> R) -> R {
697        let mut lock = self.app.borrow_mut();
698        lock.update(|cx| cx.update_global(update))
699    }
700
701    /// Returns an [`AsyncApp`] for this app.
702    ///
703    /// The handle holds a weak reference, so it can't keep the app alive.
704    pub fn to_async(&self) -> AsyncApp {
705        AsyncApp {
706            app: Rc::downgrade(&self.app),
707            background_executor: self.background_executor.clone(),
708            foreground_executor: self.foreground_executor.clone(),
709        }
710    }
711
712    /// Runs pending tasks until none can make progress.
713    ///
714    /// This doesn't move the simulated clock. Use `executor().advance_clock` for that.
715    /// Timers that haven't expired stay pending.
716    pub fn run_until_parked(&self) {
717        self.dispatcher.run_until_parked();
718    }
719
720    /// Dispatches `action` to the focused element in `window`, then runs pending tasks.
721    ///
722    /// # Panics
723    ///
724    /// Panics if `window` has been closed.
725    pub fn dispatch_action<A>(&mut self, window: AnyWindowHandle, action: A)
726    where
727        A: Action,
728    {
729        window
730            .update(self, |_, window, cx| {
731                window.dispatch_action(action.boxed_clone(), cx)
732            })
733            .unwrap();
734
735        self.background_executor.run_until_parked()
736    }
737
738    /// Sends each keystroke in a space-separated list to `window`, then runs pending tasks.
739    ///
740    /// Each part is parsed as in [`TestAppWindow::simulate_keystroke`](crate::TestAppWindow::simulate_keystroke).
741    /// The space bar is the key `"space"`, which can't appear in this list, because
742    /// a space separates keystrokes. Two spaces in a row send an empty keystroke.
743    ///
744    /// # Panics
745    ///
746    /// Panics if any part isn't a valid keystroke.
747    pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) {
748        for keystroke in keystrokes
749            .split(' ')
750            .map(Keystroke::parse)
751            .map(Result::unwrap)
752        {
753            self.dispatch_keystroke(window, keystroke);
754        }
755
756        self.background_executor.run_until_parked()
757    }
758
759    /// Types `input` into `window` by sending one keystroke per character, then runs pending tasks.
760    ///
761    /// Each character is parsed as a keystroke, so the text reaches text inputs. A
762    /// space is sent with the key `" "`, not `"space"`.
763    ///
764    /// Splitting on an empty string also sends an empty keystroke before the first
765    /// character and after the last. Don't rely on those.
766    pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) {
767        for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) {
768            self.dispatch_keystroke(window, keystroke);
769        }
770
771        self.background_executor.run_until_parked()
772    }
773
774    /// Dispatches one keystroke to the focused element in `window`.
775    ///
776    /// This doesn't run pending tasks. [`TestAppContext::simulate_keystrokes`] does.
777    pub fn dispatch_keystroke(&mut self, window: AnyWindowHandle, keystroke: Keystroke) {
778        self.update_window(window, |_, window, cx| {
779            window.dispatch_keystroke(keystroke, cx)
780        })
781        .unwrap();
782    }
783
784    /// Returns the `TestWindow` backing the given handle.
785    pub(crate) fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
786        self.app
787            .borrow_mut()
788            .windows
789            .get_mut(window.id)
790            .unwrap()
791            .as_deref_mut()
792            .unwrap()
793            .platform_window
794            .as_test()
795            .unwrap()
796            .clone()
797    }
798
799    /// Returns a stream that yields `()` each time `entity` notifies.
800    ///
801    /// The stream ends when the entity is released.
802    pub fn notifications<T: 'static>(
803        &mut self,
804        entity: &Entity<T>,
805    ) -> impl Stream<Item = ()> + use<T> {
806        let (tx, rx) = futures::channel::mpsc::unbounded();
807        self.update(|cx| {
808            cx.observe(entity, {
809                let tx = tx.clone();
810                move |_, _| {
811                    let _ = tx.unbounded_send(());
812                }
813            })
814            .detach();
815            cx.observe_release(entity, move |_, _| tx.close_channel())
816                .detach()
817        });
818        rx
819    }
820
821    /// Returns a stream of the events `entity` emits, cloned, in order.
822    pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
823        &mut self,
824        entity: &Entity<T>,
825    ) -> futures::channel::mpsc::UnboundedReceiver<Evt>
826    where
827        Evt: 'static + Clone,
828    {
829        let (tx, rx) = futures::channel::mpsc::unbounded();
830        entity
831            .update(self, |_, cx: &mut Context<T>| {
832                cx.subscribe(entity, move |_entity, _handle, event, _cx| {
833                    let _ = tx.unbounded_send(event.clone());
834                })
835            })
836            .detach();
837        rx
838    }
839
840    /// Waits until `predicate` returns `true` for `entity`, checking after each notification.
841    ///
842    /// # Panics
843    ///
844    /// Panics if the predicate isn't true within 3 seconds of simulated time, or if
845    /// the entity is released while waiting.
846    pub async fn condition<T: 'static>(
847        &mut self,
848        entity: &Entity<T>,
849        mut predicate: impl FnMut(&mut T, &mut Context<T>) -> bool,
850    ) {
851        let timer = self.executor().timer(Duration::from_secs(3));
852        let mut notifications = self.notifications(entity);
853
854        use futures::FutureExt as _;
855        use futures_concurrency::future::Race as _;
856
857        (
858            async {
859                loop {
860                    if entity.update(self, &mut predicate) {
861                        return Ok(());
862                    }
863
864                    if notifications.next().await.is_none() {
865                        bail!("entity dropped")
866                    }
867                }
868            },
869            timer.map(|_| Err(anyhow!("condition timed out"))),
870        )
871            .race()
872            .await
873            .unwrap();
874    }
875
876    /// Sets this app's name.
877    #[cfg(any(test, feature = "test-support"))]
878    pub fn set_name(&mut self, name: &'static str) {
879        self.update(|cx| cx.name = Some(name))
880    }
881}
882
883impl<T: 'static> Entity<T> {
884    /// Returns a future that resolves with the next event `self` emits.
885    ///
886    /// The subscription is made when you call this, not when you await the future, so
887    /// an event emitted in between is still received.
888    ///
889    /// # Panics
890    ///
891    /// Panics if the entity is released before it emits an event.
892    pub fn next_event<Event>(&self, cx: &mut TestAppContext) -> impl Future<Output = Event>
893    where
894        Event: Send + Clone + 'static,
895        T: EventEmitter<Event>,
896    {
897        let (tx, mut rx) = oneshot::channel();
898        let mut tx = Some(tx);
899        let subscription = self.update(cx, |_, cx| {
900            cx.subscribe(self, move |_, _, event, _| {
901                if let Some(tx) = tx.take() {
902                    _ = tx.send(event.clone());
903                }
904            })
905        });
906
907        async move {
908            let event = rx.await.expect("no event emitted");
909            drop(subscription);
910            event
911        }
912    }
913}
914
915impl<V: 'static> Entity<V> {
916    /// Returns a future that resolves on the entity's next notification.
917    ///
918    /// This advances the simulated clock by `advance_clock_by` as soon as you call it,
919    /// before the future is awaited. Use it when the notification comes from a timer
920    /// that the advance will fire.
921    ///
922    /// # Panics
923    ///
924    /// Panics if the entity is released while the future waits.
925    pub fn next_notification(
926        &self,
927        advance_clock_by: Duration,
928        cx: &TestAppContext,
929    ) -> impl Future<Output = ()> {
930        use postage::prelude::{Sink as _, Stream as _};
931
932        let (mut tx, mut rx) = postage::mpsc::channel(1);
933        let subscription = cx.app.borrow_mut().observe(self, move |_, _| {
934            tx.try_send(()).ok();
935        });
936
937        cx.executor().advance_clock(advance_clock_by);
938
939        async move {
940            rx.recv()
941                .await
942                .expect("entity dropped while test was waiting for its next notification");
943            drop(subscription);
944        }
945    }
946}
947
948impl<V> Entity<V> {
949    /// Returns a future that resolves when `predicate` returns `true` for this entity.
950    ///
951    /// The predicate is checked when the future is first polled, then again after
952    /// each notification or event from the entity.
953    ///
954    /// # Panics
955    ///
956    /// Panics if the entity is released while the future waits.
957    pub fn condition<Evt>(
958        &self,
959        cx: &TestAppContext,
960        mut predicate: impl FnMut(&V, &App) -> bool,
961    ) -> impl Future<Output = ()>
962    where
963        Evt: 'static,
964        V: EventEmitter<Evt>,
965    {
966        use postage::prelude::{Sink as _, Stream as _};
967
968        let (tx, mut rx) = postage::mpsc::channel(1024);
969
970        let mut cx = cx.app.borrow_mut();
971        let subscriptions = (
972            cx.observe(self, {
973                let mut tx = tx.clone();
974                move |_, _| {
975                    tx.blocking_send(()).ok();
976                }
977            }),
978            cx.subscribe(self, {
979                let mut tx = tx;
980                move |_, _: &Evt, _| {
981                    tx.blocking_send(()).ok();
982                }
983            }),
984        );
985
986        let cx = cx.this.upgrade().unwrap();
987        let handle = self.downgrade();
988
989        async move {
990            loop {
991                {
992                    let cx = cx.borrow();
993                    let cx = &*cx;
994                    if predicate(
995                        handle
996                            .upgrade()
997                            .expect("view dropped with pending condition")
998                            .read(cx),
999                        cx,
1000                    ) {
1001                        break;
1002                    }
1003                }
1004
1005                rx.recv()
1006                    .await
1007                    .expect("view dropped with pending condition");
1008            }
1009            drop(subscriptions);
1010        }
1011    }
1012}
1013
1014use derive_more::{Deref, DerefMut};
1015
1016use super::{Context, Entity};
1017#[derive(Deref, DerefMut, Clone)]
1018/// A [`TestAppContext`] bound to one window, the test equivalent of a [`Window`] and [`App`].
1019///
1020/// Use it to run window-specific test code. It implements [`VisualContext`] and
1021/// dereferences to the [`TestAppContext`] it wraps. Clones refer to the same window.
1022pub struct VisualTestContext {
1023    #[deref]
1024    #[deref_mut]
1025    /// The underlying [`TestAppContext`]. Methods on it are also available through deref.
1026    pub cx: TestAppContext,
1027    window: AnyWindowHandle,
1028}
1029
1030impl VisualTestContext {
1031    /// Returns the number of virtual keyboard show requests sent to this test window.
1032    pub fn virtual_keyboard_requests(&self) -> usize {
1033        self.cx.test_window(self.window).virtual_keyboard_requests()
1034    }
1035
1036    /// Runs `f` with the window and the app. This doesn't run pending tasks.
1037    pub fn update<R>(&mut self, f: impl FnOnce(&mut Window, &mut App) -> R) -> R {
1038        self.cx
1039            .update_window(self.window, |_, window, cx| f(window, cx))
1040            .unwrap()
1041    }
1042
1043    /// Creates a context for `window` that shares `cx`'s app.
1044    ///
1045    /// Shadow the [`TestAppContext`] with the result, which is usually what you want:
1046    /// `let cx = VisualTestContext::from_window(window, cx);`.
1047    pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self {
1048        Self {
1049            cx: cx.clone(),
1050            window,
1051        }
1052    }
1053
1054    /// Runs pending tasks until none can make progress.
1055    ///
1056    /// This runs the same scheduler as [`TestAppContext::run_until_parked`]. It doesn't move the simulated clock.
1057    pub fn run_until_parked(&self) {
1058        self.cx.background_executor.run_until_parked();
1059    }
1060
1061    /// Dispatches `action` to the focused element in this window, then runs pending tasks.
1062    pub fn dispatch_action<A>(&mut self, action: A)
1063    where
1064        A: Action,
1065    {
1066        self.cx.dispatch_action(self.window, action)
1067    }
1068
1069    /// Returns the title set with [`Window::set_window_title`], or `None` if none was set.
1070    pub fn window_title(&mut self) -> Option<String> {
1071        self.cx.test_window(self.window).0.lock().title.clone()
1072    }
1073
1074    /// Returns the document path set with [`Window::set_document_path`], or `None` if none was set.
1075    pub fn document_path(&mut self) -> Option<std::path::PathBuf> {
1076        self.cx
1077            .test_window(self.window)
1078            .0
1079            .lock()
1080            .document_path
1081            .clone()
1082    }
1083
1084    /// Sends a space-separated list of keystrokes to this window, then runs pending tasks.
1085    ///
1086    /// This is [`TestAppContext::simulate_keystrokes`] for this window. The same rules
1087    /// apply: a space separates keystrokes, so use the key name `"space"` only as one
1088    /// keystroke.
1089    pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
1090        self.cx.simulate_keystrokes(self.window, keystrokes)
1091    }
1092
1093    /// Types `input` into this window, one keystroke per character, then runs pending tasks.
1094    ///
1095    /// See [`TestAppContext::simulate_input`] for how characters are sent.
1096    pub fn simulate_input(&mut self, input: &str) {
1097        self.cx.simulate_input(self.window, input)
1098    }
1099
1100    /// Moves the pointer to `position`, with `button` held if it's `Some`.
1101    pub fn simulate_mouse_move(
1102        &mut self,
1103        position: Point<Pixels>,
1104        button: impl Into<Option<MouseButton>>,
1105        modifiers: Modifiers,
1106    ) {
1107        self.simulate_event(MouseMoveEvent {
1108            position,
1109            modifiers,
1110            pressed_button: button.into(),
1111        })
1112    }
1113
1114    /// Presses `button` at `position` with `modifiers` held. The click count is always `1`.
1115    pub fn simulate_mouse_down(
1116        &mut self,
1117        position: Point<Pixels>,
1118        button: MouseButton,
1119        modifiers: Modifiers,
1120    ) {
1121        self.simulate_event(MouseDownEvent {
1122            position,
1123            modifiers,
1124            button,
1125            click_count: 1,
1126            first_mouse: false,
1127        })
1128    }
1129
1130    /// Releases `button` at `position` with `modifiers` held. The click count is always `1`.
1131    pub fn simulate_mouse_up(
1132        &mut self,
1133        position: Point<Pixels>,
1134        button: MouseButton,
1135        modifiers: Modifiers,
1136    ) {
1137        self.simulate_event(MouseUpEvent {
1138            position,
1139            modifiers,
1140            button,
1141            click_count: 1,
1142        })
1143    }
1144
1145    /// Presses and releases the left button at `position`, with click count `1` on both events.
1146    pub fn simulate_click(&mut self, position: Point<Pixels>, modifiers: Modifiers) {
1147        self.simulate_event(MouseDownEvent {
1148            position,
1149            modifiers,
1150            button: MouseButton::Left,
1151            click_count: 1,
1152            first_mouse: false,
1153        });
1154        self.simulate_event(MouseUpEvent {
1155            position,
1156            modifiers,
1157            button: MouseButton::Left,
1158            click_count: 1,
1159        });
1160    }
1161
1162    /// Sends a modifiers-changed event with `modifiers`. Caps lock is reported as off.
1163    pub fn simulate_modifiers_change(&mut self, modifiers: Modifiers) {
1164        self.simulate_event(ModifiersChangedEvent {
1165            modifiers,
1166            capslock: Capslock { on: false },
1167        })
1168    }
1169
1170    /// Sends a modifiers-changed event with no modifiers and caps lock set to `on`.
1171    pub fn simulate_capslock_change(&mut self, on: bool) {
1172        self.simulate_event(ModifiersChangedEvent {
1173            modifiers: Modifiers::none(),
1174            capslock: Capslock { on },
1175        })
1176    }
1177
1178    /// Resizes the window to `size`. See [`TestAppContext::simulate_window_resize`].
1179    ///
1180    /// This doesn't paint the window. Call [`VisualTestContext::draw`] to paint at the new size.
1181    pub fn simulate_resize(&self, size: Size<Pixels>) {
1182        self.simulate_window_resize(self.window, size)
1183    }
1184
1185    /// Simulates a change in whether the platform is presenting this window.
1186    pub fn simulate_visibility_change(&self, visibility: WindowVisibility) {
1187        self.simulate_window_visibility_change(self.window, visibility);
1188    }
1189
1190    /// Simulates the window moving to a display with a different scale factor.
1191    pub fn simulate_scale_factor_change(&self, scale_factor: f32) {
1192        self.simulate_window_scale_factor_change(self.window, scale_factor)
1193    }
1194
1195    /// Returns the bounds of the element with `debug_selector(selector)` in the last painted frame, or `None`.
1196    ///
1197    /// Draw the view first, so that the frame contains the element. Use this to click
1198    /// an element without guessing its position. Builds without `debug_assertions` don't
1199    /// record selectors, so there this always returns `None`; see
1200    /// [`InteractiveElement::debug_selector`](crate::InteractiveElement::debug_selector).
1201    pub fn debug_bounds(&mut self, selector: &'static str) -> Option<Bounds<Pixels>> {
1202        self.update(|window, _| window.rendered_frame.debug_bounds.get(selector).copied())
1203    }
1204
1205    /// Lays out and paints `f`'s element as a root at `origin`, within `space`.
1206    ///
1207    /// Use this to paint an element on its own, without a root view. It returns the
1208    /// element's request-layout and prepaint state. The window is refreshed afterward.
1209    pub fn draw<E>(
1210        &mut self,
1211        origin: Point<Pixels>,
1212        space: impl Into<Size<AvailableSpace>>,
1213        f: impl FnOnce(&mut Window, &mut App) -> E,
1214    ) -> (E::RequestLayoutState, E::PrepaintState)
1215    where
1216        E: Element,
1217    {
1218        self.update(|window, cx| {
1219            let arena_scope = ElementArenaScope::enter(&cx.element_arena);
1220
1221            window.invalidator.set_phase(DrawPhase::Prepaint);
1222            let mut element = Drawable::new(f(window, cx));
1223            element.layout_as_root(space.into(), window, cx);
1224            window.with_absolute_element_offset(origin, |window| element.prepaint(window, cx));
1225
1226            window.invalidator.set_phase(DrawPhase::Paint);
1227            let (request_layout_state, prepaint_state) = element.paint(window, cx);
1228
1229            window.invalidator.set_phase(DrawPhase::None);
1230            window.refresh();
1231
1232            drop(element);
1233            arena_scope.exit(&cx.element_arena).clear(cx);
1234
1235            (request_layout_state, prepaint_state)
1236        })
1237    }
1238
1239    /// Sends `event` to the window as platform input, then runs pending tasks.
1240    ///
1241    /// Call [`VisualTestContext::draw`] first, so that hit tests see the current layout.
1242    pub fn simulate_event<E: InputEvent>(&mut self, event: E) {
1243        self.test_window(self.window)
1244            .simulate_input(event.to_platform_input());
1245        self.background_executor.run_until_parked();
1246    }
1247
1248    /// Makes this window inactive, as if the user had switched to another window.
1249    ///
1250    /// Does nothing if the window isn't active. Runs pending tasks.
1251    pub fn deactivate_window(&mut self) {
1252        if Some(self.window) == self.test_platform.active_window() {
1253            self.test_platform.set_active_window(None)
1254        }
1255        self.background_executor.run_until_parked();
1256    }
1257
1258    /// Runs the window's should-close callback and returns its result.
1259    ///
1260    /// Returns `false` if the window has no callback. This doesn't remove the window
1261    /// from the app, even when the callback returns `true`.
1262    pub fn simulate_close(&mut self) -> bool {
1263        let handler = self
1264            .cx
1265            .update_window(self.window, |_, window, _| {
1266                window
1267                    .platform_window
1268                    .as_test()
1269                    .unwrap()
1270                    .0
1271                    .lock()
1272                    .should_close_handler
1273                    .take()
1274            })
1275            .unwrap();
1276        if let Some(mut handler) = handler {
1277            let should_close = handler();
1278            self.cx
1279                .update_window(self.window, |_, window, _| {
1280                    window.platform_window.on_should_close(handler);
1281                })
1282                .unwrap();
1283            should_close
1284        } else {
1285            false
1286        }
1287    }
1288
1289    /// Leaks this context and returns a mutable reference that stays valid until the test ends.
1290    ///
1291    /// [`TestAppContext::add_window_view`] and [`TestAppContext::add_empty_window`] use
1292    /// this to return a context that borrows from the test. The memory is freed when
1293    /// the test quits.
1294    pub fn into_mut(self) -> &'static mut Self {
1295        let ptr = Box::into_raw(Box::new(self));
1296        // safety: on_quit will be called after the test has finished.
1297        // the executor will ensure that all tasks related to the test have stopped.
1298        // so there is no way for cx to be accessed after on_quit is called.
1299        // todo: This is unsound under stacked borrows (also tree borrows probably?)
1300        // the mutable reference invalidates `ptr` which is later used in the closure
1301        let cx = unsafe { &mut *ptr };
1302        cx.on_quit(move || unsafe {
1303            drop(Box::from_raw(ptr));
1304        });
1305        cx
1306    }
1307}
1308
1309impl AppContext for VisualTestContext {
1310    fn new<T: 'static>(&mut self, build_entity: impl FnOnce(&mut Context<T>) -> T) -> Entity<T> {
1311        self.window
1312            .update(&mut self.cx, |_, _, cx| cx.new(build_entity))
1313            .expect("window was unexpectedly closed")
1314    }
1315
1316    fn reserve_entity<T: 'static>(&mut self) -> crate::Reservation<T> {
1317        self.cx.reserve_entity()
1318    }
1319
1320    fn insert_entity<T: 'static>(
1321        &mut self,
1322        reservation: crate::Reservation<T>,
1323        build_entity: impl FnOnce(&mut Context<T>) -> T,
1324    ) -> Entity<T> {
1325        self.window
1326            .update(&mut self.cx, |_, _, cx| {
1327                cx.insert_entity(reservation, build_entity)
1328            })
1329            .expect("window was unexpectedly closed")
1330    }
1331
1332    fn update_entity<T, R>(
1333        &mut self,
1334        handle: &Entity<T>,
1335        update: impl FnOnce(&mut T, &mut Context<T>) -> R,
1336    ) -> R
1337    where
1338        T: 'static,
1339    {
1340        self.cx.update_entity(handle, update)
1341    }
1342
1343    fn as_mut<'a, T>(&'a mut self, handle: &Entity<T>) -> super::GpuiBorrow<'a, T>
1344    where
1345        T: 'static,
1346    {
1347        self.cx.as_mut(handle)
1348    }
1349
1350    fn read_entity<T, R>(&self, handle: &Entity<T>, read: impl FnOnce(&T, &App) -> R) -> R
1351    where
1352        T: 'static,
1353    {
1354        self.cx.read_entity(handle, read)
1355    }
1356
1357    fn notify(&mut self, entity_id: EntityId) {
1358        self.cx.notify(entity_id)
1359    }
1360
1361    fn emit<EntityType, EventType>(&mut self, entity: &Entity<EntityType>, event: EventType)
1362    where
1363        EntityType: EventEmitter<EventType>,
1364        EventType: 'static,
1365    {
1366        self.cx.emit(entity, event)
1367    }
1368
1369    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
1370    where
1371        F: FnOnce(AnyView, &mut Window, &mut App) -> T,
1372    {
1373        self.cx.update_window(window, f)
1374    }
1375
1376    fn with_window<R>(
1377        &mut self,
1378        entity_id: EntityId,
1379        f: impl FnOnce(&mut Window, &mut App) -> R,
1380    ) -> Option<R> {
1381        self.cx.with_window(entity_id, f)
1382    }
1383
1384    fn read_window<T, R>(
1385        &self,
1386        window: &WindowHandle<T>,
1387        read: impl FnOnce(Entity<T>, &App) -> R,
1388    ) -> Result<R>
1389    where
1390        T: 'static,
1391    {
1392        self.cx.read_window(window, read)
1393    }
1394
1395    fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
1396    where
1397        R: Send + 'static,
1398    {
1399        self.cx.background_spawn(future)
1400    }
1401
1402    fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> R
1403    where
1404        G: Global,
1405    {
1406        self.cx.read_global(callback)
1407    }
1408
1409    fn insert_global_entity<T: 'static>(&mut self, entity: Entity<T>) {
1410        self.cx.insert_global_entity(entity)
1411    }
1412
1413    fn remove_global_entity<T: 'static>(&mut self, entity: &Entity<T>) {
1414        self.cx.remove_global_entity(entity)
1415    }
1416
1417    fn global_entities<T: 'static>(&self) -> impl Iterator<Item = Entity<T>> {
1418        self.cx.global_entities()
1419    }
1420}
1421
1422impl VisualContext for VisualTestContext {
1423    type Result<T> = T;
1424
1425    /// Get the underlying window handle underlying this context.
1426    fn window_handle(&self) -> AnyWindowHandle {
1427        self.window
1428    }
1429
1430    fn new_window_entity<T: 'static>(
1431        &mut self,
1432        build_entity: impl FnOnce(&mut Window, &mut Context<T>) -> T,
1433    ) -> Entity<T> {
1434        self.window
1435            .update(&mut self.cx, |_, window, cx| {
1436                cx.new(|cx| build_entity(window, cx))
1437            })
1438            .expect("window was unexpectedly closed")
1439    }
1440
1441    fn update_window_entity<V: 'static, R>(
1442        &mut self,
1443        view: &Entity<V>,
1444        update: impl FnOnce(&mut V, &mut Window, &mut Context<V>) -> R,
1445    ) -> R {
1446        let view = view.clone();
1447        self.cx
1448            .app
1449            .borrow_mut()
1450            .with_window(view.entity_id(), |window, app| {
1451                view.update(app, |v, cx| update(v, window, cx))
1452            })
1453            .expect("entity has no current window; use `update` instead of `update_in`")
1454    }
1455
1456    fn replace_root_view<V>(
1457        &mut self,
1458        build_view: impl FnOnce(&mut Window, &mut Context<V>) -> V,
1459    ) -> Entity<V>
1460    where
1461        V: 'static + Render,
1462    {
1463        self.window
1464            .update(&mut self.cx, |_, window, cx| {
1465                window.replace_root(cx, build_view)
1466            })
1467            .expect("window was unexpectedly closed")
1468    }
1469
1470    fn focus<V: crate::Focusable>(&mut self, view: &Entity<V>) {
1471        self.window
1472            .update(&mut self.cx, |_, window, cx| {
1473                view.read(cx).focus_handle(cx).focus(window, cx)
1474            })
1475            .expect("window was unexpectedly closed")
1476    }
1477}
1478
1479impl AnyWindowHandle {
1480    /// Creates an entity with `build_view` in this window.
1481    ///
1482    /// # Panics
1483    ///
1484    /// Panics if the window has been closed.
1485    pub fn build_entity<V: Render + 'static>(
1486        &self,
1487        cx: &mut TestAppContext,
1488        build_view: impl FnOnce(&mut Window, &mut Context<V>) -> V,
1489    ) -> Entity<V> {
1490        self.update(cx, |_, window, cx| cx.new(|cx| build_view(window, cx)))
1491            .unwrap()
1492    }
1493}
1494
1495#[cfg(test)]
1496mod tests {
1497    use crate::{
1498        PathPromptOptions, SystemNotification, SystemNotificationAction,
1499        SystemNotificationResponse, TestAppContext,
1500    };
1501    use std::cell::RefCell;
1502    use std::path::PathBuf;
1503    use std::rc::Rc;
1504
1505    #[gpui::test]
1506    fn test_mobile_lifecycle_subscriptions(cx: &mut TestAppContext) {
1507        use crate::AppLifecyclePhase;
1508
1509        let phases = Rc::new(RefCell::new(Vec::new()));
1510        let subscription = cx.update(|cx| {
1511            cx.on_app_lifecycle({
1512                let phases = phases.clone();
1513                move |phase, cx| {
1514                    phases.borrow_mut().push(phase);
1515                    cx.defer(|cx| {
1516                        cx.set_global(LifecycleObserved);
1517                    });
1518                }
1519            })
1520        });
1521        struct LifecycleObserved;
1522        impl crate::Global for LifecycleObserved {}
1523
1524        let mut callback = cx
1525            .test_platform
1526            .app_lifecycle_callback
1527            .borrow_mut()
1528            .take()
1529            .expect("App registers the native lifecycle callback");
1530        for phase in [
1531            AppLifecyclePhase::Foreground,
1532            AppLifecyclePhase::Active,
1533            AppLifecyclePhase::Inactive,
1534            AppLifecyclePhase::Background,
1535            AppLifecyclePhase::Disconnected,
1536        ] {
1537            callback(phase);
1538        }
1539        assert_eq!(phases.borrow().len(), 5);
1540        assert_eq!(
1541            phases.borrow().last(),
1542            Some(&AppLifecyclePhase::Disconnected)
1543        );
1544        cx.update(|cx| assert!(cx.has_global::<LifecycleObserved>()));
1545        drop(subscription);
1546        callback(AppLifecyclePhase::Foreground);
1547        assert_eq!(phases.borrow().len(), 5);
1548    }
1549
1550    #[gpui::test]
1551    fn test_memory_warning_subscriptions(cx: &mut TestAppContext) {
1552        let warnings = Rc::new(std::cell::Cell::new(0));
1553        let subscription = cx.update(|cx| {
1554            cx.on_memory_warning({
1555                let warnings = warnings.clone();
1556                move |_| warnings.set(warnings.get() + 1)
1557            })
1558        });
1559        let mut callback = cx
1560            .test_platform
1561            .memory_warning_callback
1562            .borrow_mut()
1563            .take()
1564            .expect("App registers the native memory warning callback");
1565        callback();
1566        callback();
1567        assert_eq!(warnings.get(), 2);
1568        drop(subscription);
1569        callback();
1570        assert_eq!(warnings.get(), 2);
1571    }
1572
1573    #[gpui::test]
1574    async fn test_system_notifications_require_identity_and_replace_matching_tags(
1575        cx: &mut TestAppContext,
1576    ) {
1577        cx.update(|cx| {
1578            cx.show_system_notification(SystemNotification {
1579                tag: "thread-1".into(),
1580                title: "Task started".into(),
1581                body: "Running tests".into(),
1582                actions: Vec::new(),
1583            });
1584        });
1585        assert!(cx.shown_system_notifications().is_empty());
1586        assert!(cx.delivered_system_notifications().is_empty());
1587
1588        cx.update(|cx| {
1589            cx.set_app_identity("com.example.tasks", "Tasks");
1590            cx.show_system_notification(SystemNotification {
1591                tag: "thread-1".into(),
1592                title: "Task started".into(),
1593                body: "Running tests".into(),
1594                actions: Vec::new(),
1595            });
1596            cx.show_system_notification(SystemNotification {
1597                tag: "thread-1".into(),
1598                title: "Task finished".into(),
1599                body: "All tests passed".into(),
1600                actions: vec![SystemNotificationAction {
1601                    id: "open".into(),
1602                    label: "Open".into(),
1603                }],
1604            });
1605        });
1606
1607        assert_eq!(
1608            cx.app_identity(),
1609            Some(("com.example.tasks".into(), "Tasks".into()))
1610        );
1611        assert_eq!(cx.shown_system_notifications().len(), 2);
1612        assert_eq!(
1613            cx.delivered_system_notifications(),
1614            [SystemNotification {
1615                tag: "thread-1".into(),
1616                title: "Task finished".into(),
1617                body: "All tests passed".into(),
1618                actions: vec![SystemNotificationAction {
1619                    id: "open".into(),
1620                    label: "Open".into(),
1621                }],
1622            }]
1623        );
1624
1625        cx.update(|cx| cx.dismiss_system_notification("thread-1"));
1626        assert!(cx.delivered_system_notifications().is_empty());
1627        assert_eq!(cx.dismissed_system_notifications(), ["thread-1"]);
1628    }
1629
1630    #[gpui::test]
1631    async fn test_system_notification_body_and_action_responses(cx: &mut TestAppContext) {
1632        let responses = Rc::new(RefCell::new(Vec::new()));
1633        cx.update(|cx| {
1634            cx.on_system_notification_response({
1635                let responses = responses.clone();
1636                move |response, _cx| responses.borrow_mut().push(response)
1637            });
1638        });
1639
1640        cx.simulate_system_notification_response(SystemNotificationResponse {
1641            tag: "thread-1".into(),
1642            action_id: None,
1643        });
1644        cx.simulate_system_notification_response(SystemNotificationResponse {
1645            tag: "thread-1".into(),
1646            action_id: Some("default".into()),
1647        });
1648
1649        assert_eq!(
1650            responses.borrow().as_slice(),
1651            &[
1652                SystemNotificationResponse {
1653                    tag: "thread-1".into(),
1654                    action_id: None,
1655                },
1656                SystemNotificationResponse {
1657                    tag: "thread-1".into(),
1658                    action_id: Some("default".into()),
1659                },
1660            ]
1661        );
1662    }
1663
1664    #[gpui::test]
1665    async fn test_system_notification_response_handler_can_be_replaced(cx: &mut TestAppContext) {
1666        let first_responses = Rc::new(RefCell::new(Vec::new()));
1667        let second_responses = Rc::new(RefCell::new(Vec::new()));
1668        cx.update(|cx| {
1669            cx.on_system_notification_response({
1670                let first_responses = first_responses.clone();
1671                move |response, _cx| first_responses.borrow_mut().push(response)
1672            });
1673            cx.on_system_notification_response({
1674                let second_responses = second_responses.clone();
1675                move |response, _cx| second_responses.borrow_mut().push(response)
1676            });
1677        });
1678
1679        let response = SystemNotificationResponse {
1680            tag: "thread-1".into(),
1681            action_id: None,
1682        };
1683        cx.simulate_system_notification_response(response.clone());
1684
1685        assert!(first_responses.borrow().is_empty());
1686        assert_eq!(second_responses.borrow().as_slice(), &[response]);
1687    }
1688
1689    #[gpui::test]
1690    async fn test_system_notification_response_handler_can_reenter_app(cx: &mut TestAppContext) {
1691        cx.update(|cx| {
1692            cx.set_app_identity("com.example.tasks", "Tasks");
1693            cx.show_system_notification(SystemNotification {
1694                tag: "thread-1".into(),
1695                title: "Task finished".into(),
1696                body: "All tests passed".into(),
1697                actions: Vec::new(),
1698            });
1699            cx.on_system_notification_response(|response, cx| {
1700                cx.dismiss_system_notification(&response.tag);
1701            });
1702        });
1703
1704        cx.simulate_system_notification_response(SystemNotificationResponse {
1705            tag: "thread-1".into(),
1706            action_id: None,
1707        });
1708
1709        assert!(cx.delivered_system_notifications().is_empty());
1710        assert_eq!(cx.dismissed_system_notifications(), ["thread-1"]);
1711    }
1712
1713    #[gpui::test]
1714    async fn test_simulate_path_prompt_response(cx: &mut TestAppContext) {
1715        assert!(!cx.did_prompt_for_paths());
1716
1717        let receiver = cx.update(|cx| {
1718            cx.prompt_for_paths(PathPromptOptions {
1719                files: false,
1720                directories: true,
1721                multiple: true,
1722                prompt: None,
1723            })
1724        });
1725        assert!(cx.did_prompt_for_paths());
1726
1727        let selected = vec![PathBuf::from("/a"), PathBuf::from("/b")];
1728        cx.simulate_path_prompt_response({
1729            let selected = selected.clone();
1730            move |options| {
1731                assert!(options.multiple);
1732                Some(selected)
1733            }
1734        });
1735        assert!(!cx.did_prompt_for_paths());
1736
1737        let response = receiver.await.unwrap().unwrap();
1738        assert_eq!(response, Some(selected));
1739    }
1740
1741    #[gpui::test]
1742    async fn test_simulate_path_prompt_cancellation(cx: &mut TestAppContext) {
1743        let receiver = cx.update(|cx| {
1744            cx.prompt_for_paths(PathPromptOptions {
1745                files: true,
1746                directories: false,
1747                multiple: false,
1748                prompt: None,
1749            })
1750        });
1751
1752        cx.simulate_path_prompt_response(|_options| None);
1753
1754        let response = receiver.await.unwrap().unwrap();
1755        assert_eq!(response, None);
1756    }
1757}