You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

146 lines
3.8 KiB

package green.thisfieldwas.embracingnondeterminism.data
import green.thisfieldwas.embracingnondeterminism.control.{ApplicativeLaws, MonadLaws}
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 OptionSpec extends AnyWordSpec with Matchers with Inside {
"Option" can {
"get" which {
"returns the value" when {
"the option is defined" in {
val option = Option(1)
option.get shouldBe 1
}
}
"faults" when {
"the option is not defined" in {
val option = Option[Int]()
inside(the[NoSuchElementException].thrownBy(option.get)) { case e =>
e.getMessage shouldBe "None.get"
}
}
}
}
"isSome" which {
"is true" when {
"the option is defined" in {
val option = Option(1)
option.isSome shouldBe true
}
}
"is false" when {
"the option is not defined" in {
val option = Option[Int]()
option.isSome shouldBe false
}
}
}
"isNone" when {
"is true" when {
"the option is not defined" in {
val option = Option[Int]()
option.isNone shouldBe true
}
}
"is false" when {
"the option is defined" in {
val option = Option(1)
option.isNone shouldBe false
}
}
}
"fold" which {
"'folds' both cases of absent or present instances of term `A` into a new `B`" when {
"the instance of term `A` is present" in {
Option(123).fold("absent")(_.toString) shouldBe "123"
}
"the instance of term `A` is absent" in {
Option[Int]().fold("absent")(_.toString) shouldBe "absent"
}
}
}
"orElse" which {
"picks the current option if it is non-empty" in {
Some(4).orElse(Some(5)) shouldBe Some(4)
Some(4).orElse(None) shouldBe Some(4)
}
"picks the other option if the current is empty" in {
None.orElse(Some(4)) shouldBe Some(4)
None.orElse(None) shouldBe None
}
}
}
"Option object" can {
"apply" which {
"creates a Some" when {
"given a value" in {
Option(1) shouldBe Some(1)
}
}
"creates a None" when {
"given no argument" in {
Option() shouldBe None
}
}
}
"cond" which {
"conditionally creates a Some" when {
"the condition is true" in {
Option.cond(test = true)("this will be lifted into a Some") shouldBe Some("this will be lifted into a Some")
}
"the condition is false" in {
Option.cond(test = false)("this will not be lifted") shouldBe None
}
}
}
"unapply" which {
"matches Some" in {
Some(1) match {
case Option(x) => x shouldBe 1
case _ => fail("unapply didn't match")
}
}
"doesn't match None" in {
None match {
case Option(_) => fail("unapply matched")
case _ =>
}
}
}
}
}
/** Check that `Option` conforms to typeclass laws.
*/
class OptionLaws extends Laws with FunctorLaws with ApplicativeLaws with MonadLaws {
/** From a generator for some type `A`, generate in roughly equal proportions
* a `None` of an absence of `A` or a `Some` of a presence of `A`.
*/
implicit val optionLiftedGen: LiftedGen[Option] = new LiftedGen[Option] {
override def lift[A](gen: Gen[A]): Gen[Option[A]] =
Gen.lzy(
Gen.oneOf(
Gen.const(None),
gen.map(Some(_)),
)
)
}
checkFunctorLaws[Option]()
checkApplicativeLaws[Option]()
checkMonadLaws[Option]()
}