Tidy up TestContext lifecycle

Co-Authored-By: Max <max@zed.dev>
This commit is contained in:
Conrad Irwin 2024-01-05 16:28:40 -07:00
parent c7568a7d37
commit 709682e8bc
10 changed files with 82 additions and 78 deletions

View file

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

View file

@ -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 /// 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 triggered from using '|' character, what range should be replaced, and what completions
/// should be returned using '<' and '>' to delimit the range /// should be returned using '<' and '>' to delimit the range
pub fn handle_completion_request<'a>( pub fn handle_completion_request(
cx: &mut EditorLspTestContext<'a>, cx: &mut EditorLspTestContext,
marked_string: &str, marked_string: &str,
completions: Vec<&'static str>, completions: Vec<&'static str>,
) -> impl Future<Output = ()> { ) -> impl Future<Output = ()> {
@ -8177,8 +8177,8 @@ pub fn handle_completion_request<'a>(
} }
} }
fn handle_resolve_completion_request<'a>( fn handle_resolve_completion_request(
cx: &mut EditorLspTestContext<'a>, cx: &mut EditorLspTestContext,
edits: Option<Vec<(&'static str, &'static str)>>, edits: Option<Vec<(&'static str, &'static str)>>,
) -> impl Future<Output = ()> { ) -> impl Future<Output = ()> {
let edits = edits.map(|edits| { let edits = edits.map(|edits| {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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