test_context.rs

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