mod common;
use common::{TestServer, canonicalize_workspace_edit};
use expect_test::expect;
async fn bring_up() -> TestServer {
let mut server = TestServer::with_fixture("psr4-mini").await;
server.wait_for_index_ready().await;
server
}
async fn open_fixture(server: &mut TestServer, path: &str) {
let (text, _, _) = server.locate(path, "<?php", 0);
server.open(path, &text).await;
}
#[tokio::test]
async fn goto_definition_resolves_use_import_across_files() {
let mut server = bring_up().await;
open_fixture(&mut server, "src/Service/Greeter.php").await;
let (_, line, ch) = server.locate("src/Service/Greeter.php", "User $user", 0);
let resp = server.definition("src/Service/Greeter.php", line, ch).await;
let result = &resp["result"];
assert!(
!result.is_null(),
"expected cross-file definition: {resp:?}"
);
let loc = if result.is_array() {
&result[0]
} else {
result
};
let uri = loc["uri"].as_str().unwrap();
assert!(
uri.ends_with("src/Model/User.php"),
"definition must resolve to User.php, got: {uri}"
);
}
#[tokio::test]
async fn goto_definition_method_call_across_files() {
let mut server = bring_up().await;
open_fixture(&mut server, "src/Service/Greeter.php").await;
let (_, line, ch) = server.locate("src/Service/Greeter.php", "greeting()", 0);
let resp = server.definition("src/Service/Greeter.php", line, ch).await;
let result = &resp["result"];
assert!(
!result.is_null(),
"expected cross-file method definition: {resp:?}"
);
let loc = if result.is_array() {
&result[0]
} else {
result
};
assert!(
loc["uri"].as_str().unwrap().ends_with("src/Model/User.php"),
"method definition must land in User.php, got: {loc:?}"
);
}
#[tokio::test]
async fn references_include_use_imports_across_files() {
let mut server = bring_up().await;
open_fixture(&mut server, "src/Model/User.php").await;
let (_, line, ch) = server.locate("src/Model/User.php", "class User", 0);
let resp = server
.references("src/Model/User.php", line, ch + 6, false)
.await;
let refs = resp["result"].as_array().expect("references array");
let ref_uris: Vec<&str> = refs.iter().filter_map(|r| r["uri"].as_str()).collect();
assert!(
ref_uris
.iter()
.any(|u| u.ends_with("src/Service/Registry.php")),
"expected a reference in Registry.php, got: {ref_uris:?}"
);
assert!(
ref_uris
.iter()
.any(|u| u.ends_with("src/Service/Greeter.php")),
"expected a reference in Greeter.php, got: {ref_uris:?}"
);
}
#[tokio::test]
async fn rename_class_edits_all_dependents() {
let mut server = bring_up().await;
open_fixture(&mut server, "src/Model/User.php").await;
open_fixture(&mut server, "src/Service/Registry.php").await;
open_fixture(&mut server, "src/Service/Greeter.php").await;
let (_, line, ch) = server.locate("src/Model/User.php", "class User", 0);
let resp = server
.rename("src/Model/User.php", line, ch + 6, "Account")
.await;
assert!(resp["error"].is_null(), "rename error: {resp:?}");
let root = server.uri("");
let snap = canonicalize_workspace_edit(&resp["result"], &root);
expect![[r#"
// src/Model/User.php
4:6-4:10 → "Account"
// src/Service/Greeter.php
4:14-4:18 → "Account"
// src/Service/Registry.php
4:14-4:18 → "Account""#]]
.assert_eq(&snap);
}
#[tokio::test]
async fn workspace_symbol_finds_class_by_short_name() {
let mut server = bring_up().await;
let resp = server.workspace_symbols("User").await;
let symbols = resp["result"].as_array().expect("symbols array");
let matched = symbols.iter().find(|s| {
s["name"].as_str() == Some("User")
&& s["kind"].as_u64() == Some(5)
&& s["location"]["uri"]
.as_str()
.map(|u| u.ends_with("src/Model/User.php"))
.unwrap_or(false)
});
assert!(
matched.is_some(),
"expected exact class `User` in src/Model/User.php, got: {symbols:?}"
);
}