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