├── project ├── build.properties ├── build-dependencies.sbt ├── plugins.sbt ├── I18nGenerator.scala └── ScalacOptions.scala ├── .jvmopts ├── version.sbt ├── .git-blame-ignore-revs ├── .scalafmt.conf ├── integration-tests ├── common │ └── src │ │ └── test │ │ ├── scala │ │ ├── cukes │ │ │ ├── model │ │ │ │ ├── Cuke.scala │ │ │ │ ├── Person.scala │ │ │ │ └── Snake.scala │ │ │ ├── RunCukesTest.scala │ │ │ ├── TypeRegistryConfiguration.scala │ │ │ └── StepDefs.scala │ │ ├── misc │ │ │ ├── RunMiscTest.scala │ │ │ └── OptionalCaptureGroupsSteps.scala │ │ ├── object │ │ │ ├── RunObjectTest.scala │ │ │ └── ObjectSteps.scala │ │ ├── isolated │ │ │ ├── RunIsolatedTest.scala │ │ │ └── IsolatedSteps.scala │ │ ├── docstring │ │ │ ├── RunDocStringTest.scala │ │ │ └── DocStringSteps.scala │ │ ├── datatables │ │ │ ├── RunDatatablesTest.scala │ │ │ └── DatatableSteps.scala │ │ ├── parametertypes │ │ │ ├── RunParameterTypesTest.scala │ │ │ └── ParameterTypesSteps.scala │ │ └── statichooks │ │ │ ├── StaticHooksSteps.scala │ │ │ └── RunStaticHooksTest.scala │ │ └── resources │ │ ├── isolated │ │ ├── isolated2.feature │ │ └── isolated.feature │ │ ├── junit-platform.properties │ │ ├── misc │ │ └── OptionalCaptureGroups.feature │ │ ├── object │ │ └── object.feature │ │ ├── statichooks │ │ ├── statichooks.feature │ │ └── statichooks2.feature │ │ ├── docstring │ │ └── Docstring.feature │ │ ├── datatables │ │ ├── Datatable.feature │ │ ├── DatatableAsScala.feature │ │ └── DataTableType.feature │ │ ├── parametertypes │ │ └── ParameterTypes.feature │ │ └── cukes │ │ └── cukes.feature ├── jackson2 │ └── src │ │ └── test │ │ ├── resources │ │ ├── junit-platform.properties │ │ └── jackson │ │ │ └── Jackson.feature │ │ └── scala │ │ └── jackson │ │ ├── RunJacksonTest.scala │ │ └── JacksonSteps.scala ├── jackson3 │ └── src │ │ └── test │ │ ├── resources │ │ ├── junit-platform.properties │ │ └── jackson3 │ │ │ └── Jackson3.feature │ │ └── scala │ │ └── jackson3 │ │ ├── RunJackson3Test.scala │ │ └── Jackson3Steps.scala └── picocontainer │ └── src │ └── test │ ├── resources │ ├── junit-platform.properties │ └── di │ │ └── di.feature │ └── scala │ └── di │ ├── DI_A.scala │ ├── DI_B.scala │ ├── DI_C.scala │ └── RunDependencyInjectionTest.scala ├── cucumber-scala └── src │ ├── main │ ├── resources │ │ └── META-INF │ │ │ └── services │ │ │ └── io.cucumber.core.backend.BackendProviderService │ ├── scala │ │ └── io │ │ │ └── cucumber │ │ │ └── scala │ │ │ ├── PendingException.scala │ │ │ ├── package.html │ │ │ ├── ScalaStaticHookDetails.scala │ │ │ ├── ScalaHookDetails.scala │ │ │ ├── ScalaParameterTypeDetails.scala │ │ │ ├── ScalaTypeResolver.scala │ │ │ ├── ScalaDsl.scala │ │ │ ├── BaseScalaDsl.scala │ │ │ ├── ScalaDocStringTypeDetails.scala │ │ │ ├── UnknownClassType.scala │ │ │ ├── ScalaParameterInfo.scala │ │ │ ├── ScalaBackendProviderService.scala │ │ │ ├── HookType.scala │ │ │ ├── Aliases.scala │ │ │ ├── ScalaDefaultTransformerDetails.scala │ │ │ ├── DocStringTypeDsl.scala │ │ │ ├── DataTableDefinitionBody.scala │ │ │ ├── ScalaStaticHookDefinition.scala │ │ │ ├── ScalaDataTableDefinition.scala │ │ │ ├── ScalaStepDetails.scala │ │ │ ├── ScalaDataTableCellDefinition.scala │ │ │ ├── ScalaDataTableOptionalCellDefinition.scala │ │ │ ├── Utils.scala │ │ │ ├── IncorrectStepDefinitionException.scala │ │ │ ├── ScalaDataTableRowDefinition.scala │ │ │ ├── ScalaDataTableEntryDefinition.scala │ │ │ ├── ScalaDataTableOptionalRowDefinition.scala │ │ │ ├── ScalaDataTableOptionalEntryDefinition.scala │ │ │ ├── AbstractGlueDefinition.scala │ │ │ ├── ScalaDocStringTypeDefinition.scala │ │ │ ├── JacksonDefaultDataTableEntryTransformer.scala │ │ │ ├── ScalaDefaultParameterTransformerDefinition.scala │ │ │ ├── ScalaParameterTypeDefinition.scala │ │ │ ├── ScalaStepDefinition.scala │ │ │ ├── Jackson3DefaultDataTableEntryTransformer.scala │ │ │ ├── ScalaHookDefinition.scala │ │ │ ├── ScalaDefaultDataTableCellTransformerDefinition.scala │ │ │ ├── ScalaDataTableTypeDetails.scala │ │ │ ├── ScalaDefaultDataTableEntryTransformerDefinition.scala │ │ │ ├── IncorrectHookDefinitionException.scala │ │ │ ├── AbstractDatatableElementTransformerDefinition.scala │ │ │ ├── ScalaSnippet.scala │ │ │ ├── ScalaDataTableTypeDefinition.scala │ │ │ ├── GlueAdaptor.scala │ │ │ ├── DataTableTypeDsl.scala │ │ │ ├── ScalaBackend.scala │ │ │ ├── DefaultTransformerDsl.scala │ │ │ └── Scenario.scala │ └── codegen │ │ └── gen.scala │ └── test │ └── scala │ └── io │ └── cucumber │ └── scala │ ├── steps │ ├── classes │ │ ├── SingleFile.scala │ │ └── MultipleInSameFile.scala │ ├── dependencyinjection │ │ ├── Injector.scala │ │ └── Injected.scala │ ├── errors │ │ ├── staticclasshooks │ │ │ └── StaticClassHooksDefinition.scala │ │ ├── incorrectclasshooks │ │ │ └── IncorrectClassHooksDefinition.scala │ │ └── incorrectobjecthooks │ │ │ └── IncorrectObjectHooksDefinition.scala │ ├── traits │ │ └── StepsInTrait.scala │ └── objects │ │ └── StepsInObject.scala │ ├── TestFeatureParser.scala │ ├── ScalaDslDocStringTypeTest.scala │ ├── ScalaDslDefaultParameterTransformerTest.scala │ ├── ScalaDslParameterTypeTest.scala │ ├── ScalaDslDefaultDataTableEntryTransformerTest.scala │ └── ScalaDslDefaultDataTableCellTransformerTest.scala ├── renovate.json ├── examples ├── examples-junit4 │ ├── README.md │ └── src │ │ ├── test │ │ ├── resources │ │ │ └── cucumber │ │ │ │ └── examples │ │ │ │ └── scalacalculator │ │ │ │ └── basic_arithmetic.feature │ │ └── scala │ │ │ └── cucumber │ │ │ └── examples │ │ │ └── scalacalculator │ │ │ ├── RunCukesTest.scala │ │ │ └── RpnCalculatorStepDefinitions.scala │ │ └── main │ │ └── scala │ │ └── cucumber │ │ └── examples │ │ └── scalacalculator │ │ └── RpnCalculator.scala └── examples-junit5 │ ├── README.md │ └── src │ ├── test │ ├── resources │ │ ├── junit-platform.properties │ │ └── cucumber │ │ │ └── examples │ │ │ └── scalacalculator │ │ │ └── basic_arithmetic.feature │ └── scala │ │ └── cucumber │ │ └── examples │ │ └── scalacalculator │ │ ├── RunCukesTest.scala │ │ └── RpnCalculatorStepDefinitions.scala │ └── main │ └── scala │ └── cucumber │ └── examples │ └── scalacalculator │ └── RpnCalculator.scala ├── .gitignore ├── .github ├── settings.yml └── workflows │ ├── version-policy-check.yml │ ├── build.yml │ └── release-sbt.yml ├── CONTRIBUTING.md ├── scripts ├── remove-empty-sections-changelog.awk ├── update-install-doc.sh └── update-changelog.sh ├── docs ├── install.md ├── upgrade_v8.md ├── step_definitions.md ├── upgrade_v7.md ├── upgrade_v6.md ├── default_jackson_datatable_transformer.md ├── build.md ├── scala_implementation.md ├── upgrade_v5.md ├── hooks.md └── usage.md ├── Makefile ├── LICENCE ├── RELEASING.md ├── .devcontainer └── devcontainer.json └── README.md /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.11.7 2 | -------------------------------------------------------------------------------- /.jvmopts: -------------------------------------------------------------------------------- 1 | -Dsun.net.client.defaultReadTimeout=60000 -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / version := "8.38.1-SNAPSHOT" 2 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # scalafmt 2 | f48a978dfc46fabf7acdcab70a90b242c5565caa 3 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 3.10.2 2 | 3 | preset=default 4 | 5 | runner.dialect=scala3 6 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/scala/cukes/model/Cuke.scala: -------------------------------------------------------------------------------- 1 | package cukes.model 2 | 3 | case class Cukes(number: Int, color: String) 4 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/resources/META-INF/services/io.cucumber.core.backend.BackendProviderService: -------------------------------------------------------------------------------- 1 | io.cucumber.scala.ScalaBackendProviderService -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "github>cucumber/renovate-config" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /examples/examples-junit4/README.md: -------------------------------------------------------------------------------- 1 | # Cucumber Scala Example - JUnit 4 2 | 3 | This project is an example of Cucumber Scala integration in a Scala SBT project with JUnit 4. 4 | -------------------------------------------------------------------------------- /examples/examples-junit5/README.md: -------------------------------------------------------------------------------- 1 | # Cucumber Scala Example - JUnit 5 2 | 3 | This project is an example of Cucumber Scala integration in a Scala SBT project with JUnit 5. 4 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/PendingException.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | class PendingException extends RuntimeException("TODO: implement me") {} 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | **/*.iml 3 | **/target/ 4 | .bsp/ 5 | release.properties 6 | pom.xml.releaseBackup 7 | pom.xml.versionsBackup 8 | .metals/ 9 | .vscode/ 10 | .bloop/ 11 | metals.sbt -------------------------------------------------------------------------------- /project/build-dependencies.sbt: -------------------------------------------------------------------------------- 1 | libraryDependencies += "io.cucumber" % "cucumber-core" % "7.33.0" 2 | libraryDependencies += "org.scala-lang.modules" %% "scala-collection-compat" % "2.14.0" 3 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/package.html: -------------------------------------------------------------------------------- 1 |
2 |3 | See the Scala API for cucumber-jvm-scala. 4 |
5 | 6 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | _extends: .github 2 | 3 | repository: 4 | name: cucumber-jvm-scala 5 | description: Cucumber Scala 6 | 7 | collaborators: 8 | - username: gaeljw 9 | permission: maintain 10 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/resources/isolated/isolated2.feature: -------------------------------------------------------------------------------- 1 | Feature: Isolated 2 2 | 3 | Scenario: Second test 4 | Given I set the list of values to 5 | | 10 | 6 | And I multiply by 2 7 | Then the list of values is 8 | | 20 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | # Workaround for https://github.com/sbt/sbt-jupiter-interface/issues/142 2 | # See also https://github.com/cucumber/cucumber-jvm/pull/3023 3 | cucumber.junit-platform.discovery.as-root-engine=false -------------------------------------------------------------------------------- /integration-tests/jackson2/src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | # Workaround for https://github.com/sbt/sbt-jupiter-interface/issues/142 2 | # See also https://github.com/cucumber/cucumber-jvm/pull/3023 3 | cucumber.junit-platform.discovery.as-root-engine=false -------------------------------------------------------------------------------- /integration-tests/jackson3/src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | # Workaround for https://github.com/sbt/sbt-jupiter-interface/issues/142 2 | # See also https://github.com/cucumber/cucumber-jvm/pull/3023 3 | cucumber.junit-platform.discovery.as-root-engine=false -------------------------------------------------------------------------------- /integration-tests/picocontainer/src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | # Workaround for https://github.com/sbt/sbt-jupiter-interface/issues/142 2 | # See also https://github.com/cucumber/cucumber-jvm/pull/3023 3 | cucumber.junit-platform.discovery.as-root-engine=false -------------------------------------------------------------------------------- /examples/examples-junit5/src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | cucumber.plugin=pretty 2 | # Workaround for https://github.com/sbt/sbt-jupiter-interface/issues/142 3 | # See also https://github.com/cucumber/cucumber-jvm/pull/3023 4 | cucumber.junit-platform.discovery.as-root-engine=false -------------------------------------------------------------------------------- /examples/examples-junit4/src/test/resources/cucumber/examples/scalacalculator/basic_arithmetic.feature: -------------------------------------------------------------------------------- 1 | @foo 2 | Feature: Basic Arithmetic 3 | 4 | Scenario: Adding 5 | # Try to change one of the values below to provoke a failure 6 | When I add 4.0 and 5.0 7 | Then the result is 9.0 8 | -------------------------------------------------------------------------------- /examples/examples-junit5/src/test/resources/cucumber/examples/scalacalculator/basic_arithmetic.feature: -------------------------------------------------------------------------------- 1 | @foo 2 | Feature: Basic Arithmetic 3 | 4 | Scenario: Adding 5 | # Try to change one of the values below to provoke a failure 6 | When I add 4.0 and 5.0 7 | Then the result is 9.0 8 | -------------------------------------------------------------------------------- /cucumber-scala/src/test/scala/io/cucumber/scala/steps/classes/SingleFile.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala.steps.classes 2 | 3 | import io.cucumber.scala.{EN, ScalaDsl} 4 | 5 | class StepsC extends ScalaDsl with EN { 6 | 7 | Then("""stepsC""") { () => 8 | // Nothing 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/resources/misc/OptionalCaptureGroups.feature: -------------------------------------------------------------------------------- 1 | Feature: Optional capture groups are supported 2 | 3 | Scenario: present, using Java's Optional 4 | Given I have the name: Jack 5 | 6 | Scenario: absent, using Java's Optional 7 | Given I don't have the name: 8 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/scala/cukes/model/Person.scala: -------------------------------------------------------------------------------- 1 | package cukes.model 2 | 3 | /** Test model for a "Person" 4 | * @param name 5 | * of person 6 | */ 7 | case class Person(name: String) { 8 | 9 | def hello = { 10 | "Hello, I'm " + name + "!" 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/resources/isolated/isolated.feature: -------------------------------------------------------------------------------- 1 | Feature: Isolated 2 | 3 | Scenario: First test 4 | Given I set the list of values to 5 | | 1 | 6 | | 2 | 7 | | 3 | 8 | And I multiply by 2 9 | Then the list of values is 10 | | 2 | 11 | | 4 | 12 | | 6 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/ScalaStaticHookDetails.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import io.cucumber.scala.Aliases.StaticHookDefinitionBody 4 | 5 | case class ScalaStaticHookDetails( 6 | order: Int, 7 | body: StaticHookDefinitionBody, 8 | stackTraceElement: StackTraceElement 9 | ) 10 | -------------------------------------------------------------------------------- /integration-tests/picocontainer/src/test/scala/di/DI_A.scala: -------------------------------------------------------------------------------- 1 | package di 2 | 3 | import io.cucumber.scala.{EN, ScalaDsl} 4 | 5 | class DI_A extends ScalaDsl with EN { 6 | 7 | var input: String = _ 8 | 9 | Given("""a step defined in class DI-A with arg {string}""") { (arg: String) => 10 | input = arg 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /integration-tests/picocontainer/src/test/scala/di/DI_B.scala: -------------------------------------------------------------------------------- 1 | package di 2 | 3 | import io.cucumber.scala.{EN, ScalaDsl} 4 | 5 | class DI_B extends ScalaDsl with EN { 6 | 7 | var input: String = _ 8 | 9 | Given("""a step defined in class DI-B with arg {string}""") { (arg: String) => 10 | input = arg 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/ScalaHookDetails.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import Aliases.HookDefinitionBody 4 | 5 | case class ScalaHookDetails( 6 | tagExpression: String, 7 | order: Int, 8 | body: HookDefinitionBody, 9 | stackTraceElement: StackTraceElement, 10 | hookType: ScopedHookType 11 | ) 12 | -------------------------------------------------------------------------------- /cucumber-scala/src/test/scala/io/cucumber/scala/steps/dependencyinjection/Injector.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala.steps.dependencyinjection 2 | 3 | import io.cucumber.scala.{EN, ScalaDsl} 4 | 5 | class Injector(injected: Injected) extends ScalaDsl with EN { 6 | 7 | Then("""Injector""") { () => 8 | println(injected.x) 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/ScalaParameterTypeDetails.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import scala.reflect.ClassTag 4 | 5 | case class ScalaParameterTypeDetails[R]( 6 | name: String, 7 | regex: String, 8 | body: List[String] => R, 9 | tag: ClassTag[R], 10 | stackTraceElement: StackTraceElement 11 | ) 12 | -------------------------------------------------------------------------------- /cucumber-scala/src/test/scala/io/cucumber/scala/steps/dependencyinjection/Injected.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala.steps.dependencyinjection 2 | 3 | import io.cucumber.scala.{EN, ScalaDsl} 4 | 5 | class Injected extends ScalaDsl with EN { 6 | 7 | var x: String = _ 8 | 9 | Given("""injected steps""") { () => 10 | // Nothing 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/scala/cukes/model/Snake.scala: -------------------------------------------------------------------------------- 1 | package cukes.model 2 | 3 | /** Test model "Snake" to exercise the custom mapper functionality 4 | * 5 | * @param length 6 | * of the snake in characters 7 | * @param direction 8 | * in which snake is moving 'west, 'east, etc 9 | */ 10 | case class Snake(length: Int, direction: Symbol) {} 11 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/ScalaTypeResolver.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import java.lang.reflect.Type 4 | 5 | import io.cucumber.core.backend.TypeResolver 6 | 7 | class ScalaTypeResolver(val `type`: Type) extends TypeResolver { 8 | 9 | override def resolve(): Type = { 10 | // No fancy logic needed 11 | `type` 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/ScalaDsl.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | /** Base trait for a scala step definition implementation. 4 | */ 5 | trait ScalaDsl 6 | extends BaseScalaDsl 7 | with StepDsl 8 | with HookDsl 9 | with DataTableTypeDsl 10 | with DocStringTypeDsl 11 | with ParameterTypeDsl 12 | with DefaultTransformerDsl {} 13 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/BaseScalaDsl.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | private[scala] trait BaseScalaDsl { 4 | 5 | val NO_REPLACEMENT = Seq[String]() 6 | val EMPTY_TAG_EXPRESSION = "" 7 | val DEFAULT_BEFORE_ORDER = 1000 8 | val DEFAULT_AFTER_ORDER = 1000 9 | 10 | private[scala] val registry: ScalaDslRegistry = new ScalaDslRegistry() 11 | 12 | } 13 | -------------------------------------------------------------------------------- /examples/examples-junit4/src/test/scala/cucumber/examples/scalacalculator/RunCukesTest.scala: -------------------------------------------------------------------------------- 1 | package cucumber.examples.scalacalculator 2 | 3 | import io.cucumber.junit.{Cucumber, CucumberOptions} 4 | import org.junit.runner.RunWith 5 | 6 | import scala.annotation.nowarn 7 | 8 | @nowarn 9 | @RunWith(classOf[Cucumber]) 10 | @CucumberOptions(plugin = Array("pretty")) 11 | class RunCukesTest 12 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/scala/cukes/RunCukesTest.scala: -------------------------------------------------------------------------------- 1 | package cukes 2 | 3 | import io.cucumber.junit.platform.engine.Constants 4 | import org.junit.platform.suite.api._ 5 | 6 | @Suite 7 | @IncludeEngines(Array("cucumber")) 8 | @SelectPackages(Array("cukes")) 9 | @ConfigurationParameter( 10 | key = Constants.GLUE_PROPERTY_NAME, 11 | value = "cukes" 12 | ) 13 | class RunCukesTest 14 | -------------------------------------------------------------------------------- /integration-tests/picocontainer/src/test/resources/di/di.feature: -------------------------------------------------------------------------------- 1 | Feature: As Cucumber Scala, I want to be able to have some step classes depend on another one 2 | 3 | Scenario: Nominal case 4 | Given a step defined in class DI-A with arg "A" 5 | And a step defined in class DI-B with arg "B" 6 | When a step defined in class DI-C uses them both 7 | Then both values are combined into "AB" 8 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/ScalaDocStringTypeDetails.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import io.cucumber.scala.Aliases.DocStringDefinitionBody 4 | 5 | import java.lang.reflect.{Type => JType} 6 | 7 | case class ScalaDocStringTypeDetails[T]( 8 | contentType: String, 9 | body: DocStringDefinitionBody[T], 10 | `type`: JType, 11 | stackTraceElement: StackTraceElement 12 | ) 13 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/UnknownClassType.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import io.cucumber.core.backend.CucumberBackendException 4 | 5 | class UnknownClassType(clazz: Class[_], cause: Throwable) 6 | extends CucumberBackendException( 7 | s"Cucumber was not able to handle class ${clazz.getName}. Please report this issue to cucumber-scala project.", 8 | cause 9 | ) 10 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/resources/object/object.feature: -------------------------------------------------------------------------------- 1 | Feature: As Cucumber Scala, I want to be able to use steps defined in objects even though they will persist their state across scenarios 2 | 3 | Scenario: First scenario 4 | Given I have a calculator 5 | When I do 2 + 2 6 | Then I got 4 7 | 8 | Scenario: Second scenario 9 | Given I have a calculator 10 | When I do 5 + 6 11 | Then I got 11 12 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/scala/misc/RunMiscTest.scala: -------------------------------------------------------------------------------- 1 | package misc 2 | 3 | import io.cucumber.junit.platform.engine.Constants 4 | import org.junit.platform.suite.api.{ 5 | ConfigurationParameter, 6 | IncludeEngines, 7 | SelectPackages, 8 | Suite 9 | } 10 | 11 | @Suite 12 | @IncludeEngines(Array("cucumber")) 13 | @SelectPackages(Array("misc")) 14 | @ConfigurationParameter( 15 | key = Constants.GLUE_PROPERTY_NAME, 16 | value = "misc" 17 | ) 18 | class RunMiscTest 19 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/scala/object/RunObjectTest.scala: -------------------------------------------------------------------------------- 1 | package `object` 2 | 3 | import io.cucumber.junit.platform.engine.Constants 4 | import org.junit.platform.suite.api.{ 5 | ConfigurationParameter, 6 | IncludeEngines, 7 | SelectPackages, 8 | Suite 9 | } 10 | 11 | @Suite 12 | @IncludeEngines(Array("cucumber")) 13 | @SelectPackages(Array("object")) 14 | @ConfigurationParameter( 15 | key = Constants.GLUE_PROPERTY_NAME, 16 | value = "object" 17 | ) 18 | class RunObjectTest 19 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/ScalaParameterInfo.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import java.lang.reflect.Type 4 | 5 | import io.cucumber.core.backend.{ParameterInfo, TypeResolver} 6 | 7 | class ScalaParameterInfo(typeResolver: ScalaTypeResolver) 8 | extends ParameterInfo { 9 | 10 | override def getType: Type = typeResolver.`type` 11 | 12 | override def isTransposed: Boolean = false 13 | 14 | override def getTypeResolver: TypeResolver = typeResolver 15 | 16 | } 17 | -------------------------------------------------------------------------------- /examples/examples-junit5/src/test/scala/cucumber/examples/scalacalculator/RunCukesTest.scala: -------------------------------------------------------------------------------- 1 | package cucumber.examples.scalacalculator 2 | 3 | import io.cucumber.junit.platform.engine.Constants 4 | import org.junit.platform.suite.api._ 5 | 6 | @Suite 7 | @IncludeEngines(Array("cucumber")) 8 | @SelectPackages(Array("cucumber.examples.scalacalculator")) 9 | @ConfigurationParameter( 10 | key = Constants.GLUE_PROPERTY_NAME, 11 | value = "cucumber.examples.scalacalculator" 12 | ) 13 | class RunCukesTest 14 | -------------------------------------------------------------------------------- /integration-tests/jackson2/src/test/scala/jackson/RunJacksonTest.scala: -------------------------------------------------------------------------------- 1 | package jackson 2 | 3 | import io.cucumber.junit.platform.engine.Constants 4 | import org.junit.platform.suite.api.{ 5 | ConfigurationParameter, 6 | IncludeEngines, 7 | SelectPackages, 8 | Suite 9 | } 10 | 11 | @Suite 12 | @IncludeEngines(Array("cucumber")) 13 | @SelectPackages(Array("jackson")) 14 | @ConfigurationParameter( 15 | key = Constants.GLUE_PROPERTY_NAME, 16 | value = "jackson" 17 | ) 18 | class RunJacksonTest 19 | -------------------------------------------------------------------------------- /integration-tests/picocontainer/src/test/scala/di/DI_C.scala: -------------------------------------------------------------------------------- 1 | package di 2 | 3 | import io.cucumber.scala.{EN, ScalaDsl} 4 | 5 | class DI_C(a: DI_A, b: DI_B) extends ScalaDsl with EN { 6 | 7 | private var combination: String = _ 8 | 9 | When("""a step defined in class DI-C uses them both""") { () => 10 | combination = a.input + b.input 11 | } 12 | 13 | Then("""both values are combined into {string}""") { (expected: String) => 14 | assert(combination == expected) 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/scala/isolated/RunIsolatedTest.scala: -------------------------------------------------------------------------------- 1 | package isolated 2 | 3 | import io.cucumber.junit.platform.engine.Constants 4 | import org.junit.platform.suite.api.{ 5 | ConfigurationParameter, 6 | IncludeEngines, 7 | SelectPackages, 8 | Suite 9 | } 10 | 11 | @Suite 12 | @IncludeEngines(Array("cucumber")) 13 | @SelectPackages(Array("isolated")) 14 | @ConfigurationParameter( 15 | key = Constants.GLUE_PROPERTY_NAME, 16 | value = "isolated" 17 | ) 18 | class RunIsolatedTest 19 | -------------------------------------------------------------------------------- /integration-tests/jackson3/src/test/scala/jackson3/RunJackson3Test.scala: -------------------------------------------------------------------------------- 1 | package jackson3 2 | 3 | import io.cucumber.junit.platform.engine.Constants 4 | import org.junit.platform.suite.api.{ 5 | ConfigurationParameter, 6 | IncludeEngines, 7 | SelectPackages, 8 | Suite 9 | } 10 | 11 | @Suite 12 | @IncludeEngines(Array("cucumber")) 13 | @SelectPackages(Array("jackson3")) 14 | @ConfigurationParameter( 15 | key = Constants.GLUE_PROPERTY_NAME, 16 | value = "jackson3" 17 | ) 18 | class RunJackson3Test 19 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/scala/docstring/RunDocStringTest.scala: -------------------------------------------------------------------------------- 1 | package docstring 2 | 3 | import io.cucumber.junit.platform.engine.Constants 4 | import org.junit.platform.suite.api.{ 5 | ConfigurationParameter, 6 | IncludeEngines, 7 | SelectPackages, 8 | Suite 9 | } 10 | 11 | @Suite 12 | @IncludeEngines(Array("cucumber")) 13 | @SelectPackages(Array("docstring")) 14 | @ConfigurationParameter( 15 | key = Constants.GLUE_PROPERTY_NAME, 16 | value = "docstring" 17 | ) 18 | class RunDocStringTest 19 | -------------------------------------------------------------------------------- /integration-tests/picocontainer/src/test/scala/di/RunDependencyInjectionTest.scala: -------------------------------------------------------------------------------- 1 | package di 2 | 3 | import io.cucumber.junit.platform.engine.Constants 4 | import org.junit.platform.suite.api.{ 5 | ConfigurationParameter, 6 | IncludeEngines, 7 | SelectPackages, 8 | Suite 9 | } 10 | 11 | @Suite 12 | @IncludeEngines(Array("cucumber")) 13 | @SelectPackages(Array("di")) 14 | @ConfigurationParameter( 15 | key = Constants.GLUE_PROPERTY_NAME, 16 | value = "di" 17 | ) 18 | class RunDependencyInjectionTest 19 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/scala/datatables/RunDatatablesTest.scala: -------------------------------------------------------------------------------- 1 | package datatables 2 | 3 | import io.cucumber.junit.platform.engine.Constants 4 | import org.junit.platform.suite.api.{ 5 | ConfigurationParameter, 6 | IncludeEngines, 7 | SelectPackages, 8 | Suite 9 | } 10 | 11 | @Suite 12 | @IncludeEngines(Array("cucumber")) 13 | @SelectPackages(Array("datatables")) 14 | @ConfigurationParameter( 15 | key = Constants.GLUE_PROPERTY_NAME, 16 | value = "datatables" 17 | ) 18 | class RunDatatablesTest 19 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/resources/statichooks/statichooks.feature: -------------------------------------------------------------------------------- 1 | Feature: As Cucumber Scala, I want to use beforeAll/afterAll hooks 2 | 3 | Scenario: Scenario A 4 | Then BeforeAll count is 1 5 | Then AfterAll count is 0 6 | When I run scenario "A" 7 | Then BeforeAll count is 1 8 | Then AfterAll count is 0 9 | 10 | Scenario: Scenario B 11 | Then BeforeAll count is 1 12 | Then AfterAll count is 0 13 | When I run scenario "B" 14 | Then BeforeAll count is 1 15 | Then AfterAll count is 0 16 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/resources/statichooks/statichooks2.feature: -------------------------------------------------------------------------------- 1 | Feature: As Cucumber Scala, I want to use beforeAll/afterAll hooks 2 | 3 | Scenario: Scenario C 4 | Then BeforeAll count is 1 5 | Then AfterAll count is 0 6 | When I run scenario "C" 7 | Then BeforeAll count is 1 8 | Then AfterAll count is 0 9 | 10 | Scenario: Scenario D 11 | Then BeforeAll count is 1 12 | Then AfterAll count is 0 13 | When I run scenario "D" 14 | Then BeforeAll count is 1 15 | Then AfterAll count is 0 16 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/scala/parametertypes/RunParameterTypesTest.scala: -------------------------------------------------------------------------------- 1 | package parametertypes 2 | 3 | import io.cucumber.junit.platform.engine.Constants 4 | import org.junit.platform.suite.api.{ 5 | ConfigurationParameter, 6 | IncludeEngines, 7 | SelectPackages, 8 | Suite 9 | } 10 | 11 | @Suite 12 | @IncludeEngines(Array("cucumber")) 13 | @SelectPackages(Array("parametertypes")) 14 | @ConfigurationParameter( 15 | key = Constants.GLUE_PROPERTY_NAME, 16 | value = "parametertypes" 17 | ) 18 | class RunParameterTypesTest 19 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Any contribution is welcome: 4 | - completing or fixing documentation 5 | - reporting or fixing issue 6 | - reporting missing feature (compared to other implementations) 7 | - developing a new feature 8 | 9 | Please use this Github project for contributing, either through an issue or a Pull Request. 10 | 11 | ## Documentation 12 | 13 | These pages aim to help Cucumber Scala developers understand the codebase. 14 | 15 | - [Build](docs/build.md) 16 | - [Scala implementation details](docs/scala_implementation.md) 17 | -------------------------------------------------------------------------------- /cucumber-scala/src/test/scala/io/cucumber/scala/steps/errors/staticclasshooks/StaticClassHooksDefinition.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala.steps.errors.staticclasshooks 2 | 3 | import io.cucumber.scala.ScalaDsl 4 | 5 | //@formatter:off 6 | class StaticClassHooksDefinition extends ScalaDsl { 7 | 8 | // On a single line to avoid difference between Scala versions for the location 9 | 10 | // Static hook not allowed in classes 11 | BeforeAll { () } 12 | 13 | // Static hook not allowed in classes 14 | AfterAll { () } 15 | 16 | } 17 | //@formatter:on 18 | -------------------------------------------------------------------------------- /scripts/remove-empty-sections-changelog.awk: -------------------------------------------------------------------------------- 1 | function start_buffering() { 2 | buf = $0 3 | } 4 | function store_line_in_buffer() { 5 | buf = buf ORS $0 6 | } 7 | function clear_buffer() { 8 | buf = "" 9 | } 10 | /^### (Added|Changed|Deprecated|Removed|Fixed)$/ { 11 | start_buffering() 12 | next 13 | } 14 | /^## / { 15 | clear_buffer() 16 | } 17 | /^ *$/ { 18 | if (buf != "") { 19 | store_line_in_buffer() 20 | } else { 21 | print $0 22 | } 23 | } 24 | !/^ *$/ { 25 | if (buf != "") { 26 | print buf 27 | clear_buffer() 28 | } 29 | print $0 30 | } 31 | -------------------------------------------------------------------------------- /scripts/update-install-doc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -uf -o pipefail 3 | 4 | # Reads installation doc from STDIN and writes out a new one to STDOUT where: 5 | # 6 | # * the version number is updated for both Maven and sbt 7 | # 8 | 9 | new_version=$1 10 | 11 | installdoc=$([0-9]\+.[0-9]\+.[0-9]\+<\/version>/T from the doc
13 | * string
14 | * @tparam T
15 | * type to convert to
16 | */
17 | def DocStringType[T](
18 | contentType: String
19 | )(body: DocStringDefinitionBody[T])(implicit ev: Stepable[T]): Unit = {
20 | registry.registerDocStringType(
21 | ScalaDocStringTypeDetails[T](
22 | contentType,
23 | body,
24 | ev.asJavaType,
25 | Utils.frame(self)
26 | )
27 | )
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/docs/upgrade_v8.md:
--------------------------------------------------------------------------------
1 | # Upgrading from 7.x to 8.x
2 |
3 | Prior to upgrading to v8.0.0 upgrade to latest v7.x and stop using all deprecated features.
4 | Some features will log a deprecation warning.
5 |
6 | See also:
7 | - [Cucumber Scala CHANGELOG](../CHANGELOG.md)
8 | - [Cucumber JVM CHANGELOG](https://github.com/cucumber/cucumber-jvm/blob/main/CHANGELOG.md)
9 |
10 | ## Cannot use `DataTable#asX` inside a `DataTableType`
11 |
12 | You should not use the methods `DataTable#asX()` in `DataTableType`s.
13 | It was working in previous versions, but it will now raise an exception when running your tests.
14 |
15 | Instead you should use:
16 | - Replace `DataTable#asList()` with `DataTable#values()`
17 | - Replace `DataTable#asLists()` with `DataTable#cells()`
18 | - Replace `DataTable#asMaps()` with `DataTable#entries()`
19 |
20 |
21 | More context for this change at https://github.com/cucumber/common/pull/1419
22 |
--------------------------------------------------------------------------------
/integration-tests/common/src/test/scala/isolated/IsolatedSteps.scala:
--------------------------------------------------------------------------------
1 | package isolated
2 |
3 | import java.util.{List => JList}
4 | import io.cucumber.scala.{EN, ScalaDsl}
5 |
6 | import scala.jdk.CollectionConverters._
7 |
8 | class IsolatedSteps extends ScalaDsl with EN {
9 |
10 | var mutableValues: List[Int] = List()
11 |
12 | Given("""I set the list of values to""") { (values: JList[Int]) =>
13 | // Obviously this is silly, as we keep the previous value but this is exactly what we want to test
14 | // Isolated scenarios should ensure that the previous value is not kept
15 | mutableValues = mutableValues ++ values.asScala.toList
16 | }
17 |
18 | Given("""I multiply by {int}""") { (mult: Int) =>
19 | mutableValues = mutableValues.map(i => i * mult)
20 | }
21 |
22 | Then("""the list of values is""") { (values: JList[Int]) =>
23 | assert(mutableValues == values.asScala.toList)
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/cucumber-scala/src/main/scala/io/cucumber/scala/DataTableDefinitionBody.scala:
--------------------------------------------------------------------------------
1 | package io.cucumber.scala
2 |
3 | import io.cucumber.datatable.DataTable
4 |
5 | trait DataTableEntryDefinitionBody[T] {
6 |
7 | def transform(entry: Map[String, String]): T
8 |
9 | }
10 |
11 | trait DataTableOptionalEntryDefinitionBody[T] {
12 |
13 | def transform(entry: Map[String, Option[String]]): T
14 |
15 | }
16 |
17 | trait DataTableRowDefinitionBody[T] {
18 |
19 | def transform(row: Seq[String]): T
20 |
21 | }
22 |
23 | trait DataTableOptionalRowDefinitionBody[T] {
24 |
25 | def transform(row: Seq[Option[String]]): T
26 |
27 | }
28 |
29 | trait DataTableCellDefinitionBody[T] {
30 |
31 | def transform(cell: String): T
32 |
33 | }
34 |
35 | trait DataTableOptionalCellDefinitionBody[T] {
36 |
37 | def transform(cell: Option[String]): T
38 |
39 | }
40 |
41 | trait DataTableDefinitionBody[T] {
42 |
43 | def transform(dataTable: DataTable): T
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/cucumber-scala/src/main/scala/io/cucumber/scala/ScalaStaticHookDefinition.scala:
--------------------------------------------------------------------------------
1 | package io.cucumber.scala
2 |
3 | import io.cucumber.core.backend.StaticHookDefinition
4 |
5 | trait ScalaStaticHookDefinition
6 | extends StaticHookDefinition
7 | with AbstractGlueDefinition {
8 |
9 | val hookDetails: ScalaStaticHookDetails
10 |
11 | override val location: StackTraceElement = hookDetails.stackTraceElement
12 |
13 | override def execute(): Unit = {
14 | executeAsCucumber(hookDetails.body.apply())
15 | }
16 |
17 | override def getOrder: Int = hookDetails.order
18 |
19 | }
20 |
21 | object ScalaStaticHookDefinition {
22 |
23 | def apply(
24 | scalaHookDetails: ScalaStaticHookDetails
25 | ): ScalaStaticHookDefinition = {
26 | new ScalaGlobalStaticHookDefinition(scalaHookDetails)
27 | }
28 |
29 | }
30 |
31 | class ScalaGlobalStaticHookDefinition(
32 | override val hookDetails: ScalaStaticHookDetails
33 | ) extends ScalaStaticHookDefinition {}
34 |
--------------------------------------------------------------------------------
/examples/examples-junit4/src/main/scala/cucumber/examples/scalacalculator/RpnCalculator.scala:
--------------------------------------------------------------------------------
1 | package cucumber.examples.scalacalculator
2 |
3 | import scala.collection.mutable.Queue
4 |
5 | sealed trait Arg
6 |
7 | object Arg {
8 | implicit def op(s: String): Op = Op(s)
9 | implicit def value(v: Double): Val = Val(v)
10 | }
11 |
12 | case class Op(value: String) extends Arg
13 | case class Val(value: Double) extends Arg
14 |
15 | class RpnCalculator {
16 | private val stack = Queue.empty[Double]
17 |
18 | private def op(f: (Double, Double) => Double) =
19 | stack += f(stack.dequeue(), stack.dequeue())
20 |
21 | def push(arg: Arg): Unit = {
22 | arg match {
23 | case Op("+") => op(_ + _)
24 | case Op("-") => op(_ - _)
25 | case Op("*") => op(_ * _)
26 | case Op("/") => op(_ / _)
27 | case Val(value) => stack += value
28 | case _ => ()
29 | }
30 | ()
31 | }
32 |
33 | def value: Double = stack.head
34 | }
35 |
--------------------------------------------------------------------------------
/examples/examples-junit5/src/main/scala/cucumber/examples/scalacalculator/RpnCalculator.scala:
--------------------------------------------------------------------------------
1 | package cucumber.examples.scalacalculator
2 |
3 | import scala.collection.mutable.Queue
4 |
5 | sealed trait Arg
6 |
7 | object Arg {
8 | implicit def op(s: String): Op = Op(s)
9 | implicit def value(v: Double): Val = Val(v)
10 | }
11 |
12 | case class Op(value: String) extends Arg
13 | case class Val(value: Double) extends Arg
14 |
15 | class RpnCalculator {
16 | private val stack = Queue.empty[Double]
17 |
18 | private def op(f: (Double, Double) => Double) =
19 | stack += f(stack.dequeue(), stack.dequeue())
20 |
21 | def push(arg: Arg): Unit = {
22 | arg match {
23 | case Op("+") => op(_ + _)
24 | case Op("-") => op(_ - _)
25 | case Op("*") => op(_ * _)
26 | case Op("/") => op(_ / _)
27 | case Val(value) => stack += value
28 | case _ => ()
29 | }
30 | ()
31 | }
32 |
33 | def value: Double = stack.head
34 | }
35 |
--------------------------------------------------------------------------------
/integration-tests/common/src/test/scala/misc/OptionalCaptureGroupsSteps.scala:
--------------------------------------------------------------------------------
1 | package misc
2 |
3 | import java.util.Optional
4 |
5 | import io.cucumber.scala.{EN, ScalaDsl}
6 |
7 | class OptionalCaptureGroupsSteps extends ScalaDsl with EN {
8 |
9 | // Scala 2.13 only
10 | // import scala.jdk.OptionConverters._
11 |
12 | import OptionalCaptureGroupsSteps._
13 |
14 | Given("""^I have the name:\s?(.+)?$""") { (name: Optional[String]) =>
15 | val option = name.toScala
16 | assert(option.isDefined)
17 | assert(option.getOrElse("Nope") == "Jack")
18 | }
19 |
20 | Given("""^I don't have the name:\s?(.+)?$""") { (name: Optional[String]) =>
21 | val option = name.toScala
22 | assert(option.isEmpty)
23 | }
24 |
25 | }
26 |
27 | object OptionalCaptureGroupsSteps {
28 |
29 | implicit class RichOptional[A](private val o: java.util.Optional[A])
30 | extends AnyVal {
31 |
32 | def toScala: Option[A] = if (o.isPresent) Some(o.get) else None
33 |
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/integration-tests/common/src/test/scala/statichooks/StaticHooksSteps.scala:
--------------------------------------------------------------------------------
1 | package statichooks
2 |
3 | import io.cucumber.scala.{EN, ScalaDsl}
4 | import org.junit.jupiter.api.Assertions.assertEquals
5 |
6 | import scala.annotation.nowarn
7 |
8 | @nowarn
9 | object StaticHooksSteps extends ScalaDsl with EN {
10 |
11 | var countBeforeAll: Int = 0
12 | var countAfterAll: Int = 0
13 |
14 | BeforeAll {
15 | countBeforeAll = countBeforeAll + 1
16 | }
17 |
18 | AfterAll {
19 | countAfterAll = countAfterAll + 1
20 | }
21 |
22 | When("""I run scenario {string}""") { (scenarioName: String) =>
23 | println(s"Running scenario $scenarioName")
24 | ()
25 | }
26 |
27 | Then("""BeforeAll count is {int}""") { (count: Int) =>
28 | println(s"BeforeAll = $countBeforeAll")
29 | assertEquals(count, countBeforeAll)
30 | }
31 |
32 | Then("""AfterAll count is {int}""") { (count: Int) =>
33 | println(s"AfterAll = $countAfterAll")
34 | assertEquals(count, countAfterAll)
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/integration-tests/common/src/test/resources/docstring/Docstring.feature:
--------------------------------------------------------------------------------
1 | Feature: As Cucumber Scala, I want to use DocStringType
2 |
3 | Scenario: Using a DocStringType
4 | Given the following json text
5 | """json
6 | {
7 | "key": "value"
8 | }
9 | """
10 | Then I have a json text
11 |
12 | Scenario: Using another DocStringType
13 | Given the following xml text
14 | """xml
15 | This trait register a `DefaultDataTableEntryTransformer` using Jackson 7 | * `ObjectMapper`.
8 | * 9 | *The `[empty]` string is used as default empty string replacement. You can 10 | * override it if you need to.
11 | * 12 | *Note: Jackson is not included with Cucumber Scala, you have to add the 13 | * dependency: `com.fasterxml.jackson.module:jackson-module-scala` to your 14 | * project if you want to use this trait.
15 | * 16 | *For Jackson 3.x, use `Jackson3DefaultDataTableEntryTransformer` 17 | * instead.
18 | */ 19 | trait JacksonDefaultDataTableEntryTransformer extends ScalaDsl { 20 | 21 | /** Define the string to be used as replacement for empty. Default is 22 | * `[empty]`. 23 | */ 24 | def emptyStringReplacement: String = "[empty]" 25 | 26 | /** Create the Jackson ObjectMapper to be used. Default is a simple 27 | * ObjectMapper with DefaultScalaModule registered. 28 | */ 29 | def createObjectMapper(): ObjectMapper = { 30 | val objectMapper = new ObjectMapper() 31 | objectMapper.registerModule(DefaultScalaModule) 32 | } 33 | 34 | private lazy val objectMapper: ObjectMapper = createObjectMapper() 35 | 36 | DefaultDataTableEntryTransformer(emptyStringReplacement) { 37 | (fromValue: Map[String, String], toValueType: java.lang.reflect.Type) => 38 | objectMapper.convertValue[AnyRef]( 39 | fromValue, 40 | objectMapper.constructType(toValueType) 41 | ) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /cucumber-scala/src/test/scala/io/cucumber/scala/ScalaDslDocStringTypeTest.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import io.cucumber.core.backend._ 4 | import org.junit.jupiter.api.Test 5 | 6 | import scala.annotation.nowarn 7 | 8 | @nowarn 9 | class ScalaDslDocStringTypeTest { 10 | 11 | @Test 12 | def testDocStringType(): Unit = { 13 | 14 | class Glue extends ScalaDsl with EN { 15 | DocStringType("doc") { docString => 16 | new StringBuilder(docString) 17 | } 18 | } 19 | 20 | val glue = new Glue() 21 | 22 | assertClassDocStringType(glue.registry.docStringTypes.head) 23 | } 24 | 25 | // -------------------- Test on object -------------------- 26 | // Note: for now there is no difference between the two in ScalaDsl but better safe than sorry 27 | 28 | @Test 29 | def testObjectDocStringType(): Unit = { 30 | 31 | object Glue extends ScalaDsl with EN { 32 | DocStringType("doc") { docString => 33 | new StringBuilder(docString) 34 | } 35 | } 36 | 37 | assertObjectDocStringType(Glue.registry.docStringTypes.head) 38 | } 39 | 40 | private def assertClassDocStringType( 41 | details: ScalaDocStringTypeDetails[_] 42 | ): Unit = { 43 | assertDocStringType(ScalaDocStringTypeDefinition(details, true)) 44 | } 45 | 46 | private def assertObjectDocStringType( 47 | details: ScalaDocStringTypeDetails[_] 48 | ): Unit = { 49 | assertDocStringType(ScalaDocStringTypeDefinition(details, false)) 50 | } 51 | 52 | private def assertDocStringType( 53 | docStringType: DocStringTypeDefinition 54 | ): Unit = { 55 | // Cannot assert much because everything is strangely private in DocStringTypeDefinition 56 | // Real feature tests will do the job 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/ScalaDefaultParameterTransformerDefinition.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import java.lang.reflect.Type 4 | import io.cucumber.core.backend.{ 5 | DefaultParameterTransformerDefinition, 6 | ScenarioScoped 7 | } 8 | import io.cucumber.cucumberexpressions.ParameterByTypeTransformer 9 | 10 | import scala.annotation.nowarn 11 | 12 | trait ScalaDefaultParameterTransformerDefinition 13 | extends DefaultParameterTransformerDefinition 14 | with AbstractGlueDefinition { 15 | 16 | val details: ScalaDefaultParameterTransformerDetails 17 | 18 | override val location: StackTraceElement = details.stackTraceElement 19 | 20 | override val parameterByTypeTransformer: ParameterByTypeTransformer = 21 | (fromValue: String, toValue: Type) => { 22 | details.body.apply(fromValue, toValue) 23 | } 24 | 25 | } 26 | 27 | object ScalaDefaultParameterTransformerDefinition { 28 | 29 | def apply( 30 | details: ScalaDefaultParameterTransformerDetails, 31 | scenarioScoped: Boolean 32 | ): ScalaDefaultParameterTransformerDefinition = { 33 | if (scenarioScoped) { 34 | new ScalaScenarioScopedDefaultParameterTransformerDefinition(details) 35 | } else { 36 | new ScalaGlobalDefaultParameterTransformerDefinition(details) 37 | } 38 | } 39 | 40 | } 41 | 42 | @nowarn 43 | class ScalaScenarioScopedDefaultParameterTransformerDefinition( 44 | override val details: ScalaDefaultParameterTransformerDetails 45 | ) extends ScalaDefaultParameterTransformerDefinition 46 | with ScenarioScoped {} 47 | 48 | class ScalaGlobalDefaultParameterTransformerDefinition( 49 | override val details: ScalaDefaultParameterTransformerDetails 50 | ) extends ScalaDefaultParameterTransformerDefinition {} 51 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/ScalaParameterTypeDefinition.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import io.cucumber.core.backend.{ParameterTypeDefinition, ScenarioScoped} 4 | import io.cucumber.cucumberexpressions.{CaptureGroupTransformer, ParameterType} 5 | 6 | import scala.annotation.nowarn 7 | import scala.jdk.CollectionConverters._ 8 | 9 | trait ScalaParameterTypeDefinition[R] 10 | extends ParameterTypeDefinition 11 | with AbstractGlueDefinition { 12 | 13 | val details: ScalaParameterTypeDetails[R] 14 | 15 | override val location: StackTraceElement = details.stackTraceElement 16 | 17 | private val transformer: CaptureGroupTransformer[R] = 18 | (parameterContent: Array[String]) => { 19 | details.body.apply(parameterContent.toList) 20 | } 21 | 22 | override val parameterType: ParameterType[R] = new ParameterType[R]( 23 | details.name, 24 | Seq(details.regex).asJava, 25 | details.tag.runtimeClass.asInstanceOf[Class[R]], 26 | transformer 27 | ) 28 | 29 | } 30 | 31 | object ScalaParameterTypeDefinition { 32 | 33 | def apply[R]( 34 | stepDetails: ScalaParameterTypeDetails[R], 35 | scenarioScoped: Boolean 36 | ): ScalaParameterTypeDefinition[R] = { 37 | if (scenarioScoped) { 38 | new ScalaScenarioScopedParameterTypeDefinition(stepDetails) 39 | } else { 40 | new ScalaGlobalParameterTypeDefinition(stepDetails) 41 | } 42 | } 43 | 44 | } 45 | 46 | @nowarn 47 | class ScalaScenarioScopedParameterTypeDefinition[R]( 48 | override val details: ScalaParameterTypeDetails[R] 49 | ) extends ScalaParameterTypeDefinition[R] 50 | with ScenarioScoped {} 51 | 52 | class ScalaGlobalParameterTypeDefinition[R]( 53 | override val details: ScalaParameterTypeDetails[R] 54 | ) extends ScalaParameterTypeDefinition[R] {} 55 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/ScalaStepDefinition.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import java.lang.reflect.{Type => JType} 4 | import java.util.{List => JList} 5 | import io.cucumber.core.backend.{ParameterInfo, ScenarioScoped, StepDefinition} 6 | 7 | import scala.annotation.nowarn 8 | import scala.jdk.CollectionConverters._ 9 | 10 | trait ScalaStepDefinition extends StepDefinition with AbstractGlueDefinition { 11 | 12 | val stepDetails: ScalaStepDetails 13 | 14 | override val location: StackTraceElement = stepDetails.frame 15 | 16 | override val parameterInfos: JList[ParameterInfo] = fromTypes( 17 | stepDetails.types 18 | ) 19 | 20 | private def fromTypes(types: Seq[JType]): JList[ParameterInfo] = { 21 | types 22 | .map(new ScalaTypeResolver(_)) 23 | .map(new ScalaParameterInfo(_)) 24 | .toList 25 | .asInstanceOf[List[ParameterInfo]] 26 | .asJava 27 | } 28 | 29 | override def execute(args: Array[AnyRef]): Unit = { 30 | executeAsCucumber { 31 | stepDetails.body(args.toList) 32 | () 33 | } 34 | } 35 | 36 | override def getPattern: String = stepDetails.pattern 37 | 38 | } 39 | 40 | object ScalaStepDefinition { 41 | 42 | def apply( 43 | stepDetails: ScalaStepDetails, 44 | scenarioScoped: Boolean 45 | ): ScalaStepDefinition = { 46 | if (scenarioScoped) { 47 | new ScalaScenarioScopedStepDefinition(stepDetails) 48 | } else { 49 | new ScalaGlobalStepDefinition(stepDetails) 50 | } 51 | } 52 | 53 | } 54 | 55 | @nowarn 56 | class ScalaScenarioScopedStepDefinition( 57 | override val stepDetails: ScalaStepDetails 58 | ) extends ScalaStepDefinition 59 | with ScenarioScoped {} 60 | 61 | class ScalaGlobalStepDefinition(override val stepDetails: ScalaStepDetails) 62 | extends ScalaStepDefinition {} 63 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/Jackson3DefaultDataTableEntryTransformer.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import tools.jackson.databind.ObjectMapper 4 | import tools.jackson.databind.json.JsonMapper 5 | import tools.jackson.module.scala.ScalaModule 6 | 7 | /**This trait register a `DefaultDataTableEntryTransformer` using Jackson 8 | * `ObjectMapper`.
9 | * 10 | *The `[empty]` string is used as default empty string replacement. You can 11 | * override it if you need to.
12 | * 13 | *Note: Jackson is not included with Cucumber Scala, you have to add the 14 | * dependency: `tools.jackson.module:jackson-module-scala` to your project if 15 | * you want to use this trait.
16 | * 17 | *For Jackson 2.x, use `JacksonDefaultDataTableEntryTransformer` 18 | * instead.
19 | */ 20 | trait Jackson3DefaultDataTableEntryTransformer extends ScalaDsl { 21 | 22 | /** Define the string to be used as replacement for empty. Default is 23 | * `[empty]`. 24 | */ 25 | def emptyStringReplacement: String = "[empty]" 26 | 27 | /** Create the Jackson ObjectMapper to be used. Default is a simple JsonMapper 28 | * with ScalaModule (including all builtin modules) registered. 29 | */ 30 | def createObjectMapper(): ObjectMapper = { 31 | val scalaModule = ScalaModule 32 | .builder() 33 | .addAllBuiltinModules() 34 | .build() 35 | JsonMapper.builder().addModule(scalaModule).build() 36 | } 37 | 38 | private lazy val objectMapper: ObjectMapper = createObjectMapper() 39 | 40 | DefaultDataTableEntryTransformer(emptyStringReplacement) { 41 | (fromValue: Map[String, String], toValueType: java.lang.reflect.Type) => 42 | objectMapper.convertValue[AnyRef]( 43 | fromValue, 44 | objectMapper.constructType(toValueType) 45 | ) 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/ScalaHookDefinition.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import io.cucumber.core.backend.{HookDefinition, ScenarioScoped, TestCaseState} 4 | import io.cucumber.scala.ScopedHookType.{AFTER, AFTER_STEP, BEFORE, BEFORE_STEP} 5 | 6 | import java.util.Optional 7 | import scala.annotation.nowarn 8 | 9 | trait ScalaHookDefinition extends HookDefinition with AbstractGlueDefinition { 10 | 11 | val hookDetails: ScalaHookDetails 12 | 13 | override val location: StackTraceElement = hookDetails.stackTraceElement 14 | 15 | override def execute(state: TestCaseState): Unit = { 16 | executeAsCucumber(hookDetails.body.apply(new Scenario(state))) 17 | } 18 | 19 | override def getTagExpression: String = hookDetails.tagExpression 20 | 21 | override def getOrder: Int = hookDetails.order 22 | 23 | override def getHookType: Optional[HookDefinition.HookType] = { 24 | val javaHookType = hookDetails.hookType match { 25 | case BEFORE => HookDefinition.HookType.BEFORE 26 | case AFTER => HookDefinition.HookType.AFTER 27 | case BEFORE_STEP => HookDefinition.HookType.BEFORE_STEP 28 | case AFTER_STEP => HookDefinition.HookType.AFTER_STEP 29 | } 30 | Optional.of(javaHookType) 31 | } 32 | 33 | } 34 | 35 | object ScalaHookDefinition { 36 | 37 | def apply( 38 | scalaHookDetails: ScalaHookDetails, 39 | scenarioScoped: Boolean 40 | ): ScalaHookDefinition = { 41 | if (scenarioScoped) { 42 | new ScalaScenarioScopedHookDefinition(scalaHookDetails) 43 | } else { 44 | new ScalaGlobalHookDefinition(scalaHookDetails) 45 | } 46 | } 47 | 48 | } 49 | 50 | @nowarn 51 | class ScalaScenarioScopedHookDefinition( 52 | override val hookDetails: ScalaHookDetails 53 | ) extends ScalaHookDefinition 54 | with ScenarioScoped {} 55 | 56 | class ScalaGlobalHookDefinition(override val hookDetails: ScalaHookDetails) 57 | extends ScalaHookDefinition {} 58 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/ScalaDefaultDataTableCellTransformerDefinition.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import java.lang.reflect.Type 4 | import io.cucumber.core.backend.{ 5 | DefaultDataTableCellTransformerDefinition, 6 | ScenarioScoped 7 | } 8 | import io.cucumber.datatable.TableCellByTypeTransformer 9 | 10 | import scala.annotation.nowarn 11 | 12 | trait ScalaDefaultDataTableCellTransformerDefinition 13 | extends DefaultDataTableCellTransformerDefinition 14 | with AbstractDatatableElementTransformerDefinition { 15 | 16 | val details: ScalaDefaultDataTableCellTransformerDetails 17 | 18 | override val emptyPatterns: Seq[String] = details.emptyPatterns 19 | 20 | override val location: StackTraceElement = details.stackTraceElement 21 | 22 | override val tableCellByTypeTransformer: TableCellByTypeTransformer = 23 | (fromValue: String, toTypeValue: Type) => { 24 | details.body.apply( 25 | replaceEmptyPatternsWithEmptyString(fromValue), 26 | toTypeValue 27 | ) 28 | } 29 | 30 | } 31 | 32 | object ScalaDefaultDataTableCellTransformerDefinition { 33 | 34 | def apply( 35 | details: ScalaDefaultDataTableCellTransformerDetails, 36 | scenarioScoped: Boolean 37 | ): ScalaDefaultDataTableCellTransformerDefinition = { 38 | if (scenarioScoped) { 39 | new ScalaScenarioScopedDataTableCellTransformerDefinition(details) 40 | } else { 41 | new ScalaGlobalDataTableCellTransformerDefinition(details) 42 | } 43 | } 44 | 45 | } 46 | 47 | @nowarn 48 | class ScalaScenarioScopedDataTableCellTransformerDefinition( 49 | override val details: ScalaDefaultDataTableCellTransformerDetails 50 | ) extends ScalaDefaultDataTableCellTransformerDefinition 51 | with ScenarioScoped {} 52 | 53 | class ScalaGlobalDataTableCellTransformerDefinition( 54 | override val details: ScalaDefaultDataTableCellTransformerDetails 55 | ) extends ScalaDefaultDataTableCellTransformerDefinition {} 56 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/resources/parametertypes/ParameterTypes.feature: -------------------------------------------------------------------------------- 1 | Feature: As Cucumber Scala, I want to handle ParameterType definitions 2 | 3 | Scenario: define parameter type with single argument 4 | Given "string builder" parameter, defined by lambda 5 | 6 | Scenario: define parameter type with two arguments 7 | Given balloon coordinates 123,456, defined by lambda 8 | 9 | Scenario: define parameter type with three arguments 10 | Given kebab made from mushroom, meat and veg, defined by lambda 11 | 12 | Scenario: define parameter type with parameterized type, string undefined 13 | Given an optional string parameter value "" undefined 14 | 15 | Scenario: define parameter type with parameterized type, string defined 16 | Given an optional string parameter value "toto" defined 17 | 18 | Scenario: define parameter type with parameterized type, int undefined 19 | Given an optional int parameter value undefined 20 | 21 | Scenario: define parameter type with parameterized type, int defined 22 | Given an optional int parameter value 5 defined 23 | 24 | Scenario: define default parameter transformer 25 | Given kebab made from anonymous meat, defined by lambda 26 | 27 | Scenario: define default data table cell transformer - DataTable 28 | Given default data table cells, defined by lambda 29 | | Kebab | 30 | | [empty] | 31 | 32 | Scenario: define default data table cell transformer - JList[Jlist] 33 | Given default data table cells, defined by lambda, as rows 34 | | Kebab | 35 | | [empty] | 36 | 37 | Scenario: define default data table entry transformer - DataTable 38 | Given default data table entries, defined by lambda 39 | | dinner | 40 | | Kebab | 41 | | [empty] | 42 | 43 | Scenario: define default data table entry transformer - JList 44 | Given default data table entries, defined by lambda, as rows 45 | | dinner | 46 | | Kebab | 47 | | [empty] | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/ScalaDataTableTypeDetails.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import scala.reflect.ClassTag 4 | 5 | sealed trait ScalaDataTableTypeDetails[T] { 6 | def emptyPatterns: Seq[String] 7 | def tag: ClassTag[T] 8 | def stackTraceElement: StackTraceElement 9 | } 10 | 11 | case class ScalaDataTableEntryTypeDetails[T]( 12 | emptyPatterns: Seq[String], 13 | body: DataTableEntryDefinitionBody[T], 14 | tag: ClassTag[T], 15 | stackTraceElement: StackTraceElement 16 | ) extends ScalaDataTableTypeDetails[T] 17 | 18 | case class ScalaDataTableOptionalEntryTypeDetails[T]( 19 | emptyPatterns: Seq[String], 20 | body: DataTableOptionalEntryDefinitionBody[T], 21 | tag: ClassTag[T], 22 | stackTraceElement: StackTraceElement 23 | ) extends ScalaDataTableTypeDetails[T] 24 | 25 | case class ScalaDataTableRowTypeDetails[T]( 26 | emptyPatterns: Seq[String], 27 | body: DataTableRowDefinitionBody[T], 28 | tag: ClassTag[T], 29 | stackTraceElement: StackTraceElement 30 | ) extends ScalaDataTableTypeDetails[T] 31 | 32 | case class ScalaDataTableOptionalRowTypeDetails[T]( 33 | emptyPatterns: Seq[String], 34 | body: DataTableOptionalRowDefinitionBody[T], 35 | tag: ClassTag[T], 36 | stackTraceElement: StackTraceElement 37 | ) extends ScalaDataTableTypeDetails[T] 38 | 39 | case class ScalaDataTableCellTypeDetails[T]( 40 | emptyPatterns: Seq[String], 41 | body: DataTableCellDefinitionBody[T], 42 | tag: ClassTag[T], 43 | stackTraceElement: StackTraceElement 44 | ) extends ScalaDataTableTypeDetails[T] 45 | 46 | case class ScalaDataTableOptionalCellTypeDetails[T]( 47 | emptyPatterns: Seq[String], 48 | body: DataTableOptionalCellDefinitionBody[T], 49 | tag: ClassTag[T], 50 | stackTraceElement: StackTraceElement 51 | ) extends ScalaDataTableTypeDetails[T] 52 | 53 | case class ScalaDataTableTableTypeDetails[T]( 54 | emptyPatterns: Seq[String], 55 | body: DataTableDefinitionBody[T], 56 | tag: ClassTag[T], 57 | stackTraceElement: StackTraceElement 58 | ) extends ScalaDataTableTypeDetails[T] 59 | -------------------------------------------------------------------------------- /integration-tests/common/src/test/scala/docstring/DocStringSteps.scala: -------------------------------------------------------------------------------- 1 | package docstring 2 | 3 | import io.cucumber.scala.{EN, ScalaDsl} 4 | 5 | class DocStringSteps extends ScalaDsl with EN { 6 | 7 | case class JsonText(json: String) 8 | 9 | case class XmlText(xml: String) 10 | 11 | case class RawText(raw: String) 12 | 13 | var _text: Any = _ 14 | 15 | DocStringType("json") { (text) => 16 | JsonText(text) 17 | } 18 | 19 | DocStringType("xml") { (text) => 20 | XmlText(text) 21 | } 22 | 23 | DocStringType("") { (text) => 24 | RawText(text) 25 | } 26 | 27 | // Tests generic type 28 | DocStringType[Seq[String]]("") { (text) => 29 | text.split('\n').toSeq 30 | } 31 | 32 | DocStringType[Seq[Int]]("") { (text) => 33 | text.split('\n').map(_.toInt).toSeq 34 | } 35 | 36 | Given("the following json text") { (json: JsonText) => 37 | _text = json 38 | } 39 | 40 | Given("the following xml text") { (xml: XmlText) => 41 | _text = xml 42 | } 43 | 44 | Given("the following raw text") { (raw: RawText) => 45 | _text = raw 46 | } 47 | 48 | Given("the following string list") { (list: Seq[String]) => 49 | _text = list 50 | } 51 | 52 | Given("the following int list") { (list: Seq[Int]) => 53 | _text = list 54 | } 55 | 56 | Then("I have a json text") { 57 | assert(_text.isInstanceOf[JsonText]) 58 | } 59 | 60 | Then("I have a xml text") { 61 | assert(_text.isInstanceOf[XmlText]) 62 | } 63 | 64 | Then("I have a raw text") { 65 | assert(_text.isInstanceOf[RawText]) 66 | } 67 | 68 | Then("I have a string list {string}") { (expectedList: String) => 69 | assert(_text.isInstanceOf[Seq[_]]) 70 | assert(_text.asInstanceOf[Seq[_]].head.isInstanceOf[String]) 71 | assert(_text.asInstanceOf[Seq[String]] == expectedList.split(',').toSeq) 72 | } 73 | 74 | Then("I have a int list {string}") { (expectedList: String) => 75 | assert(_text.isInstanceOf[Seq[_]]) 76 | assert(_text.asInstanceOf[Seq[_]].head.isInstanceOf[Int]) 77 | assert( 78 | _text.asInstanceOf[Seq[Int]] == expectedList.split(',').map(_.toInt).toSeq 79 | ) 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/ScalaDefaultDataTableEntryTransformerDefinition.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import java.lang.reflect.Type 4 | import java.util.{Map => JavaMap} 5 | import io.cucumber.core.backend.{ 6 | DefaultDataTableEntryTransformerDefinition, 7 | ScenarioScoped 8 | } 9 | import io.cucumber.datatable.{ 10 | TableCellByTypeTransformer, 11 | TableEntryByTypeTransformer 12 | } 13 | 14 | import scala.annotation.nowarn 15 | import scala.jdk.CollectionConverters._ 16 | 17 | trait ScalaDefaultDataTableEntryTransformerDefinition 18 | extends DefaultDataTableEntryTransformerDefinition 19 | with AbstractDatatableElementTransformerDefinition { 20 | 21 | val details: ScalaDefaultDataTableEntryTransformerDetails 22 | 23 | override val emptyPatterns: Seq[String] = details.emptyPatterns 24 | 25 | override val location: StackTraceElement = details.stackTraceElement 26 | 27 | override val tableEntryByTypeTransformer: TableEntryByTypeTransformer = ( 28 | fromValue: JavaMap[String, String], 29 | toValueType: Type, 30 | _: TableCellByTypeTransformer 31 | ) => { 32 | replaceEmptyPatternsWithEmptyString(fromValue.asScala.toMap) 33 | .map(details.body.apply(_, toValueType)) 34 | .get 35 | } 36 | 37 | override val headersToProperties: Boolean = true 38 | 39 | } 40 | 41 | object ScalaDefaultDataTableEntryTransformerDefinition { 42 | 43 | def apply( 44 | details: ScalaDefaultDataTableEntryTransformerDetails, 45 | scenarioScoped: Boolean 46 | ): ScalaDefaultDataTableEntryTransformerDefinition = { 47 | if (scenarioScoped) { 48 | new ScalaScenarioScopedDataTableEntryTransformerDefinition(details) 49 | } else { 50 | new ScalaGlobalDataTableEntryTransformerDefinition(details) 51 | } 52 | } 53 | 54 | } 55 | 56 | @nowarn 57 | class ScalaScenarioScopedDataTableEntryTransformerDefinition( 58 | override val details: ScalaDefaultDataTableEntryTransformerDetails 59 | ) extends ScalaDefaultDataTableEntryTransformerDefinition 60 | with ScenarioScoped {} 61 | 62 | class ScalaGlobalDataTableEntryTransformerDefinition( 63 | override val details: ScalaDefaultDataTableEntryTransformerDetails 64 | ) extends ScalaDefaultDataTableEntryTransformerDefinition {} 65 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/codegen/gen.scala: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Generates the evil looking apply methods in StepDsl#StepBody for Function1 to Function22 4 | * Scala 3 5 | */ 6 | for (i <- 1 to 22) { 7 | val ts = (1 to i).map("T".+).mkString(", ") 8 | val implicits = (1 to i).map(n => s"t$n: Stepable[T$n]").mkString(", ") 9 | val implicitsParams = (1 to i).map(n => s"t$n").mkString(", ") 10 | val listParams = (1 to i).map("a" + _ + ":AnyRef").mkString(", ") 11 | val pf = (1 to i).map(n => "a" + n + ".asInstanceOf[T" + n + "]").mkString(",\n ") 12 | 13 | println(s""" 14 | |def apply[$ts](f: ($ts) => Any)(using $implicits): Unit = { 15 | | register($implicitsParams) { 16 | | case List($listParams) => 17 | | f($pf) 18 | | case _ => 19 | | throw new IncorrectStepDefinitionException() 20 | | } 21 | |}""".stripMargin) 22 | } 23 | 24 | /* 25 | * Generates the apply methods in ParameterTypeDsl for Function1 to Function22 26 | */ 27 | for (i <- 1 to 22) { 28 | // String, String, ..., String 29 | val types = (1 to i).map(_ => "String").mkString(", ") 30 | // p1, p2, ..., p22 31 | val args = (1 to i).map(j => s"p$j").mkString(", ") 32 | 33 | val template = 34 | s""" 35 | |def apply[R](f: ($types) => R)(implicit tag: ClassTag[R]): Unit = { 36 | | register { 37 | | case List($args) => 38 | | f($args) 39 | | } 40 | |} 41 | |""".stripMargin 42 | 43 | println(template) 44 | } 45 | 46 | /* 47 | * Generates the Stepable implicit methods 48 | */ 49 | for (i <- (1 to 9).reverse) { 50 | 51 | val underscores = (1 to i).map(_ => "_").mkString(", ") 52 | val types = (1 to i).map(j => s"X$j").mkString(", ") 53 | val typesStepable = (1 to i).map(j => s"X$j: Stepable").mkString(", ") 54 | val typeArgs = (1 to i).map(j => s"implicitly[Stepable[X$j]].asJavaType").mkString(", ") 55 | 56 | val template = 57 | s""" 58 | |implicit def stepable$i[T[$underscores], $typesStepable](implicit ct: ClassTag[T[$types]]): Stepable[T[$types]] = 59 | | new Stepable[T[$types]] { 60 | | def asJavaType: JavaType = 61 | | new ScalaParameterizedType( 62 | | ct.runtimeClass, 63 | | Array( 64 | | $typeArgs 65 | | ) 66 | | ) 67 | | }""".stripMargin 68 | 69 | println(template) 70 | } 71 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/IncorrectHookDefinitionException.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import io.cucumber.core.backend.CucumberBackendException 4 | 5 | sealed abstract class IncorrectHookDefinitionException(message: String) 6 | extends CucumberBackendException(message) 7 | 8 | object IncorrectHookDefinitionException { 9 | 10 | def undefinedHooksErrorMessage(expectedHooks: Seq[UndefinedHook]): String = { 11 | val hooksListToDisplay = expectedHooks.map { eh => 12 | s" - ${eh.stackTraceElement.getFileName}:${eh.stackTraceElement.getLineNumber} (${eh.hookType})" 13 | } 14 | 15 | s"""Some hooks are not defined properly: 16 | |${hooksListToDisplay.mkString("\n")} 17 | | 18 | |This can be caused by defining hooks where the body returns a Int or String rather than Unit. 19 | | 20 | |For instance, the following code: 21 | | 22 | | Before { 23 | | someInitMethodReturningInt() 24 | | } 25 | | 26 | |Should be replaced with: 27 | | 28 | | Before { 29 | | someInitMethodReturningInt() 30 | | () 31 | | } 32 | |""".stripMargin 33 | } 34 | 35 | def scenarioScopedStaticHookErrorMessage( 36 | staticHooks: Seq[ScalaStaticHookDetails] 37 | ): String = { 38 | val hooksListToDisplay: Seq[String] = staticHooks.map { h => 39 | s" - ${h.stackTraceElement.getFileName}:${h.stackTraceElement.getLineNumber}" 40 | } 41 | 42 | s"""Some hooks are not defined properly: 43 | |${hooksListToDisplay.mkString("\n")} 44 | | 45 | |This can be caused by defining static hooks (BeforeAll/AfterAll) in a class rather than in a object. 46 | |Such hooks can only be defined in a static context. 47 | |""".stripMargin 48 | } 49 | 50 | } 51 | 52 | class UndefinedHooksException(val undefinedHooks: Seq[UndefinedHook]) 53 | extends IncorrectHookDefinitionException( 54 | IncorrectHookDefinitionException.undefinedHooksErrorMessage( 55 | undefinedHooks 56 | ) 57 | ) {} 58 | 59 | class ScenarioScopedStaticHookException( 60 | val staticHooks: Seq[ScalaStaticHookDetails] 61 | ) extends IncorrectHookDefinitionException( 62 | IncorrectHookDefinitionException.scenarioScopedStaticHookErrorMessage( 63 | staticHooks 64 | ) 65 | ) {} 66 | 67 | case class UndefinedHook( 68 | hookType: HookType, 69 | stackTraceElement: StackTraceElement 70 | ) 71 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/AbstractDatatableElementTransformerDefinition.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import io.cucumber.datatable.DataTable 4 | 5 | import scala.util.{Failure, Success, Try} 6 | import scala.jdk.CollectionConverters._ 7 | 8 | trait AbstractDatatableElementTransformerDefinition 9 | extends AbstractGlueDefinition { 10 | 11 | val emptyPatterns: Seq[String] 12 | 13 | protected def replaceEmptyPatternsWithEmptyString( 14 | row: Seq[String] 15 | ): Seq[String] = { 16 | row.map(replaceEmptyPatternsWithEmptyString) 17 | } 18 | 19 | protected def replaceEmptyPatternsWithEmptyString( 20 | table: DataTable 21 | ): DataTable = { 22 | val rawWithEmptyStrings = table 23 | .cells() 24 | .asScala 25 | .map(_.asScala.toSeq) 26 | .map(replaceEmptyPatternsWithEmptyString) 27 | .map(_.asJava) 28 | .toSeq 29 | .asJava 30 | 31 | DataTable.create(rawWithEmptyStrings, table.getTableConverter) 32 | } 33 | 34 | protected def replaceEmptyPatternsWithEmptyString( 35 | fromValue: Map[String, String] 36 | ): Try[Map[String, String]] = { 37 | val replacement = fromValue.toSeq.map { case (key, value) => 38 | val potentiallyEmptyKey = replaceEmptyPatternsWithEmptyString(key) 39 | val potentiallyEmptyValue = replaceEmptyPatternsWithEmptyString(value) 40 | 41 | (potentiallyEmptyKey, potentiallyEmptyValue) 42 | } 43 | 44 | if (containsDuplicateKey(replacement)) { 45 | Failure(createDuplicateKeyAfterReplacement(fromValue)) 46 | } else { 47 | Success(replacement.toMap) 48 | } 49 | } 50 | 51 | protected def replaceEmptyPatternsWithEmptyString(t: String): String = { 52 | if (emptyPatterns.contains(t)) { 53 | "" 54 | } else { 55 | t 56 | } 57 | } 58 | 59 | private def containsDuplicateKey(seq: Seq[(String, Any)]): Boolean = { 60 | seq.map { case (key, _) => key }.toSet.size != seq.size 61 | } 62 | 63 | private def createDuplicateKeyAfterReplacement( 64 | fromValue: Map[String, String] 65 | ): IllegalArgumentException = { 66 | val conflict = 67 | emptyPatterns.filter(emptyPattern => fromValue.contains(emptyPattern)) 68 | val msg = 69 | s"After replacing ${conflict.headOption 70 | .getOrElse("")} and ${conflict.drop(1).headOption.getOrElse("")} with empty strings the datatable entry contains duplicate keys: $fromValue" 71 | new IllegalArgumentException(msg) 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /scripts/update-changelog.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -uf -o pipefail 3 | 4 | # Reads a changelog from STDIN and writes out a new one to STDOUT where: 5 | # 6 | # * The [Unreleased] diff link is updated 7 | # * A new diff link for the new release is added 8 | # * The ## [Unreleased] header is changed to a version header with date 9 | # * The empty sections are removed 10 | # * A new, empty [Unreleased] paragraph is added at the top 11 | # 12 | 13 | changelog=$(&2 echo "No version found in link: ${unreleased_link}" 54 | exit 1 55 | fi 56 | 57 | # Insert a new release diff link 58 | 59 | insertion_line_number=$((line_number + 1)) 60 | release_link=$(echo "${changelog}" | head -n ${insertion_line_number} | tail -1) 61 | new_release_link=$(echo "${release_link}" | \ 62 | sed "s/${last_version}/${new_version}/g" | \ 63 | sed "s/v[0-9]\+.[0-9]\+.[0-9]\+/v${last_version}/") 64 | 65 | changelog=$(echo "${changelog}" | sed "${insertion_line_number} i \\ 66 | ${new_release_link} 67 | ") 68 | 69 | # Remove empty sections 70 | 71 | scripts_path="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" 72 | changelog=$(echo "${changelog}" | awk -f "${scripts_path}/remove-empty-sections-changelog.awk") 73 | 74 | # Insert a new [Unreleased] header 75 | 76 | changelog=$(echo "${changelog}" | sed "s/----/----\\ 77 | ${header_escaped}\\ 78 | /g") 79 | 80 | echo "${changelog}" 81 | -------------------------------------------------------------------------------- /cucumber-scala/src/main/scala/io/cucumber/scala/ScalaSnippet.scala: -------------------------------------------------------------------------------- 1 | package io.cucumber.scala 2 | 3 | import java.lang.reflect.Type 4 | import java.text.MessageFormat 5 | import java.util.{Map => JMap} 6 | import java.util.Optional 7 | 8 | import io.cucumber.core.backend.Snippet 9 | import io.cucumber.datatable.DataTable 10 | 11 | import scala.jdk.CollectionConverters._ 12 | 13 | object ScalaSnippet { 14 | 15 | // Allows to use """ in """xxx"""" strings 16 | val tripleDoubleQuotes = "\"\"\"" 17 | 18 | } 19 | 20 | class ScalaSnippet extends Snippet { 21 | 22 | import ScalaSnippet.tripleDoubleQuotes 23 | 24 | override def language(): Optional[String] = { 25 | Optional.of("scala") 26 | } 27 | 28 | override def template(): MessageFormat = { 29 | new MessageFormat( 30 | s"""{0}(${tripleDoubleQuotes}{1}${tripleDoubleQuotes}) '{' ({3}) => 31 | | // {4} 32 | | throw new ${classOf[PendingException].getName}() 33 | |'}'""".stripMargin 34 | ) 35 | } 36 | 37 | override def tableHint(): String = { 38 | """| // For automatic transformation, change DataTable to one of 39 | | // E, List