Tidy up TestContext lifecycle

Conrad Irwin and Max created

Co-Authored-By: Max <max@zed.dev>

Change summary

crates/collab/src/tests/following_tests.rs                | 18 ++++-
crates/editor/src/editor_tests.rs                         |  8 +-
crates/editor/src/test/editor_lsp_test_context.rs         | 24 +++---
crates/editor/src/test/editor_test_context.rs             | 12 +-
crates/file_finder/src/file_finder.rs                     |  2 
crates/gpui/src/app/test_context.rs                       | 29 ++++----
crates/search/src/buffer_search.rs                        |  7 -
crates/vim/src/test/neovim_backed_binding_test_context.rs | 21 ++---
crates/vim/src/test/neovim_backed_test_context.rs         | 21 +++---
crates/vim/src/test/vim_test_context.rs                   | 18 ++--
10 files changed, 82 insertions(+), 78 deletions(-)

Detailed changes

crates/collab/src/tests/following_tests.rs 🔗

@@ -4,7 +4,7 @@ use collab_ui::notifications::project_shared_notification::ProjectSharedNotifica
 use editor::{Editor, ExcerptRange, MultiBuffer};
 use gpui::{
     point, BackgroundExecutor, Context, SharedString, TestAppContext, View, VisualContext,
-    VisualTestContext, WindowContext,
+    VisualTestContext,
 };
 use language::Capability;
 use live_kit_client::MacOSDisplay;
@@ -12,7 +12,6 @@ use project::project_settings::ProjectSettings;
 use rpc::proto::PeerId;
 use serde_json::json;
 use settings::SettingsStore;
-use std::borrow::Cow;
 use workspace::{
     dock::{test::TestPanel, DockPosition},
     item::{test::TestItem, ItemHandle as _},
@@ -1433,6 +1432,15 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
     });
 
     executor.run_until_parked();
+    let window_b_project_a = cx_b
+        .windows()
+        .iter()
+        .max_by_key(|window| window.window_id())
+        .unwrap()
+        .clone();
+
+    let mut cx_b_project_a = VisualTestContext::from_window(window_b_project_a, cx_b);
+
     let workspace_b_project_a = cx_b
         .windows()
         .iter()
@@ -1444,7 +1452,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
         .unwrap();
 
     // assert that b is following a in project a in w.rs
