embracing-nondeterminism-code/src/test/scala/green/thisfieldwas/embracingnondeterminism/data/ListSpec.scala

181 lines
4.8 KiB
Scala

package green.thisfieldwas.embracingnondeterminism.data
import green.thisfieldwas.embracingnondeterminism.control.ApplicativeLaws
import green.thisfieldwas.embracingnondeterminism.util._
import org.scalacheck.Gen
import org.scalatest.Inside
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class ListSpec extends AnyWordSpec with Matchers with Inside {
"List" can {
"head" which {
"faults" when {
"the list is empty" in {
inside(the[NoSuchElementException].thrownBy(Nil.head)) { case e: NoSuchElementException =>
e.getMessage shouldBe "Nil.head"
}
}
}
"gets the first element" when {
"the list is not empty" in {
List(1, 2, 3).head shouldBe 1
}
}
}
"tail" which {
"faults" when {
"list list is empty" in {
inside(the[NoSuchElementException].thrownBy(Nil.tail)) { case e: NoSuchElementException =>
e.getMessage shouldBe "Nil.tail"
}
}
}
"gets all but the first element" when {
"the list is not empty" in {
List(1, 2, 3).tail shouldBe List(2, 3)
}
}
}
"isEmpty" which {
"is true" when {
"the list is empty" in {
List[Int]().isEmpty shouldBe true
}
}
"is false" when {
"the list is not empty" in {
List(1, 2, 3).isEmpty shouldBe false
}
}
}
"foldLeft" which {
"returns the accumulator" when {
"the list is empty" in {
List[Int]().foldLeft(0)(_ + _) shouldBe 0
}
}
"applies the function" when {
"the list is non-empty" in {
List(1, 2, 3).foldLeft("$")(_ + _.toString) shouldBe "$123"
}
}
}
"foldRight" which {
"returns the accumulator" when {
"the list is empty" in {
List[Int]().foldRight(0)(_ + _) shouldBe 0
}
}
"applies the function" when {
"the list is non-empty" in {
List(1, 2, 3).foldRight("$")(_.toString + _) shouldBe "123$"
}
}
}
"reverse" which {
"reverses the order of instances in the list" in {
List(1, 2, 3, 4, 5).reverse shouldBe List(5, 4, 3, 2, 1)
}
}
"zip" which {
"zips two lists of equal length together" in {
List(1, 2, 3, 4).zip(List("a", "b", "c", "d")) shouldBe List((1, "a"), (2, "b"), (3, "c"), (4, "d"))
}
"zips two lists with the left list being shorter" in {
List(1, 2, 3).zip(List("a", "b", "c", "d")) shouldBe List((1, "a"), (2, "b"), (3, "c"))
}
"zips two lists with the right list being shorter" in {
List(1, 2, 3, 4).zip(List("a", "b", "c")) shouldBe List((1, "a"), (2, "b"), (3, "c"))
}
}
"::" which {
"prepends an element to the list" in {
0 :: List(1, 2, 3) shouldBe List(0, 1, 2, 3)
0 :: Nil shouldBe List(0)
}
}
"|:" which {
"creates a NonEmptyList" in {
0 |: List(1, 2, 3) shouldBe NonEmptyList(0, List(1, 2, 3))
0 |: Nil shouldBe NonEmptyList(0)
}
}
"toSeq" which {
"converts the List to a Seq" in {
List(1, 2, 3).toSeq shouldBe Seq(1, 2, 3)
}
}
}
"List object" can {
"apply" which {
"creates an empty list" when {
"given no arguments" in {
List[Int]() shouldBe Nil
}
}
"creates a list with elements" when {
"given arguments" in {
List(1, 2, 3) shouldBe (1 :: 2 :: 3 :: Nil)
}
}
}
"unapply" which {
"matches the length of a list" when {
"the list has no elements" in {
List[Int]() match {
case List() => succeed
case _ => fail("List should be empty")
}
}
"the list has one element" in {
List(1) match {
case List(x) => x shouldBe 1
case _ => fail("List should have one element")
}
}
}
}
}
}
/** Check that `List` conforms to typeclass laws.
*/
class ListLaws extends Laws with FunctorLaws with ApplicativeLaws {
// Applicative properties are multiplicative in time, so keep number of
// successes required to a minimum.
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
PropertyCheckConfiguration(minSuccessful = 10)
/** From a generator for some type `A`, generate a `List` of some length of
* `A`.
*/
implicit val listLiftedGen: LiftedGen[List] = new LiftedGen[List] {
override def lift[A](gen: Gen[A]): Gen[List[A]] =
for {
size <- Gen.sized(Gen.choose(0, _))
items <- Gen.listOfN(size, gen)
} yield List(items: _*)
}
checkFunctorLaws[List]()
checkApplicativeLaws[List]()
}