use super::*;
use expect_test::expect;
use serde_json::{Value, json};
#[cfg(unix)]
fn write_fake_phpunit(root: &std::path::Path, exit_code: i32, stdout: &str) {
let dir = root.join("vendor/bin");
std::fs::create_dir_all(&dir).unwrap();
let out_file = dir.join("phpunit.out");
std::fs::write(&out_file, stdout).unwrap();
let script_content = format!(
"#!/bin/sh\ncat \"{}\"\nprintf '\\n'\nexit {exit_code}\n",
out_file.display()
);
let script = dir.join("phpunit");
std::fs::write(&script, script_content).unwrap();
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap();
}
async fn send_run_test(
s: &mut TestServer,
file_uri: Option<&str>,
filter: &str,
) -> serde_json::Value {
s.client()
.request(
"workspace/executeCommand",
json!({
"command": "php-lsp.runTest",
"arguments": [
file_uri.map(|u| json!(u)).unwrap_or(json!(null)),
json!(filter),
],
}),
)
.await
}
fn message_type_name(t: u64) -> &'static str {
match t {
1 => "ERROR",
2 => "WARNING",
3 => "INFO",
4 => "LOG",
_ => "?",
}
}
fn render_show_message_request(params: &Value) -> String {
let t = message_type_name(params["type"].as_u64().unwrap_or(0));
let raw = params["message"].as_str().unwrap_or("");
let sentinel = "failed to spawn phpunit — ";
let msg = if let Some(pos) = raw.find(sentinel) {
format!("{}<os error>", &raw[..pos + sentinel.len()])
} else {
raw.to_owned()
};
let actions: Vec<&str> = params["actions"]
.as_array()
.map(|a| a.iter().filter_map(|x| x["title"].as_str()).collect())
.unwrap_or_default();
if actions.is_empty() {
format!("{t}: {msg}")
} else {
format!("{t}: {msg}\nactions: {}", actions.join(", "))
}
}
fn render_show_message(params: &Value) -> String {
let t = message_type_name(params["type"].as_u64().unwrap_or(0));
let msg = params["message"].as_str().unwrap_or("");
format!("{t}: {msg}")
}
fn render_show_document(params: &Value, root_uri: &str) -> String {
let uri = params["uri"].as_str().unwrap_or("?");
let prefix = format!("{}/", root_uri.trim_end_matches('/'));
let short = uri.strip_prefix(&prefix).unwrap_or(uri);
let take_focus = params["takeFocus"].as_bool().unwrap_or(false);
let mut out = format!("uri: {short}\ntakeFocus: {take_focus}");
if let Some(e) = params["external"].as_bool() {
out.push_str(&format!("\nexternal: {e}"));
}
out
}
#[tokio::test]
async fn unknown_command_returns_null() {
let mut s = TestServer::new().await;
let resp = s
.client()
.request(
"workspace/executeCommand",
json!({ "command": "unknown.command", "arguments": [] }),
)
.await;
expect!["null"].assert_eq(&resp["result"].to_string());
}
#[tokio::test]
async fn run_test_phpunit_not_found_reports_error() {
let tmp = tempfile::tempdir().unwrap();
let mut s = TestServer::with_root(tmp.path()).await;
send_run_test(&mut s, None, "FooTest::testSomething").await;
let (_id, params) = s
.client()
.expect_server_request("window/showMessageRequest")
.await;
expect![[r#"
ERROR: php-lsp.runTest: failed to spawn phpunit — <os error>
actions: Run Again"#]]
.assert_eq(&render_show_message_request(¶ms));
}
#[tokio::test]
async fn run_test_phpunit_not_found_with_file_uri_offers_open_file() {
let tmp = tempfile::tempdir().unwrap();
let mut s = TestServer::with_root(tmp.path()).await;
let file_uri = s.uri("FooTest.php");
send_run_test(&mut s, Some(&file_uri), "FooTest::testSomething").await;
let (_id, params) = s
.client()
.expect_server_request("window/showMessageRequest")
.await;
expect![[r#"
ERROR: php-lsp.runTest: failed to spawn phpunit — <os error>
actions: Run Again, Open File"#]]
.assert_eq(&render_show_message_request(¶ms));
}
#[cfg(unix)]
#[tokio::test]
async fn run_test_phpunit_success_shows_info_message() {
let tmp = tempfile::tempdir().unwrap();
write_fake_phpunit(tmp.path(), 0, "OK (1 test, 1 assertion)");
let mut s = TestServer::with_root(tmp.path()).await;
send_run_test(&mut s, None, "PassTest::testPass").await;
let (_id, params) = s
.client()
.expect_server_request("window/showMessageRequest")
.await;
expect![[r#"
INFO: ✓ PassTest::testPass: OK (1 test, 1 assertion)
actions: Run Again"#]]
.assert_eq(&render_show_message_request(¶ms));
}
#[cfg(unix)]
#[tokio::test]
async fn run_test_phpunit_failure_shows_error_with_open_file() {
let tmp = tempfile::tempdir().unwrap();
write_fake_phpunit(tmp.path(), 1, "FAILURES!");
let mut s = TestServer::with_root(tmp.path()).await;
let file_uri = s.uri("FailTest.php");
send_run_test(&mut s, Some(&file_uri), "FailTest::testFail").await;
let (_id, params) = s
.client()
.expect_server_request("window/showMessageRequest")
.await;
expect![[r#"
ERROR: ✗ FailTest::testFail: FAILURES!
actions: Run Again, Open File"#]]
.assert_eq(&render_show_message_request(¶ms));
}
#[cfg(unix)]
#[tokio::test]
async fn run_test_run_again_reruns_test() {
let tmp = tempfile::tempdir().unwrap();
write_fake_phpunit(tmp.path(), 0, "OK (1 test, 1 assertion)");
let mut s = TestServer::with_root(tmp.path()).await;
send_run_test(&mut s, None, "PassTest::testPass").await;
let (req_id, _params) = s
.client()
.expect_server_request("window/showMessageRequest")
.await;
s.client()
.reply_to_server_request(req_id, json!({ "title": "Run Again" }))
.await;
let notif = s.client().read_notification("window/showMessage").await;
expect!["INFO: ✓ PassTest::testPass: OK (1 test, 1 assertion)"]
.assert_eq(&render_show_message(¬if["params"]));
}
#[cfg(unix)]
#[tokio::test]
async fn run_test_open_file_shows_document() {
let tmp = tempfile::tempdir().unwrap();
write_fake_phpunit(tmp.path(), 1, "FAILURES!");
let mut s = TestServer::with_root(tmp.path()).await;
let file_uri = s.uri("FailTest.php");
send_run_test(&mut s, Some(&file_uri), "FailTest::testFail").await;
let (req_id, _params) = s
.client()
.expect_server_request("window/showMessageRequest")
.await;
s.client()
.reply_to_server_request(req_id, json!({ "title": "Open File" }))
.await;
let (doc_id, doc_params) = s
.client()
.expect_server_request("window/showDocument")
.await;
expect![[r#"
uri: FailTest.php
takeFocus: true
external: false"#]]
.assert_eq(&render_show_document(&doc_params, &s.uri("")));
s.client()
.reply_to_server_request(doc_id, json!({ "success": true }))
.await;
}
#[cfg(unix)]
#[tokio::test]
async fn run_test_success_with_file_uri_does_not_offer_open_file() {
let tmp = tempfile::tempdir().unwrap();
write_fake_phpunit(tmp.path(), 0, "OK (1 test, 1 assertion)");
let mut s = TestServer::with_root(tmp.path()).await;
let file_uri = s.uri("PassTest.php");
send_run_test(&mut s, Some(&file_uri), "PassTest::testPass").await;
let (_id, params) = s
.client()
.expect_server_request("window/showMessageRequest")
.await;
expect![[r#"
INFO: ✓ PassTest::testPass: OK (1 test, 1 assertion)
actions: Run Again"#]]
.assert_eq(&render_show_message_request(¶ms));
}