Kotlin Ultimate: Support Jest run configurations for Kotlin sources

#KT-21531 Fixed
This commit is contained in:
Alexey Sedunov
2018-01-17 17:37:40 +03:00
parent 2c0af794ea
commit 82a997e0a2
5 changed files with 157 additions and 48 deletions
@@ -4,5 +4,6 @@
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.nodejs.mocha.KotlinMochaRunConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.nodejs.protractor.KotlinProtractorRunConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.nodejs.jest.KotlinJestRunConfigurationProducer"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,99 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.nodejs.jest
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.javascript.jest.JestRunConfiguration
import com.intellij.javascript.jest.JestRunConfigurationProducer
import com.intellij.javascript.jest.JestRunSettings
import com.intellij.javascript.jest.scope.JestScopeKind
import com.intellij.openapi.util.Ref
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiUtilCore
import org.jetbrains.kotlin.idea.js.jsOrJsImpl
import org.jetbrains.kotlin.idea.js.jsTestOutputFilePath
import org.jetbrains.kotlin.idea.nodejs.TestElementInfo
import org.jetbrains.kotlin.idea.nodejs.TestElementPath
import org.jetbrains.kotlin.idea.nodejs.getNodeJsEnvironmentVars
import org.jetbrains.kotlin.idea.util.projectStructure.module
private typealias JestTestElementInfo = TestElementInfo<JestRunSettings>
class KotlinJestRunConfigurationProducer : JestRunConfigurationProducer() {
private fun createTestElementRunInfo(element: PsiElement, originalSettings: JestRunSettings): JestTestElementInfo? {
val module = element.module?.jsOrJsImpl() ?: return null
val project = module.project
val testFilePath = module.jsTestOutputFilePath ?: return null
val settings = if (originalSettings.workingDirSystemDependentPath.isBlank()) {
val workingDir = FileUtil.toSystemDependentName(project.baseDir.path)
originalSettings.toBuilder().setWorkingDir(workingDir).build()
} else originalSettings
val testElementPath = TestElementPath.forElement(element, module) ?: return null
val builder = settings.toBuilder()
builder.setTestFilePath(testFilePath)
when (testElementPath) {
is TestElementPath.BySuite -> {
val (suiteNames, testName) = testElementPath
if (testName == null) {
builder.setScopeKind(JestScopeKind.SUITE)
builder.setTestNames(suiteNames)
} else {
builder.setScopeKind(JestScopeKind.TEST)
builder.setTestNames(suiteNames + testName)
}
}
is TestElementPath.BySingleFile -> {
builder.setScopeKind(JestScopeKind.TEST_FILE)
}
}
builder.setEnvData(module.getNodeJsEnvironmentVars())
return JestTestElementInfo(builder.build(), element)
}
override fun isConfigurationFromCompatibleContext(configuration: JestRunConfiguration, context: ConfigurationContext): Boolean {
val element = context.psiLocation ?: return false
val (thisRunSettings, _) = createTestElementRunInfo(element, configuration.runSettings) ?: return false
val thatRunSettings = configuration.runSettings
if (thisRunSettings.configFileSystemDependentPath != thatRunSettings.configFileSystemDependentPath) return false
if (thatRunSettings.scopeKind != thisRunSettings.scopeKind) return false
return when (thisRunSettings.scopeKind) {
JestScopeKind.ALL -> true
JestScopeKind.TEST_FILE -> thisRunSettings.testFileSystemDependentPath == thatRunSettings.testFileSystemDependentPath
JestScopeKind.SUITE, JestScopeKind.TEST ->
thisRunSettings.testFileSystemDependentPath == thatRunSettings.testFileSystemDependentPath &&
thisRunSettings.testNames == thatRunSettings.testNames
else -> false
}
}
override fun setupConfigurationFromCompatibleContext(
configuration: JestRunConfiguration,
context: ConfigurationContext,
sourceElement: Ref<PsiElement>
): Boolean {
val element = context.psiLocation ?: return false
val module = element.module
val jsModule = module?.jsOrJsImpl() ?: return false
val file = if (jsModule != module) {
jsModule.moduleFile
} else {
PsiUtilCore.getVirtualFile(element)
} ?: return false
val project = module.project
if (!isTestRunnerPackageAvailableFor(project, file)) return false
val (runSettings, enclosingTestElement) = createTestElementRunInfo(element, configuration.runSettings) ?: return false
configuration.runSettings = runSettings
sourceElement.set(enclosingTestElement)
configuration.setGeneratedName()
return true
}
}
@@ -0,0 +1,48 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.nodejs
import com.intellij.openapi.module.Module
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.util.projectStructure.getModuleDir
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
data class TestElementInfo<out Settings>(val runSettings: Settings, val enclosingTestElement: PsiElement)
sealed class TestElementPath {
data class BySuite(val suiteNames: List<String>, val testName: String?) : TestElementPath()
object BySingleFile : TestElementPath()
companion object {
fun isModuleAssociatedDir(element: PsiElement, module: Module): Boolean {
return element is PsiDirectory && module.getModuleDir() == element.virtualFile.path
}
fun forElement(element: PsiElement, module: Module): TestElementPath? {
if (isModuleAssociatedDir(element, module)) return TestElementPath.BySingleFile
val declaration = element.getNonStrictParentOfType<KtNamedDeclaration>() ?: return null
val klass = when (declaration) {
is KtClassOrObject -> declaration
is KtNamedFunction -> declaration.containingClassOrObject ?: return null
else -> return null
}
val suiteNames = klass.parentsWithSelf
.filterIsInstance<KtClassOrObject>()
.mapNotNull { it.name }
.toList()
.asReversed()
val testName = (declaration as? KtNamedFunction)?.name
return TestElementPath.BySuite(suiteNames, testName)
}
}
}
@@ -18,15 +18,12 @@ package org.jetbrains.kotlin.idea.nodejs.mocha
import com.intellij.execution.RunManager
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Ref
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiUtilCore
import com.intellij.util.SmartList
@@ -35,25 +32,14 @@ import com.jetbrains.nodejs.mocha.MochaUtil
import com.jetbrains.nodejs.mocha.execution.*
import org.jetbrains.kotlin.idea.js.jsOrJsImpl
import org.jetbrains.kotlin.idea.js.jsTestOutputFilePath
import org.jetbrains.kotlin.idea.nodejs.TestElementInfo
import org.jetbrains.kotlin.idea.nodejs.TestElementPath
import org.jetbrains.kotlin.idea.nodejs.getNodeJsEnvironmentVars
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.util.projectStructure.getModuleDir
import org.jetbrains.kotlin.idea.util.projectStructure.module
import org.jetbrains.kotlin.js.resolve.JsPlatform
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
private typealias MochaTestElementInfo = TestElementInfo<MochaRunSettings>
class KotlinMochaRunConfigurationProducer : MochaRunConfigurationProducer() {
private data class TestElementInfo(val runSettings: MochaRunSettings, val enclosingTestElement: PsiElement)
private sealed class TestElementPath {
data class BySuite(val suiteNames: List<String>, val testName: String?) : TestElementPath()
object BySingleFile : TestElementPath()
}
// Copied from MochaRunConfigurationProducer.collectMochaTestRoots()
private fun collectMochaTestRoots(project: Project): List<VirtualFile> {
return RunManager
@@ -103,27 +89,7 @@ class KotlinMochaRunConfigurationProducer : MochaRunConfigurationProducer() {
return VfsUtilCore.isUnder(file, dirs)
}
private fun createSuiteOrTestData(element: PsiElement, module: Module): TestElementPath? {
if (element is PsiDirectory && module.getModuleDir() == element.virtualFile.path) {
return TestElementPath.BySingleFile
}
val declaration = element.getNonStrictParentOfType<KtNamedDeclaration>() ?: return null
val klass = when (declaration) {
is KtClassOrObject -> declaration
is KtNamedFunction -> declaration.containingClassOrObject ?: return null
else -> return null
}
val suiteNames = klass.parentsWithSelf
.filterIsInstance<KtClassOrObject>()
.mapNotNull { it.name }
.toList()
.asReversed()
val testName = (declaration as? KtNamedFunction)?.name
return TestElementPath.BySuite(suiteNames, testName)
}
private fun createTestElementRunInfo(element: PsiElement, originalSettings: MochaRunSettings): TestElementInfo? {
private fun createTestElementRunInfo(element: PsiElement, originalSettings: MochaRunSettings): MochaTestElementInfo? {
val module = element.module?.jsOrJsImpl() ?: return null
val project = module.project
val testFilePath = module.jsTestOutputFilePath ?: return null
@@ -132,7 +98,7 @@ class KotlinMochaRunConfigurationProducer : MochaRunConfigurationProducer() {
originalSettings.builder().setWorkingDir(workingDir).build()
}
else originalSettings
val testElementPath = createSuiteOrTestData(element, module) ?: return null
val testElementPath = TestElementPath.forElement(element, module) ?: return null
val builder = settings.builder()
builder.setTestFilePath(testFilePath)
if (settings.ui.isEmpty()) {
@@ -158,7 +124,7 @@ class KotlinMochaRunConfigurationProducer : MochaRunConfigurationProducer() {
builder.setEnvData(module.getNodeJsEnvironmentVars())
return TestElementInfo(builder.build(), element)
return MochaTestElementInfo(builder.build(), element)
}
override fun isConfigurationFromCompatibleContext(configuration: MochaRunConfiguration, context: ConfigurationContext): Boolean {
@@ -21,17 +21,12 @@ import com.intellij.execution.actions.ConfigurationContext
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.util.Ref
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.facet.implementingModules
import org.jetbrains.kotlin.idea.js.jsOrJsImpl
import org.jetbrains.kotlin.idea.js.jsTestOutputFilePath
import org.jetbrains.kotlin.idea.nodejs.TestElementPath
import org.jetbrains.kotlin.idea.nodejs.getNodeJsEnvironmentVars
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.resolve.TargetPlatform.Common
import org.jetbrains.kotlin.idea.util.projectStructure.getModuleDir
import org.jetbrains.kotlin.idea.util.projectStructure.module
import org.jetbrains.kotlin.js.resolve.JsPlatform
class KotlinProtractorRunConfigurationProducer :
CompatibleRunConfigurationProducer<KotlinProtractorRunConfiguration>(KotlinProtractorConfigurationType.getInstance()) {
@@ -52,7 +47,7 @@ class KotlinProtractorRunConfigurationProducer :
): Boolean {
val element = context.psiLocation ?: return false
val module = ModuleUtilCore.findModuleForPsiElement(element) ?: return false
if (element !is PsiDirectory || module.getModuleDir() != element.virtualFile.path) return false
if (!TestElementPath.isModuleAssociatedDir(element, module)) return false
val jsModule = module.jsOrJsImpl() ?: return false
val testFilePath = jsModule.jsTestOutputFilePath ?: return false