Skip to content

Commit

Permalink
#94: Improve matching steps with patterns with empty parameter values
Browse files Browse the repository at this point in the history
  • Loading branch information
picimako committed Dec 28, 2024
1 parent 6aff683 commit a3cfad1
Show file tree
Hide file tree
Showing 4 changed files with 289 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

## [1.67.2]
### Fixed
- [#94](https://github.com/witspirit/IntelliJBehave/issues/94): Improved matching steps with patterns with empty parameter values.

## [1.67.1]
### Fixed
- Fixed the K2 compiler compatibility configuration.
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pluginGroup = com.github.kumaraman21.intellijbehave
pluginName = JBehave Support
pluginRepositoryUrl = https://github.com/witspirit/IntelliJBehave
# SemVer format -> https://semver.org
pluginVersion = 1.67.1
pluginVersion = 1.67.2

# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 242.21829.142
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ internal class OptimizedStepMatcher(stepMatcher: StepMatcher) : StepMatcher by s
init {
val patterns = stepMatcher.pattern().resolved().split(StepPatterns.Plain.VARIABLE)
fun List<String>.toRegexes() = asSequence()
.map { it.replace(StepPatterns.Regex.SPACE, "\\s") }
.map(::Regex)
.toList()
//Replaces sequences of spaces with single \s symbols. This is in line with the trimming
// of space in the 'matches()' function below.
.map { it.replace(StepPatterns.Regex.SPACE, "\\s") }
.map(::Regex)
.toList()

matchesRegexes = patterns.toRegexes().mapIndexed { i, regex ->
when (i) {
Expand All @@ -39,12 +41,20 @@ internal class OptimizedStepMatcher(stepMatcher: StepMatcher) : StepMatcher by s

//TODO implement catching optimization strategy

fun matches(text: String): Boolean = text.trimSpaces().find(matchesRegexes)
fun matches(text: String): Boolean = text.trimSpaces().find()

private fun String.find(regexes: List<Regex>, textIndex: Int = 0, regexIndex: Int = 0)
: Boolean = regexIndex == regexes.size || textIndex < length && run {
val matchResult = regexes[regexIndex].find(this, textIndex)
matchResult != null && find(regexes, matchResult.range.endInclusive + 1, regexIndex + 1)
private fun String.find(textIndex: Int = 0, regexIndex: Int = 0): Boolean {
return if (regexIndex == matchesRegexes.size) true
else if (textIndex < length) doFind(textIndex, regexIndex)
// This helps to solve the case when the parameter is right at the end of the step pattern,
// e.g. '@When("a user$thing")' and the parameter is passed an empty value.
// In this case the remaining regex pattern would be a single $ sign which has to be matched against an empty string.
else textIndex == length
}

private fun String.doFind(textIndex: Int, regexIndex: Int): Boolean {
val matchResult = matchesRegexes[regexIndex].find(this, textIndex)
return matchResult != null && find(matchResult.range.last + 1, regexIndex + 1)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
package com.github.kumaraman21.intellijbehave.service

import org.assertj.core.api.Assertions.assertThat
import org.jbehave.core.parsers.RegexPrefixCapturingPatternParser
import org.jbehave.core.parsers.StepPatternParser
import org.jbehave.core.steps.StepType.GIVEN
import org.junit.jupiter.api.DynamicTest
import org.junit.jupiter.api.DynamicTest.dynamicTest
import org.junit.jupiter.api.TestFactory
import java.util.stream.Stream

/**
* Unit test for [OptimizedStepMatcher].
*/
class OptimizedStepMatcherTest {

private val stepPatternParser: StepPatternParser = RegexPrefixCapturingPatternParser()

@TestFactory
fun validatesStepMatching(): Stream<DynamicTest> {
return Stream.of(
//This is an edge case, whether it returns false or true doesn't seem to make a difference
dynamicTest("empty texts", {
assertThat(matches("", "")).isTrue()
}),

//No parameter - match

dynamicTest("one word - no parameter") {
assertThat(matches("opened", "opened")).isTrue()
},
dynamicTest("two words - no parameter") {
assertThat(matches("I opened", "I opened")).isTrue()
},
dynamicTest("two-plus words - no parameter") {
assertThat(matches("I opened the HU homepage", "I opened the HU homepage")).isTrue()
},
dynamicTest("multiple words - multiple spaces in step text - no parameter") {
assertThat(matches("I opened the HU homepage", "I opened the HU homepage")).isTrue()
},
dynamicTest("multiple words - multiple spaces in annotation text - no parameter") {
assertThat(matches("I opened the HU homepage", "I opened the HU homepage")).isTrue()
},

//No parameter - mismatch

dynamicTest("one word - no parameter - mismatch") {
assertThat(matches("opened", "closed")).isFalse()
},
dynamicTest("two words - no parameter - mismatch") {
assertThat(matches("I opened", "I closed")).isFalse()
},
dynamicTest("two-plus words - no parameter - mismatch") {
assertThat(matches("I opened the HU homepage", "I closed the HU homepage")).isFalse()
},
dynamicTest("multiple words - multiple spaces in step text - no parameter - mismatch") {
assertThat(matches("I opened the HU homepage", "I closed the HU homepage")).isFalse()
},
dynamicTest("multiple words - multiple spaces in annotation text - no parameter - mismatch") {
assertThat(matches("I opened the HU homepage", "I closed the HU homepage")).isFalse()
},
dynamicTest("multiple words - spaces before annotation text - no parameter - mismatch trim") {
assertThat(matches(" I opened the HU homepage", "I opened the HU homepage")).isFalse()
},
dynamicTest("multiple words - spaces after annotation text - no parameter - mismatch trim") {
assertThat(matches("I opened the HU homepage ", "I opened the HU homepage")).isFalse()
},
dynamicTest("multiple words - spaces before annotation text - no parameter - mismatch") {
assertThat(matches(" I opened the HU homepage", "I closed the HU homepage")).isFalse()
},
dynamicTest("multiple words - spaces after annotation text - no parameter - mismatch") {
assertThat(matches("I opened the HU homepage ", "I closed the HU homepage")).isFalse()
},

//Parameters - match

dynamicTest("one parameter only - blank") {
assertThat(matches("\$thing", " ")).isTrue()
},
dynamicTest("one parameter only") {
assertThat(matches("\$thing", "opened")).isTrue()
},
dynamicTest("one parameter only - empty") {
assertThat(matches("\$thing", "")).isTrue()
},
dynamicTest("two parameters only") {
assertThat(matches("\$some \$thing", "I opened")).isTrue()
},
dynamicTest("two parameters only - empty") {
assertThat(matches("\$some \$thing", " ")).isTrue()
},
dynamicTest("two parameters without whitespace - empty") {
assertThat(matches("\$some\$thing", "")).isTrue()
},

dynamicTest("one word - parameter") {
assertThat(matches("opened \$thing", "opened homepage")).isTrue()
},
dynamicTest("two words - parameter") {
assertThat(matches("I \$indeed opened", "I really opened")).isTrue()
},
dynamicTest("two-plus words - parameter") {
assertThat(matches("I opened the \$locale homepage", "I opened the HU homepage")).isTrue()
},
dynamicTest("multiple words - multiple spaces in step text - parameter") {
assertThat(matches("I opened the \$locale homepage", "I opened the HU homepage")).isTrue()
},
dynamicTest("multiple words - multiple spaces in annotation text - parameter") {
assertThat(matches("I opened the \$locale homepage", "I opened the HU homepage")).isTrue()
},
dynamicTest("multiple words - spaces before annotation text - parameter") {
assertThat(matches(" I opened the \$locale homepage", " I opened the HU homepage")).isTrue()
},
dynamicTest("multiple words - spaces after annotation text - parameter") {
assertThat(matches("I opened the \$locale homepage ", "I opened the HU homepage ")).isTrue()
},

dynamicTest("parameter - empty - beginning of annotation text") {
assertThat(matches("\$person opened the HU homepage", " opened the HU homepage")).isTrue()
},
dynamicTest("parameter - empty and trimmed - beginning of annotation text") {
assertThat(matches("\$person opened the HU homepage", " opened the HU homepage")).isTrue()
},
dynamicTest("parameter - non-empty - beginning of annotation text") {
assertThat(matches("\$person opened the HU homepage", "I opened the HU homepage")).isTrue()
},
//FIXME: this should match because it matches in JBehave
dynamicTest("parameter - empty - middle of annotation text") {
assertThat(matches("I opened the \$locale homepage", "I opened the homepage")).isFalse()
},
dynamicTest("parameter - non-empty - middle of annotation text") {
assertThat(matches("I opened the \$locale homepage", "I opened the HU homepage")).isTrue()
},
dynamicTest("parameter - empty - end of annotation text") {
assertThat(matches("I opened the HU \$page", "I opened the HU ")).isTrue()
},
dynamicTest("parameter - empty and trimmed - end of annotation text") {
assertThat(matches("I opened the HU \$page", "I opened the HU ")).isTrue()
},
dynamicTest("parameter - non-empty - end of annotation text") {
assertThat(matches("I opened the HU \$page", "I opened the HU homepage")).isTrue()
},

dynamicTest("multiple parameters - empty - beginning of annotation text") {
assertThat(matches("\$person opened the \$locale homepage", " opened the HU homepage")).isTrue()
},
dynamicTest("multiple parameters - empty and trimmed - beginning of annotation text") {
assertThat(matches("\$person opened the \$locale homepage", " opened the HU homepage")).isTrue()
},
dynamicTest("multiple parameters - non-empty - beginning of annotation text") {
assertThat(matches("\$person opened the \$locale homepage", "I opened the HU homepage")).isTrue()
},
//FIXME: this should match because it matches in JBehave
dynamicTest("multiple parameters - empty - middle of annotation text") {
assertThat(matches("\$person opened the \$locale homepage", "I opened the homepage")).isFalse()
},
//FIXME: this should match because it matches in JBehave
dynamicTest("multiple parameters - empty and trimmed - middle of annotation text") {
assertThat(matches("\$person opened the \$locale homepage", " opened the homepage")).isFalse()
},
dynamicTest("multiple parameters - non-empty - middle of annotation text") {
assertThat(matches("\$person opened the \$locale homepage", "I opened the HU homepage")).isTrue()
},
dynamicTest("multiple parameters - empty - end of annotation text") {
assertThat(matches("\$person opened the HU \$page", "I opened the HU ")).isTrue()
},
dynamicTest("multiple parameters - empty and trimmed - end of annotation text") {
assertThat(matches("\$person opened the HU \$page", "I opened the HU ")).isTrue()
},
dynamicTest("multiple parameters - non-empty - end of annotation text") {
assertThat(matches("\$person opened the HU \$page", "I opened the HU homepage")).isTrue()
},

//Parameters - mismatch

dynamicTest("two parameters - no whitespace at all- mismatch") {
assertThat(matches("\$some\$thing", "opened")).isFalse()
},
dynamicTest("two parameters - no whitespace in annotation text - mismatch") {
assertThat(matches("\$some\$thing", "I opened the homepage")).isFalse()
},

dynamicTest("one word - parameter - mismatch") {
assertThat(matches("opened \$thing", "closed homepage")).isFalse()
},
dynamicTest("two words - parameter - mismatch") {
assertThat(matches("I \$indeed opened", "I really closed")).isFalse()
},
dynamicTest("two-plus words - parameter - mismatch") {
assertThat(matches("I opened the \$locale homepage", "I closed the HU homepage")).isFalse()
},
dynamicTest("multiple words - multiple spaces in step text - parameter - mismatch") {
assertThat(matches("I opened the \$locale homepage", "I closed the HU homepage")).isFalse()
},
dynamicTest("multiple words - multiple spaces in annotation text - parameter - mismatch") {
assertThat(matches("I opened the \$locale homepage", "I closed the HU homepage")).isFalse()
},
dynamicTest("multiple words - spaces before annotation text - parameter - mismatch") {
assertThat(matches(" I opened the \$locale homepage", "I closed the HU homepage")).isFalse()
},
dynamicTest("multiple words - spaces after annotation text - parameter - mismatch") {
assertThat(matches("I opened the \$locale homepage ", "I closed the HU homepage")).isFalse()
},

dynamicTest("parameter - empty - beginning of annotation text - mismatch") {
assertThat(matches("\$person opened the HU homepage", " closed the HU homepage")).isFalse()
},
dynamicTest("parameter - empty and trimmed - beginning of annotation text - mismatch") {
assertThat(matches("\$person opened the HU homepage", "closed the HU homepage")).isFalse()
},
dynamicTest("parameter - non-empty - beginning of annotation text - mismatch") {
assertThat(matches("\$person opened the HU homepage", "I closed the HU homepage")).isFalse()
},
dynamicTest("parameter - empty - middle of annotation text - mismatch") {
assertThat(matches("I opened the \$locale homepage", "I closed the homepage")).isFalse()
},
dynamicTest("parameter - empty and trimmed - middle of annotation text - mismatch") {
assertThat(matches("I opened the \$locale homepage", "I opened the homepage")).isFalse()
},
dynamicTest("parameter - non-empty - middle of annotation text - mismatch") {
assertThat(matches("I opened the \$locale homepage", "I closed the HU homepage")).isFalse()
},
dynamicTest("parameter - empty - end of annotation text - mismatch") {
assertThat(matches("I opened the HU \$page", "I closed the HU ")).isFalse()
},
dynamicTest("parameter - empty and trimmed - end of annotation text - mismatch") {
assertThat(matches("I opened the HU \$page", "I closed the HU")).isFalse()
},
dynamicTest("parameter - non-empty - end of annotation text - mismatch") {
assertThat(matches("I opened the HU \$page", "I closed the HU homepage")).isFalse()
},

dynamicTest("multiple parameters - empty - beginning of annotation text - mismatch") {
assertThat(matches("\$person opened the \$locale homepage", " closed the HU homepage")).isFalse()
},
dynamicTest("multiple parameters - empty and trimmed - beginning of annotation text - mismatch") {
assertThat(matches("\$person opened the \$locale homepage", "opened the HU homepage")).isFalse()
},
dynamicTest("multiple parameters - non-empty - beginning of annotation text - mismatch") {
assertThat(matches("\$person opened the \$locale homepage", "I closed the HU homepage")).isFalse()
},
dynamicTest("multiple parameters - empty - middle of annotation text - mismatch") {
assertThat(matches("\$person opened the \$locale homepage", "I closed the homepage")).isFalse()
},
dynamicTest("multiple parameters - empty and trimmed - middle of annotation text - mismatch") {
assertThat(matches("\$person opened the \$locale homepage", "opened the homepage")).isFalse()
},
dynamicTest("multiple parameters - non-empty - middle of annotation text - mismatch") {
assertThat(matches("\$person opened the \$locale homepage", "I closed the HU homepage")).isFalse()
},
dynamicTest("multiple parameters - empty - end of annotation text - mismatch") {
assertThat(matches("\$person opened the HU \$page", "I closed the HU ")).isFalse()
},
dynamicTest("multiple parameters - empty and trimmed - end of annotation text - mismatch") {
assertThat(matches("\$person opened the HU \$page", "I opened the HU")).isFalse()
},
dynamicTest("multiple parameters - non-empty - end of annotation text - mismatch") {
assertThat(matches("\$person opened the HU \$page", "I closed the HU homepage")).isFalse()
}
)
}

private fun matches(annotationText: String, stepText: String): Boolean {
return OptimizedStepMatcher(stepPatternParser.parseStep(GIVEN, annotationText)).matches(stepText)
}
}

0 comments on commit a3cfad1

Please sign in to comment.