use crate::{
description::Description,
matcher::{Describable, Matcher, MatcherResult},
matchers::containers::{OwnedItems, RefItems},
};
use std::{fmt::Debug, marker::PhantomData};
pub fn contains<InnerMatcherT, ModeT>(
inner: InnerMatcherT,
) -> ContainsMatcher<InnerMatcherT, ModeT> {
ContainsMatcher { inner, count: None, phantom: Default::default() }
}
pub struct ContainsMatcher<InnerMatcherT, ModeT> {
inner: InnerMatcherT,
count: Option<Box<dyn Matcher<usize>>>,
phantom: PhantomData<ModeT>,
}
impl<InnerMatcherT, ModeT> ContainsMatcher<InnerMatcherT, ModeT> {
pub fn times(mut self, count: impl Matcher<usize> + 'static) -> Self {
self.count = Some(Box::new(count));
self
}
}
impl<T: Debug, InnerMatcherT: Matcher<T>, ContainerT: Debug + ?Sized> Matcher<ContainerT>
for ContainsMatcher<InnerMatcherT, OwnedItems>
where
for<'a> &'a ContainerT: IntoIterator<Item = T>,
{
fn matches(&self, actual: &ContainerT) -> MatcherResult {
if let Some(count) = &self.count {
count.matches(&self.count_matches_owned(actual))
} else {
for v in actual.into_iter() {
if self.inner.matches(&v).into() {
return MatcherResult::Match;
}
}
MatcherResult::NoMatch
}
}
fn explain_match(&self, actual: &ContainerT) -> Description {
let count = self.count_matches_owned(actual);
match (count, &self.count) {
(_, Some(_)) => format!("which contains {} matching elements", count).into(),
(0, None) => "which does not contain a matching element".into(),
(_, None) => "which contains a matching element".into(),
}
}
}
impl<T: Debug, InnerMatcherT: Matcher<T>, ContainerT: Debug + ?Sized> Matcher<ContainerT>
for ContainsMatcher<InnerMatcherT, RefItems>
where
for<'a> &'a ContainerT: IntoIterator<Item = &'a T>,
{
fn matches(&self, actual: &ContainerT) -> MatcherResult {
if let Some(count) = &self.count {
count.matches(&self.count_matches_ref(actual))
} else {
for v in actual.into_iter() {
if self.inner.matches(v).into() {
return MatcherResult::Match;
}
}
MatcherResult::NoMatch
}
}
fn explain_match(&self, actual: &ContainerT) -> Description {
let count = self.count_matches_ref(actual);
match (count, &self.count) {
(_, Some(_)) => format!("which contains {} matching elements", count).into(),
(0, None) => "which does not contain a matching element".into(),
(_, None) => "which contains a matching element".into(),
}
}
}
impl<InnerMatcherT: Describable, ModeT> Describable for ContainsMatcher<InnerMatcherT, ModeT> {
fn describe(&self, matcher_result: MatcherResult) -> Description {
match (matcher_result, &self.count) {
(MatcherResult::Match, Some(count)) => format!(
"contains n elements which {}\n where n {}",
self.inner.describe(MatcherResult::Match),
count.describe(MatcherResult::Match)
)
.into(),
(MatcherResult::NoMatch, Some(count)) => format!(
"doesn't contain n elements which {}\n where n {}",
self.inner.describe(MatcherResult::Match),
count.describe(MatcherResult::Match)
)
.into(),
(MatcherResult::Match, None) => format!(
"contains at least one element which {}",
self.inner.describe(MatcherResult::Match)
)
.into(),
(MatcherResult::NoMatch, None) => {
format!("contains no element which {}", self.inner.describe(MatcherResult::Match))
.into()
}
}
}
}
impl<InnerMatcherT, ModeT> ContainsMatcher<InnerMatcherT, ModeT> {
fn count_matches_ref<T: Debug, ContainerT: ?Sized>(&self, actual: &ContainerT) -> usize
where
for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
InnerMatcherT: Matcher<T>,
{
let mut count = 0;
for v in actual.into_iter() {
if self.inner.matches(v).into() {
count += 1;
}
}
count
}
fn count_matches_owned<T: Debug, ContainerT: ?Sized>(&self, actual: &ContainerT) -> usize
where
for<'b> &'b ContainerT: IntoIterator<Item = T>,
InnerMatcherT: Matcher<T>,
{
let mut count = 0;
for v in actual.into_iter() {
if self.inner.matches(&v).into() {
count += 1;
}
}
count
}
}
#[cfg(test)]
mod tests {
use super::{ContainsMatcher, contains};
use crate::matcher::{Describable as _, Matcher, MatcherResult};
use crate::matchers::containers::RefItems;
use crate::prelude::*;
#[test]
fn contains_matches_singleton_slice_with_value() -> TestResult<()> {
let matcher = contains(eq(1));
let result = matcher.matches(&vec![1]);
verify_that!(result, eq(MatcherResult::Match))
}
#[test]
fn contains_matches_singleton_vec_with_value() -> TestResult<()> {
let matcher = contains(eq(1));
let result = matcher.matches(&vec![1]);
verify_that!(result, eq(MatcherResult::Match))
}
#[test]
fn contains_matches_two_element_slice_with_value() -> TestResult<()> {
let matcher = contains(eq(1));
let result = matcher.matches(&[0, 1]);
verify_that!(result, eq(MatcherResult::Match))
}
#[test]
fn contains_does_not_match_singleton_slice_with_wrong_value() -> TestResult<()> {
let matcher = contains(eq(1));
let result = matcher.matches(&[0]);
verify_that!(result, eq(MatcherResult::NoMatch))
}
#[test]
fn contains_does_not_match_empty_slice() -> TestResult<()> {
let value = Vec::<i32>::new();
verify_that!(value.as_slice(), not(points_to(contains(eq(1)))))
}
#[test]
fn contains_matches_slice_with_repeated_value() -> TestResult<()> {
let matcher = contains(eq(1)).times(eq(2));
let result = matcher.matches(&[1, 1]);
verify_that!(result, eq(MatcherResult::Match))
}
#[test]
fn contains_does_not_match_slice_with_too_few_of_value() -> TestResult<()> {
let matcher = contains(eq(1)).times(eq(2));
let result = matcher.matches(&[0, 1]);
verify_that!(result, eq(MatcherResult::NoMatch))
}
#[test]
fn contains_does_not_match_slice_with_too_many_of_value() -> TestResult<()> {
let matcher = contains(eq(1)).times(eq(1));
let result = matcher.matches(&[1, 1]);
verify_that!(result, eq(MatcherResult::NoMatch))
}
#[test]
fn contains_matches_on_vec_of_values() -> TestResult<()> {
verify_that!(vec![1, 2, 3], contains(eq(1)))
}
#[test]
fn contains_matches_on_array_of_values() -> TestResult<()> {
verify_that!([1, 2, 3], contains(eq(1)))
}
#[test]
fn contains_matches_on_slice_of_values_with_points_to_slice() -> TestResult<()> {
verify_that!(&[1, 2, 3], points_to(contains(eq(1))))
}
#[test]
fn contains_matches_on_slice_of_values_with_deref_notation_on_macro() -> TestResult<()> {
let slice = &[1, 2, 3];
verify_that!(*slice, contains(eq(1)))
}
#[derive(Debug)]
struct OwnedItemContainer(Vec<i32>);
impl<'a> IntoIterator for &'a OwnedItemContainer {
type Item = i32;
type IntoIter = std::iter::Copied<std::slice::Iter<'a, i32>>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter().copied()
}
}
#[test]
fn contains_matches_on_container_when_ref_to_container_has_into_iterator_producing_owned_values()
-> TestResult<()> {
verify_that!(OwnedItemContainer(vec![1, 2, 3]), contains(eq(1)))
}
#[test]
fn contains_matches_vec_of_string_slices() -> TestResult<()> {
verify_that!(vec!["String 1", "String 2", "String 3"], contains(contains_substring("1")))
}
#[test]
fn contains_matches_vec_of_string_slices_with_non_static_lifetime() -> TestResult<()> {
let string_1 = String::from("String 1");
let string_2 = String::from("String 2");
let string_3 = String::from("String 3");
verify_that!(
vec![string_1.as_str(), string_2.as_str(), string_3.as_str()],
contains(contains_substring("1"))
)
}
#[test]
fn contains_matches_slice_of_string_slices() -> TestResult<()> {
let value = vec!["String 1", "String 2", "String 3"];
verify_that!(value.as_slice(), points_to(contains(contains_substring("1"))))
}
#[test]
fn contains_matches_slice_of_string_slices_with_non_static_lifetime() -> TestResult<()> {
let string_1 = String::from("String 1");
let string_2 = String::from("String 2");
let string_3 = String::from("String 3");
let value = vec![string_1.as_str(), string_2.as_str(), string_3.as_str()];
verify_that!(value.as_slice(), points_to(contains(contains_substring("1"))))
}
#[test]
fn contains_formats_without_multiplicity_by_default() -> TestResult<()> {
let matcher: ContainsMatcher<_, RefItems> = contains(eq(1));
verify_that!(
matcher.describe(MatcherResult::Match),
displays_as(eq("contains at least one element which is equal to 1"))
)
}
#[test]
fn contains_formats_with_multiplicity_when_specified() -> TestResult<()> {
let matcher: ContainsMatcher<_, RefItems> = contains(eq(1)).times(eq(2));
verify_that!(
matcher.describe(MatcherResult::Match),
displays_as(eq("contains n elements which is equal to 1\n where n is equal to 2"))
)
}
#[test]
fn contains_mismatch_shows_number_of_times_element_was_found() -> TestResult<()> {
verify_that!(
contains(eq(3)).times(eq(1)).explain_match(&vec![1, 2, 3, 3]),
displays_as(eq("which contains 2 matching elements"))
)
}
#[test]
fn contains_mismatch_shows_when_matches() -> TestResult<()> {
verify_that!(
contains(eq(3)).explain_match(&vec![1, 2, 3, 3]),
displays_as(eq("which contains a matching element"))
)
}
#[test]
fn contains_mismatch_shows_when_no_matches() -> TestResult<()> {
verify_that!(
contains(eq(3)).explain_match(&vec![1, 2]),
displays_as(eq("which does not contain a matching element"))
)
}
}