Skip to content

Commit c877ddd

Browse files
authored
Merge pull request #100 from cucumber/v6.2
Merge new features for v6.2
2 parents b386a6c + c72306b commit c877ddd

16 files changed

+421
-60
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ See also the [CHANGELOG](https://github.com/cucumber/cucumber-jvm/blob/master/CH
1111

1212
### Added
1313

14+
- Add `asScalaRawList[T]`, `asScalaRawMaps[T]` and `asScalaRawLists[T]` on `DataTable` (through `io.cucumber.scala.Implicits`) ([#83](https://github.com/cucumber/cucumber-jvm-scala/issues/83) Gaël Jourdan-Weil)
15+
- Add new `DataTableType` definitions with optional input values ([#84](https://github.com/cucumber/cucumber-jvm-scala/issues/84) Gaël Jourdan-Weil)
16+
- `DataTableType { (entry: Map[String, Option[String]]) => ... }`
17+
- `DataTableType { (row: Seq[Option[String]]) => ... }`
18+
- `DataTableType { (cell: Option[String]) => ... }`
19+
1420
### Changed
1521

1622
- [Core] Update `cucumber-core` dependency to [6.2.0](https://github.com/cucumber/cucumber-jvm/blob/main/CHANGELOG.md)

docs/datatables.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Cucumber Scala support DataTables with either:
88

99
See below the exhaustive list of possible mappings.
1010

11+
_Note: you can use [transformers](transformers.md) to map DataTables to custom types._
12+
1113
## As Map of Map
1214

1315
```gherkin

docs/transformers.md

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ This can be achieved in different ways:
8787
- transform cells content to any type
8888

8989
Note that DataTables in Gherkin can not represent `null` or the empty string unambiguously.
90-
Cucumber will interpret empty cells as `null`.
90+
Cucumber will interpret empty cells as `None` or `null`.
9191
But you can use a replacement to represent empty strings.
9292
See below.
9393

@@ -99,8 +99,8 @@ For instance, the following transformer can be defined:
9999
```scala
100100
case class Author(name: String, surname: String, famousBook: String)
101101

102-
DataTableType { entry: Map[String, String] =>
103-
Author(entry("name"), entry("surname"), entry("famousBook"))
102+
DataTableType { entry: Map[String, Option[String]] => // Or Map[String, String]
103+
Author(entry("name").getOrElse("NoValue"), entry("surname").getOrElse("NoValue"), entry("famousBook").getOrElse("NoValue"))
104104
}
105105
```
106106

@@ -113,13 +113,15 @@ Given the following authors
113113
```
114114

115115
```scala
116-
Given("the following authors") { (authors: java.util.List[Author]) =>
117-
// Do something
118-
}
116+
import io.cucumber.scala.Implicits._
119117

120-
// Or using DataTable
121118
Given("the following authors") { (table: DataTable) =>
122-
val authors = table.asList[Author](classOf[Author])
119+
val authors = table.asScalaRawList[Author]
120+
}
121+
122+
// Or using Java type
123+
Given("the following authors") { (authors: java.util.List[Author]) =>
124+
// Do something
123125
}
124126
```
125127

@@ -129,8 +131,8 @@ For instance, the following transformer can be defined:
129131
```scala
130132
case class Author(name: String, surname: String, famousBook: String)
131133

132-
DataTableType { row: Seq[String] =>
133-
Author(row(0), row(1), row(2))
134+
DataTableType { row: Seq[Option[String]] => // Or Seq[String]
135+
Author(row(0).getOrElse("NoValue"), row(1).getOrElse("NoValue"), row(2).getOrElse("NoValue"))
134136
}
135137
```
136138

@@ -142,20 +144,24 @@ Given the following authors
142144
```
143145

144146
```scala
145-
Given("the following authors") { (authors: java.util.List[Author]) =>
146-
// Do something
147-
}
147+
import io.cucumber.scala.Implicits._
148148

149-
// Or using DataTable
150149
Given("the following authors") { (table: DataTable) =>
151-
val authors = table.asList[Author](classOf[Author])
150+
val authors = table.asScalaRawList[Author]
151+
}
152+
153+
// Or using Java types
154+
Given("the following authors") { (authors: java.util.List[Author]) =>
155+
// Do something
152156
}
153157
```
154158

155159
### DataTable
156160

157161
For instance, the following transformer can be defined:
158162
```scala
163+
import io.cucumber.scala.Implicits._
164+
159165
case class Author(name: String, surname: String, famousBook: String)
160166
case class GroupOfAuthor(authors: Seq[Author])
161167

@@ -191,8 +197,8 @@ For instance, the following transformer can be defined:
191197
```scala
192198
case class RichCell(content: String)
193199

194-
DataTableType { cell: String =>
195-
RichCell(cell)
200+
DataTableType { cell: Option[String] => // Or String
201+
RichCell(cell.getOrElse("NoValue"))
196202
}
197203
```
198204

@@ -204,13 +210,15 @@ Given the following authors
204210
```
205211

206212
```scala
207-
Given("the following authors") { (authors: java.util.List[java.util.List[RichCell]]) =>
208-
// Do something
209-
}
213+
import io.cucumber.scala.Implicits._
210214

211-
// Or using DataTable
212215
Given("the following authors") { (table: DataTable) =>
213-
val authors = table.asLists[RichCell](classOf[RichCell]))
216+
val authors = table.asScalaRawLists[RichCell]
217+
}
218+
219+
// Or using Java types
220+
Given("the following authors") { (authors: java.util.List[java.util.List[RichCell]]) =>
221+
// Do something
214222
}
215223
```
216224

@@ -223,19 +231,21 @@ Given the following authors
223231
```
224232

225233
```scala
226-
Given("the following authors") { (authors: java.util.List[java.util.Map[String, RichCell]]) =>
227-
// Do something
228-
}
234+
import io.cucumber.scala.Implicits._
229235

230-
// Or using DataTable
231236
Given("the following authors") { (table: DataTable) =>
232-
val authors = table.asMaps[String, RichCell](classOf[String], classOf[RichCell])
237+
val authors = table.asScalaRawMaps[String, RichCell]
238+
}
239+
240+
// Or with Java Types
241+
Given("the following authors") { (authors: java.util.List[java.util.Map[String, RichCell]]) =>
242+
// Do something
233243
}
234244
```
235245

236246
### Empty values
237247

238-
By default empty values in DataTable are treated as `null` by Cucumber.
248+
By default empty values in DataTable are treated as `None` or `null` by Cucumber.
239249
If you need to have empty values, you can define a replacement like `[empty]` that will be automatically replaced to empty when parsing DataTable.
240250

241251
To do so, you can add a parameter to a `DataTableType` definition.
@@ -244,7 +254,7 @@ For instance, with the following definition:
244254
```scala
245255
case class Author(name: String, surname: String, famousBook: String)
246256

247-
DataTableType("[empty]") { (entry: Map[String, String]) =>
257+
DataTableType("[empty]") { (entry: Map[String, String]) => // Or Map[String, Option[String]]
248258
Author(entry("name"), entry("surname"), entry("famousBook"))
249259
}
250260
```
@@ -296,13 +306,15 @@ DefaultDataTableEntryTransformer("[empty]") { (fromValue: Map[String, String], t
296306

297307
Will be used to convert with such step definitions:
298308
```scala
299-
Given("A step with a datatable") { (rows: java.util.List[SomeType]) =>
300-
// Do something
301-
}
309+
import io.cucumber.scala.Implicits._
302310

303-
// Or DataTable
304311
Given("A step with a datatable") { (dataTable: DataTable) =>
305-
val table = dataTable.asList[SomeType](classOf[SomeType])
312+
val table = dataTable.asScalaRawList[SomeType]
313+
}
314+
315+
// Or with Java types
316+
Given("A step with a datatable") { (rows: java.util.List[SomeType]) =>
317+
// Do something
306318
}
307319
```
308320

@@ -319,12 +331,14 @@ DefaultDataTableCellTransformer("[empty]") { (fromValue: String, toValueType: ja
319331

320332
Will be used to convert with such step definitions:
321333
```scala
322-
Given("A step with a datatable") { (rows: java.util.List[java.util.List[SomeType]]) =>
323-
// Do something
324-
}
334+
import io.cucumber.scala.Implicits._
325335

326-
// Or DataTable
327336
Given("A step with a datatable") { (dataTable: DataTable) =>
328-
val table = dataTable.asLists[SomeType](classOf[SomeType])
337+
val table = dataTable.asScalaRawLists[SomeType]
338+
}
339+
340+
// Or with Java Types
341+
Given("A step with a datatable") { (rows: java.util.List[java.util.List[SomeType]]) =>
342+
// Do something
329343
}
330344
```

scala/scala_2.11/src/main/scala/io/cucumber/scala/package.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,25 @@ package object scala {
2121
override def transform(entry: Map[String, String]): T = f.apply(entry)
2222
}
2323

24+
implicit def function1AsDataTableOptionalEntryDefinitionBody[T](f: (Map[String, Option[String]]) => T): DataTableOptionalEntryDefinitionBody[T] = new DataTableOptionalEntryDefinitionBody[T] {
25+
override def transform(entry: Map[String, Option[String]]): T = f.apply(entry)
26+
}
2427

2528
implicit def function1AsDataTableRowDefinitionBody[T](f: (Seq[String]) => T): DataTableRowDefinitionBody[T] = new DataTableRowDefinitionBody[T] {
2629
override def transform(row: Seq[String]): T = f.apply(row)
2730
}
2831

32+
implicit def function1AsDataTableOptionalRowDefinitionBody[T](f: (Seq[Option[String]]) => T): DataTableOptionalRowDefinitionBody[T] = new DataTableOptionalRowDefinitionBody[T] {
33+
override def transform(row: Seq[Option[String]]): T = f.apply(row)
34+
}
2935

3036
implicit def function1AsDataTableCellDefinitionBody[T](f: (String) => T): DataTableCellDefinitionBody[T] = new DataTableCellDefinitionBody[T] {
3137
override def transform(cell: String): T = f.apply(cell)
3238
}
3339

40+
implicit def function1AsDataTableOptionalCellDefinitionBody[T](f: (Option[String]) => T): DataTableOptionalCellDefinitionBody[T] = new DataTableOptionalCellDefinitionBody[T] {
41+
override def transform(cell: Option[String]): T = f.apply(cell)
42+
}
3443

3544
implicit def function1AsDataTableDefinitionBody[T](f: (DataTable) => T): DataTableDefinitionBody[T] = new DataTableDefinitionBody[T] {
3645
override def transform(dataTable: DataTable): T = f.apply(dataTable)

scala/sources/src/main/scala/io/cucumber/scala/DataTableDefinitionBody.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,36 @@ trait DataTableEntryDefinitionBody[T] {
88

99
}
1010

11+
trait DataTableOptionalEntryDefinitionBody[T] {
12+
13+
def transform(entry: Map[String, Option[String]]): T
14+
15+
}
16+
1117
trait DataTableRowDefinitionBody[T] {
1218

1319
def transform(row: Seq[String]): T
1420

1521
}
1622

23+
trait DataTableOptionalRowDefinitionBody[T] {
24+
25+
def transform(row: Seq[Option[String]]): T
26+
27+
}
28+
1729
trait DataTableCellDefinitionBody[T] {
1830

1931
def transform(cell: String): T
2032

2133
}
2234

35+
trait DataTableOptionalCellDefinitionBody[T] {
36+
37+
def transform(cell: Option[String]): T
38+
39+
}
40+
2341
trait DataTableDefinitionBody[T] {
2442

2543
def transform(dataTable: DataTable): T

scala/sources/src/main/scala/io/cucumber/scala/Implicits.scala

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ object Implicits {
2323
* Provides a view of the DataTable as a sequence of rows, each row being a key-value map where key is the column name.
2424
* Equivalent of `.asMaps[K,V](classOf[K], classOf[V])` but returned as Scala collection types without `null` values.
2525
*
26+
* See also `asScalaRawMaps[T]` if you don't need `Option`s (for instance if you are using a DataTableType).
27+
*
2628
* @tparam K key type
2729
* @tparam V value type
2830
* @return sequence of rows
@@ -42,6 +44,23 @@ object Implicits {
4244
*/
4345
def asScalaMaps: Seq[Map[String, Option[String]]] = asScalaMaps[String, String]
4446

47+
/**
48+
* Provides a view of the DataTable as a sequence of rows, each row being a key-value map where key is the column name.
49+
* Equivalent of `.asMaps[K,V](classOf[K], classOf[V])` but returned as Scala collection types.
50+
*
51+
* See also `asScalaMaps[T]`.
52+
*
53+
* @tparam K key type
54+
* @tparam V value type
55+
* @return sequence of rows
56+
*/
57+
def asScalaRawMaps[K, V](implicit evK: ClassTag[K], evV: ClassTag[V]): Seq[Map[K, V]] = {
58+
table.asMaps[K, V](evK.runtimeClass, evV.runtimeClass)
59+
.asScala
60+
.map(_.asScala.toMap)
61+
.toSeq
62+
}
63+
4564
/**
4665
* Provides a view of the DataTable as a key-value map where key are the first column values.
4766
* Equivalent of `.asMap[K,V](classOf[K],classOf[V])` but returned as Scala collection types without `null` values.
@@ -61,6 +80,8 @@ object Implicits {
6180
* Provides a view of the DataTable as a matrix.
6281
* Equivalent of `.asLists[T](classOf[T])` but returned as Scala collection types without `null` values.
6382
*
83+
* See also `asScalaRawLists[T]` if you don't need `Option`s (for instance if you are using a DataTableType).
84+
*
6485
* @tparam T cell type
6586
* @return matrix
6687
*/
@@ -79,10 +100,28 @@ object Implicits {
79100
*/
80101
def asScalaLists: Seq[Seq[Option[String]]] = asScalaLists[String]
81102

103+
/**
104+
* Provides a view of the DataTable as a matrix.
105+
* Equivalent of `.asLists[T](classOf[T])` but returned as Scala collection types.
106+
*
107+
* See also `asScalaLists[T]`
108+
*
109+
* @tparam T cell type
110+
* @return matrix
111+
*/
112+
def asScalaRawLists[T](implicit ev: ClassTag[T]): Seq[Seq[T]] = {
113+
table.asLists[T](ev.runtimeClass)
114+
.asScala
115+
.map(_.asScala.toSeq)
116+
.toSeq
117+
}
118+
82119
/**
83120
* Provides a view of the DataTable as a simple list of values.
84121
* Equivalent of `.asList[T](classOf[T])` but returned as Scala collection types without `null` values.
85122
*
123+
* See also `asScalaRawList[T]` if you don't need `Option`s (for instance if you are using a DataTableType).
124+
*
86125
* @tparam T cell type
87126
* @return list of values
88127
*/
@@ -101,6 +140,21 @@ object Implicits {
101140
*/
102141
def asScalaList: Seq[Option[String]] = asScalaList[String]
103142

143+
/**
144+
* Provides a view of the DataTable as a simple list of values.
145+
* Equivalent of `.asList[T](classOf[T])` but returned as Scala collection types.
146+
*
147+
* See also `asScalaList[T]`.
148+
*
149+
* @tparam T cell/row type
150+
* @return list of values
151+
*/
152+
def asScalaRawList[T](implicit ev: ClassTag[T]): Seq[T] = {
153+
table.asList[T](ev.runtimeClass)
154+
.asScala
155+
.toSeq
156+
}
157+
104158
/**
105159
* Provides a view of the DataTable as a full table: a key-value map of row where keys are the first column values
106160
* and each row being itself a key-value map where key is the column name.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.cucumber.scala
2+
3+
import io.cucumber.core.backend.ScenarioScoped
4+
import io.cucumber.datatable.{DataTableType, TableCellTransformer}
5+
6+
trait ScalaDataTableOptionalCellDefinition[T] extends ScalaDataTableTypeDefinition {
7+
8+
val details: ScalaDataTableOptionalCellTypeDetails[T]
9+
10+
override val emptyPatterns: Seq[String] = details.emptyPatterns
11+
12+
override val location: StackTraceElement = new Exception().getStackTrace()(3)
13+
14+
private val transformer: TableCellTransformer[T] = (cell: String) => {
15+
details.body.transform(Option(replaceEmptyPatternsWithEmptyString(cell)))
16+
}
17+
18+
override val dataTableType = new DataTableType(details.tag.runtimeClass, transformer)
19+
20+
}
21+
22+
class ScalaScenarioScopedDataTableOptionalCellDefinition[T](override val details: ScalaDataTableOptionalCellTypeDetails[T]) extends ScalaDataTableOptionalCellDefinition[T] with ScenarioScoped {
23+
}
24+
25+
class ScalaGlobalDataTableOptionalCellDefinition[T](override val details: ScalaDataTableOptionalCellTypeDetails[T]) extends ScalaDataTableOptionalCellDefinition[T] {
26+
}

0 commit comments

Comments
 (0)