test_context.rs

  1#![deny(missing_docs)]
  2
  3use crate::{
  4    Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
  5    AvailableSpace, BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter,
  6    ForegroundExecutor, InputEvent, Keystroke, Model, ModelContext, Pixels, Platform, Point,
  7    Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow, TextSystem, View,
  8    ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,
  9};
 10use anyhow::{anyhow, bail};
 11use futures::{Stream, StreamExt};
 12use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration};
 13
 14/// A TestAppContext is provided to tests created with `#[gpui::test]`, it provides
 15/// an implementation of `Context` with additional methods that are useful in tests.
 16#[derive(Clone)]
 17pub struct TestAppContext {
 18    #[doc(hidden)]
 19    pub app: Rc<AppCell>,
 20    #[doc(hidden)]
 21    pub background_executor: BackgroundExecutor,
 22    #[doc(hidden)]
 23    pub foreground_executor: ForegroundExecutor,
 24    #[doc(hidden)]
 25    pub dispatcher: TestDispatcher,
 26    test_platform: Rc<TestPlatform>,
 27    text_system: Arc<TextSystem>,
 28    fn_name: Option<&'static str>,
 29}
 30
 31impl Context for TestAppContext {
 32    type Result<T> = T;
 33
 34    fn new_model<T: 'static>(
 35        &mut self,
 36        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
 37    ) -> Self::Result<Model<T>>
 38    where
 39        T: 'static,
 40    {
 41        let mut app = self.app.borrow_mut();
 42        app.new_model(build_model)
 43    }
 44
 45    fn update_model<T: 'static, R>(
 46        &mut self,
 47        handle: &Model<T>,
 48        update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
 49    ) -> Self::Result<R> {
 50        let mut app = self.app.borrow_mut();
 51        app.update_model(handle, update)
 52    }
 53
 54    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
 55    where
 56        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
 57    {
 58        let mut lock = self.app.borrow_mut();
 59        lock.update_window(window, f)
 60    }
 61
 62    fn read_model<T, R>(
 63        &self,
 64        handle: &Model<T>,
 65        read: impl FnOnce(&T, &AppContext) -> R,
 66    ) -> Self::Result<R>
 67    where
 68        T: 'static,
 69    {
 70        let app = self.app.borrow();
 71        app.read_model(handle, read)
 72    }
 73
 74    fn read_window<T, R>(
 75        &self,
 76        window: &WindowHandle<T>,
 77        read: impl FnOnce(View<T>, &AppContext) -> R,
 78    ) -> Result<R>
 79    where
 80        T: 'static,
 81    {
 82        let app = self.app.borrow();
 83        app.read_window(window, read)
 84    }
 85}
 86
 87impl TestAppContext {
 88    /// Creates a new `TestAppContext`. Usually you can rely on `#[gpui::test]` to do this for you.
 89    pub fn new(dispatcher: TestDispatcher, fn_name: Option<&'static str>) -> Self {
 90        let arc_dispatcher = Arc::new(dispatcher.clone());
 91        let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
 92        let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
 93        let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
 94        let asset_source = Arc::new(());
 95        let http_client = util::http::FakeHttpClient::with_404_response();
 96        let text_system = Arc::new(TextSystem::new(platform.text_system()));
 97
 98        Self {
 99            app: AppContext::new(platform.clone(), asset_source, http_client),
100            background_executor,
101            foreground_executor,
102            dispatcher: dispatcher.clone(),
103            test_platform: platform,
104            text_system,
105            fn_name,
106        }
107    }
108
109    /// The name of the test function that created this `TestAppContext`
110    pub fn test_function_name(&self) -> Option<&'static str> {
111        self.fn_name
112    }
113
114    /// Checks whether there have been any new path prompts received by the platform.
115    pub fn did_prompt_for_new_path(&self) -> bool {
116        self.test_platform.did_prompt_for_new_path()
117    }
118
119    /// returns a new `TestAppContext` re-using the same executors to interleave tasks.
120    pub fn new_app(&self) -> TestAppContext {
121        Self::new(self.dispatcher.clone(), self.fn_name)
122    }
123
124    /// Simulates quitting the app.
125    pub fn quit(&self) {
126        self.app.borrow_mut().shutdown();
127    }
128
129    /// Schedules all windows to be redrawn on the next effect cycle.
130    pub fn refresh(&mut self) -> Result<()> {
131        let mut app = self.app.borrow_mut();
132        app.refresh();
133        Ok(())
134    }
135
136    /// Returns an executor (for running tasks in the background)
137    pub fn executor(&self) -> BackgroundExecutor {
138        self.background_executor.clone()
139    }
140
141    /// Returns an executor (for running tasks on the main thread)
142    pub fn foreground_executor(&self) -> &ForegroundExecutor {
143        &self.foreground_executor
144    }
145
146    /// Gives you an `&mut AppContext` for the duration of the closure
147    pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {
148        let mut cx = self.app.borrow_mut();
149        cx.update(f)
150    }
151
152    /// Gives you an `&AppContext` for the duration of the closure
153    pub fn read<R>(&self, f: impl FnOnce(&AppContext) -> R) -> R {
154        let cx = self.app.borrow();
155        f(&*cx)
156    }
157
158    /// Adds a new window. The Window will always be backed by a `TestWindow` which
159    /// can be retrieved with `self.test_window(handle)`
160    pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
161    where
162        F: FnOnce(&mut ViewContext<V>) -> V,
163        V: 'static + Render,
164    {
165        let mut cx = self.app.borrow_mut();
166        cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window))
167    }
168
169    /// Adds a new window with no content.
170    pub fn add_empty_window(&mut self) -> &mut VisualTestContext {
171        let mut cx = self.app.borrow_mut();
172        let window = cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| ()));
173        drop(cx);
174        let cx = Box::new(VisualTestContext::from_window(*window.deref(), self));
175        cx.run_until_parked();
176        // it might be nice to try and cleanup these at the end of each test.
177        Box::leak(cx)
178    }
179
180    /// Adds a new window, and returns its root view and a `VisualTestContext` which can be used
181    /// as a `WindowContext` for the rest of the test. Typically you would shadow this context with
182    /// the returned one. `let (view, cx) = cx.add_window_view(...);`
183    pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext)
184    where
185        F: FnOnce(&mut ViewContext<V>) -> V,
186        V: 'static + Render,
187    {
188        let mut cx = self.app.borrow_mut();
189        let window = cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window));
190        drop(cx);
191        let view = window.root_view(self).unwrap();
192        let cx = Box::new(VisualTestContext::from_window(*window.deref(), self));
193        cx.run_until_parked();
194        // it might be nice to try and cleanup these at the end of each test.
195        (view, Box::leak(cx))
196    }
197
198    /// returns the TextSystem
199    pub fn text_system(&self) -> &Arc<TextSystem> {
200        &self.text_system
201    }
202
203    /// Simulates writing to the platform clipboard
204    pub fn write_to_clipboard(&self, item: ClipboardItem) {
205        self.test_platform.write_to_clipboard(item)
206    }
207
208    /// Simulates reading from the platform clipboard.
209    /// This will return the most recent value from `write_to_clipboard`.
210    pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
211        self.test_platform.read_from_clipboard()
212    }
213
214    /// Simulates choosing a File in the platform's "Open" dialog.
215    pub fn simulate_new_path_selection(
216        &self,
217        select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
218    ) {
219        self.test_platform.simulate_new_path_selection(select_path);
220    }
221
222    /// Simulates clicking a button in an platform-level alert dialog.
223    pub fn simulate_prompt_answer(&self, button_ix: usize) {
224        self.test_platform.simulate_prompt_answer(button_ix);
225    }
226
227    /// Returns true if there's an alert dialog open.
228    pub fn has_pending_prompt(&self) -> bool {
229        self.test_platform.has_pending_prompt()
230    }
231
232    /// Simulates the user resizing the window to the new size.
233    pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {
234        self.test_window(window_handle).simulate_resize(size);
235    }
236
237    /// Returns all windows open in the test.
238    pub fn windows(&self) -> Vec<AnyWindowHandle> {
239        self.app.borrow().windows().clone()
240    }
241
242    /// Run the given task on the main thread.
243    pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
244    where
245        Fut: Future<Output = R> + 'static,
246        R: 'static,
247    {
248        self.foreground_executor.spawn(f(self.to_async()))
249    }
250
251    /// true if the given global is defined
252    pub fn has_global<G: 'static>(&self) -> bool {
253        let app = self.app.borrow();
254        app.has_global::<G>()
255    }
256
257    /// runs the given closure with a reference to the global
258    /// panics if `has_global` would return false.
259    pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R {
260        let app = self.app.borrow();
261        read(app.global(), &app)
262    }
263
264    /// runs the given closure with a reference to the global (if set)
265    pub fn try_read_global<G: 'static, R>(
266        &self,
267        read: impl FnOnce(&G, &AppContext) -> R,
268    ) -> Option<R> {
269        let lock = self.app.borrow();
270        Some(read(lock.try_global()?, &lock))
271    }
272
273    /// sets the global in this context.
274    pub fn set_global<G: 'static>(&mut self, global: G) {
275        let mut lock = self.app.borrow_mut();
276        lock.set_global(global);
277    }
278
279    /// updates the global in this context. (panics if `has_global` would return false)
280    pub fn update_global<G: 'static, R>(
281        &mut self,
282        update: impl FnOnce(&mut G, &mut AppContext) -> R,
283    ) -> R {
284        let mut lock = self.app.borrow_mut();
285        lock.update_global(update)
286    }
287
288    /// Returns an `AsyncAppContext` which can be used to run tasks that expect to be on a background
289    /// thread on the current thread in tests.
290    pub fn to_async(&self) -> AsyncAppContext {
291        AsyncAppContext {
292            app: Rc::downgrade(&self.app),
293            background_executor: self.background_executor.clone(),
294            foreground_executor: self.foreground_executor.clone(),
295        }
296    }
297
298    /// Wait until there are no more pending tasks.
299    pub fn run_until_parked(&mut self) {
300        self.background_executor.run_until_parked()
301    }
302
303    /// Simulate dispatching an action to the currently focused node in the window.
304    pub fn dispatch_action<A>(&mut self, window: AnyWindowHandle, action: A)
305    where
306        A: Action,
307    {
308        window
309            .update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
310            .unwrap();
311
312        self.background_executor.run_until_parked()
313    }
314
315    /// simulate_keystrokes takes a space-separated list of keys to type.
316    /// cx.simulate_keystrokes("cmd-shift-p b k s p enter")
317    /// in Zed, this will run backspace on the current editor through the command palette.
318    /// This will also run the background executor until it's parked.
319    pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) {
320        for keystroke in keystrokes
321            .split(" ")
322            .map(Keystroke::parse)
323            .map(Result::unwrap)
324        {
325            self.dispatch_keystroke(window, keystroke.into(), false);
326        }
327
328        self.background_executor.run_until_parked()
329    }
330
331    /// simulate_input takes a string of text to type.
332    /// cx.simulate_input("abc")
333    /// will type abc into your current editor
334    /// This will also run the background executor until it's parked.
335    pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) {
336        for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) {
337            self.dispatch_keystroke(window, keystroke.into(), false);
338        }
339
340        self.background_executor.run_until_parked()
341    }
342
343    /// dispatches a single Keystroke (see also `simulate_keystrokes` and `simulate_input`)
344    pub fn dispatch_keystroke(
345        &mut self,
346        window: AnyWindowHandle,
347        keystroke: Keystroke,
348        is_held: bool,
349    ) {
350        self.test_window(window)
351            .simulate_keystroke(keystroke, is_held)
352    }
353
354    /// Returns the `TestWindow` backing the given handle.
355    pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
356        self.app
357            .borrow_mut()
358            .windows
359            .get_mut(window.id)
360            .unwrap()
361            .as_mut()
362            .unwrap()
363            .platform_window
364            .as_test()
365            .unwrap()
366            .clone()
367    }
368
369    /// Returns a stream of notifications whenever the View or Model is updated.
370    pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
371        let (tx, rx) = futures::channel::mpsc::unbounded();
372        self.update(|cx| {
373            cx.observe(entity, {
374                let tx = tx.clone();
375                move |_, _| {
376                    let _ = tx.unbounded_send(());
377                }
378            })
379            .detach();
380            cx.observe_release(entity, move |_, _| tx.close_channel())
381                .detach()
382        });
383        rx
384    }
385
386    /// Retuens a stream of events emitted by the given Model.
387    pub fn events<Evt, T: 'static + EventEmitter<Evt>>(
388        &mut self,
389        entity: &Model<T>,
390    ) -> futures::channel::mpsc::UnboundedReceiver<Evt>
391    where
392        Evt: 'static + Clone,
393    {
394        let (tx, rx) = futures::channel::mpsc::unbounded();
395        entity
396            .update(self, |_, cx: &mut ModelContext<T>| {
397                cx.subscribe(entity, move |_model, _handle, event, _cx| {
398                    let _ = tx.unbounded_send(event.clone());
399                })
400            })
401            .detach();
402        rx
403    }
404
405    /// Runs until the given condition becomes true. (Prefer `run_until_parked` if you
406    /// don't need to jump in at a specific time).
407    pub async fn condition<T: 'static>(
408        &mut self,
409        model: &Model<T>,
410        mut predicate: impl FnMut(&mut T, &mut ModelContext<T>) -> bool,
411    ) {
412        let timer = self.executor().timer(Duration::from_secs(3));
413        let mut notifications = self.notifications(model);
414
415        use futures::FutureExt as _;
416        use smol::future::FutureExt as _;
417
418        async {
419            loop {
420                if model.update(self, &mut predicate) {
421                    return Ok(());
422                }
423
424                if notifications.next().await.is_none() {
425                    bail!("model dropped")
426                }
427            }
428        }
429        .race(timer.map(|_| Err(anyhow!("condition timed out"))))
430        .await
431        .unwrap();
432    }
433}
434
435impl<T: Send> Model<T> {
436    /// Block until the next event is emitted by the model, then return it.
437    pub fn next_event<Evt>(&self, cx: &mut TestAppContext) -> Evt
438    where
439        Evt: Send + Clone + 'static,
440        T: EventEmitter<Evt>,
441    {
442        let (tx, mut rx) = futures::channel::mpsc::unbounded();
443        let _subscription = self.update(cx, |_, cx| {
444            cx.subscribe(self, move |_, _, event, _| {
445                tx.unbounded_send(event.clone()).ok();
446            })
447        });
448
449        // Run other tasks until the event is emitted.
450        loop {
451            match rx.try_next() {
452                Ok(Some(event)) => return event,
453                Ok(None) => panic!("model was dropped"),
454                Err(_) => {
455                    if !cx.executor().tick() {
456                        break;
457                    }
458                }
459            }
460        }
461        panic!("no event received")
462    }
463}
464
465impl<V: 'static> View<V> {
466    /// Returns a future that resolves when the view is next updated.
467    pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
468        use postage::prelude::{Sink as _, Stream as _};
469
470        let (mut tx, mut rx) = postage::mpsc::channel(1);
471        let mut cx = cx.app.app.borrow_mut();
472        let subscription = cx.observe(self, move |_, _| {
473            tx.try_send(()).ok();
474        });
475
476        let duration = if std::env::var("CI").is_ok() {
477            Duration::from_secs(5)
478        } else {
479            Duration::from_secs(1)
480        };
481
482        async move {
483            let notification = crate::util::timeout(duration, rx.recv())
484                .await
485                .expect("next notification timed out");
486            drop(subscription);
487            notification.expect("model dropped while test was waiting for its next notification")
488        }
489    }
490}
491
492impl<V> View<V> {
493    /// Returns a future that resolves when the condition becomes true.
494    pub fn condition<Evt>(
495        &self,
496        cx: &TestAppContext,
497        mut predicate: impl FnMut(&V, &AppContext) -> bool,
498    ) -> impl Future<Output = ()>
499    where
500        Evt: 'static,
501        V: EventEmitter<Evt>,
502    {
503        use postage::prelude::{Sink as _, Stream as _};
504
505        let (tx, mut rx) = postage::mpsc::channel(1024);
506        let timeout_duration = Duration::from_millis(100);
507
508        let mut cx = cx.app.borrow_mut();
509        let subscriptions = (
510            cx.observe(self, {
511                let mut tx = tx.clone();
512                move |_, _| {
513                    tx.blocking_send(()).ok();
514                }
515            }),
516            cx.subscribe(self, {
517                let mut tx = tx.clone();
518                move |_, _: &Evt, _| {
519                    tx.blocking_send(()).ok();
520                }
521            }),
522        );
523
524        let cx = cx.this.upgrade().unwrap();
525        let handle = self.downgrade();
526
527        async move {
528            crate::util::timeout(timeout_duration, async move {
529                loop {
530                    {
531                        let cx = cx.borrow();
532                        let cx = &*cx;
533                        if predicate(
534                            handle
535                                .upgrade()
536                                .expect("view dropped with pending condition")
537                                .read(cx),
538                            cx,
539                        ) {
540                            break;
541                        }
542                    }
543
544                    cx.borrow().background_executor().start_waiting();
545                    rx.recv()
546                        .await
547                        .expect("view dropped with pending condition");
548                    cx.borrow().background_executor().finish_waiting();
549                }
550            })
551            .await
552            .expect("condition timed out");
553            drop(subscriptions);
554        }
555    }
556}
557
558use derive_more::{Deref, DerefMut};
559#[derive(Deref, DerefMut, Clone)]
560/// A VisualTestContext is the test-equivalent of a `WindowContext`. It allows you to
561/// run window-specific test code.
562pub struct VisualTestContext {
563    #[deref]
564    #[deref_mut]
565    /// cx is the original TestAppContext (you can more easily access this using Deref)
566    pub cx: TestAppContext,
567    window: AnyWindowHandle,
568}
569
570impl<'a> VisualTestContext {
571    /// Get the underlying window handle underlying this context.
572    pub fn handle(&self) -> AnyWindowHandle {
573        self.window
574    }
575
576    /// Provides the `WindowContext` for the duration of the closure.
577    pub fn update<R>(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R {
578        self.cx.update_window(self.window, |_, cx| f(cx)).unwrap()
579    }
580
581    /// Create a new VisualTestContext. You would typically shadow the passed in
582    /// TestAppContext with this, as this is typically more useful.
583    /// `let cx = VisualTestContext::from_window(window, cx);`
584    pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self {
585        Self {
586            cx: cx.clone(),
587            window,
588        }
589    }
590
591    /// Wait until there are no more pending tasks.
592    pub fn run_until_parked(&self) {
593        self.cx.background_executor.run_until_parked();
594    }
595
596    /// Dispatch the action to the currently focused node.
597    pub fn dispatch_action<A>(&mut self, action: A)
598    where
599        A: Action,
600    {
601        self.cx.dispatch_action(self.window, action)
602    }
603
604    /// Read the title off the window (set by `WindowContext#set_window_title`)
605    pub fn window_title(&mut self) -> Option<String> {
606        self.cx.test_window(self.window).0.lock().title.clone()
607    }
608
609    /// Simulate a sequence of keystrokes `cx.simulate_keystrokes("cmd-p escape")`
610    /// Automatically runs until parked.
611    pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
612        self.cx.simulate_keystrokes(self.window, keystrokes)
613    }
614
615    /// Simulate typing text `cx.simulate_input("hello")`
616    /// Automatically runs until parked.
617    pub fn simulate_input(&mut self, input: &str) {
618        self.cx.simulate_input(self.window, input)
619    }
620
621    /// Draw an element to the window. Useful for simulating events or actions
622    pub fn draw(
623        &mut self,
624        origin: Point<Pixels>,
625        space: Size<AvailableSpace>,
626        f: impl FnOnce(&mut WindowContext) -> AnyElement,
627    ) {
628        self.update(|cx| {
629            let entity_id = cx
630                .window
631                .root_view
632                .as_ref()
633                .expect("Can't draw to this window without a root view")
634                .entity_id();
635            cx.with_view_id(entity_id, |cx| {
636                f(cx).draw(origin, space, cx);
637            });
638
639            cx.refresh();
640        })
641    }
642
643    /// Simulate an event from the platform, e.g. a SrollWheelEvent
644    /// Make sure you've called [VisualTestContext::draw] first!
645    pub fn simulate_event<E: InputEvent>(&mut self, event: E) {
646        self.test_window(self.window)
647            .simulate_input(event.to_platform_input());
648        self.background_executor.run_until_parked();
649    }
650
651    /// Simulates the user blurring the window.
652    pub fn deactivate_window(&mut self) {
653        if Some(self.window) == self.test_platform.active_window() {
654            self.test_platform.set_active_window(None)
655        }
656        self.background_executor.run_until_parked();
657    }
658
659    /// Simulates the user closing the window.
660    /// Returns true if the window was closed.
661    pub fn simulate_close(&mut self) -> bool {
662        let handler = self
663            .cx
664            .update_window(self.window, |_, cx| {
665                cx.window
666                    .platform_window
667                    .as_test()
668                    .unwrap()
669                    .0
670                    .lock()
671                    .should_close_handler
672                    .take()
673            })
674            .unwrap();
675        if let Some(mut handler) = handler {
676            let should_close = handler();
677            self.cx
678                .update_window(self.window, |_, cx| {
679                    cx.window.platform_window.on_should_close(handler);
680                })
681                .unwrap();
682            should_close
683        } else {
684            false
685        }
686    }
687}
688
689impl Context for VisualTestContext {
690    type Result<T> = <TestAppContext as Context>::Result<T>;
691
692    fn new_model<T: 'static>(
693        &mut self,
694        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
695    ) -> Self::Result<Model<T>> {
696        self.cx.new_model(build_model)
697    }
698
699    fn update_model<T, R>(
700        &mut self,
701        handle: &Model<T>,
702        update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
703    ) -> Self::Result<R>
704    where
705        T: 'static,
706    {
707        self.cx.update_model(handle, update)
708    }
709
710    fn read_model<T, R>(
711        &self,
712        handle: &Model<T>,
713        read: impl FnOnce(&T, &AppContext) -> R,
714    ) -> Self::Result<R>
715    where
716        T: 'static,
717    {
718        self.cx.read_model(handle, read)
719    }
720
721    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
722    where
723        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
724    {
725        self.cx.update_window(window, f)
726    }
727
728    fn read_window<T, R>(
729        &self,
730        window: &WindowHandle<T>,
731        read: impl FnOnce(View<T>, &AppContext) -> R,
732    ) -> Result<R>
733    where
734        T: 'static,
735    {
736        self.cx.read_window(window, read)
737    }
738}
739
740impl VisualContext for VisualTestContext {
741    fn new_view<V>(
742        &mut self,
743        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
744    ) -> Self::Result<View<V>>
745    where
746        V: 'static + Render,
747    {
748        self.window
749            .update(&mut self.cx, |_, cx| cx.new_view(build_view))
750            .unwrap()
751    }
752
753    fn update_view<V: 'static, R>(
754        &mut self,
755        view: &View<V>,
756        update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
757    ) -> Self::Result<R> {
758        self.window
759            .update(&mut self.cx, |_, cx| cx.update_view(view, update))
760            .unwrap()
761    }
762
763    fn replace_root_view<V>(
764        &mut self,
765        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
766    ) -> Self::Result<View<V>>
767    where
768        V: 'static + Render,
769    {
770        self.window
771            .update(&mut self.cx, |_, cx| cx.replace_root_view(build_view))
772            .unwrap()
773    }
774
775    fn focus_view<V: crate::FocusableView>(&mut self, view: &View<V>) -> Self::Result<()> {
776        self.window
777            .update(&mut self.cx, |_, cx| {
778                view.read(cx).focus_handle(cx).clone().focus(cx)
779            })
780            .unwrap()
781    }
782
783    fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
784    where
785        V: crate::ManagedView,
786    {
787        self.window
788            .update(&mut self.cx, |_, cx| {
789                view.update(cx, |_, cx| cx.emit(crate::DismissEvent))
790            })
791            .unwrap()
792    }
793}
794
795impl AnyWindowHandle {
796    /// Creates the given view in this window.
797    pub fn build_view<V: Render + 'static>(
798        &self,
799        cx: &mut TestAppContext,
800        build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
801    ) -> View<V> {
802        self.update(cx, |_, cx| cx.new_view(build_view)).unwrap()
803    }
804}