All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.timgroup.matchless.CollectionMatchers.scala Maven / Gradle / Ivy

The newest version!
package com.timgroup.matchless

import org.specs2.matcher.{ Matcher, Expectable }
import org.specs2.matcher.MustMatchers._
import scala.collection.GenTraversableOnce
import com.timgroup.matchless.utils.Bag

trait CollectionMatchers {
  def haveThePairs[K, V](pairs: (K, V)*) = PairsLikeMatcher(pairs.map(p => (p._1, beEqualTo(p._2))).toMap)
  def havePairsLike[K, V](pairs: (K, Matcher[V])*) = PairsLikeMatcher(pairs.toMap)
  def haveItemsLike[A](itemMatchers: Matcher[A]*) = ItemsLikeMatcher(itemMatchers.toList)
}

object CollectionMatchers extends CollectionMatchers

case class PairsLikeMatcher[K, V](pairMatchers: Map[K, Matcher[V]]) extends Matcher[Map[K, V]] {
  def apply[S <: Map[K, V]](s: Expectable[S]) = {
    val sharedKeys = pairMatchers.keySet.intersect(s.value.keySet)
    val missingKeys = pairMatchers.keySet.diff(s.value.keySet)

    val nonMatchingValues =
      pairMatchers.filterKeys(sharedKeys)
        .filter { case (key, matcher) => (s.value(key) must matcher).isFailure }
        .map { case (key, matcher) => (key, (s.value(key) must matcher).message) }
        .toMap

    val report = pairMatchers.keySet.map(key =>
      if (missingKeys.contains(key)) "* %s: ".format(key)
      else if (nonMatchingValues.contains(key)) "* %s: %s".format(key, nonMatchingValues(key))
      else "%s: %s".format(key, s.value(key))).mkString("\n")

    result(missingKeys.isEmpty && nonMatchingValues.isEmpty,
      report,
      "some of the expected key/value pairs were not present in the collection: \n\n" + report,
      s)
  }
}

case class ItemsLikeMatcher[A](matchers: Iterable[Matcher[A]]) extends Matcher[GenTraversableOnce[A]] {
  def apply[S <: GenTraversableOnce[A]](s: Expectable[S]) = {
    if (s.value.size < matchers.size) result(false, "", "Not enough items to find a unique match for every matcher", s)
    else {
      val matches = (for {
        value <- s.value.toList
        matcher <- matchers if (value must matcher).isSuccess
      } yield matcher -> value).groupBy(_._1).mapValues(_.map(_._2))
      if (matches.size < matchers.size) result(false, "", "Some of the expected items were not present in the collection", s)
      else {
        val search = matches.values.toList.sortBy(_.size).map(Bag(_))
        val uniqueMatches = findUnique(search.head, Bag.empty, search.tail)
        uniqueMatches match {
          case None => result(false, "", "No set of items in the collection uniquely matched the supplied matchers", s)
          case Some(matches) => result(true, "The items %s uniquely matched the supplied matchers".format(matches), "", s)
        }
      }
    }
  }

  def inOrder = ItemsLikeInOrderMatcher(matchers)

  private[this] def findUnique(current: Bag[A], claimed: Bag[A], remaining: List[Bag[A]]): Option[Bag[A]] =
    remaining match {
      case Nil => current.headOption.map(claimed + _)
      case l if l.exists(_.isEmpty) => None
      case h :: t =>
        if (current.isEmpty) None
        else {
          val x = current.head
          val xs = current - x
          findUnique(h - x, claimed + x, t.map(_ - x))
            .orElse(findUnique(xs, claimed, remaining))
        }
    }
}

case class ItemsLikeInOrderMatcher[A](matchers: Iterable[Matcher[A]]) extends Matcher[GenTraversableOnce[A]] {
  def apply[S <: GenTraversableOnce[A]](s: Expectable[S]) = {
    def checkMatchers(matchers: Iterable[Matcher[A]], values: GenTraversableOnce[A]): Boolean =
      matchers match {
        case Nil => true
        case m :: ms => values match {
          case Nil => false
          case v :: vs => if ((v must m).isSuccess) checkMatchers(ms, vs) else checkMatchers(matchers, vs)
        }
      }
    result(checkMatchers(matchers, s.value),
      "The items %s matched the supplied matchers in order",
      "Some of the expected items were not present in the collection",
      s)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy