use super::*;
use expect_test::expect;
use serde_json::Value;
use crate::common::render_text_edits;
#[tokio::test]
async fn did_close_clears_diagnostics() {
let mut server = TestServer::new().await;
let uri = server.uri("close_test.php");
let open_notif = server.open("close_test.php", "<?php function() {}\n").await;
let open_rendered = render_diagnostics_notification(&open_notif);
expect![[r#"
0:6-0:19 [3] MissingClosureReturnType: Closure has no return type annotation
1:0-1:1 [1] ?: expected ';' after expression"#]]
.assert_eq(&open_rendered);
server.close("close_test.php").await;
let close_notif = server.client().wait_for_diagnostics(&uri).await;
expect!["<empty>"].assert_eq(&render_diagnostics_notification(&close_notif));
}
#[tokio::test]
async fn did_close_unopened_does_not_crash() {
let mut server = TestServer::new().await;
let uri = server.uri("never_opened.php");
server.close("never_opened.php").await;
let notif = server.client().wait_for_diagnostics(&uri).await;
expect!["<empty>"].assert_eq(&render_diagnostics_notification(¬if));
}
#[tokio::test]
async fn did_save_republishes_empty_diagnostics_for_clean_file() {
let mut server = TestServer::new().await;
server.open("save_clean.php", "<?php\n").await;
let save_notif = server.save("save_clean.php").await;
expect!["<empty>"].assert_eq(&render_diagnostics_notification(&save_notif));
}
#[tokio::test]
async fn did_save_republishes_diagnostics_for_duplicate_functions() {
let mut server = TestServer::new().await;
let open_notif = server
.open(
"save_dup.php",
"<?php\nfunction doWork() {}\nfunction doWork() {}\n",
)
.await;
expect!["2:0-2:20 [1] DuplicateFunction: Function doWork() has already been defined"]
.assert_eq(&render_diagnostics_notification(&open_notif));
let save_notif = server.save("save_dup.php").await;
expect!["2:0-2:20 [1] DuplicateFunction: Function doWork() has already been defined"]
.assert_eq(&render_diagnostics_notification(&save_notif));
}
#[tokio::test]
async fn did_save_republishes_semantic_diagnostics() {
let mut server = TestServer::new().await;
let open_notif = server
.open(
"save_semantic.php",
"<?php\nfunction _wrap(): void {\n nonexistent_fn();\n}\n",
)
.await;
expect!["2:4-2:20 [1] UndefinedFunction: Function nonexistent_fn() is not defined"]
.assert_eq(&render_diagnostics_notification(&open_notif));
let save_notif = server.save("save_semantic.php").await;
expect!["2:4-2:20 [1] UndefinedFunction: Function nonexistent_fn() is not defined"]
.assert_eq(&render_diagnostics_notification(&save_notif));
}
#[tokio::test]
async fn will_save_keeps_document_state_unchanged() {
let mut server = TestServer::new().await;
let open_notif = server
.open(
"ws_state.php",
"<?php\nfunction _wrap(): void {\n nonexistent_fn();\n}\n",
)
.await;
expect!["2:4-2:20 [1] UndefinedFunction: Function nonexistent_fn() is not defined"]
.assert_eq(&render_diagnostics_notification(&open_notif));
for reason in [1u32, 2, 3] {
server.will_save("ws_state.php", reason).await;
}
let save_notif = server.save("ws_state.php").await;
expect!["2:4-2:20 [1] UndefinedFunction: Function nonexistent_fn() is not defined"]
.assert_eq(&render_diagnostics_notification(&save_notif));
}
#[tokio::test]
async fn will_save_does_not_publish_diagnostics() {
let mut server = TestServer::new().await;
server
.open("ws_nodiag.php", "<?php\nfunction foo() {}\n")
.await;
for reason in [1u32, 2, 3] {
server.will_save("ws_nodiag.php", reason).await;
}
let hover = server.hover("ws_nodiag.php", 1, 10).await;
assert!(hover["error"].is_null(), "hover errored: {hover:?}");
let uris = server
.client()
.drain_publish_diagnostics_uris(tokio::time::Duration::from_millis(100))
.await;
expect!["[]"].assert_eq(&format!("{uris:?}"));
}
#[tokio::test]
async fn will_save_for_unopened_file_does_not_crash() {
let mut server = TestServer::new().await;
server.will_save("ws_never_opened.php", 1).await;
server.will_save("ws_never_opened.php", 2).await;
server.will_save("ws_never_opened.php", 3).await;
let open_notif = server
.open(
"ws_after.php",
"<?php\nfunction _wrap(): void {\n nonexistent_fn();\n}\n",
)
.await;
expect!["2:4-2:20 [1] UndefinedFunction: Function nonexistent_fn() is not defined"]
.assert_eq(&render_diagnostics_notification(&open_notif));
}
#[tokio::test]
async fn will_save_after_did_close_does_not_crash() {
let mut server = TestServer::new().await;
server
.open("ws_closed.php", "<?php\nfunction foo() {}\n")
.await;
server.close("ws_closed.php").await;
let _ = server
.client()
.drain_publish_diagnostics_uris(tokio::time::Duration::from_millis(50))
.await;
server.will_save("ws_closed.php", 1).await;
let open_notif = server.open("ws_after_close.php", "<?php\n").await;
expect!["<empty>"].assert_eq(&render_diagnostics_notification(&open_notif));
}
#[tokio::test]
async fn will_save_does_not_disturb_pending_did_change() {
let mut server = TestServer::new().await;
server.open("ws_change.php", "<?php\n").await;
server
.change(
"ws_change.php",
2,
"<?php\nfunction _wrap(): void {\n nonexistent_fn();\n}\n",
)
.await;
server.will_save("ws_change.php", 1).await;
let save_notif = server.save("ws_change.php").await;
expect!["2:4-2:20 [1] UndefinedFunction: Function nonexistent_fn() is not defined"]
.assert_eq(&render_diagnostics_notification(&save_notif));
}
#[tokio::test]
async fn will_save_wait_until_returns_null_or_empty_for_formatted_file() {
let mut server = TestServer::new().await;
server.open("wswu_clean.php", "<?php\n").await;
let resp = server.will_save_wait_until("wswu_clean.php").await;
assert!(resp["error"].is_null(), "unexpected error: {resp:?}");
expect![r#"(no formatter available)"#].assert_eq(&render_text_edits(&resp));
}
#[tokio::test]
async fn will_save_wait_until_on_already_formatted_code() {
let mut server = TestServer::new().await;
server
.open(
"wswu_formatted.php",
"<?php\n\nfunction greet(): void\n{\n}\n",
)
.await;
let resp = server.will_save_wait_until("wswu_formatted.php").await;
assert!(resp["error"].is_null(), "unexpected error: {resp:?}");
let result = &resp["result"];
assert!(
result.is_null() || result.as_array().map(|a| a.is_empty()).unwrap_or(false),
"expected null or empty edits for already-formatted file: {resp:?}"
);
}
#[tokio::test]
async fn will_save_wait_until_returns_edits_or_null_for_unformatted_file() {
let mut server = TestServer::new().await;
server
.open("wswu_ugly.php", "<?php\nfunction ugly( $x ){return $x;}\n")
.await;
let resp = server.will_save_wait_until("wswu_ugly.php").await;
assert!(resp["error"].is_null(), "unexpected error: {resp:?}");
let result = &resp["result"];
if let Some(edits) = result.as_array() {
for edit in edits {
assert!(
edit["range"]["start"].is_object() && edit["range"]["end"].is_object(),
"edit missing range: {edit:?}"
);
assert!(
edit["newText"].is_string(),
"edit missing newText: {edit:?}"
);
}
} else {
assert!(result.is_null(), "expected null or array, got: {result:?}");
}
}
#[tokio::test]
async fn will_save_wait_until_on_unopened_file_returns_null() {
let mut server = TestServer::new().await;
let resp = server.will_save_wait_until("wswu_never_opened.php").await;
assert!(resp["error"].is_null(), "unexpected error: {resp:?}");
expect!["(no formatter available)"].assert_eq(&render_text_edits(&resp));
}
#[tokio::test]
async fn will_save_wait_until_on_empty_file() {
let mut server = TestServer::new().await;
server.open("wswu_empty.php", "").await;
let resp = server.will_save_wait_until("wswu_empty.php").await;
assert!(resp["error"].is_null(), "unexpected error: {resp:?}");
expect!["(no formatter available)"].assert_eq(&render_text_edits(&resp));
}
#[tokio::test]
async fn will_save_wait_until_without_php_tag() {
let mut server = TestServer::new().await;
server.open("wswu_no_tag.php", "function test( ){}\n").await;
let resp = server.will_save_wait_until("wswu_no_tag.php").await;
assert!(resp["error"].is_null(), "unexpected error: {resp:?}");
let result = &resp["result"];
assert!(
result.is_null() || result.as_array().is_some(),
"expected null or TextEdit array, got: {result:?}"
);
}
#[tokio::test]
async fn did_change_updates_document() {
let mut server = TestServer::new().await;
server.open("change.php", "<?php\n").await;
server
.change("change.php", 2, "<?php\nfunction updated() {}\n")
.await;
let resp = server.hover("change.php", 1, 10).await;
assert!(
resp["error"].is_null(),
"hover after change should not error"
);
}
#[tokio::test]
async fn document_link_returns_array() {
let mut server = TestServer::new().await;
server
.open("dlink.php", "<?php\nrequire_once 'vendor/autoload.php';\n")
.await;
let resp = server.document_link("dlink.php").await;
assert!(resp["error"].is_null(), "documentLink error: {:?}", resp);
let links = resp["result"]
.as_array()
.expect("documentLink must return an array");
let out = if links.is_empty() {
"<empty>".to_owned()
} else {
links
.iter()
.map(|l| {
let start = &l["range"]["start"];
let line = start["line"].as_u64().unwrap_or(0);
let col = start["character"].as_u64().unwrap_or(0);
let target = l["target"].as_str().unwrap_or("?");
let display = if let Some(rest) = target.rfind('/').map(|i| &target[i + 1..]) {
rest.to_owned()
} else {
target.to_owned()
};
format!("{line}:{col} -> {display}")
})
.collect::<Vec<_>>()
.join("\n")
};
expect!["1:14 -> autoload.php"].assert_eq(&out);
}
#[tokio::test]
async fn dependency_edit_refreshes_cross_file_hover_type() {
let mut server = TestServer::new().await;
server
.open(
"maker.php",
"<?php\nclass Maker { public function make(): Apple { return new Apple(); } }\nclass Apple {}\nclass Banana {}\n",
)
.await;
server
.open(
"use_maker.php",
"<?php\n$m = new Maker();\n$x = $m->make();\necho $x;\n",
)
.await;
let before = server.hover("use_maker.php", 3, 5).await;
expect!["`$x` `Apple`"].assert_eq(&render_hover(&before));
server
.change(
"maker.php",
2,
"<?php\nclass Maker { public function make(): Banana { return new Banana(); } }\nclass Apple {}\nclass Banana {}\n",
)
.await;
let after = server.hover("use_maker.php", 3, 5).await;
expect!["`$x` `Banana`"].assert_eq(&render_hover(&after));
}
#[tokio::test]
async fn dependency_edit_refreshes_cross_file_completion_members() {
let mut server = TestServer::new().await;
server
.open(
"dep.php",
"<?php\nclass Maker { public function make(): Alpha { return new Alpha(); } }\nclass Alpha { public function alpha(): void {} }\nclass Beta { public function beta(): void {} }\n",
)
.await;
server
.open(
"uses.php",
"<?php\n$m = new Maker();\n$x = $m->make();\n$x->\n",
)
.await;
let before = server.completion("uses.php", 3, 4).await;
expect!["Method alpha"].assert_eq(&render_completion(&before));
server
.change(
"dep.php",
2,
"<?php\nclass Maker { public function make(): Beta { return new Beta(); } }\nclass Alpha { public function alpha(): void {} }\nclass Beta { public function beta(): void {} }\n",
)
.await;
let after = server.completion("uses.php", 3, 4).await;
expect!["Method beta"].assert_eq(&render_completion(&after));
}