Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ jobs:
node-version: lts/*

- name: Install elm tools
run: npm install --global elm elm-test elm-format elm-review lamdera
run: npm install --global elm elm-test elm-test-rs elm-format elm-review lamdera

# Run tests
- name: Run Tests
Expand Down
5 changes: 5 additions & 0 deletions src/main/kotlin/org/elm/ide/icons/ElmIcons.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ object ElmIcons {
val UNION_TYPE = getIcon("type.png")
val TYPE_ALIAS = getIcon("type.png")

// TEST ICONS

val RUN = getIcon("run.svg")
val RUN_ALL = getIcon("runAll.svg")

private fun getIcon(path: String): Icon {
return IconLoader.getIcon("/icons/$path", ElmIcons::class.java)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ class ElmLineMarkerProvider : LineMarkerProviderDescriptor() {

private val optionProviders = mapOf(
ElmExposureLineMarkerProvider.OPTION to { ElmExposureLineMarkerProvider() },
ElmRecursiveCallLineMarkerProvider.OPTION to { ElmRecursiveCallLineMarkerProvider() }
ElmRecursiveCallLineMarkerProvider.OPTION to { ElmRecursiveCallLineMarkerProvider() },
ElmTestModuleLineMarkerProvider.OPTION to { ElmTestModuleLineMarkerProvider() },
ElmTestDescribeLineMarkerProvider.OPTION to { ElmTestDescribeLineMarkerProvider() },
ElmTestSingleLineMarkerProvider.OPTION to { ElmTestSingleLineMarkerProvider() }
)

private val OPTIONS = optionProviders.keys.toTypedArray()
44 changes: 44 additions & 0 deletions src/main/kotlin/org/elm/ide/lineMarkers/ElmRunActions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.elm.ide.lineMarkers

import com.intellij.execution.ProgramRunnerUtil
import com.intellij.execution.executors.DefaultRunExecutor
import com.intellij.execution.impl.SingleConfigurationConfigurable
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.psi.PsiElement
import org.elm.ide.test.run.ElmTestRunConfiguration
import org.elm.ide.test.run.ElmTestRunConfigurationSettingsBuilder

/** * Action to run all tests for a given file or directory */
class RunAllTestsAction(private val element: PsiElement) : AnAction("Run All Tests") {
override fun actionPerformed(event: AnActionEvent) {
ProgramRunnerUtil.executeConfiguration(
ElmTestRunConfigurationSettingsBuilder.createAndRegisterFromElement(element),
DefaultRunExecutor.getRunExecutorInstance()
)
}
}

/** * Action to run a test filtered by test or describe description */
class RunFilteredTestAction(private val element: PsiElement, private val filter: String) : AnAction("Run Filtered Test") {
override fun actionPerformed(event: AnActionEvent) {
ProgramRunnerUtil.executeConfiguration(
ElmTestRunConfigurationSettingsBuilder.createAndRegisterFromElement(element, filter),
DefaultRunExecutor.getRunExecutorInstance()
)
}
}

