Kotlin Ultimate: Support Protractor run configurations for module directory

This commit is contained in:
Alexey Sedunov
2017-10-05 17:50:43 +03:00
parent 18d4b91b2d
commit e68f8d6d45
11 changed files with 663 additions and 14 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ dependencies {
compile(ideaUltimatePreloadedDeps("*.jar", subdir = "nodejs_plugin/NodeJS/lib")) compile(ideaUltimatePreloadedDeps("*.jar", subdir = "nodejs_plugin/NodeJS/lib"))
compile(ideaUltimateSdkCoreDeps("annotations", "trove4j", "intellij-core")) compile(ideaUltimateSdkCoreDeps("annotations", "trove4j", "intellij-core"))
compile(ideaUltimateSdkDeps("openapi", "idea", "util")) compile(ideaUltimateSdkDeps("openapi", "idea", "util", "jdom"))
compile(ideaUltimatePluginDeps("*.jar", plugin = "CSS")) compile(ideaUltimatePluginDeps("*.jar", plugin = "CSS"))
compile(ideaUltimatePluginDeps("*.jar", plugin = "DatabaseTools")) compile(ideaUltimatePluginDeps("*.jar", plugin = "DatabaseTools"))
compile(ideaUltimatePluginDeps("*.jar", plugin = "JavaEE")) compile(ideaUltimatePluginDeps("*.jar", plugin = "JavaEE"))
@@ -1,5 +1,8 @@
<idea-plugin> <idea-plugin>
<extensions defaultExtensionNs="com.intellij"> <extensions defaultExtensionNs="com.intellij">
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.nodejs.KotlinMochaRunConfigurationProducer"/> <configurationType implementation="org.jetbrains.kotlin.idea.nodejs.protractor.KotlinProtractorConfigurationType"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.nodejs.mocha.KotlinMochaRunConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.nodejs.protractor.KotlinProtractorRunConfigurationProducer"/>
</extensions> </extensions>
</idea-plugin> </idea-plugin>
@@ -35,4 +35,4 @@ fun getJsClasspath(module: Module): List<String> {
true true
} }
return result return result
} }
@@ -1,8 +1,23 @@
package org.jetbrains.kotlin.idea.nodejs /*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.nodejs.mocha
import com.intellij.execution.RunManager import com.intellij.execution.RunManager
import com.intellij.execution.actions.ConfigurationContext import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.configuration.EnvironmentVariablesData
import com.intellij.openapi.module.Module import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtilCore import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
@@ -13,14 +28,13 @@ import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.impl.file.PsiJavaDirectoryImpl
import com.intellij.psi.util.PsiUtilCore import com.intellij.psi.util.PsiUtilCore
import com.intellij.util.SmartList import com.intellij.util.SmartList
import com.intellij.util.containers.SmartHashSet import com.intellij.util.containers.SmartHashSet
import com.jetbrains.nodejs.mocha.MochaUtil import com.jetbrains.nodejs.mocha.MochaUtil
import com.jetbrains.nodejs.mocha.execution.* import com.jetbrains.nodejs.mocha.execution.*
import org.jetbrains.kotlin.idea.js.getJsClasspath
import org.jetbrains.kotlin.idea.js.getJsOutputFilePath import org.jetbrains.kotlin.idea.js.getJsOutputFilePath
import org.jetbrains.kotlin.idea.nodejs.getNodeJsEnvironmentVars
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.util.projectStructure.getModuleDir import org.jetbrains.kotlin.idea.util.projectStructure.getModuleDir
import org.jetbrains.kotlin.js.resolve.JsPlatform import org.jetbrains.kotlin.js.resolve.JsPlatform
@@ -30,7 +44,6 @@ import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import java.io.File
class KotlinMochaRunConfigurationProducer : MochaRunConfigurationProducer() { class KotlinMochaRunConfigurationProducer : MochaRunConfigurationProducer() {
private data class TestElementInfo(val runSettings: MochaRunSettings, val enclosingTestElement: PsiElement) private data class TestElementInfo(val runSettings: MochaRunSettings, val enclosingTestElement: PsiElement)
@@ -80,7 +93,7 @@ class KotlinMochaRunConfigurationProducer : MochaRunConfigurationProducer() {
} }
private fun createSuiteOrTestData(element: PsiElement, module: Module): TestElementPath? { private fun createSuiteOrTestData(element: PsiElement, module: Module): TestElementPath? {
if (element is PsiDirectory && module.getModuleDir() == (element as PsiJavaDirectoryImpl).virtualFile.path) { if (element is PsiDirectory && module.getModuleDir() == element.virtualFile.path) {
return TestElementPath.BySingleFile return TestElementPath.BySingleFile
} }
@@ -133,11 +146,7 @@ class KotlinMochaRunConfigurationProducer : MochaRunConfigurationProducer() {
} }
} }
val nodeJsClasspath = getJsClasspath(module).joinToString(File.pathSeparator) { builder.setEnvData(module.getNodeJsEnvironmentVars())
val basePath = project.basePath ?: return@joinToString it
FileUtil.getRelativePath(basePath, it, '/') ?: it
}
builder.setEnvData(EnvironmentVariablesData.create(mapOf("NODE_PATH" to nodeJsClasspath), true))
return TestElementInfo(builder.build(), element) return TestElementInfo(builder.build(), element)
} }
@@ -0,0 +1,33 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.nodejs
import com.intellij.execution.configuration.EnvironmentVariablesData
import com.intellij.openapi.module.Module
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.idea.js.getJsClasspath
import java.io.File
const val NODE_PATH_VAR = "NODE_PATH"
fun Module.getNodeJsEnvironmentVars(): EnvironmentVariablesData {
val nodeJsClasspath = getJsClasspath(this).joinToString(File.pathSeparator) {
val basePath = project.basePath ?: return@joinToString it
FileUtil.getRelativePath(basePath, it, '/') ?: it
}
return EnvironmentVariablesData.create(mapOf(NODE_PATH_VAR to nodeJsClasspath), true)
}
@@ -0,0 +1,52 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.nodejs.protractor
import com.intellij.execution.configuration.ConfigurationFactoryEx
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.ConfigurationTypeBase
import com.intellij.execution.configurations.ConfigurationTypeUtil
import com.intellij.openapi.project.Project
import icons.JavaScriptLanguageIcons
class KotlinProtractorConfigurationType : ConfigurationTypeBase(
"KotlinJavaScriptTestRunnerProtractor",
"Protractor",
NAME,
JavaScriptLanguageIcons.Protractor.Protractor
) {
init {
addFactory(object : ConfigurationFactoryEx<KotlinProtractorRunConfiguration>(this) {
override fun createTemplateConfiguration(project: Project) = KotlinProtractorRunConfiguration(project, this, NAME)
override fun isConfigurationSingletonByDefault() = true
override fun canConfigurationBeSingleton() = false
override fun onNewConfigurationCreated(configuration: KotlinProtractorRunConfiguration) = configuration.initialize()
})
}
companion object {
private val NAME = "Protractor"
fun getInstance() = ConfigurationTypeUtil.findConfigurationType(KotlinProtractorConfigurationType::class.java)
val factory: ConfigurationFactory
get() = getInstance().configurationFactories.first()
}
}
@@ -0,0 +1,103 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.nodejs.protractor
import com.intellij.execution.Executor
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.LocatableConfigurationBase
import com.intellij.execution.configurations.RuntimeConfigurationError
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.javascript.nodejs.interpreter.local.NodeJsLocalInterpreter
import com.intellij.javascript.nodejs.util.NodePackage
import com.intellij.javascript.protractor.ProtractorUtil
import com.intellij.openapi.project.Project
import com.intellij.util.PathUtil
import org.jdom.Element
import java.io.File
// Based on com.intellij.javascript.protractor.ProtractorRunConfiguration
class KotlinProtractorRunConfiguration(
project: Project,
factory: ConfigurationFactory,
name: String
) : LocatableConfigurationBase(project, factory, name) {
var runSettings = KotlinProtractorRunSettings()
val protractorPackage: NodePackage
get() {
val project = project
val interpreter = runSettings.interpreterRef.resolve(project)
return ProtractorUtil.getProtractorPackage(project, null, interpreter, true)
}
override fun getState(executor: Executor, environment: ExecutionEnvironment) =
KotlinProtractorRunState(this, environment, protractorPackage)
override fun getConfigurationEditor() = KotlinProtractorRunConfigurationEditor(project)
override fun readExternal(element: Element) {
super.readExternal(element)
runSettings = KotlinProtractorRunSettings.readFromXML(element)
}
override fun writeExternal(element: Element) {
super.writeExternal(element)
runSettings.writeToXML(element)
}
fun initialize() {
setNameChangedByUser(false)
}
override fun checkConfiguration() {
val isOptionalConfig = runSettings.testFileSystemDependentPath.isNotBlank() || runSettings.seleniumAddress.isNotBlank()
if (isOptionalConfig) {
validatePath("test file", runSettings.testFileSystemDependentPath, true, false)
if (runSettings.seleniumAddress.isBlank()) {
throw RuntimeConfigurationError("Unspecified Selenium address")
}
}
if (!isOptionalConfig || runSettings.configFileSystemDependentPath.isNotBlank()) {
validatePath("configuration file", runSettings.configFileSystemDependentPath, true, false)
}
val interpreter = runSettings.interpreterRef.resolve(project)
NodeJsLocalInterpreter.checkForRunConfiguration(interpreter)
validatePath("protractor package", protractorPackage.systemDependentPath, true, true)
}
override fun suggestedName(): String? {
val name = PathUtil.getFileName(runSettings.testFileSystemDependentPath)
return if (name.isNotBlank()) name else null
}
private fun validatePath(name: String,
path: String?,
shouldBeAbsolute: Boolean,
shouldBeDirectory: Boolean
) {
if (path.isNullOrBlank()) throw RuntimeConfigurationError("Unspecified " + name)
val file = File(path!!)
if (shouldBeAbsolute && !file.isAbsolute) throw RuntimeConfigurationError("No such " + name)
val exists = if (shouldBeDirectory) file.isDirectory else file.isFile
if (!exists) throw RuntimeConfigurationError("No such " + name)
}
}
@@ -0,0 +1,144 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.nodejs.protractor
import com.intellij.execution.configuration.EnvironmentVariablesTextFieldWithBrowseButton
import com.intellij.javascript.nodejs.interpreter.NodeJsInterpreterField
import com.intellij.javascript.nodejs.util.NodePackageField
import com.intellij.javascript.protractor.ProtractorUtil
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.options.SettingsEditor
import com.intellij.openapi.options.ex.SingleConfigurableEditor
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.util.io.FileUtil
import com.intellij.ui.RawCommandLineEditor
import com.intellij.ui.TextFieldWithHistoryWithBrowseButton
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.ui.FormBuilder
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.SwingHelper
import javax.swing.*
import java.util.Collections
// Based com.intellij.javascript.protractor.ProtractorRunConfigurationEditor
class KotlinProtractorRunConfigurationEditor(private val myProject: Project) : SettingsEditor<KotlinProtractorRunConfiguration>() {
private val testFilePathTextFieldWithBrowseButton: TextFieldWithHistoryWithBrowseButton
private val seleniumAddressTextField: JTextField
private val extraOptionsEditor: RawCommandLineEditor
private val configPathTextFieldWithBrowseButton: TextFieldWithHistoryWithBrowseButton
private val interpreterField: NodeJsInterpreterField
private val protractorPackageField: NodePackageField
private val envVarsComponent: EnvironmentVariablesTextFieldWithBrowseButton
private val panel: JPanel
init {
testFilePathTextFieldWithBrowseButton = createTestFileTextField(myProject)
seleniumAddressTextField = JTextField()
extraOptionsEditor = RawCommandLineEditor().apply { dialogCaption = "Extra Protractor Options" }
configPathTextFieldWithBrowseButton = createConfigurationFileTextField(myProject)
interpreterField = NodeJsInterpreterField(myProject, false)
protractorPackageField = NodePackageField(myProject, ProtractorUtil.PACKAGE_NAME) { interpreterField.interpreter }
envVarsComponent = EnvironmentVariablesTextFieldWithBrowseButton()
panel = FormBuilder()
.setAlignLabelOnRight(false)
.addLabeledComponent("&Test file:", testFilePathTextFieldWithBrowseButton)
.addLabeledComponent("&Selenium address:", seleniumAddressTextField)
.addLabeledComponent("E&xtra Protractor options:", extraOptionsEditor)
.addComponent(JSeparator(), JBUI.scale(8))
.addLabeledComponent("&Configuration file:", configPathTextFieldWithBrowseButton)
.addComponent(JSeparator(), JBUI.scale(8))
.addLabeledComponent("Node &interpreter:", interpreterField, JBUI.scale(8))
.addLabeledComponent("&Protractor package:", protractorPackageField)
.addLabeledComponent("&Environment variables:", envVarsComponent)
.panel
}
private fun createConfigurationFileTextField(project: Project) = TextFieldWithHistoryWithBrowseButton().apply {
val textFieldWithHistory = childComponent
textFieldWithHistory.setHistorySize(-1)
textFieldWithHistory.setMinimumAndPreferredWidth(0)
SwingHelper.addHistoryOnExpansion(textFieldWithHistory) {
val newFiles = ProtractorUtil.listPossibleConfigFilesInProject(project)
val newFilePaths = ContainerUtil.map(newFiles) { file -> FileUtil.toSystemDependentName(file.path) }
Collections.sort(newFilePaths)
newFilePaths
}
SwingHelper.installFileCompletionAndBrowseDialog(
project,
this,
"Select Protractor Configuration File",
FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()
)
}
private fun createTestFileTextField(project: Project) = TextFieldWithHistoryWithBrowseButton().apply {
val textFieldWithHistory = childComponent
textFieldWithHistory.setHistorySize(-1)
textFieldWithHistory.setMinimumAndPreferredWidth(0)
SwingHelper.installFileCompletionAndBrowseDialog(
project,
this,
"Select Protractor Configuration File",
FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()
)
}
override fun resetEditorFrom(runConfiguration: KotlinProtractorRunConfiguration) {
with(runConfiguration.runSettings) {
testFilePathTextFieldWithBrowseButton.setTextAndAddToHistory(testFileSystemDependentPath)
seleniumAddressTextField.text = seleniumAddress
extraOptionsEditor.text = extraOptions
configPathTextFieldWithBrowseButton.setTextAndAddToHistory(configFileSystemDependentPath)
interpreterField.interpreterRef = interpreterRef
protractorPackageField.selected = runConfiguration.protractorPackage
envVarsComponent.data = envData
}
updatePreferredWidth()
}
private fun updatePreferredWidth() {
val dialogWrapper = DialogWrapper.findInstance(panel)
if (dialogWrapper is SingleConfigurableEditor) {
// dialog for single run configuration
interpreterField.setPreferredWidthToFitText()
protractorPackageField.setPreferredWidthToFitText()
SwingHelper.setPreferredWidthToFitText(testFilePathTextFieldWithBrowseButton)
SwingHelper.setPreferredWidthToFitText(seleniumAddressTextField)
SwingHelper.setPreferredWidthToFitText(configPathTextFieldWithBrowseButton)
ApplicationManager.getApplication().invokeLater { SwingHelper.adjustDialogSizeToFitPreferredSize(dialogWrapper) }
}
}
override fun applyEditorTo(runConfiguration: KotlinProtractorRunConfiguration) {
ProtractorUtil.setProtractorPackage(myProject, protractorPackageField.selected)
runConfiguration.runSettings = runConfiguration.runSettings.copy(
interpreterRef = interpreterField.interpreterRef,
configFilePath = configPathTextFieldWithBrowseButton.text,
testFilePath = testFilePathTextFieldWithBrowseButton.text,
seleniumAddress = seleniumAddressTextField.text,
extraOptions = extraOptionsEditor.text,
envData = envVarsComponent.data
)
}
override fun createEditor() = panel
}
@@ -0,0 +1,64 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.nodejs.protractor
import com.intellij.execution.actions.CompatibleRunConfigurationProducer
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.js.getJsOutputFilePath
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
class KotlinProtractorRunConfigurationProducer :
CompatibleRunConfigurationProducer<KotlinProtractorRunConfiguration>(KotlinProtractorConfigurationType.getInstance()) {
override fun isConfigurationFromCompatibleContext(
configuration: KotlinProtractorRunConfiguration,
context: ConfigurationContext
): Boolean {
val contextPsi = context.psiLocation ?: return false
val module = contextPsi.module ?: return false
val testFilePath = getJsOutputFilePath(module, isTests = true, isMeta = false) ?: return false
return configuration.runSettings.testFileSystemDependentPath == FileUtil.toSystemDependentName(testFilePath)
}
override fun setupConfigurationFromCompatibleContext(
configuration: KotlinProtractorRunConfiguration,
context: ConfigurationContext,
sourceElement: Ref<PsiElement>
): 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 (TargetPlatformDetector.getPlatform(module) !is JsPlatform) return false
val testFilePath = getJsOutputFilePath(module, true, false) ?: return false
sourceElement.set(element)
configuration.runSettings = configuration.runSettings.copy(
testFilePath = testFilePath,
envData = module.getNodeJsEnvironmentVars()
)
configuration.name = configuration.suggestedName()
return true
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.nodejs.protractor
import com.intellij.execution.configuration.EnvironmentVariablesData
import com.intellij.javascript.nodejs.interpreter.NodeJsInterpreterRef
import com.intellij.openapi.util.JDOMExternalizerUtil.*
import com.intellij.openapi.util.io.FileUtil
import org.jdom.Element
data class KotlinProtractorRunSettings(
val interpreterRef: NodeJsInterpreterRef = NodeJsInterpreterRef.createProjectRef(),
private val configFilePath: String = "",
private val testFilePath: String = "",
val seleniumAddress: String = "",
val extraOptions: String = "",
val envData: EnvironmentVariablesData = EnvironmentVariablesData.DEFAULT
) {
val configFileSystemDependentPath get() = FileUtil.toSystemDependentName(configFilePath)
val testFileSystemDependentPath get() = FileUtil.toSystemDependentName(testFilePath)
fun writeToXML(element: Element) {
addElementWithValueAttribute(element, "config-file", FileUtil.toSystemIndependentName(configFileSystemDependentPath))
addElementWithValueAttribute(element, "test-file", FileUtil.toSystemIndependentName(testFileSystemDependentPath))
addElementWithValueAttribute(element, "selenium-address", seleniumAddress)
addElementWithValueAttribute(element, "extra-protractor-options", extraOptions)
addElementWithValueAttribute(element, "node-interpreter", interpreterRef.referenceName)
envData.writeExternal(element)
}
companion object {
fun readFromXML(element: Element): KotlinProtractorRunSettings {
val configFilePath = getFirstChildValueAttribute(element, "config-file")
val testFilePath = getFirstChildValueAttribute(element, "test-file")
val seleniumAddress = getFirstChildValueAttribute(element, "selenium-address")
val extraOptions = getFirstChildValueAttribute(element, "extra-protractor-options")
val interpreterRefName = getFirstChildValueAttribute(element, "node-interpreter")
val envData = EnvironmentVariablesData.readExternal(element)
return KotlinProtractorRunSettings(
NodeJsInterpreterRef.create(interpreterRefName ?: "project"),
configFilePath ?: "",
testFilePath ?: "",
seleniumAddress ?: "",
extraOptions ?: "",
envData
)
}
}
}
@@ -0,0 +1,178 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.nodejs.protractor
import com.intellij.execution.DefaultExecutionResult
import com.intellij.execution.ExecutionException
import com.intellij.execution.ExecutionResult
import com.intellij.execution.Executor
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.configurations.RunProfileState
import com.intellij.execution.process.ProcessHandler
import com.intellij.execution.process.ProcessTerminatedListener
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.execution.testframework.TestConsoleProperties
import com.intellij.execution.testframework.autotest.ToggleAutoTestAction
import com.intellij.execution.testframework.sm.SMCustomMessagesParsing
import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil
import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties
import com.intellij.execution.testframework.sm.runner.SMTestLocator
import com.intellij.execution.ui.ConsoleView
import com.intellij.javascript.HelperFilesLocator
import com.intellij.javascript.nodejs.NodeCommandLineUtil
import com.intellij.javascript.nodejs.debug.NodeLocalDebugRunProfileState
import com.intellij.javascript.nodejs.interpreter.local.NodeJsLocalInterpreter
import com.intellij.javascript.nodejs.util.NodePackage
import com.intellij.javascript.protractor.ProtractorConsoleFilter
import com.intellij.javascript.protractor.ProtractorOutputToGeneralTestEventsConverter
import com.intellij.javascript.protractor.ProtractorUtil
import com.intellij.javascript.testFramework.navigation.JSTestLocationProvider
import com.intellij.lang.javascript.buildTools.base.JsbtUtil
import com.intellij.openapi.util.Disposer
import com.intellij.util.PathUtil
import com.intellij.util.execution.ParametersListUtil
import java.io.File
import java.io.IOException
import java.nio.charset.StandardCharsets
class KotlinProtractorRunState(
private val runConfiguration: KotlinProtractorRunConfiguration,
private val environment: ExecutionEnvironment,
private val protractorPackage: NodePackage
) : RunProfileState, NodeLocalDebugRunProfileState {
companion object {
val FRAMEWORK_NAME = "KotlinProtractorJavaScriptTestRunner"
private val INTELLIJ_CONFIG_FILE_PATH = "protractor-intellij/lib/protractor-intellij-config.js"
}
private val project = runConfiguration.project
private val runSettings = runConfiguration.runSettings
override fun execute(debugPort: Int): ExecutionResult {
val interpreter = runSettings.interpreterRef.resolveAsLocal(project)
val commandLine = createCommandLine(interpreter, debugPort)
val processHandler = NodeCommandLineUtil.createProcessHandler(commandLine, false)
val consoleView = createSmtRunnerConsoleView(commandLine.workDirectory)
ProcessTerminatedListener.attach(processHandler)
consoleView.attachToProcess(processHandler)
foldCommandLine(consoleView, processHandler)
val executionResult = DefaultExecutionResult(consoleView, processHandler)
executionResult.setRestartActions(ToggleAutoTestAction())
return executionResult
}
private fun createSmtRunnerConsoleView(workingDirectory: File?): ConsoleView {
val testConsoleProperties = ProtractorConsoleProperties(runConfiguration, environment.executor, JSTestLocationProvider())
val consoleView = SMTestRunnerConnectionUtil.createConsole(FRAMEWORK_NAME, testConsoleProperties)
consoleView.addMessageFilter(ProtractorConsoleFilter(project, workingDirectory))
Disposer.register(environment, consoleView)
return consoleView
}
override fun foldCommandLine(consoleView: ConsoleView, processHandler: ProcessHandler) {
val configFileName = PathUtil.getFileName(runSettings.configFileSystemDependentPath)
val testFileName = PathUtil.getFileName(runSettings.testFileSystemDependentPath)
val seleniumAddress = runSettings.seleniumAddress
val parameters = ArrayList<String>().apply {
add("protractor")
if (configFileName.isNotBlank()) {
add(configFileName)
}
if (testFileName.isNotBlank()) {
add("--specs=$testFileName")
}
if (seleniumAddress.isNotBlank()) {
add("--seleniumAddress=$seleniumAddress")
}
addAll(ParametersListUtil.parse(runSettings.extraOptions.trim()))
}
val foldedCommandLine = ParametersListUtil.join(parameters)
JsbtUtil.foldCommandLine(consoleView, processHandler, foldedCommandLine)
}
private fun createCommandLine(interpreter: NodeJsLocalInterpreter, debugPort: Int): GeneralCommandLine {
val commandLine = GeneralCommandLine().apply { charset = StandardCharsets.UTF_8 }
runSettings.envData.configureCommandLine(commandLine, true)
val originalConfigFilePath = runSettings.configFileSystemDependentPath
val testFilePath = runSettings.testFileSystemDependentPath
val seleniumAddress = runSettings.seleniumAddress
val withConfigFile = originalConfigFilePath.isNotBlank()
commandLine.setWorkDirectory(project.basePath)
commandLine.exePath = interpreter.interpreterSystemDependentPath
NodeCommandLineUtil.addNodeOptionsForDebugging(commandLine, emptyList(), debugPort, true, interpreter, true)
NodeCommandLineUtil.configureUsefulEnvironment(commandLine)
commandLine.addParameter(ProtractorUtil.getProtractorMainJsFile(protractorPackage).absolutePath)
val intellijConfigFile = try {
HelperFilesLocator.getFileRelativeToHelpersDir(INTELLIJ_CONFIG_FILE_PATH)
}
catch (e: IOException) {
throw ExecutionException("Cannot locate wrapper config file", e)
}
commandLine.addParameter(intellijConfigFile.absolutePath)
val actualConfigFilePath = if (withConfigFile) {
originalConfigFilePath
}
else {
val tempConfigFile = File.createTempFile("protractor", "conf.js")
tempConfigFile.writeText("exports.config = {}")
tempConfigFile.absolutePath
}
commandLine.addParameter("--intellijOriginalConfigFile=$actualConfigFilePath")
if (testFilePath.isNotBlank()) {
commandLine.addParameter("--specs=$testFilePath")
}
if (seleniumAddress.isNotBlank()) {
commandLine.addParameter("--seleniumAddress=$seleniumAddress")
}
commandLine.addParameters(ParametersListUtil.parse(runSettings.extraOptions.trim()))
commandLine.addParameter("--disableChecks")
return commandLine
}
private class ProtractorConsoleProperties(
configuration: KotlinProtractorRunConfiguration,
executor: Executor,
private val myLocator: SMTestLocator
) : SMTRunnerConsoleProperties(configuration, FRAMEWORK_NAME, executor), SMCustomMessagesParsing {
init {
isUsePredefinedMessageFilter = false
setIfUndefined(TestConsoleProperties.HIDE_PASSED_TESTS, false)
setIfUndefined(TestConsoleProperties.HIDE_IGNORED_TEST, true)
setIfUndefined(TestConsoleProperties.SCROLL_TO_SOURCE, true)
setIfUndefined(TestConsoleProperties.SELECT_FIRST_DEFECT, true)
isIdBasedTestTree = true
isPrintTestingStartedTime = false
}
override fun getTestLocator() = myLocator
override fun createTestEventsConverter(testFrameworkName: String, consoleProperties: TestConsoleProperties) =
ProtractorOutputToGeneralTestEventsConverter(testFrameworkName, consoleProperties.isEditable)
}
}