mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-26 10:40:54 +00:00
Add integration test for code actions
This commit is contained in:
parent
68917c78be
commit
1eea2f3653
5 changed files with 259 additions and 9 deletions
|
@ -533,14 +533,14 @@ impl Client {
|
|||
match future.await {
|
||||
Ok(()) => {
|
||||
log::debug!(
|
||||
"{}: rpc message '{}' handled",
|
||||
"rpc message handled. client_id:{}, name:{}",
|
||||
client_id,
|
||||
type_name
|
||||
);
|
||||
}
|
||||
Err(error) => {
|
||||
log::error!(
|
||||
"{}: error handling rpc message '{}', {}",
|
||||
"error handling rpc message. client_id:{}, name:{}, error:{}",
|
||||
client_id,
|
||||
type_name,
|
||||
error
|
||||
|
|
|
@ -2090,7 +2090,7 @@ impl Editor {
|
|||
}))
|
||||
}
|
||||
|
||||
fn toggle_code_actions(
|
||||
pub fn toggle_code_actions(
|
||||
&mut self,
|
||||
&ToggleCodeActions(deployed_from_indicator): &ToggleCodeActions,
|
||||
cx: &mut ViewContext<Self>,
|
||||
|
@ -2136,7 +2136,7 @@ impl Editor {
|
|||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn confirm_code_action(
|
||||
pub fn confirm_code_action(
|
||||
workspace: &mut Workspace,
|
||||
ConfirmCodeAction(action_ix): &ConfirmCodeAction,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
|
@ -2297,7 +2297,7 @@ impl Editor {
|
|||
self.completion_tasks.clear();
|
||||
}
|
||||
self.context_menu = Some(menu);
|
||||
cx.notify()
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<ContextMenu> {
|
||||
|
|
|
@ -2703,7 +2703,7 @@ impl<T: Entity> ModelHandle<T> {
|
|||
let (mut tx, mut rx) = mpsc::channel(1);
|
||||
let mut cx = cx.cx.borrow_mut();
|
||||
let subscription = cx.observe(self, move |_, _| {
|
||||
tx.blocking_send(()).ok();
|
||||
tx.try_send(()).ok();
|
||||
});
|
||||
|
||||
let duration = if std::env::var("CI").is_ok() {
|
||||
|
@ -3007,7 +3007,7 @@ impl<T: View> ViewHandle<T> {
|
|||
let (mut tx, mut rx) = mpsc::channel(1);
|
||||
let mut cx = cx.cx.borrow_mut();
|
||||
let subscription = cx.observe(self, move |_, _| {
|
||||
tx.blocking_send(()).ok();
|
||||
tx.try_send(()).ok();
|
||||
});
|
||||
|
||||
let duration = if std::env::var("CI").is_ok() {
|
||||
|
|
|
@ -179,7 +179,12 @@ impl Peer {
|
|||
let channel = response_channels.lock().as_mut()?.remove(&responding_to);
|
||||
if let Some(mut tx) = channel {
|
||||
let mut requester_resumed = barrier::channel();
|
||||
tx.send((incoming, requester_resumed.0)).await.ok();
|
||||
if let Err(error) = tx.send((incoming, requester_resumed.0)).await {
|
||||
log::debug!(
|
||||
"received RPC but request future was dropped {:?}",
|
||||
error.0 .0
|
||||
);
|
||||
}
|
||||
// Drop response channel before awaiting on the barrier. This allows the
|
||||
// barrier to get dropped even if the request's future is dropped before it
|
||||
// has a chance to observe the response.
|
||||
|
|
|
@ -1089,7 +1089,10 @@ mod tests {
|
|||
self, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Credentials,
|
||||
EstablishConnectionError, UserStore,
|
||||
},
|
||||
editor::{ConfirmCompletion, Editor, EditorSettings, Input, MultiBuffer},
|
||||
editor::{
|
||||
self, ConfirmCodeAction, ConfirmCompletion, Editor, EditorSettings, Input, MultiBuffer,
|
||||
Redo, ToggleCodeActions, Undo,
|
||||
},
|
||||
fs::{FakeFs, Fs as _},
|
||||
language::{
|
||||
tree_sitter_rust, AnchorRangeExt, Diagnostic, DiagnosticEntry, Language,
|
||||
|
@ -1097,6 +1100,7 @@ mod tests {
|
|||
},
|
||||
lsp,
|
||||
project::{worktree::WorktreeHandle, DiagnosticSummary, Project, ProjectPath},
|
||||
workspace::{Workspace, WorkspaceParams},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -2724,6 +2728,247 @@ mod tests {
|
|||
assert_eq!(definitions[0].target_buffer, buffer_b2);
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_collaborating_with_code_actions(
|
||||
mut cx_a: TestAppContext,
|
||||
mut cx_b: TestAppContext,
|
||||
) {
|
||||
cx_a.foreground().forbid_parking();
|
||||
let mut lang_registry = Arc::new(LanguageRegistry::new());
|
||||
let fs = Arc::new(FakeFs::new(cx_a.background()));
|
||||
let mut path_openers_b = Vec::new();
|
||||
cx_b.update(|cx| editor::init(cx, &mut path_openers_b));
|
||||
|
||||
// Set up a fake language server.
|
||||
let (language_server_config, mut fake_language_server) =
|
||||
LanguageServerConfig::fake_with_capabilities(
|
||||
lsp::ServerCapabilities {
|
||||
..Default::default()
|
||||
},
|
||||
&cx_a,
|
||||
)
|
||||
.await;
|
||||
Arc::get_mut(&mut lang_registry)
|
||||
.unwrap()
|
||||
.add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".to_string(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
language_server: Some(language_server_config),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
)));
|
||||
|
||||
// Connect to a server as 2 clients.
|
||||
let mut server = TestServer::start(cx_a.foreground()).await;
|
||||
let client_a = server.create_client(&mut cx_a, "user_a").await;
|
||||
let client_b = server.create_client(&mut cx_b, "user_b").await;
|
||||
|
||||
// Share a project as client A
|
||||
fs.insert_tree(
|
||||
"/a",
|
||||
json!({
|
||||
".zed.toml": r#"collaborators = ["user_b"]"#,
|
||||
"main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
|
||||
"other.rs": "pub fn foo() -> usize { 4 }",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project_a = cx_a.update(|cx| {
|
||||
Project::local(
|
||||
client_a.clone(),
|
||||
client_a.user_store.clone(),
|
||||
lang_registry.clone(),
|
||||
fs.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let (worktree_a, _) = project_a
|
||||
.update(&mut cx_a, |p, cx| {
|
||||
p.find_or_create_local_worktree("/a", false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
worktree_a
|
||||
.read_with(&cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
let project_id = project_a.update(&mut cx_a, |p, _| p.next_remote_id()).await;
|
||||
let worktree_id = worktree_a.read_with(&cx_a, |tree, _| tree.id());
|
||||
project_a
|
||||
.update(&mut cx_a, |p, cx| p.share(cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Join the worktree as client B.
|
||||
let project_b = Project::remote(
|
||||
project_id,
|
||||
client_b.clone(),
|
||||
client_b.user_store.clone(),
|
||||
lang_registry.clone(),
|
||||
fs.clone(),
|
||||
&mut cx_b.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut params = cx_b.update(WorkspaceParams::test);
|
||||
params.languages = lang_registry.clone();
|
||||
params.client = client_b.client.clone();
|
||||
params.user_store = client_b.user_store.clone();
|
||||
params.project = project_b;
|
||||
params.path_openers = path_openers_b.into();
|
||||
|
||||
let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(¶ms, cx));
|
||||
let editor_b = workspace_b
|
||||
.update(&mut cx_b, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs").into(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
fake_language_server
|
||||
.handle_request::<lsp::request::CodeActionRequest, _>(|params| {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
assert_eq!(params.range.start, lsp::Position::new(0, 0));
|
||||
assert_eq!(params.range.end, lsp::Position::new(0, 0));
|
||||
None
|
||||
})
|
||||
.next()
|
||||
.await;
|
||||
|
||||
// Move cursor to a location that contains code actions.
|
||||
editor_b.update(&mut cx_b, |editor, cx| {
|
||||
editor.select_ranges([Point::new(1, 31)..Point::new(1, 31)], None, cx);
|
||||
cx.focus(&editor_b);
|
||||
});
|
||||
fake_language_server.handle_request::<lsp::request::CodeActionRequest, _>(|params| {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
assert_eq!(params.range.start, lsp::Position::new(1, 31));
|
||||
assert_eq!(params.range.end, lsp::Position::new(1, 31));
|
||||
|
||||
Some(vec![lsp::CodeActionOrCommand::CodeAction(
|
||||
lsp::CodeAction {
|
||||
title: "Inline into all callers".to_string(),
|
||||
edit: Some(lsp::WorkspaceEdit {
|
||||
changes: Some(
|
||||
[
|
||||
(
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
vec![lsp::TextEdit::new(
|
||||
lsp::Range::new(
|
||||
lsp::Position::new(1, 22),
|
||||
lsp::Position::new(1, 34),
|
||||
),
|
||||
"4".to_string(),
|
||||
)],
|
||||
),
|
||||
(
|
||||
lsp::Url::from_file_path("/a/other.rs").unwrap(),
|
||||
vec![lsp::TextEdit::new(
|
||||
lsp::Range::new(
|
||||
lsp::Position::new(0, 0),
|
||||
lsp::Position::new(0, 27),
|
||||
),
|
||||
"".to_string(),
|
||||
)],
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
),
|
||||
..Default::default()
|
||||
}),
|
||||
data: Some(json!({
|
||||
"codeActionParams": {
|
||||
"range": {
|
||||
"start": {"line": 1, "column": 31},
|
||||
"end": {"line": 1, "column": 31},
|
||||
}
|
||||
}
|
||||
})),
|
||||
..Default::default()
|
||||
},
|
||||
)])
|
||||
});
|
||||
|
||||
// Toggle code actions and wait for them to display.
|
||||
editor_b.update(&mut cx_b, |editor, cx| {
|
||||
editor.toggle_code_actions(&ToggleCodeActions(false), cx);
|
||||
});
|
||||
editor_b
|
||||
.condition(&cx_b, |editor, _| editor.context_menu_visible())
|
||||
.await;
|
||||
|
||||
// Confirming the code action will trigger a resolve request.
|
||||
let confirm_action = workspace_b
|
||||
.update(&mut cx_b, |workspace, cx| {
|
||||
Editor::confirm_code_action(workspace, &ConfirmCodeAction(Some(0)), cx)
|
||||
})
|
||||
.unwrap();
|
||||
fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _>(|_| {
|
||||
lsp::CodeAction {
|
||||
title: "Inline into all callers".to_string(),
|
||||
edit: Some(lsp::WorkspaceEdit {
|
||||
changes: Some(
|
||||
[
|
||||
(
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
vec![lsp::TextEdit::new(
|
||||
lsp::Range::new(
|
||||
lsp::Position::new(1, 22),
|
||||
lsp::Position::new(1, 34),
|
||||
),
|
||||
"4".to_string(),
|
||||
)],
|
||||
),
|
||||
(
|
||||
lsp::Url::from_file_path("/a/other.rs").unwrap(),
|
||||
vec![lsp::TextEdit::new(
|
||||
lsp::Range::new(
|
||||
lsp::Position::new(0, 0),
|
||||
lsp::Position::new(0, 27),
|
||||
),
|
||||
"".to_string(),
|
||||
)],
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
|
||||
// After the action is confirmed, an editor containing both modified files is opened.
|
||||
confirm_action.await.unwrap();
|
||||
let code_action_editor = workspace_b.read_with(&cx_b, |workspace, cx| {
|
||||
workspace
|
||||
.active_item(cx)
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap()
|
||||
});
|
||||
code_action_editor.update(&mut cx_b, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "\nmod other;\nfn main() { let foo = 4; }");
|
||||
editor.undo(&Undo, cx);
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
"pub fn foo() -> usize { 4 }\nmod other;\nfn main() { let foo = other::foo(); }"
|
||||
);
|
||||
editor.redo(&Redo, cx);
|
||||
assert_eq!(editor.text(cx), "\nmod other;\nfn main() { let foo = 4; }");
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_basic_chat(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
|
||||
cx_a.foreground().forbid_parking();
|
||||
|
|
Loading…
Reference in a new issue