/** * Creates a run configuration and opens editor */
class ModifyRunConfiguration(private val element: PsiElement, private val filter: String? = null) : AnAction("Modify Run Configuration") {
override fun actionPerformed(event: AnActionEvent) {
ShowSettingsUtil.getInstance().editConfigurable(
element.project,
SingleConfigurationConfigurable.editSettings<ElmTestRunConfiguration>(
ElmTestRunConfigurationSettingsBuilder.createAndRegisterFromElement(element, filter),
null
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.elm.ide.lineMarkers

import com.intellij.codeInsight.daemon.GutterIconDescriptor
import com.intellij.codeInsight.daemon.LineMarkerInfo
import com.intellij.psi.PsiElement
import org.elm.ide.icons.ElmIcons
import org.elm.ide.test.core.ElmTestElementNavigator
import org.elm.workspace.elmToolchain

/** Handles adding a gutter icon for running tests under a describe */
class ElmTestDescribeLineMarkerProvider : ElmTestLineMarkerProvider() {
companion object {
val OPTION = GutterIconDescriptor.Option("elm.testDescribe", "Test describe", ElmIcons.RUN)
}

/** Add gutter icons for the describe line */
override fun shouldAddGutterIcon(element: PsiElement): Boolean {
return element.project.elmToolchain.isElmTestRsEnabled && element.text == "describe"
}

override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? {
if (!shouldAddGutterIcon(element)) return null
val filter = ElmTestElementNavigator.findTestDescription(element) ?: return null

return createLineMarkerInfo(
element,
ElmIcons.RUN,
"Run describe",
listOf(RunFilteredTestAction(element, filter), ModifyRunConfiguration(element, filter)),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.elm.ide.lineMarkers

import com.intellij.codeInsight.daemon.GutterIconNavigationHandler
import com.intellij.codeInsight.daemon.LineMarkerInfo
import com.intellij.codeInsight.daemon.LineMarkerProvider
import com.intellij.ide.DataManager
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.editor.markup.GutterIconRenderer
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.psi.PsiElement
import com.intellij.ui.awt.RelativePoint
import com.intellij.util.FunctionUtil
import org.elm.workspace.elmToolchain
import java.awt.event.MouseEvent
import javax.swing.Icon

/** * Abstract class to handle common operations for all test marker classes */
abstract class ElmTestLineMarkerProvider : LineMarkerProvider {
/** * Create a popup with a list of given actions */
private class PopupHandler(val actions: List<AnAction>) : GutterIconNavigationHandler<PsiElement> {
override fun navigate(e: MouseEvent, elt: PsiElement) {
val group = DefaultActionGroup().apply {
addAll(actions)
}

val popup = JBPopupFactory.getInstance()
.createActionGroupPopup(
null,
group,
DataManager.getInstance().getDataContext(e.component),
JBPopupFactory.ActionSelectionAid.MNEMONICS,
true
)

popup.show(RelativePoint(e))
}
}

/** * Returns true if the line should contain a gutter icon for the type of marker */
abstract fun shouldAddGutterIcon(element: PsiElement): Boolean

/** * Returns true if the file contains tests */
protected fun isTestFile(element: PsiElement): Boolean {
return element.containingFile.text.contains("import Test exposing")
}

/** Create a gutter icon on a given line with an icon, tooltip, and list of popup actions */
protected fun createLineMarkerInfo(element: PsiElement, icon: Icon, tooltip: String, actions: List<AnAction>, ): LineMarkerInfo<*>? {
if (!isTestFile(element) || !shouldAddGutterIcon(element)) return null

return LineMarkerInfo(
element,
element.textRange,
icon,
FunctionUtil.constant(tooltip),
PopupHandler(actions),
GutterIconRenderer.Alignment.LEFT
) { tooltip }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.elm.ide.lineMarkers

import com.intellij.codeInsight.daemon.GutterIconDescriptor
import com.intellij.codeInsight.daemon.LineMarkerInfo
import com.intellij.psi.PsiElement
import org.elm.ide.icons.ElmIcons
import org.elm.lang.core.psi.ElmTypes

/** * Handles adding a gutter icon for running all tests in a module */
class ElmTestModuleLineMarkerProvider : ElmTestLineMarkerProvider() {
companion object {
val OPTION = GutterIconDescriptor.Option("elm.testModule", "Test module", ElmIcons.RUN_ALL)
}

/** * Add gutter icons for the module line */
override fun shouldAddGutterIcon(element: PsiElement): Boolean {
return element.node.elementType == ElmTypes.MODULE
}

override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? {
if (!shouldAddGutterIcon(element)) return null

return createLineMarkerInfo(
element,
ElmIcons.RUN_ALL,
"Run all tests in this module",
listOf(RunAllTestsAction(element), ModifyRunConfiguration(element)),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.elm.ide.lineMarkers

import com.intellij.codeInsight.daemon.GutterIconDescriptor
import com.intellij.codeInsight.daemon.LineMarkerInfo
import com.intellij.psi.PsiElement
import org.elm.ide.icons.ElmIcons
import org.elm.ide.test.core.ElmTestElementNavigator
import org.elm.workspace.elmToolchain

/** * Handles adding a gutter icon for running a specific test */
class ElmTestSingleLineMarkerProvider : ElmTestLineMarkerProvider() {
companion object {
val OPTION = GutterIconDescriptor.Option("elm.testSingle", "Test single", ElmIcons.RUN)
}

/** * Add gutter icons for the test line */
override fun shouldAddGutterIcon(element: PsiElement): Boolean {
return element.project.elmToolchain.isElmTestRsEnabled && element.text == "test"
}

override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? {
if (!shouldAddGutterIcon(element)) return null
val filter = ElmTestElementNavigator.findTestDescription(element) ?: return null

return createLineMarkerInfo(
element,
ElmIcons.RUN,
"Run test",
listOf(RunFilteredTestAction(element, filter), ModifyRunConfiguration(element, filter)),
)
}
}
48 changes: 48 additions & 0 deletions src/main/kotlin/org/elm/ide/test/core/ElmTestElementNavigator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.elm.ide.test.core

import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiWhiteSpace
import org.elm.lang.core.psi.elements.ElmBinOpExpr
import org.elm.lang.core.psi.elements.ElmFunctionCallExpr
import org.elm.lang.core.psi.elements.ElmStringConstantExpr

object ElmTestElementNavigator {
/**
* Find the test or describe description text without surrounding quotes
*
* This will only match
* - test "this works"
* - describe "this test"
*
* Exactly and not any programatic calls
*/
fun findTestDescription(element: PsiElement?): String? {
if (element == null) return null
val callExpr = getFunctionCallExpr(element) ?: return null

// look for a string directly after the test or describe function call
return (callExpr
.children
.filterNot { it is PsiWhiteSpace || it is PsiComment }
.getOrNull(1) as? ElmStringConstantExpr)
?.text
?.removeSurrounding("\"")
}

/** Find the next parent function call given a list of target names */
private fun getFunctionCallExpr(element: PsiElement?, targets: List<String> = listOf("test", "describe")): ElmFunctionCallExpr? {
var current = element
while (current != null) {
val unwrapped = if (current is ElmBinOpExpr) {
// handles creating a run config from in a test body
current.children.firstOrNull { it is ElmFunctionCallExpr } ?: current
} else {
current
}
if (unwrapped is ElmFunctionCallExpr && unwrapped.target.text.substringAfterLast(".") in targets) return unwrapped
current = current.parent
}
return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.elm.ide.test.core.json.CompileErrors
import org.elm.ide.test.core.json.Error
import java.nio.file.Path
import kotlin.math.roundToLong

/**
* Processes events from a test run by elm-test.
Expand Down Expand Up @@ -75,7 +76,7 @@
fun testEvents(path: Path, obj: JsonObject): Sequence<TreeNodeEvent> {
return when (getStatus(obj)) {
"pass" -> {
val duration = java.lang.Long.parseLong(obj.get("duration").asString)
val duration = obj.get("duration").asDouble.roundToLong()
sequenceOf(newTestStartedEvent(path))
.plus(newTestFinishedEvent(path, duration))
}
Expand Down Expand Up @@ -159,7 +160,7 @@
null
}

fun getReason(obj: JsonObject): JsonObject? {

Check notice on line 163 in src/main/kotlin/org/elm/ide/test/core/ElmTestJsonProcessor.kt

View workflow job for this annotation

GitHub Actions / Inspect code

Class member can have 'private' visibility

Function 'getReason' could be private
return if (getFirstFailure(obj).isJsonObject)
getFirstFailure(obj).asJsonObject.get("reason").asJsonObject
else
Expand Down
Loading
Loading