#![doc(hidden)]
#[macro_export]
#[doc(hidden)]
macro_rules! __all {
($($matcher:expr),* $(,)?) => {{
$crate::matchers::__internal::AllMatcher::new([$($crate::__alloc::boxed::Box::new($matcher)),*])
}}
}
#[doc(hidden)]
pub mod __internal {
use crate::description::Description;
use crate::matcher::{Describable, Matcher, MatcherResult};
use crate::matchers::anything;
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::fmt::Debug;
#[doc(hidden)]
pub struct AllMatcher<'a, T: Debug + ?Sized, const N: usize> {
components: [Box<dyn Matcher<T> + 'a>; N],
}
impl<'a, T: Debug + ?Sized, const N: usize> AllMatcher<'a, T, N> {
pub fn new(components: [Box<dyn Matcher<T> + 'a>; N]) -> Self {
Self { components }
}
}
impl<'a, T: Debug + ?Sized, const N: usize> Matcher<T> for AllMatcher<'a, T, N> {
fn matches(&self, actual: &T) -> MatcherResult {
for component in &self.components {
match component.matches(actual) {
MatcherResult::NoMatch => {
return MatcherResult::NoMatch;
}
MatcherResult::Match => {}
}
}
MatcherResult::Match
}
fn explain_match(&self, actual: &T) -> Description {
match N {
0 => anything().explain_match(actual),
1 => self.components[0].explain_match(actual),
_ => {
let failures = self
.components
.iter()
.filter(|component| component.matches(actual).is_no_match())
.collect::<Vec<_>>();
if failures.len() == 1 {
failures[0].explain_match(actual)
} else {
Description::new()
.collect(
failures
.into_iter()
.map(|component| component.explain_match(actual)),
)
.bullet_list()
}
}
}
}
}
impl<'a, T: Debug + ?Sized, const N: usize> Describable for AllMatcher<'a, T, N> {
fn describe(&self, matcher_result: MatcherResult) -> Description {
match N {
0 => anything().describe(matcher_result),
1 => self.components[0].describe(matcher_result),
_ => {
let header = if matcher_result.into() {
"has all the following properties:"
} else {
"has at least one of the following properties:"
};
Description::new().text(header).nested(
Description::new()
.bullet_list()
.collect(self.components.iter().map(|m| m.describe(matcher_result))),
)
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::__internal;
use crate::matcher::{Describable as _, Matcher, MatcherResult};
use crate::prelude::*;
use alloc::string::String;
use indoc::indoc;
#[test]
fn description_shows_more_than_one_matcher() -> TestResult<()> {
let first_matcher = starts_with("A");
let second_matcher = ends_with("string");
let matcher: __internal::AllMatcher<String, 2> = all!(first_matcher, second_matcher);
verify_that!(
matcher.describe(MatcherResult::Match),
displays_as(eq(indoc!(
"
has all the following properties:
* starts with prefix \"A\"
* ends with suffix \"string\""
)))
)
}
#[test]
fn description_shows_one_matcher_directly() -> TestResult<()> {
let first_matcher = starts_with("A");
let matcher: __internal::AllMatcher<String, 1> = all!(first_matcher);
verify_that!(
matcher.describe(MatcherResult::Match),
displays_as(eq("starts with prefix \"A\""))
)
}
#[test]
fn mismatch_description_shows_which_matcher_failed_if_more_than_one_constituent()
-> TestResult<()> {
let first_matcher = starts_with("Another");
let second_matcher = ends_with("string");
let matcher: __internal::AllMatcher<str, 2> = all!(first_matcher, second_matcher);
verify_that!(
matcher.explain_match("A string"),
displays_as(eq("which does not start with \"Another\""))
)
}
#[test]
fn mismatch_description_is_simple_when_only_one_consistuent() -> TestResult<()> {
let first_matcher = starts_with("Another");
let matcher: __internal::AllMatcher<str, 1> = all!(first_matcher);
verify_that!(
matcher.explain_match("A string"),
displays_as(eq("which does not start with \"Another\""))
)
}
}