Skip to content

Commit

Permalink
Merge pull request #53 from pestphp/feat-test-finder
Browse files Browse the repository at this point in the history
feat(goto): Navigate between test and implementation
  • Loading branch information
olivernybroe authored Aug 19, 2020
2 parents e10d4e1 + aebad6f commit 143ec8a
Show file tree
Hide file tree
Showing 21 changed files with 278 additions and 7 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

## [Unreleased]
### Added
- Add support for showing pest version
- Added support for showing pest version ([#52](https://github.com/pestphp/pest-intellij/pull/52))
- Type provider for Pest test functions ([#48](https://github.com/pestphp/pest-intellij/pull/48))
- Added support for navigation between tests and test subject ([#53](https://github.com/pestphp/pest-intellij/pull/53))

### Changed

### Deprecated
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

pluginGroup = com.pestphp
pluginName = PEST PHP
pluginVersion = 0.3.3
pluginVersion = 0.4.0-alpha.1
pluginSinceBuild = 201
pluginUntilBuild = null

Expand Down
10 changes: 6 additions & 4 deletions src/main/kotlin/com/pestphp/pest/PestUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ package com.pestphp.pest

import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiTreeUtil
import com.jetbrains.php.lang.psi.PhpFile
import com.jetbrains.php.lang.psi.elements.impl.FunctionReferenceImpl
import com.jetbrains.php.lang.psi.elements.Statement
import com.jetbrains.php.phpunit.PhpUnitUtil
import com.jetbrains.php.testFramework.PhpTestFrameworkSettingsManager

fun PsiFile.isPestTestFile(): Boolean {
if (this !is PhpFile) return false

return PsiTreeUtil.findChildrenOfType(this, FunctionReferenceImpl::class.java)
.any(FunctionReferenceImpl::isPestTestFunction)
return this.firstChild.children
.filterIsInstance<Statement>()
.mapNotNull { it.firstChild }
.any(PsiElement::isPestTestReference)
}

fun PsiFile.isPestConfigurationFile(): Boolean {
Expand Down
49 changes: 49 additions & 0 deletions src/main/kotlin/com/pestphp/pest/goto/PestTestFinder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.pestphp.pest.goto

import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.testIntegration.TestFinder
import com.intellij.util.indexing.FileBasedIndex
import com.jetbrains.php.PhpIndex
import com.jetbrains.php.lang.psi.elements.PhpClass
import com.pestphp.pest.indexers.PestTestIndex
import com.pestphp.pest.isPestTestFile
import java.util.ArrayList

class PestTestFinder : TestFinder {
override fun findClassesForTest(element: PsiElement): MutableCollection<PhpClass> {
return PhpIndex.getInstance(element.project)
.getClassesByNameInScope(
element.containingFile.name.removeSuffix("Test.php"),
GlobalSearchScope.projectScope(element.project)
)
}

override fun findSourceElement(from: PsiElement): PsiElement? {
return from.containingFile
}

override fun isTest(element: PsiElement): Boolean {
return element.containingFile.isPestTestFile()
}

override fun findTestsForClass(element: PsiElement): MutableCollection<PsiElement> {
val phpClass = PsiTreeUtil.getNonStrictParentOfType(element, PhpClass::class.java) ?: return arrayListOf()

return FileBasedIndex.getInstance().getAllKeys(
PestTestIndex.key,
element.project
).filter { it.contains(phpClass.name) }
.flatMap {
FileBasedIndex.getInstance().getContainingFiles(
PestTestIndex.key,
it,
GlobalSearchScope.projectScope(element.project)
)
}
.map { PsiManager.getInstance(element.project).findFile(it)!! }
.toCollection(ArrayList())
}
}
66 changes: 66 additions & 0 deletions src/main/kotlin/com/pestphp/pest/indexers/PestTestIndex.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.pestphp.pest.indexers

import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.roots.TestSourcesFilter
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.indexing.DataIndexer
import com.intellij.util.indexing.DefaultFileTypeSpecificInputFilter
import com.intellij.util.indexing.FileBasedIndex
import com.intellij.util.indexing.FileContent
import com.intellij.util.indexing.ID
import com.intellij.util.indexing.ScalarIndexExtension
import com.intellij.util.io.EnumeratorStringDescriptor
import com.intellij.util.io.KeyDescriptor
import com.jetbrains.php.lang.PhpFileType
import com.pestphp.pest.isPestTestFile
import gnu.trove.THashMap

class PestTestIndex : ScalarIndexExtension<String>() {
override fun getName(): ID<String, Void> {
return key
}

override fun getVersion(): Int {
return 0
}

override fun dependsOnFileContent(): Boolean {
return true
}

override fun getIndexer(): DataIndexer<String, Void, FileContent> {
return DataIndexer { inputData ->
val file = inputData.psiFile

if (!file.isPestTestFile()) {
return@DataIndexer mapOf()
}

val map = THashMap<String, Void>()
map[file.name] = null
return@DataIndexer map
}
}

override fun getInputFilter(): FileBasedIndex.InputFilter {
return object : DefaultFileTypeSpecificInputFilter(PhpFileType.INSTANCE) {
override fun acceptInput(file: VirtualFile): Boolean {
if (file.path.contains(""".*?test.*?/.*\..*""".toRegex())) {
return true
}

return ProjectManager.getInstance().openProjects.any {
TestSourcesFilter.isTestSources(file, it)
}
}
}
}

override fun getKeyDescriptor(): KeyDescriptor<String> {
return EnumeratorStringDescriptor.INSTANCE
}

companion object {
val key = ID.create<String, Void>("php.pest")
}
}
2 changes: 2 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
<iconProvider implementation="com.pestphp.pest.PestIconProvider"/>

<projectService serviceImplementation="com.pestphp.pest.PestSettings"/>
<testFinder implementation="com.pestphp.pest.goto.PestTestFinder"/>
<fileBasedIndex implementation="com.pestphp.pest.indexers.PestTestIndex"/>
</extensions>

<extensions defaultExtensionNs="com.jetbrains.php">
Expand Down
47 changes: 47 additions & 0 deletions src/test/kotlin/com/pestphp/pest/PestUtil/IsPestTestFileTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.pestphp.pest.PestUtil

import com.pestphp.pest.isPestTestFile
import com.pestphp.pest.isPestTestReference
import com.pestphp.pest.tests.PestLightCodeFixture

class IsPestTestFileTest : PestLightCodeFixture() {
override fun getTestDataPath(): String? {
return "src/test/resources/com/pestphp/pest/PestUtil"
}

fun testMethodCallNamedTestIsNotPestTest() {
val file = myFixture.configureByFile("MethodCallNamedTest.php")

assertFalse(file.isPestTestFile())
}

fun testMethodCallNamedItIsNotPestTest() {
val file = myFixture.configureByFile("MethodCallNamedIt.php")

assertFalse(file.isPestTestFile())
}

fun testFunctionCallNamedItWithDescriptionAndClosure() {
val file = myFixture.configureByFile("PestItFunctionCallWithDescriptionAndClosure.php")

assertTrue(file.isPestTestFile())
}

fun testFunctionCallNamedItWithDescriptionAndHigherOrder() {
val file = myFixture.configureByFile("PestItFunctionCallWithDescriptionAndHigherOrder.php")

assertTrue(file.isPestTestFile())
}

fun testFunctionCallNamedTestWithDescriptionAndHigherOrder() {
val file = myFixture.configureByFile("PestTestFunctionCallWithDescriptionAndHigherOrder.php")

assertTrue(file.isPestTestFile())
}

fun testMethodCallNamedItAndVariableTestIsNotPestTest() {
val file = myFixture.configureByFile("MethodCallNamedItAndVariableTest.php")

assertFalse(file.isPestTestFile())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.pestphp.pest.tests.PestLightCodeFixture

class IsPestTestFunctionTest : PestLightCodeFixture() {
override fun getTestDataPath(): String? {
return "src/test/resources/com/pestphp/pest/PestUtil/IsPestTestFunctionTest"
return "src/test/resources/com/pestphp/pest/PestUtil"
}

fun testMethodCallNamedTestIsNotPestTest() {
Expand Down
43 changes: 43 additions & 0 deletions src/test/kotlin/com/pestphp/pest/goto/PestTestFinderTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.pestphp.pest.goto

import com.jetbrains.php.lang.psi.PhpFile
import com.jetbrains.php.lang.psi.PhpPsiUtil
import com.pestphp.pest.tests.PestLightCodeFixture
import junit.framework.TestCase

class PestTestFinderTest : PestLightCodeFixture() {
override fun getTestDataPath(): String? {
return "src/test/resources/com/pestphp/pest/goto/PestTestFinder"
}

fun testPestTestIsTest() {
val file = myFixture.configureByFile("test/App/UserTest.php")

val testElement = file.firstChild.lastChild.firstChild

assertTrue(PestTestFinder().isTest(testElement))
}

fun testFileIsTest() {
val file = myFixture.configureByFile("test/App/UserTest.php")

assertTrue(PestTestFinder().isTest(file))
}

fun testRandomElementIsTest() {
val file = myFixture.configureByFile("test/App/UserTest.php")

assertTrue(PestTestFinder().isTest(file.firstChild.children.random()))
}

fun testCanFindSourceElement() {
val file = myFixture.configureByFile("App/User.php")

TestCase.assertSame(
file,
PestTestFinder().findSourceElement(
PhpPsiUtil.findAllClasses(file as PhpFile).first().methods.first()
)
)
}
}
35 changes: 35 additions & 0 deletions src/test/kotlin/com/pestphp/pest/indexers/PestTestIndexTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.pestphp.pest.indexers

import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.indexing.FileBasedIndex
import com.pestphp.pest.tests.PestLightCodeFixture

class PestTestIndexTest : PestLightCodeFixture() {
override fun getTestDataPath(): String? {
return "src/test/resources/com/pestphp/pest/indexers/PestTestIndexTest"
}

fun testPestTestFileIsIndexed() {
myFixture.copyFileToProject("FileWithPestTest.php", "tests/FileWithPestTest.php")

val fileBasedIndex = FileBasedIndex.getInstance()

val indexKeys = fileBasedIndex.getAllKeys(PestTestIndex.key, project).filter {
fileBasedIndex.getContainingFiles(PestTestIndex.key, it, GlobalSearchScope.allScope(project)).isNotEmpty()
}

assertContainsElements(indexKeys, "FileWithPestTest.php")
}

fun testPhpFileIsNotIndexed() {
myFixture.copyFileToProject("FileWithoutPestTest.php", "tests/FileWithoutPestTest.php")

val fileBasedIndex = FileBasedIndex.getInstance()

val indexKeys = fileBasedIndex.getAllKeys(PestTestIndex.key, project).filter {
fileBasedIndex.getContainingFiles(PestTestIndex.key, it, GlobalSearchScope.allScope(project)).isNotEmpty()
}

assertDoesntContain(indexKeys, "FileWithoutPestTest.php")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace App;

class User {
public function getName(): String
{
return "Oliver Nybroe";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

test("Can get user's name", function () {
$user = new \App\User();

$this->asserEquals("Oliver Nybroe", $user->getName());
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

test('basic', function () {
$this->assertTrue(true);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

echo "works";

0 comments on commit 143ec8a

Please sign in to comment.