use super::*;
use expect_test::expect;
#[tokio::test]
async fn hover_class_identifier() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.check_hover_annotated(
r#"<?php
class Gre$0eter {}
"#,
expect![[r#"
```php
class Greeter
```"#]],
)
.await;
}
#[tokio::test]
async fn hover_enum_identifier() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.check_hover_annotated(
r#"<?php
enum Stat$0us { case Active; case Inactive; }
"#,
expect![[r#"
```php
enum Status
```"#]],
)
.await;
}
#[tokio::test]
async fn hover_function() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.check_hover_annotated(
r#"<?php function gr$0eet(): void {}"#,
expect![[r#"
```php
function greet(): void
```"#]],
)
.await;
}
#[tokio::test]
async fn hover_function_with_template_shows_template_in_docblock() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.check_hover_annotated(
r#"<?php
/**
* @template T
* @param T $value
* @return T
*/
function identi$0ty($value) { return $value; }
"#,
expect![[r#"
```php
function identity($value)
```
---
**@return** `T`
**@param** `T` `$value`
**@template** `T`"#]],
)
.await;
}
#[tokio::test]
async fn hover_function_with_throws_shows_tag() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.check_hover_annotated(
r#"<?php
/**
* @throws \RuntimeException When the operation fails
*/
function ri$0sky(): void {}
"#,
expect![[r#"
```php
function risky(): void
```
---
**@throws** `\RuntimeException` — When the operation fails"#]],
)
.await;
}
#[tokio::test]
async fn hover_interface_identifier() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.check_hover_annotated(
r#"<?php
interface Writ$0able { public function write(): void; }
"#,
expect![[r#"
```php
interface Writable
```"#]],
)
.await;
}
#[tokio::test]
async fn hover_method() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.check_hover_annotated(
r#"<?php
class Greeter {
public function he$0llo(): string { return 'hi'; }
}"#,
expect![[r#"
```php
public function hello(): string
```"#]],
)
.await;
}
#[tokio::test]
async fn hover_method_after_instanceof_narrows_type() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.check_hover_annotated(
r#"<?php
class Greeter { public function hello() {} }
function process(mixed $x) {
if ($x instanceof Greeter) {
$x->hel$0lo();
}
}
"#,
expect![[r#"
```php
Greeter::hello()
```"#]],
)
.await;
}
#[tokio::test]
async fn hover_method_after_instanceof_with_param_type() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.check_hover_annotated(
r#"<?php
class Service {
public function execute() {}
}
function handle(object $obj) {
if ($obj instanceof Service) {
$obj->exec$0ute();
}
}
"#,
expect![[r#"
```php
Service::execute()
```"#]],
)
.await;
}
#[tokio::test]
async fn hover_method_call_resolves_receiver_class() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.check_hover_annotated(
r#"<?php
class Mailer { public function process(string $to): bool {} }
class Queue { public function process(int $id): void {} }
$mailer = new Mailer();
$mailer->pro$0cess('');
"#,
expect![[r#"
```php
Mailer::process(string $to): bool
```"#]],
)
.await;
}
#[tokio::test]
async fn hover_method_without_instanceof_does_not_narrow() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.check_hover_annotated(
r#"<?php
class Processor { public function process() {} }
function test(mixed $obj) {
// Outside the if block, $obj has no narrowed type
$obj->proce$0ss();
}
"#,
expect![[r#"
```php
public function process()
```"#]],
)
.await;
}
#[tokio::test]
async fn hover_missing_symbol_returns_nothing() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.check_hover_annotated(r#"<?php fo$0o();"#, expect!["<no hover>"])
.await;
}
#[tokio::test]
async fn hover_on_empty_file_returns_null_not_error() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.open("empty.php", "").await;
let resp = s.hover("empty.php", 0, 0).await;
assert!(
resp["error"].is_null(),
"hover errored on empty file: {resp:?}"
);
assert!(
resp["result"].is_null(),
"hover on empty file should be null, got: {:?}",
resp["result"]
);
}
#[tokio::test]
async fn hover_past_eof_does_not_crash() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.open("short.php", "<?php\nfunction f(): void {}\n").await;
let resp = s.hover("short.php", 500, 500).await;
assert!(resp["error"].is_null(), "hover past EOF errored: {resp:?}");
assert!(
resp["result"].is_null(),
"hover past EOF should have null result, got: {resp:?}"
);
}
#[tokio::test]
async fn hover_static_method() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.check_hover_annotated(
r#"<?php
class Registry {
public static function ge$0t(string $k): mixed {}
}"#,
expect![[r#"
```php
public static function get(string $k): mixed
```"#]],
)
.await;
}
#[tokio::test]
async fn hover_static_method_in_match_not_broken() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.check_hover_annotated(
r#"<?php
class Checker {
public static function isValid($x) { return true; }
}
match ($x) {
1 => Checker::isVal$0id($x),
}
"#,
expect![[r#"
```php
Checker::isValid($x)
```"#]],
)
.await;
}
#[tokio::test]
async fn hover_clone_with_result_inherits_type() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.check_hover_annotated(
r#"<?php
class Point { public int $x; public int $y; }
$p = new Point();
$q = clone($p, ['x' => 1]);
echo $q$0->x;
"#,
expect!["`$q` `Point`"],
)
.await;
}
#[tokio::test]
async fn hover_no_result_without_known_source_type() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.check_hover_annotated(
r#"<?php
$q = clone($unknown, ['x' => 1]);
echo $q$0->x;
"#,
expect!["<no hover>"],
)
.await;
}
#[tokio::test]
async fn hover_variable_is_scoped_to_method() {
let mut s = TestServer::new().await;
s.validate_syntax(false);
s.check_hover_annotated(
r#"<?php
class Widget {}
class Invoice {}
class Service {
public function a(): void { $result = new Widget(); }
public function b(): void { $res$0ult = new Invoice(); }
}
"#,
expect!["`$result` `Invoice`"],
)
.await;
}