use alloc::borrow::ToOwned;
use alloc::format;
use alloc::vec::Vec;
use core::fmt::Debug;
use crate::{AssertThat, AssertrPartialEq, Mode, tracking::AssertionTracking};
#[allow(clippy::return_self_not_must_use)]
#[cfg_attr(feature = "fluent", assertr_derive::fluent_aliases)]
pub trait SliceAssertions<'t, T> {
fn contains<E>(self, expected: E) -> Self
where
E: Debug,
T: AssertrPartialEq<E> + Debug;
fn contains_exactly<E, EE>(self, expected: EE) -> Self
where
E: Debug + 't,
EE: AsRef<[E]>,
T: AssertrPartialEq<E> + Debug;
fn contains_exactly_in_any_order<E: AsRef<[T]>>(self, expected: E) -> Self
where
T: PartialEq + Debug;
fn contains_exactly_matching_in_any_order<P>(self, expected: impl AsRef<[P]>) -> Self
where
T: Debug,
P: Fn(&T) -> bool;
}
impl<'t, T, M: Mode> SliceAssertions<'t, T> for AssertThat<'t, &[T], M> {
#[track_caller]
fn contains<E>(self, expected: E) -> Self
where
E: Debug,
T: Debug + AssertrPartialEq<E>,
{
self.track_assertion();
let actual = self.actual().iter().collect::<Vec<_>>();
let expected = expected;
if !self
.actual()
.iter()
.any(|it| AssertrPartialEq::eq(it, &expected, None))
{
self.fail(format_args!(
"Actual: {actual:#?}\n\ndoes not contain expected: {expected:#?}\n",
));
}
self
}
#[track_caller]
fn contains_exactly<E, EE>(self, expected: EE) -> Self
where
E: Debug + 't,
EE: AsRef<[E]>,
T: AssertrPartialEq<E> + Debug,
{
self.track_assertion();
let actual = *self.actual();
let expected = expected.as_ref();
let result = crate::util::slice::compare(actual, expected);
if !result.strictly_equal {
if !result.not_in_b.is_empty() {
self.add_detail_message(format!("Elements not expected: {:#?}", result.not_in_b));
}
if !result.not_in_a.is_empty() {
self.add_detail_message(format!("Elements not found: {:#?}", result.not_in_a));
}
if result.only_differing_in_order() {
self.add_detail_message("The order of elements does not match!".to_owned());
}
let actual = self.actual();
self.fail(format_args!(
"Actual: {actual:#?},\n\ndid not exactly match\n\nExpected: {expected:#?}\n",
));
}
self
}
#[track_caller]
fn contains_exactly_in_any_order<E: AsRef<[T]>>(self, expected: E) -> Self
where
T: PartialEq + Debug,
{
self.track_assertion();
let actual: &[T] = self.actual();
let expected: &[T] = expected.as_ref();
let mut elements_found = Vec::new();
let mut elements_not_found: Vec<&T> = Vec::new();
let mut elements_not_expected = Vec::new();
for e in expected {
let found = actual.as_ref().iter().find(|it| *it == e);
match found {
Some(_e) => elements_found.push(e),
None => elements_not_found.push(e),
}
}
for e in actual {
match elements_found.iter().find(|it| **it == e) {
Some(_) => {}
None => elements_not_expected.push(e),
}
}
if !elements_not_found.is_empty() || !elements_not_expected.is_empty() {
self.fail(format_args!(
"Actual: {actual:#?},\n\nElements expected: {expected:#?}\n\nElements not found: {elements_not_found:#?}\n\nElements not expected: {elements_not_expected:#?}\n",
));
}
self
}
#[track_caller]
fn contains_exactly_matching_in_any_order<P>(self, expected: impl AsRef<[P]>) -> Self
where
T: Debug,
P: Fn(&T) -> bool,
{
self.track_assertion();
let actual = *self.actual();
let expected = expected.as_ref();
let result = crate::util::slice::test_matching_any(actual, expected);
if !result.not_matched.is_empty() {
self.add_detail_message(format!("Elements not matched: {:#?}", result.not_matched));
let actual = self.actual();
self.fail(format_args!(
"Actual: {actual:#?},\n\ndid not exactly match predicates in any order.\n",
));
}
self
}
}
#[cfg(test)]
mod tests {
mod contains_exactly {
use crate::prelude::*;
use indoc::formatdoc;
#[test]
fn succeeds_when_exact_match() {
assert_that!([1, 2, 3].as_slice()).contains_exactly([1, 2, 3]);
}
#[test]
fn compiles_for_different_type_combinations() {
assert_that!(["foo".to_owned()].as_slice()).contains_exactly(["foo"]);
assert_that!(["foo"].as_slice()).contains_exactly(["foo"]);
assert_that!(["foo"].as_slice()).contains_exactly(["foo".to_owned()]);
assert_that!(["foo"].as_slice()).contains_exactly(vec!["foo".to_owned()]);
assert_that!(vec!["foo"].as_slice())
.contains_exactly(vec!["foo".to_owned()].into_iter());
}
#[test]
fn succeeds_when_exact_match_provided_as_slice() {
assert_that!([1, 2, 3].as_slice()).contains_exactly(&[1, 2, 3]);
}
#[test]
fn panics_when_not_exact_match() {
assert_that_panic_by(|| {
assert_that!([1, 2, 3].as_slice())
.with_location(false)
.contains_exactly([2, 3, 4]);
})
.has_type::<String>()
.is_equal_to(formatdoc! {r#"
-------- assertr --------
Actual: [
1,
2,
3,
],
did not exactly match
Expected: [
2,
3,
4,
]
Details: [
Elements not expected: [
1,
],
Elements not found: [
4,
],
]
-------- assertr --------
"#});
}
#[test]
fn panics_with_detail_message_when_only_differing_in_order() {
assert_that_panic_by(|| {
assert_that!([1, 2, 3].as_slice())
.with_location(false)
.contains_exactly([3, 2, 1]);
})
.has_type::<String>()
.is_equal_to(formatdoc! {r#"
-------- assertr --------
Actual: [
1,
2,
3,
],
did not exactly match
Expected: [
3,
2,
1,
]
Details: [
The order of elements does not match!,
]
-------- assertr --------
"#});
}
}
mod contains_exactly_in_any_order {
use crate::prelude::*;
use indoc::formatdoc;
#[test]
fn succeeds_when_slices_match() {
assert_that!([1, 2, 3].as_slice()).contains_exactly_in_any_order([2, 3, 1]);
}
#[test]
fn panics_when_slice_contains_unknown_data() {
assert_that_panic_by(|| {
assert_that!([1, 2, 3].as_slice())
.with_location(false)
.contains_exactly_in_any_order([2, 3, 4]);
})
.has_type::<String>()
.is_equal_to(formatdoc! {"
-------- assertr --------
Actual: [
1,
2,
3,
],
Elements expected: [
2,
3,
4,
]
Elements not found: [
4,
]
Elements not expected: [
1,
]
-------- assertr --------
"});
}
}
mod contains_exactly_matching_in_any_order {
use crate::prelude::*;
use indoc::formatdoc;
#[test]
fn succeeds_when_slices_match() {
assert_that!([1, 2, 3].as_slice()).contains_exactly_matching_in_any_order(
[
move |it: &i32| *it == 1,
move |it: &i32| *it == 2,
move |it: &i32| *it == 3,
]
.as_slice(),
);
}
#[test]
fn succeeds_when_slices_match_in_different_order() {
assert_that!([1, 2, 3].as_slice()).contains_exactly_matching_in_any_order(
[
move |it: &i32| *it == 3,
move |it: &i32| *it == 1,
move |it: &i32| *it == 2,
]
.as_slice(),
);
}
#[test]
fn panics_when_slice_contains_non_matching_data() {
assert_that_panic_by(|| {
assert_that!([1, 2, 3].as_slice())
.with_location(false)
.contains_exactly_matching_in_any_order(
[
move |it: &i32| *it == 2,
move |it: &i32| *it == 3,
move |it: &i32| *it == 4,
]
.as_slice(),
);
})
.has_type::<String>()
.is_equal_to(formatdoc! {"
-------- assertr --------
Actual: [
1,
2,
3,
],
did not exactly match predicates in any order.
Details: [
Elements not matched: [
1,
],
]
-------- assertr --------
"});
}
}
}