Skip to content

Commit e2f999d

Browse files
Support for data test gutter icons (#393)
1 parent 538de18 commit e2f999d

36 files changed

+526
-188
lines changed

src/main/kotlin/io/kotest/plugin/intellij/Test.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ data class Test(
5656
val specClassName: KtClassOrObject, // the containing class name, which all tests must have
5757
val testType: TestType,
5858
val xdisabled: Boolean, // if true then this test was defined using one of the x methods
59-
val psi: PsiElement // the canonical element that identifies this test
59+
val psi: PsiElement, // the canonical element that identifies this test
60+
val isDataTest: Boolean = false
6061
) {
6162

6263
// true if this test is not xdisabled and not disabled by a bang and not nested inside another disabled test

src/main/kotlin/io/kotest/plugin/intellij/psi/utils.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,8 @@ fun KtValueArgumentList.isSingleStringTemplateArg(): Boolean =
127127
&& children[0] is KtValueArgument
128128
&& children[0].children.size == 1
129129
&& children[0].children[0] is KtStringTemplateExpression
130+
131+
fun LeafPsiElement.isDataTestMethodCall(dataTestMethodNames:Set<String>): KtCallExpression? {
132+
val lambdaCall = ifCallExpressionLambdaOpenBrace()
133+
return lambdaCall.takeIf {lambdaCall?.functionName() in dataTestMethodNames}
134+
}

src/main/kotlin/io/kotest/plugin/intellij/run/gradle/GradleTaskNamesBuilder.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ data class GradleTaskNamesBuilder(
3939
private fun includeArg(): String? {
4040
return when (test) {
4141
null -> "$PROPERTY_INCLUDE='${spec.fqName?.asString()}'"
42-
else -> "$PROPERTY_INCLUDE='${test.descriptorPath()}'"
42+
else ->
43+
when(test.isDataTest){
44+
false -> "$PROPERTY_INCLUDE='${test.descriptorPath()}'"
45+
true -> "$PROPERTY_INCLUDE='${spec.fqName?.asString()}'"
46+
}
4347
}
4448
}
4549
}

src/main/kotlin/io/kotest/plugin/intellij/run/gradle/TestOrSpecGradleRunConfigurationProducer.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ class TestOrSpecGradleRunConfigurationProducer : GradleRunConfigurationProducer(
155155
if (test != null) {
156156
// if we specified a test descriptor before, it needs to match for this configuration to be the same
157157
val descriptorArg = GradleUtils.getIncludeArg(configuration.settings.taskNames) ?: return false
158+
if (test.isDataTest) {
159+
val spec = element.enclosingSpec()
160+
return spec?.fqName?.asString() == descriptorArg
161+
}
158162
if (test.descriptorPath() == descriptorArg) return true
159163
}
160164
}

src/main/kotlin/io/kotest/plugin/intellij/run/idea/TestPathRunConfigurationProducer.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ class TestPathRunConfigurationProducer : LazyRunConfigurationProducer<KotestRunC
5151

5252
val ktclass = element.enclosingKtClass()
5353
if (ktclass != null) {
54-
configuration.setTestPath(test.testPath())
54+
if (test.isDataTest)
55+
configuration.setTestPath(null)
56+
else
57+
configuration.setTestPath(test.testPath())
5558
configuration.setSpec(ktclass)
5659
configuration.setModule(context.module)
5760
configuration.name = generateName(ktclass, test)

src/main/kotlin/io/kotest/plugin/intellij/styles/BehaviorSpecStyle.kt

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import io.kotest.plugin.intellij.psi.extractLhsStringArgForDotExpressionWithRhsF
1010
import io.kotest.plugin.intellij.psi.extractStringArgForFunctionWithStringAndLambdaArgs
1111
import io.kotest.plugin.intellij.psi.ifDotExpressionSeparator
1212
import io.kotest.plugin.intellij.psi.ifOpenQuoteOfFunctionName
13+
import io.kotest.plugin.intellij.psi.isDataTestMethodCall
1314
import org.jetbrains.kotlin.name.FqName
1415
import org.jetbrains.kotlin.psi.KtCallExpression
1516
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
@@ -23,6 +24,16 @@ object BehaviorSpecStyle : SpecStyle {
2324
override fun fqn() = FqName("io.kotest.core.spec.style.BehaviorSpec")
2425
override fun specStyleName(): String = "Behavior Spec"
2526

27+
override fun getDataTestMethodNames(): Set<String> =
28+
setOf(
29+
"withData",
30+
"withContexts",
31+
"withGivens",
32+
"withWhens",
33+
"withThens",
34+
"withAnds"
35+
)
36+
2637
override fun isTestElement(element: PsiElement): Boolean = test(element) != null
2738

2839
private val contexts = listOf("Context", "context", "`Context`", "`context`")
@@ -173,6 +184,7 @@ object BehaviorSpecStyle : SpecStyle {
173184
?: element.tryXWhen()
174185
?: element.tryThen()
175186
?: element.tryXThen()
187+
?: element.tryDataTest()
176188
is KtDotQualifiedExpression -> element.tryThenWithConfig()
177189
else -> null
178190
}
@@ -191,13 +203,49 @@ object BehaviorSpecStyle : SpecStyle {
191203
return setOf("OPEN_QUOTE", "DOT")
192204
}
193205

206+
/**
207+
* For a BehaviorSpec we consider the following scenarios:
208+
*
209+
* context("test name") {}
210+
* xcontext("test name") {}
211+
* context("test name").config(...) {}
212+
* xcontext("test name").config(...) {}
213+
* given("test name") {}
214+
* xgiven("test name") {}
215+
* given("test name").config(...) {}
216+
* xgiven("test name").config(...) {}
217+
* when("test name") {}
218+
* xwhen("test name") {}
219+
* when("test name").config(...) {}
220+
* xwhen("test name").config(...) {}
221+
* then("test name") {}
222+
* xthen("test name") {}
223+
* then("test name").config(...) {}
224+
* xthen("test name").config(...) {}
225+
* and("test name") {}
226+
* xand("test name") {}
227+
* and("test name").config(...) {}
228+
* xand("test name").config(...) {}
229+
* withData(...) { }
230+
* withContexts(...) { }
231+
* withGivens(...) { }
232+
* withWhens(...) { }
233+
* withThens(...) { }
234+
* withAnds(...) { }
235+
*/
194236
override fun test(element: LeafPsiElement): Test? {
195237
val ktcall1 = element.ifOpenQuoteOfFunctionName(fnNames)
196238
if (ktcall1 != null) return test(ktcall1)
197239

198240
val ktdot = element.ifDotExpressionSeparator()
199241
if (ktdot != null) return test(ktdot)
200242

243+
// try to find Data Test Method by finding lambda openings
244+
val dataMethodCall = element.isDataTestMethodCall(getDataTestMethodNames())
245+
if (dataMethodCall != null) {
246+
return test(dataMethodCall)
247+
}
248+
201249
return null
202250
}
203251
}

src/main/kotlin/io/kotest/plugin/intellij/styles/DescribeSpecStyle.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import io.kotest.plugin.intellij.psi.extractLhsStringArgForDotExpressionWithRhsF
1010
import io.kotest.plugin.intellij.psi.extractStringArgForFunctionWithStringAndLambdaArgs
1111
import io.kotest.plugin.intellij.psi.ifDotExpressionSeparator
1212
import io.kotest.plugin.intellij.psi.ifOpenQuoteOfFunctionName
13+
import io.kotest.plugin.intellij.psi.isDataTestMethodCall
1314
import org.jetbrains.kotlin.name.FqName
1415
import org.jetbrains.kotlin.psi.KtCallExpression
1516
import org.jetbrains.kotlin.psi.KtClassOrObject
@@ -25,6 +26,14 @@ object DescribeSpecStyle : SpecStyle {
2526
return "describe(\"$name\") { }"
2627
}
2728

29+
override fun getDataTestMethodNames(): Set<String> =
30+
setOf(
31+
"withData",
32+
"withContexts",
33+
"withDescribes",
34+
"withIts"
35+
)
36+
2837
private val fnNames = setOf("describe", "xdescribe", "context", "xcontext", "it", "xit")
2938

3039
override fun isTestElement(element: PsiElement): Boolean = test(element) != null
@@ -246,6 +255,7 @@ object DescribeSpecStyle : SpecStyle {
246255
?: element.tryXDescribe()
247256
?: element.tryContext()
248257
?: element.tryXContent()
258+
?: element.tryDataTest()
249259
is KtDotQualifiedExpression ->
250260
element.tryDescribeWithConfig()
251261
?: element.tryXDescribeWithConfig()
@@ -261,13 +271,38 @@ object DescribeSpecStyle : SpecStyle {
261271
return setOf("OPEN_QUOTE", "DOT")
262272
}
263273

274+
/**
275+
* For a DescribeSpec we consider the following scenarios:
276+
*
277+
* describe("test name") { }
278+
* xdescribe("test name") { }
279+
* context("test name") { }
280+
* xcontext("test name") { }
281+
* it("test name") { }
282+
* xit("test name") { }
283+
* describe("test name").config(...) {}
284+
* xdescribe("test name").config(...) {}
285+
* context("test name").config(...) {}
286+
* xcontext("test name").config(...) {}
287+
* it("test name").config(...) {}
288+
* xit("test name").config(...) {}
289+
* withData(...) { }
290+
* withContexts(...) { }
291+
* withDescribes(...) { }
292+
* withIts(...) { }
293+
*/
264294
override fun test(element: LeafPsiElement): Test? {
265295
val call = element.ifOpenQuoteOfFunctionName(fnNames)
266296
if (call != null) return test(call)
267297

268298
val dot = element.ifDotExpressionSeparator()
269299
if (dot != null) return test(dot)
270300

301+
// try to find Data Test Method by finding lambda openings
302+
val dataMethodCall = element.isDataTestMethodCall(getDataTestMethodNames())
303+
if (dataMethodCall != null) {
304+
return test(dataMethodCall)
305+
}
271306
return null
272307
}
273308
}

src/main/kotlin/io/kotest/plugin/intellij/styles/ExpectSpecStyle.kt

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import io.kotest.plugin.intellij.psi.extractLhsStringArgForDotExpressionWithRhsF
1010
import io.kotest.plugin.intellij.psi.extractStringArgForFunctionWithStringAndLambdaArgs
1111
import io.kotest.plugin.intellij.psi.ifCallExpressionLambdaOpenBrace
1212
import io.kotest.plugin.intellij.psi.ifDotExpressionSeparator
13+
import io.kotest.plugin.intellij.psi.isDataTestMethodCall
1314
import org.jetbrains.kotlin.name.FqName
1415
import org.jetbrains.kotlin.psi.KtCallExpression
1516
import org.jetbrains.kotlin.psi.KtClassOrObject
@@ -25,6 +26,13 @@ object ExpectSpecStyle : SpecStyle {
2526
return "expect(\"$name\") { }"
2627
}
2728

29+
override fun getDataTestMethodNames(): Set<String> =
30+
setOf(
31+
"withData",
32+
"withContexts",
33+
"withExpects"
34+
)
35+
2836
override fun isTestElement(element: PsiElement): Boolean = test(element) != null
2937

3038
private fun locateParent(element: PsiElement): Test? {
@@ -60,7 +68,7 @@ object ExpectSpecStyle : SpecStyle {
6068

6169
override fun test(element: PsiElement): Test? {
6270
return when (element) {
63-
is KtCallExpression -> element.tryExpect() ?: element.tryContext()
71+
is KtCallExpression -> element.tryExpect() ?: element.tryContext() ?: element.tryDataTest()
6472
is KtDotQualifiedExpression -> element.tryExpectWithConfig()
6573
else -> null
6674
}
@@ -70,13 +78,30 @@ object ExpectSpecStyle : SpecStyle {
7078
return setOf("OPEN_QUOTE")
7179
}
7280

81+
/**
82+
* For a ExpectSpec we consider the following scenarios:
83+
*
84+
* expect("test name") { }
85+
* expect("test name").config(...) {}
86+
* context("test name") {}
87+
* context("test name").config(...) {}
88+
* withData(...) { }
89+
* withContexts(...) { }
90+
* withExpects(...) { }
91+
*/
7392
override fun test(element: LeafPsiElement): Test? {
7493
val ktcall = element.ifCallExpressionLambdaOpenBrace()
7594
if (ktcall != null) return test(ktcall)
7695

7796
val ktdot = element.ifDotExpressionSeparator()
7897
if (ktdot != null) return test(ktdot)
7998

99+
// try to find Data Test Method by finding lambda openings
100+
val dataMethodCall = element.isDataTestMethodCall(getDataTestMethodNames())
101+
if (dataMethodCall != null) {
102+
return test(dataMethodCall)
103+
}
104+
80105
return null
81106
}
82107
}

src/main/kotlin/io/kotest/plugin/intellij/styles/FeatureSpecStyle.kt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import io.kotest.plugin.intellij.psi.extractLhsStringArgForDotExpressionWithRhsF
1010
import io.kotest.plugin.intellij.psi.extractStringArgForFunctionWithStringAndLambdaArgs
1111
import io.kotest.plugin.intellij.psi.ifCallExpressionLambdaOpenBrace
1212
import io.kotest.plugin.intellij.psi.ifDotExpressionSeparator
13+
import io.kotest.plugin.intellij.psi.isDataTestMethodCall
1314
import org.jetbrains.kotlin.name.FqName
1415
import org.jetbrains.kotlin.psi.KtCallExpression
1516
import org.jetbrains.kotlin.psi.KtClassOrObject
@@ -25,6 +26,13 @@ object FeatureSpecStyle : SpecStyle {
2526
return "feature(\"$name\") { }"
2627
}
2728

29+
override fun getDataTestMethodNames(): Set<String> =
30+
setOf(
31+
"withData",
32+
"withFeatures",
33+
"withScenarios"
34+
)
35+
2836
override fun isTestElement(element: PsiElement): Boolean = test(element) != null
2937

3038
private fun locateParent(element: PsiElement): Test? {
@@ -67,7 +75,7 @@ object FeatureSpecStyle : SpecStyle {
6775

6876
override fun test(element: PsiElement): Test? {
6977
return when (element) {
70-
is KtCallExpression -> element.tryScenario() ?: element.tryFeature()
78+
is KtCallExpression -> element.tryScenario() ?: element.tryFeature() ?: element.tryDataTest()
7179
is KtDotQualifiedExpression -> element.tryFeatureWithConfig() ?: element.tryScenarioWithConfig()
7280
else -> null
7381
}
@@ -77,13 +85,34 @@ object FeatureSpecStyle : SpecStyle {
7785
return setOf("OPEN_QUOTE")
7886
}
7987

88+
/**
89+
* For a FeatureSpec we consider the following scenarios:
90+
*
91+
* feature("test name") { }
92+
* xfeature("test name") { }
93+
* feature("test name").config(...) {}
94+
* xfeature("test name").config(...) {}
95+
* scenario("test name") {}
96+
* xscenario("test name") {}
97+
* scenario("test name").config(...) {}
98+
* xscenario("test name").config(...) {}
99+
* withData(...) { }
100+
* withFeatures(...) { }
101+
* withScenarios(...) { }
102+
*/
80103
override fun test(element: LeafPsiElement): Test? {
81104
val ktcall = element.ifCallExpressionLambdaOpenBrace()
82105
if (ktcall != null) return test(ktcall)
83106

84107
val ktdot = element.ifDotExpressionSeparator()
85108
if (ktdot != null) return test(ktdot)
86109

110+
// try to find Data Test Method by finding lambda openings
111+
val dataMethodCall = element.isDataTestMethodCall(getDataTestMethodNames())
112+
if (dataMethodCall != null) {
113+
return test(dataMethodCall)
114+
}
115+
87116
return null
88117
}
89118
}

src/main/kotlin/io/kotest/plugin/intellij/styles/FreeSpecStyle.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import io.kotest.plugin.intellij.psi.extractStringLiteralFromLhsOfInfixFunction
1212
import io.kotest.plugin.intellij.psi.ifMinusOperator
1313
import io.kotest.plugin.intellij.psi.ifCallExpressionLhsStringOpenQuote
1414
import io.kotest.plugin.intellij.psi.ifDotExpressionSeparator
15+
import io.kotest.plugin.intellij.psi.isDataTestMethodCall
1516
import org.jetbrains.kotlin.name.FqName
1617
import org.jetbrains.kotlin.psi.KtBinaryExpression
1718
import org.jetbrains.kotlin.psi.KtCallExpression
@@ -28,6 +29,11 @@ object FreeSpecStyle : SpecStyle {
2829
return "\"$name\" { }"
2930
}
3031

32+
override fun getDataTestMethodNames(): Set<String> =
33+
setOf(
34+
"withData"
35+
)
36+
3137
override fun isTestElement(element: PsiElement): Boolean = test(element) != null
3238

3339
private fun locateParent(element: PsiElement): Test? {
@@ -79,7 +85,7 @@ object FreeSpecStyle : SpecStyle {
7985

8086
override fun test(element: PsiElement): Test? {
8187
return when (element) {
82-
is KtCallExpression -> element.tryTest()
88+
is KtCallExpression -> element.tryTest() ?: element.tryDataTest()
8389
is KtDotQualifiedExpression -> element.tryTestWithConfig()
8490
is KtBinaryExpression -> element.tryContainer()
8591
else -> null
@@ -95,6 +101,7 @@ object FreeSpecStyle : SpecStyle {
95101
*
96102
* "test name" {} // a test
97103
* "test name" - {} // a container
104+
* withData(...) { }
98105
*/
99106
override fun test(element: LeafPsiElement): Test? {
100107
val ktcall = element.ifCallExpressionLhsStringOpenQuote()
@@ -106,6 +113,12 @@ object FreeSpecStyle : SpecStyle {
106113
val ktbinary = element.ifMinusOperator()
107114
if (ktbinary != null) return test(ktbinary)
108115

116+
// try to find Data Test Method by finding lambda openings
117+
val dataMethodCall = element.isDataTestMethodCall(getDataTestMethodNames())
118+
if (dataMethodCall != null) {
119+
return test(dataMethodCall)
120+
}
121+
109122
return null
110123
}
111124
}

0 commit comments

Comments
 (0)