-    workspace_b_project_a.update(cx_b, |workspace, cx| {
+    workspace_b_project_a.update(&mut cx_b_project_a, |workspace, cx| {
         assert!(workspace.is_being_followed(client_a.peer_id().unwrap()));
         assert_eq!(
             client_a.peer_id(),
@@ -1459,7 +1467,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
 
     // TODO: in app code, this would be done by the collab_ui.
     active_call_b
-        .update(cx_b, |call, cx| {
+        .update(&mut cx_b_project_a, |call, cx| {
             let project = workspace_b_project_a.read(cx).project().clone();
             call.set_location(Some(&project), cx)
         })
@@ -1738,7 +1746,7 @@ fn followers_by_leader(project_id: u64, cx: &TestAppContext) -> Vec<(PeerId, Vec
     })
 }
 
-fn pane_summaries(workspace: &View<Workspace>, cx: &mut VisualTestContext<'_>) -> Vec<PaneSummary> {
+fn pane_summaries(workspace: &View<Workspace>, cx: &mut VisualTestContext) -> Vec<PaneSummary> {
     workspace.update(cx, |workspace, cx| {
         let active_pane = workspace.active_pane();
         workspace

crates/editor/src/editor_tests.rs 🔗

@@ -8131,8 +8131,8 @@ fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewCo
 /// Handle completion request passing a marked string specifying where the completion
 /// should be triggered from using '|' character, what range should be replaced, and what completions
 /// should be returned using '<' and '>' to delimit the range
-pub fn handle_completion_request<'a>(
-    cx: &mut EditorLspTestContext<'a>,
+pub fn handle_completion_request(
+    cx: &mut EditorLspTestContext,
     marked_string: &str,
     completions: Vec<&'static str>,
 ) -> impl Future<Output = ()> {
@@ -8177,8 +8177,8 @@ pub fn handle_completion_request<'a>(
     }
 }
 
-fn handle_resolve_completion_request<'a>(
-    cx: &mut EditorLspTestContext<'a>,
+fn handle_resolve_completion_request(
+    cx: &mut EditorLspTestContext,
     edits: Option<Vec<(&'static str, &'static str)>>,
 ) -> impl Future<Output = ()> {
     let edits = edits.map(|edits| {

crates/editor/src/test/editor_lsp_test_context.rs 🔗

@@ -21,19 +21,19 @@ use workspace::{AppState, Workspace, WorkspaceHandle};
 
 use super::editor_test_context::{AssertionContextManager, EditorTestContext};
 
-pub struct EditorLspTestContext<'a> {
-    pub cx: EditorTestContext<'a>,
+pub struct EditorLspTestContext {
+    pub cx: EditorTestContext,
     pub lsp: lsp::FakeLanguageServer,
     pub workspace: View<Workspace>,
     pub buffer_lsp_url: lsp::Url,
 }
 
-impl<'a> EditorLspTestContext<'a> {
+impl EditorLspTestContext {
     pub async fn new(
         mut language: Language,
         capabilities: lsp::ServerCapabilities,
-        cx: &'a mut gpui::TestAppContext,
-    ) -> EditorLspTestContext<'a> {
+        cx: &mut gpui::TestAppContext,
+    ) -> EditorLspTestContext {
         let app_state = cx.update(AppState::test);
 
         cx.update(|cx| {
@@ -110,8 +110,8 @@ impl<'a> EditorLspTestContext<'a> {
 
     pub async fn new_rust(
         capabilities: lsp::ServerCapabilities,
-        cx: &'a mut gpui::TestAppContext,
-    ) -> EditorLspTestContext<'a> {
+        cx: &mut gpui::TestAppContext,
+    ) -> EditorLspTestContext {
         let language = Language::new(
             LanguageConfig {
                 name: "Rust".into(),
@@ -152,8 +152,8 @@ impl<'a> EditorLspTestContext<'a> {
 
     pub async fn new_typescript(
         capabilities: lsp::ServerCapabilities,
-        cx: &'a mut gpui::TestAppContext,
-    ) -> EditorLspTestContext<'a> {
+        cx: &mut gpui::TestAppContext,
+    ) -> EditorLspTestContext {
         let mut word_characters: HashSet<char> = Default::default();
         word_characters.insert('$');
         word_characters.insert('#');
@@ -283,15 +283,15 @@ impl<'a> EditorLspTestContext<'a> {
     }
 }
 
-impl<'a> Deref for EditorLspTestContext<'a> {
-    type Target = EditorTestContext<'a>;
+impl Deref for EditorLspTestContext {
+    type Target = EditorTestContext;
 
     fn deref(&self) -> &Self::Target {
         &self.cx
     }
 }
 
-impl<'a> DerefMut for EditorLspTestContext<'a> {
+impl DerefMut for EditorLspTestContext {
     fn deref_mut(&mut self) -> &mut Self::Target {
         &mut self.cx
     }

crates/editor/src/test/editor_test_context.rs 🔗

@@ -26,15 +26,15 @@ use util::{
 
 use super::build_editor_with_project;
 
-pub struct EditorTestContext<'a> {
-    pub cx: gpui::VisualTestContext<'a>,
+pub struct EditorTestContext {
+    pub cx: gpui::VisualTestContext,
     pub window: AnyWindowHandle,
     pub editor: View<Editor>,
     pub assertion_cx: AssertionContextManager,
 }
 
-impl<'a> EditorTestContext<'a> {
-    pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
+impl EditorTestContext {
+    pub async fn new(cx: &mut gpui::TestAppContext) -> EditorTestContext {
         let fs = FakeFs::new(cx.executor());
         // fs.insert_file("/file", "".to_owned()).await;
         fs.insert_tree(
@@ -342,7 +342,7 @@ impl<'a> EditorTestContext<'a> {
     }
 }
 
-impl<'a> Deref for EditorTestContext<'a> {
+impl Deref for EditorTestContext {
     type Target = gpui::TestAppContext;
 
     fn deref(&self) -> &Self::Target {
@@ -350,7 +350,7 @@ impl<'a> Deref for EditorTestContext<'a> {
     }
 }
 
-impl<'a> DerefMut for EditorTestContext<'a> {
+impl DerefMut for EditorTestContext {
     fn deref_mut(&mut self) -> &mut Self::Target {
         &mut self.cx
     }

crates/file_finder/src/file_finder.rs 🔗

@@ -1843,7 +1843,7 @@ mod tests {
         expected_matches: usize,
         expected_editor_title: &str,
         workspace: &View<Workspace>,
-        cx: &mut gpui::VisualTestContext<'_>,
+        cx: &mut gpui::VisualTestContext,
     ) -> Vec<FoundPath> {
         let picker = open_file_picker(&workspace, cx);
         cx.simulate_input(input);

crates/gpui/src/app/test_context.rs 🔗

@@ -483,21 +483,24 @@ impl<V> View<V> {
 }
 
 use derive_more::{Deref, DerefMut};
-#[derive(Deref, DerefMut)]
-pub struct VisualTestContext<'a> {
+#[derive(Deref, DerefMut, Clone)]
+pub struct VisualTestContext {
     #[deref]
     #[deref_mut]
-    cx: &'a mut TestAppContext,
+    cx: TestAppContext,
     window: AnyWindowHandle,
 }
 
-impl<'a> VisualTestContext<'a> {
+impl<'a> VisualTestContext {
     pub fn update<R>(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R {
         self.cx.update_window(self.window, |_, cx| f(cx)).unwrap()
     }
 
-    pub fn from_window(window: AnyWindowHandle, cx: &'a mut TestAppContext) -> Self {
-        Self { cx, window }
+    pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self {
+        Self {
+            cx: cx.clone(),
+            window,
+        }
     }
 
     pub fn run_until_parked(&self) {
@@ -531,7 +534,7 @@ impl<'a> VisualTestContext<'a> {
     }
 }
 
-impl<'a> Context for VisualTestContext<'a> {
+impl Context for VisualTestContext {
     type Result<T> = <TestAppContext as Context>::Result<T>;
 
     fn new_model<T: 'static>(
@@ -582,7 +585,7 @@ impl<'a> Context for VisualTestContext<'a> {
     }
 }
 
-impl<'a> VisualContext for VisualTestContext<'a> {
+impl VisualContext for VisualTestContext {
     fn new_view<V>(
         &mut self,
         build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
@@ -591,7 +594,7 @@ impl<'a> VisualContext for VisualTestContext<'a> {
         V: 'static + Render,
     {
         self.window
-            .update(self.cx, |_, cx| cx.new_view(build_view))
+            .update(&mut self.cx, |_, cx| cx.new_view(build_view))
             .unwrap()
     }
 
@@ -601,7 +604,7 @@ impl<'a> VisualContext for VisualTestContext<'a> {
         update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
     ) -> Self::Result<R> {
         self.window
-            .update(self.cx, |_, cx| cx.update_view(view, update))
+            .update(&mut self.cx, |_, cx| cx.update_view(view, update))
             .unwrap()
     }
 
@@ -613,13 +616,13 @@ impl<'a> VisualContext for VisualTestContext<'a> {
         V: 'static + Render,
     {
         self.window
-            .update(self.cx, |_, cx| cx.replace_root_view(build_view))
+            .update(&mut self.cx, |_, cx| cx.replace_root_view(build_view))
             .unwrap()
     }
 
     fn focus_view<V: crate::FocusableView>(&mut self, view: &View<V>) -> Self::Result<()> {
         self.window
-            .update(self.cx, |_, cx| {
+            .update(&mut self.cx, |_, cx| {
                 view.read(cx).focus_handle(cx).clone().focus(cx)
             })
             .unwrap()
@@ -630,7 +633,7 @@ impl<'a> VisualContext for VisualTestContext<'a> {
         V: crate::ManagedView,
     {
         self.window
-            .update(self.cx, |_, cx| {
+            .update(&mut self.cx, |_, cx| {
                 view.update(cx, |_, cx| cx.emit(crate::DismissEvent))
             })
             .unwrap()

crates/search/src/buffer_search.rs 🔗

@@ -1091,13 +1091,10 @@ mod tests {
             theme::init(theme::LoadThemes::JustBase, cx);
         });
     }
+
     fn init_test(
         cx: &mut TestAppContext,
-    ) -> (
-        View<Editor>,
-        View<BufferSearchBar>,
-        &mut VisualTestContext<'_>,
-    ) {
+    ) -> (View<Editor>, View<BufferSearchBar>, &mut VisualTestContext) {
         init_globals(cx);
         let buffer = cx.new_model(|cx| {
             Buffer::new(

crates/vim/src/test/neovim_backed_binding_test_context.rs 🔗

@@ -4,30 +4,27 @@ use crate::state::Mode;
 
 use super::{ExemptionFeatures, NeovimBackedTestContext, SUPPORTED_FEATURES};
 
-pub struct NeovimBackedBindingTestContext<'a, const COUNT: usize> {
-    cx: NeovimBackedTestContext<'a>,
+pub struct NeovimBackedBindingTestContext<const COUNT: usize> {
+    cx: NeovimBackedTestContext,
     keystrokes_under_test: [&'static str; COUNT],
 }
 
-impl<'a, const COUNT: usize> NeovimBackedBindingTestContext<'a, COUNT> {
-    pub fn new(
-        keystrokes_under_test: [&'static str; COUNT],
-        cx: NeovimBackedTestContext<'a>,
-    ) -> Self {
+impl<const COUNT: usize> NeovimBackedBindingTestContext<COUNT> {
+    pub fn new(keystrokes_under_test: [&'static str; COUNT], cx: NeovimBackedTestContext) -> Self {
         Self {
             cx,
             keystrokes_under_test,
         }
     }
 
-    pub fn consume(self) -> NeovimBackedTestContext<'a> {
+    pub fn consume(self) -> NeovimBackedTestContext {
         self.cx
     }
 
     pub fn binding<const NEW_COUNT: usize>(
         self,
         keystrokes: [&'static str; NEW_COUNT],
-    ) -> NeovimBackedBindingTestContext<'a, NEW_COUNT> {
+    ) -> NeovimBackedBindingTestContext<NEW_COUNT> {
         self.consume().binding(keystrokes)
     }
 
@@ -80,15 +77,15 @@ impl<'a, const COUNT: usize> NeovimBackedBindingTestContext<'a, COUNT> {
     }
 }
 
-impl<'a, const COUNT: usize> Deref for NeovimBackedBindingTestContext<'a, COUNT> {
-    type Target = NeovimBackedTestContext<'a>;
+impl<const COUNT: usize> Deref for NeovimBackedBindingTestContext<COUNT> {
+    type Target = NeovimBackedTestContext;
 
     fn deref(&self) -> &Self::Target {
         &self.cx
     }
 }
 
-impl<'a, const COUNT: usize> DerefMut for NeovimBackedBindingTestContext<'a, COUNT> {
+impl<const COUNT: usize> DerefMut for NeovimBackedBindingTestContext<COUNT> {
     fn deref_mut(&mut self) -> &mut Self::Target {
         &mut self.cx
     }

crates/vim/src/test/neovim_backed_test_context.rs 🔗

@@ -47,8 +47,8 @@ impl ExemptionFeatures {
     }
 }
 
-pub struct NeovimBackedTestContext<'a> {
-    cx: VimTestContext<'a>,
+pub struct NeovimBackedTestContext {
+    cx: VimTestContext,
     // Lookup for exempted assertions. Keyed by the insertion text, and with a value indicating which
     // bindings are exempted. If None, all bindings are ignored for that insertion text.
     exemptions: HashMap<String, Option<HashSet<String>>>,
@@ -60,8 +60,8 @@ pub struct NeovimBackedTestContext<'a> {
     is_dirty: bool,
 }
 
-impl<'a> NeovimBackedTestContext<'a> {
-    pub async fn new(cx: &'a mut gpui::TestAppContext) -> NeovimBackedTestContext<'a> {
+impl NeovimBackedTestContext {
+    pub async fn new(cx: &mut gpui::TestAppContext) -> NeovimBackedTestContext {
         // rust stores the name of the test on the current thread.
         // We use this to automatically name a file that will store
         // the neovim connection's requests/responses so that we can
@@ -393,20 +393,20 @@ impl<'a> NeovimBackedTestContext<'a> {
     pub fn binding<const COUNT: usize>(
         self,
         keystrokes: [&'static str; COUNT],
-    ) -> NeovimBackedBindingTestContext<'a, COUNT> {
+    ) -> NeovimBackedBindingTestContext<COUNT> {
         NeovimBackedBindingTestContext::new(keystrokes, self)
     }
 }
 
-impl<'a> Deref for NeovimBackedTestContext<'a> {
-    type Target = VimTestContext<'a>;
+impl Deref for NeovimBackedTestContext {
+    type Target = VimTestContext;
 
     fn deref(&self) -> &Self::Target {
         &self.cx
     }
 }
 
-impl<'a> DerefMut for NeovimBackedTestContext<'a> {
+impl DerefMut for NeovimBackedTestContext {
     fn deref_mut(&mut self) -> &mut Self::Target {
         &mut self.cx
     }
@@ -415,7 +415,7 @@ impl<'a> DerefMut for NeovimBackedTestContext<'a> {
 // a common mistake in tests is to call set_shared_state when
 // you mean asswert_shared_state. This notices that and lets
 // you know.
-impl<'a> Drop for NeovimBackedTestContext<'a> {
+impl Drop for NeovimBackedTestContext {
     fn drop(&mut self) {
         if self.is_dirty {
             panic!("Test context was dropped after set_shared_state before assert_shared_state")
@@ -425,9 +425,8 @@ impl<'a> Drop for NeovimBackedTestContext<'a> {
 
 #[cfg(test)]
 mod test {
-    use gpui::TestAppContext;
-
     use crate::test::NeovimBackedTestContext;
+    use gpui::TestAppContext;
 
     #[gpui::test]
     async fn neovim_backed_test_context_works(cx: &mut TestAppContext) {

crates/vim/src/test/vim_test_context.rs 🔗

@@ -10,11 +10,11 @@ use search::BufferSearchBar;
 
 use crate::{state::Operator, *};
 
-pub struct VimTestContext<'a> {
-    cx: EditorLspTestContext<'a>,
+pub struct VimTestContext {
+    cx: EditorLspTestContext,
 }
 
-impl<'a> VimTestContext<'a> {
+impl VimTestContext {
     pub fn init(cx: &mut gpui::TestAppContext) {
         if cx.has_global::<Vim>() {
             dbg!("OOPS");
@@ -29,13 +29,13 @@ impl<'a> VimTestContext<'a> {
         });
     }
 
-    pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
+    pub async fn new(cx: &mut gpui::TestAppContext, enabled: bool) -> VimTestContext {
         Self::init(cx);
         let lsp = EditorLspTestContext::new_rust(Default::default(), cx).await;
         Self::new_with_lsp(lsp, enabled)
     }
 
-    pub async fn new_typescript(cx: &'a mut gpui::TestAppContext) -> VimTestContext<'a> {
+    pub async fn new_typescript(cx: &mut gpui::TestAppContext) -> VimTestContext {
         Self::init(cx);
         Self::new_with_lsp(
             EditorLspTestContext::new_typescript(Default::default(), cx).await,
@@ -43,7 +43,7 @@ impl<'a> VimTestContext<'a> {
         )
     }
 
-    pub fn new_with_lsp(mut cx: EditorLspTestContext<'a>, enabled: bool) -> VimTestContext<'a> {
+    pub fn new_with_lsp(mut cx: EditorLspTestContext, enabled: bool) -> VimTestContext {
         cx.update(|cx| {
             cx.update_global(|store: &mut SettingsStore, cx| {
                 store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(enabled));
@@ -162,15 +162,15 @@ impl<'a> VimTestContext<'a> {
     }
 }
 
-impl<'a> Deref for VimTestContext<'a> {
-    type Target = EditorTestContext<'a>;
+impl Deref for VimTestContext {
+    type Target = EditorTestContext;
 
     fn deref(&self) -> &Self::Target {
         &self.cx
     }
 }
 
-impl<'a> DerefMut for VimTestContext<'a> {
+impl DerefMut for VimTestContext {
     fn deref_mut(&mut self) -> &mut Self::Target {
         &mut self.cx
     }