Skip to content

Commit 5bc3694

Browse files
Add support for 'But' step type in Gherkin scenarios (#34)
1 parent dd81803 commit 5bc3694

5 files changed

Lines changed: 33 additions & 6 deletions

File tree

core/src/main/scala/zio/bdd/core/ScenarioExecutor.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ object ScenarioExecutor {
6060
ZIO
6161
.foldLeft(steps)((List.empty[(Step, StepType)], Option.empty[StepType])) { case ((acc, prevType), step) =>
6262
val effectiveType = step.stepType match {
63-
case StepType.AndStep =>
63+
case StepType.AndStep | StepType.ButStep =>
6464
prevType match {
6565
case Some(t) => ZIO.succeed(t)
66-
case None => ZIO.fail(new Exception("And step without previous step"))
66+
case None => ZIO.fail(new Exception("And or But step without previous step"))
6767
}
6868
case other => ZIO.succeed(other)
6969
}

core/src/test/scala/zio/bdd/core/FeatureExecutorSpec.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ object FeatureExecutorSpec extends ZIOSpecDefault {
6666
Then("an error occurs") {
6767
ZIO.fail(new RuntimeException("Simulated error")).unit
6868
}
69+
70+
Then("the user " / string / " should not have the role " / string) { (name: String, role: String) =>
71+
for {
72+
state <- ScenarioContext.get
73+
user <- ZIO.fromOption(state.users.get(name)).orElseFail(new RuntimeException(s"User $name does not exist"))
74+
_ <- Assertions.assertTrue(user.role != role, s"User $name should not have role $role")
75+
} yield ()
76+
}
6977
}
7078

7179
private val testFile = "test.feature"
@@ -203,6 +211,20 @@ object FeatureExecutorSpec extends ZIOSpecDefault {
203211
feature <- GherkinParser.parseFeature(content, testFile)
204212
results <- FeatureExecutor.executeFeatures[Any, SystemState](List(feature), steps.getSteps, steps)
205213
} yield assertTrue(results.head.isPassed)
214+
},
215+
test("execute scenario with But step for role check") {
216+
val content = """
217+
|Feature: User Role Check
218+
| Scenario: Check user role
219+
| Given a system is running
220+
| Given a user named "Alice" with age 30 and role "admin"
221+
| Then the user "Alice" should exist
222+
| But the user "Alice" should not have the role "manager"
223+
""".stripMargin
224+
for {
225+
feature <- GherkinParser.parseFeature(content, testFile)
226+
results <- FeatureExecutor.executeFeatures[Any, SystemState](List(feature), steps.getSteps, steps)
227+
} yield assertTrue(results.head.isPassed)
206228
}
207229
)
208230
}

gherkin/src/main/scala/zio/bdd/gherkin/GherkinParser.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ case class Scenario(
4848
}
4949

5050
enum StepType {
51-
case GivenStep, WhenStep, ThenStep, AndStep
51+
case GivenStep, WhenStep, ThenStep, ButStep, AndStep
5252
}
5353

5454
case class DataTableRow(cells: List[String]) {
@@ -100,6 +100,7 @@ case class Step(
100100
case StepType.GivenStep => "Given"
101101
case StepType.WhenStep => "When"
102102
case StepType.ThenStep => "Then"
103+
case StepType.ButStep => "But"
103104
case StepType.AndStep => "And"
104105
}
105106
val stepLine = indent + stepTypeStr + " " + pattern + "\n"
@@ -124,7 +125,7 @@ object GherkinParser {
124125

125126
// Keyword parser: handles Gherkin keywords with optional colon
126127
private def keyword(using P[?]): P[String] = P(
127-
("Feature" | "Background" | "Scenario Outline" | "Scenario" | "Given" | "When" | "Then" | "And" | "Examples") ~ (":".?)
128+
("Feature" | "Background" | "Scenario Outline" | "Scenario" | "Given" | "When" | "Then" | "But" | "And" | "Examples") ~ (":".?)
128129
).!.map(_.stripSuffix(":"))
129130

130131
// Text parser: captures text until newline, including typed placeholders like {name:String}
@@ -161,12 +162,13 @@ object GherkinParser {
161162

162163
// Step parser: captures step type as StepType and pattern separately
163164
private def step(ctx: ParseContext)(using P[?]): P[Step] =
164-
P(Index ~ ("Given" | "When" | "Then" | "And").! ~ ":".? ~/ text ~ dataTableParser.?).map {
165+
P(Index ~ ("Given" | "When" | "Then" | "But" | "And").! ~ ":".? ~/ text ~ dataTableParser.?).map {
165166
case (idx, stepTypeStr, pattern, dataTableOpt) =>
166167
val stepType = stepTypeStr match {
167168
case "Given" => StepType.GivenStep
168169
case "When" => StepType.WhenStep
169170
case "Then" => StepType.ThenStep
171+
case "But" => StepType.ButStep
170172
case "And" => StepType.AndStep
171173
}
172174
Step(stepType, pattern.trim, dataTableOpt, Some(ctx.file), Some(ctx.lineAt(idx)))

gherkin/src/test/scala/zio/bdd/gherkin/GherkinParserSpec.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@ object GherkinParserSpec extends ZIOSpecDefault {
436436
| Scenario: Successful password reset with logging
437437
| When the user requests a password reset
438438
| And the reset email is logged
439+
| But the user is not deactivated
439440
| Then an email should be sent to "default@example.com"
440441
""".stripMargin
441442
checkParse(content, testFile) { feature =>
@@ -451,11 +452,12 @@ object GherkinParserSpec extends ZIOSpecDefault {
451452
Step(StepType.GivenStep, """a user exists with name "Default"""", file = Some(testFile), line = Some(4)),
452453
Step(StepType.WhenStep, "the user requests a password reset", file = Some(testFile), line = Some(6)),
453454
Step(StepType.AndStep, "the reset email is logged", file = Some(testFile), line = Some(7)),
455+
Step(StepType.ButStep, "the user is not deactivated", file = Some(testFile), line = Some(8)),
454456
Step(
455457
StepType.ThenStep,
456458
"""an email should be sent to "default@example.com"""",
457459
file = Some(testFile),
458-
line = Some(8)
460+
line = Some(9)
459461
)
460462
)
461463
)

project/StepGeneratorPlugin.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ object StepMethodGenerator {
4343
("Given", "StepType.GivenStep"),
4444
("When", "StepType.WhenStep"),
4545
("Then", "StepType.ThenStep"),
46+
("But", "StepType.ButStep"),
4647
("And", "StepType.AndStep")
4748
)
4849

0 commit comments

Comments
 (0)