Skip to content

Commit f99d46b

Browse files
committed
feat: enhance isTestoMethod and isTestoClass logic; add tests for methods in Testo-annotated classes
1 parent 2bb53af commit f99d46b

2 files changed

Lines changed: 107 additions & 3 deletions

File tree

src/main/kotlin/com/github/xepozz/testo/mixin.kt

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,20 @@ fun PsiElement.isTestoFunction() = when(this) {
2424
else -> false
2525
}
2626

27-
fun PsiElement.isTestoMethod() = when(this) {
28-
is Method -> (modifier.isPublic && name.startsWith("test")) || hasAnyAttribute(*TestoClasses.TEST_ATTRIBUTES)
27+
fun PsiElement.isTestoMethod() = when (this) {
28+
is Method -> hasAnyAttribute(*TestoClasses.TEST_ATTRIBUTES)
29+
|| (modifier.isPublic && name.startsWith("test"))
30+
|| isPublicMethodOfTestoMarkedClass()
2931
else -> false
3032
}
3133

34+
private fun Method.isPublicMethodOfTestoMarkedClass() = when {
35+
!modifier.isPublic -> false
36+
modifier.isAbstract -> false
37+
name.startsWith("__") -> false
38+
else -> containingClass?.hasAnyAttribute(*TestoClasses.TEST_ATTRIBUTES) == true
39+
}
40+
3241
fun PsiElement.isTestoDataProviderLike() = when (this) {
3342
is Method -> modifier.isPublic && modifier.isStatic
3443
is Function -> true
@@ -39,7 +48,9 @@ fun PhpAttributesOwner.hasAttribute(fqn: String) = getAttributes(fqn).isNotEmpty
3948
fun PhpAttributesOwner.hasAnyAttribute(vararg fqn: String) = attributes.any { it.fqn in fqn }
4049

4150
fun PsiElement.isTestoClass() = when (this) {
42-
is PhpClass -> TestoTestDescriptor.isTestClassName(name) || ownMethods.any { it.isTestoMethod() || it.isTestoBench() }
51+
is PhpClass -> TestoTestDescriptor.isTestClassName(name)
52+
|| hasAnyAttribute(*TestoClasses.TEST_ATTRIBUTES)
53+
|| ownMethods.any { it.isTestoMethod() || it.isTestoBench() }
4354
else -> false
4455
}
4556

src/test/kotlin/com/github/xepozz/testo/MixinPsiTest.kt

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)