Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(goto): Navigate between test and implementation #53

Merged
merged 5 commits into from
Aug 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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";