Skip to content

Commit

Permalink
feature: replace the deprecated Traversable with Iterable
Browse files Browse the repository at this point in the history
  • Loading branch information
etorreborre committed Nov 4, 2023
1 parent ea110a3 commit 28779be
Show file tree
Hide file tree
Showing 11 changed files with 52 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package org.specs2.specification.mutable
import org.specs2.concurrent.ExecutionEnv
import org.specs2.execute.Result
import org.specs2.main.ArgumentsShortcuts
import org.specs2.matcher.{Matcher, TraversableMatchers, TypedEqual, ValueCheck}
import org.specs2.matcher.{Matcher, TypedEqual, ValueCheck}
import org.specs2.matcher.IterableMatchers
import org.specs2.specification.core.{Description, Fragment, SpecStructure, StacktraceLocation}
import org.specs2.specification.create.DefaultFragmentFactory.*
import org.specs2.specification.dsl.mutable.{ArgumentsCreation, MutableDsl, MutableFragmentBuilder}
import org.specs2.matcher.Matcher.{given}

class MutableFragmentsDslSpec(ee: ExecutionEnv) extends org.specs2.Spec with TypedEqual with TraversableMatchers {
class MutableFragmentsDslSpec(ee: ExecutionEnv) extends org.specs2.Spec with TypedEqual with IterableMatchers {
def is = s2"""

create examples
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ When creating expectations:
Use should for expectations | `org.specs2.matcher.ShouldMatchers` |
Describe expectations with `==>` | `org.specs2.matcher.ExpectationsDescription` |
Describe expectations with `aka` and must | `org.specs2.matcher.MustExpectations` |
Use `list must have size(3)` | `org.specs2.matcher.TraversableMatchers` |
Use `list must have size(3)` | `org.specs2.matcher.IterableMatchers` |
Use matchers in `contain` or `beSome` matchers | `org.specs2.matcher.ValueChecks` |
Use `===`, `====` to check for equality | `org.specs2.matcher.TypedEqual` |
Create matchers from functions | `org.specs2.matcher.MatcherCreation` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package org.specs2
package guide
package matchers

object TraversableMatchers extends UserGuideCard {
def title = "Traversable"
object IterableMatchers extends UserGuideCard {
def title = "Iterable"
def text = s2"""
Traversables can be checked with several matchers. If you want to check the size of a `Traversable`
Iterables can be checked with several matchers. If you want to check the size of a `Iterable`

* check if it is empty
${snippet{ Seq() must beEmpty }}
Expand All @@ -21,7 +21,7 @@ Traversables can be checked with several matchers. If you want to check the size

#### Check each element individually

Then you can check the elements which are contained in the Traversable
Then you can check the elements which are contained in the Iterable

* if a simple value is contained
${snippet{ Seq(1, 2, 3) must contain(2) }}
Expand Down Expand Up @@ -52,7 +52,7 @@ For each of the check above you can indicate how many times the check should be

#### Check all elements

The other types of checks involve comparing the Traversable elements to other elements (values, matchers, function returning a `Result`)
The other types of checks involve comparing the Iterable elements to other elements (values, matchers, function returning a `Result`)

* with a set of values
${snippet{ Seq(1, 2, 3, 4) must contain(2, 4) }}
Expand All @@ -71,20 +71,20 @@ ${snippet{ Seq(1) must contain(allOf(1, 1)).onDistinctValues }}
The `eachOf` method does the same thing (and this example will fail as well):
${snippet{ Seq(1) must contain(eachOf(1, 1)) }}

Another frequent use of Traversable matchers is to check if the Traversable have the right number of elements. For this you can use:
Another frequent use of Iterable matchers is to check if the Iterable have the right number of elements. For this you can use:

* `atLeast`, which is actually another name for `allOf`, where the traversable can contain more elements than required
* `atLeast`, which is actually another name for `allOf`, where the iterable can contain more elements than required
${snippet{ Seq(1, 2, 3, 4) must contain(atLeast(2, 4)) }}

* `atMost` where the traversable can not contain more elements than required
* `atMost` where the iterable can not contain more elements than required
${snippet{ Seq(2, 3) must contain(atMost(2, 3, 4)) }}

* `exactly` where the traversable must contain exactly the specified number of elements
* `exactly` where the iterable must contain exactly the specified number of elements
${snippet{ Seq(1, 2) must contain(exactly(2, 1)) }}

The `atLeast/atMost/exactly` operators work on distinct values by default (because this is easier for counting the correspondence between actual values and expected ones). However you can use `onDistinctValues(false)` if you don't care.

Finally, if you want to get the differences between 2 traversables:
Finally, if you want to get the differences between 2 iterables:

${snippet{ Seq(2, 4, 1) must containTheSameElementsAs(Seq(1, 4, 2)) }}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object MatcherCards extends Cards {
def title = "Specification matchers"
def cards = Seq(
StringMatchers,
TraversableMatchers,
IterableMatchers,
NumericMatchers,
OptionEitherMatchers,
TryMatchers,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,5 @@ private[specs2] trait LinesContentBaseMatchers extends DifferenceFilters with Ex
*/
private[specs2] trait SeqsContents:
// default implementation for reading seq lines
implicit protected def seqContentForMatchers[T, CC[_] <: Traversable[?]]: LinesContent[CC[T]] =
implicit protected def seqContentForMatchers[T, CC[_] <: Iterable[?]]: LinesContent[CC[T]] =
SeqLinesContent[T, CC]()
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import execute.ResultImplicits.*
trait FilesContentMatchers
extends FileMatchers
with LinesContentMatchers
with TraversableMatchers
with IterableMatchers
with ExpectationsCreation:
/** check that all the paths in `expectedDir` are the same as the ones in `actualDir`
*/
Expand Down
2 changes: 1 addition & 1 deletion matcher/js/src/main/scala/org/specs2/matcher/Machers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package matcher
*/
trait Matchers
extends AnyMatchers
with TraversableMatchers
with IterableMatchers
with MapMatchers
with StringMatchers
with ExceptionMatchers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package matcher
*/
trait Matchers
extends AnyMatchers
with TraversableMatchers
with IterableMatchers
with MapMatchers
with StringMatchers
with ExceptionMatchers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ import text.Regexes.*
import text.Plural.*
import text.NotNullStrings.*

import scala.collection.Traversable
import scala.collection.Iterable
import scala.annotation.tailrec
import execute.*
import ValueChecks.{given, *}
import StringMatchers.{given, *}
import describe.Diffable
import Result.*

/** Matchers for traversables
/** Matchers for iterables
*/
trait TraversableMatchers extends TraversableBaseMatchers with TraversableBaseMatchersLowImplicits with NumberOfTimes
trait IterableMatchers extends TraversableBaseMatchers with TraversableBaseMatchersLowImplicits with NumberOfTimes

object TraversableMatchers extends TraversableMatchers
object IterableMatchers extends IterableMatchers

trait TraversableBaseMatchers:
outer =>
Expand All @@ -41,30 +41,30 @@ trait TraversableBaseMatchers:
def atLeast[T](checks: ValueCheck[T]*): ContainWithResultSeq[T] = new ContainWithResultSeq(checks).atLeast
def atMost[T](checks: ValueCheck[T]*): ContainWithResultSeq[T] = new ContainWithResultSeq(checks).atMost

/** match if a traversable contains all the elements of seq (and maybe more) */
/** match if an iterable contains all the elements of seq (and maybe more) */
def containAllOf[T: Diffable](seq: Seq[T]) =
contain(atLeast(seq.map(v => valueIsTypedValueCheck(v))*))

/** match if a traversable contains one of (t1, t2) */
/** match if an iterable contains one of (t1, t2) */
def containAnyOf[T](seq: Seq[T]): ContainWithResult[T] =
contain(new BeOneOf(seq))

/** match if traversable contains (x matches .*+t+.*) */
def containMatch[T](s: =>String): Matcher[Traversable[T]] =
/** match if iterable contains (x matches .*+t+.*) */
def containMatch[T](s: =>String): Matcher[Iterable[T]] =
containPattern(s.regexPart)

/** match if traversable contains (x matches p) */
def containPattern[T, S: MatchingExpression](s: S): Matcher[Traversable[T]] =
/** match if iterable contains (x matches p) */
def containPattern[T, S: MatchingExpression](s: S): Matcher[Iterable[T]] =
contain(atLeast(ValueChecks.matcherIsValueCheck(beMatching(s)))) ^^ (_.map(_.toString))

/** does a containAll comparison in both ways */
def containTheSameElementsAs[T](
seq: Seq[T],
equality: (T, T) => Boolean = (_: T) == (_: T)
): Matcher[Traversable[T]] =
new Matcher[Traversable[T]]:
): Matcher[Iterable[T]] =
new Matcher[Iterable[T]]:

def apply[S <: Traversable[T]](t: Expectable[S]) =
def apply[S <: Iterable[T]](t: Expectable[S]) =
val missing = seq.difference(t.value.toSeq, equality)
val added = t.value.toSeq.difference(seq, equality)
def message(diffs: scala.collection.Seq[?], msg: String) =
Expand Down Expand Up @@ -425,21 +425,21 @@ private[specs2] trait TraversableBaseMatchersLowImplicits extends ValueChecksLow
seq.map(matcherIsValueCheck[T])

class SizedMatcher[T: Sized](n: Int, sizeWord: String) extends Matcher[T]:
def apply[S <: T](traversable: Expectable[S]) =
def apply[S <: T](iterable: Expectable[S]) =
val s = implicitly[Sized[T]]
val valueSize = s.size(traversable.value)
result(valueSize == n, s"'${traversable.description}' doesn't have $sizeWord $n but $sizeWord $valueSize")
val valueSize = s.size(iterable.value)
result(valueSize == n, s"'${iterable.description}' doesn't have $sizeWord $n but $sizeWord $valueSize")

class SizedCheckedMatcher[T: Sized](check: ValueCheck[Int], sizeWord: String) extends Matcher[T]:
def apply[S <: T](traversable: Expectable[S]) =
def apply[S <: T](iterable: Expectable[S]) =
val s = implicitly[Sized[T]]
val valueSize = s.size(traversable.value)
val valueSize = s.size(iterable.value)
val checked = check.check(valueSize)
result(checked.isSuccess, traversable.description + " doesn't have the right " + sizeWord + ": " + checked.message)
result(checked.isSuccess, iterable.description + " doesn't have the right " + sizeWord + ": " + checked.message)

class OrderingMatcher[T: Ordering] extends Matcher[Seq[T]]:
def apply[S <: Seq[T]](traversable: Expectable[S]) =
result(traversable.value == traversable.value.sorted, traversable.description + " is not sorted")
def apply[S <: Seq[T]](iterable: Expectable[S]) =
result(iterable.value == iterable.value.sorted, iterable.description + " is not sorted")

import control.NumberOfTimes.*
import text.Plural.*
Expand All @@ -450,9 +450,9 @@ case class ContainWithResult[T](
timesMax: Option[Times] = None,
checkAll: Boolean = true,
negate: Boolean = false
) extends Matcher[Traversable[T]]:
) extends Matcher[Iterable[T]]:

def apply[S <: Traversable[T]](t: Expectable[S]) =
def apply[S <: Iterable[T]](t: Expectable[S]) =
val seq = Vector(t.value.toSeq*)

// stop after the first failure if !checkAll
Expand Down Expand Up @@ -506,7 +506,7 @@ case class ContainWithResult[T](
def forall = copy(timesMin = None, timesMax = None, checkAll = false)
def foreach = copy(timesMin = None, timesMax = None)

private def messages[S <: Traversable[T]](expectable: String, successes: Seq[Result], failures: Seq[Result]) =
private def messages[S <: Iterable[T]](expectable: String, successes: Seq[Result], failures: Seq[Result]) =
def equalValueCheckMessages(expected: Any) =
val containsMessage = s"$expectable contains $expected"
val doesNotContainMessage = s"$expectable does not contain $expected"
Expand Down Expand Up @@ -546,9 +546,9 @@ case class ContainWithResultSeq[T](
eachCheck: Boolean = false,
checkOrder: Boolean = false,
negate: Boolean = false
) extends Matcher[Traversable[T]]:
) extends Matcher[Iterable[T]]:

def apply[S <: Traversable[T]](t: Expectable[S]) =
def apply[S <: Iterable[T]](t: Expectable[S]) =
val seq = t.value.toSeq

// results for each element, either checked in order or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import scala.jdk.CollectionConverters.*
import org.specs2.specification.core.{Env, OwnEnv}

// format: off
class TraversableMatchersSpec(val env: Env) extends Specification with ResultMatchers with MustMatchers with OwnEnv {
class IterableMatchersSpec(val env: Env) extends Specification with ResultMatchers with MustMatchers with OwnEnv {
def is = s2"""

We can check the elements of a collection by using matchers
Expand Down Expand Up @@ -137,15 +137,15 @@ class TraversableMatchersSpec(val env: Env) extends Specification with ResultMat
${(Seq(1, 2, 3) must not(contain(atLeast(1, 2, 3, 4, 5))))}
${(Seq(1, 2, 3) must not(contain(atMost(1, 2))))}

we can check if a traversable contains elements following a given pattern
we can check if an iterable contains elements following a given pattern
${Seq("Hello", "World") must containMatch("ll")}
${Seq("Hello", "World") must containPattern(".*llo")}


Size
===

We can check the size of an traversable
We can check the size of an iterable
${Seq() must beEmpty}
${(Seq() must not(beEmpty)) must beFailing}
${Seq(1, 2) must haveSize(2)}
Expand All @@ -167,10 +167,10 @@ class TraversableMatchersSpec(val env: Env) extends Specification with ResultMat
//{ Seq(2, 1, 3) must not(beSorted) }
${(Seq(2, 1, 3) must beSorted) returns "List(2, 1, 3) is not sorted"}

Compare to another traversable
Compare to another iterable
==============================

We can check if 2 traversables are contained in each other
We can check if 2 iterables are contained in each other
${List("1", "2") must containTheSameElementsAs(Seq("2", "1"))}
${{ List("1", "2", "3") must containTheSameElementsAs(Seq("2", "4", "1")) } returns Seq(
"List(1, 2, 3)",
Expand All @@ -193,7 +193,7 @@ class TraversableMatchersSpec(val env: Env) extends Specification with ResultMat
With Java collections
=====================

Java collections can also be used with Traversable matchers but generally require explicit conversion
Java collections can also be used with Iterable matchers but generally require explicit conversion
${asList("Hello", "World") must haveSize(2)}
${asList("Hello", "World").asScala must containMatch("ll")}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class StringMatchersSpec
extends Spec
with StringMatchers
with MustExpectations
with TraversableMatchers
with IterableMatchers
with AnyMatchers {
def is = s2"""

Expand Down

0 comments on commit 28779be

Please sign in to comment.