@@ -235,4 +235,97 @@ class MixinPsiTest : BasePlatformTestCase() {
235235 val method = PsiTreeUtil .findChildOfType(psiFile, Method ::class .java)!!
236236 assertTrue(" Method named exactly 'test' should be a Testo method" , method.isTestoMethod())
237237 }
238+
239+ // ---- Class-level #[Testo\Test] attribute ----
240+
241+ fun testIsTestoClass_classLevelTestAttribute () {
242+ val psiFile = myFixture.configureByText(
243+ PhpFileType .INSTANCE ,
244+ """ <?php
245+ namespace App;
246+ #[\Testo\Test]
247+ class UserService { public function it_works(): void {} }"""
248+ )
249+ val phpClass = PsiTreeUtil .findChildOfType(psiFile, PhpClass ::class .java)!!
250+ assertTrue(" Class with #[Testo\\ Test] attribute should be a Testo class" , phpClass.isTestoClass())
251+ }
252+
253+ fun testIsTestoMethod_publicMethodInClassWithTestAttribute () {
254+ val psiFile = myFixture.configureByText(
255+ PhpFileType .INSTANCE ,
256+ """ <?php
257+ #[\Testo\Test]
258+ class Foo { public function it_works(): void {} }"""
259+ )
260+ val method = PsiTreeUtil .findChildOfType(psiFile, Method ::class .java)!!
261+ assertTrue(" Public method in class marked with #[Testo\\ Test] should be runnable" , method.isTestoMethod())
262+ }
263+
264+ fun testIsTestoMethod_privateMethodInClassWithTestAttribute () {
265+ val psiFile = myFixture.configureByText(
266+ PhpFileType .INSTANCE ,
267+ """ <?php
268+ #[\Testo\Test]
269+ class Foo { private function helper(): void {} }"""
270+ )
271+ val method = PsiTreeUtil .findChildOfType(psiFile, Method ::class .java)!!
272+ assertFalse(" Private method in #[Testo\\ Test] class should not be runnable" , method.isTestoMethod())
273+ }
274+
275+ fun testIsTestoMethod_staticMethodInClassWithTestAttribute () {
276+ val psiFile = myFixture.configureByText(
277+ PhpFileType .INSTANCE ,
278+ """ <?php
279+ #[\Testo\Test]
280+ class Foo { public static function provide(): iterable { yield [1]; } }"""
281+ )
282+ val method = PsiTreeUtil .findChildOfType(psiFile, Method ::class .java)!!
283+ assertFalse(" Public static method in #[Testo\\ Test] class should not be runnable" , method.isTestoMethod())
284+ }
285+
286+ fun testIsTestoMethod_magicMethodInClassWithTestAttribute () {
287+ val psiFile = myFixture.configureByText(
288+ PhpFileType .INSTANCE ,
289+ """ <?php
290+ #[\Testo\Test]
291+ class Foo { public function __construct() {} }"""
292+ )
293+ val method = PsiTreeUtil .findChildOfType(psiFile, Method ::class .java)!!
294+ assertFalse(" Magic method in #[Testo\\ Test] class should not be runnable" , method.isTestoMethod())
295+ }
296+
297+ fun testIsTestoMethod_abstractMethodInClassWithTestAttribute () {
298+ val psiFile = myFixture.configureByText(
299+ PhpFileType .INSTANCE ,
300+ """ <?php
301+ #[\Testo\Test]
302+ abstract class Foo { abstract public function it_works(): void; }"""
303+ )
304+ val method = PsiTreeUtil .findChildOfType(psiFile, Method ::class .java)!!
305+ assertFalse(" Abstract method in #[Testo\\ Test] class should not be runnable" , method.isTestoMethod())
306+ }
307+
308+ fun testIsTestoMethod_publicMethodInRegularClass () {
309+ val psiFile = myFixture.configureByText(
310+ PhpFileType .INSTANCE ,
311+ """ <?php class Foo { public function it_works(): void {} }"""
312+ )
313+ val method = PsiTreeUtil .findChildOfType(psiFile, Method ::class .java)!!
314+ assertFalse(" Public method in non-Testo class should not be runnable" , method.isTestoMethod())
315+ }
316+
317+ fun testIsTestoMethod_benchMethodInClassWithTestAttribute () {
318+ val psiFile = myFixture.configureByText(
319+ PhpFileType .INSTANCE ,
320+ """ <?php
321+ #[\Testo\Test]
322+ class Foo {
323+ #[\Testo\Bench]
324+ public function bench_it(): void {}
325+ }"""
326+ )
327+ val method = PsiTreeUtil .findChildOfType(psiFile, Method ::class .java)!!
328+ assertFalse(" Bench method should not be reported as a Testo test method" , method.isTestoMethod())
329+ assertTrue(" Bench method should still be detected as a Testo bench" , method.isTestoBench())
330+ }
238331}
0 commit comments