use std::fmt;
use std::marker::PhantomData;
use crate::description::Description;
use crate::matcher::{MatchResult, Matcher, Mismatch};
struct PredicateMatcher<T, F> {
name: &'static str,
pred: F,
_marker: PhantomData<fn(&T)>,
}
impl<T, F> Matcher<T> for PredicateMatcher<T, F>
where
T: fmt::Debug,
F: Fn(&T) -> bool,
{
fn check(&self, actual: &T) -> MatchResult {
if (self.pred)(actual) {
MatchResult::pass()
} else {
MatchResult::fail(Mismatch::new(self.description(), format!("{actual:?}")))
}
}
fn description(&self) -> Description {
Description::text(self.name)
}
}
#[must_use]
pub fn predicate<T, F>(name: &'static str, pred: F) -> impl Matcher<T>
where
T: fmt::Debug,
F: Fn(&T) -> bool,
{
PredicateMatcher {
name,
pred,
_marker: PhantomData,
}
}
#[cfg(test)]
mod tests {
use test_better_core::{OrFail, TestResult};
use super::*;
use crate::{check, eq, is_false, is_true};
#[test]
fn predicate_runs_the_closure() -> TestResult {
check!(predicate("even", |n: &i32| n % 2 == 0).check(&4).matched).satisfies(is_true())?;
check!(predicate("even", |n: &i32| n % 2 == 0).check(&3).matched).satisfies(is_false())?;
Ok(())
}
#[test]
fn predicate_failure_reports_the_name_not_the_closure() -> TestResult {
let failure = predicate("a positive number", |n: &i32| *n > 0)
.check(&-1)
.failure
.or_fail_with("-1 is not positive")?;
check!(failure.expected.to_string()).satisfies(eq("a positive number".to_string()))?;
check!(failure.actual).satisfies(eq("-1".to_string()))?;
Ok(())
}
}