assertr/assertions/core/
slice.rs

1use alloc::borrow::ToOwned;
2use alloc::format;
3use alloc::vec::Vec;
4use core::fmt::Debug;
5
6use crate::{AssertThat, AssertrPartialEq, Mode, tracking::AssertionTracking};
7
8pub trait SliceAssertions<'t, T> {
9    fn contains<E>(self, expected: E) -> Self
10    where
11        E: Debug,
12        T: AssertrPartialEq<E> + Debug;
13
14    /// Test that the subject contains exactly the expected elements. Order is important. Lengths must be identical.
15    ///
16    /// - [T]: Original subject type. The "actual value" is of type `&[T]` (slice T).
17    /// - [E]: Type of elements in our "expected value" slice.
18    /// - [EE]: The "expected value". Anything that can be seen as `&[E]` (slice E). Having this extra type, instead of directly accepting `&[E]` allows us to be generic over the input in both the element type and collection type.
19    fn contains_exactly<E, EE>(self, expected: EE) -> Self
20    where
21        E: Debug + 't,
22        EE: AsRef<[E]>,
23        T: AssertrPartialEq<E> + Debug;
24
25    fn contains_exactly_in_any_order<E: AsRef<[T]>>(self, expected: E) -> Self
26    where
27        T: PartialEq + Debug;
28
29    /// [P] - Predicate
30    fn contains_exactly_matching_in_any_order<P>(self, expected: impl AsRef<[P]>) -> Self
31    where
32        T: Debug,
33        P: Fn(&T) -> bool;
34}
35
36impl<'t, T, M: Mode> SliceAssertions<'t, T> for AssertThat<'t, &[T], M> {
37    #[track_caller]
38    fn contains<E>(self, expected: E) -> Self
39    where
40        E: Debug,
41        T: Debug + AssertrPartialEq<E>,
42    {
43        self.track_assertion();
44        let actual = self.actual().iter().collect::<Vec<_>>();
45        let expected = expected;
46        if !self
47            .actual()
48            .iter()
49            .any(|it| AssertrPartialEq::eq(it, &expected, None))
50        {
51            self.fail(format_args!(
52                "Actual: {actual:#?}\n\ndoes not contain expected: {expected:#?}\n",
53            ));
54        }
55        self
56    }
57
58    #[track_caller]
59    fn contains_exactly<E, EE>(self, expected: EE) -> Self
60    where
61        E: Debug + 't,
62        EE: AsRef<[E]>,
63        T: AssertrPartialEq<E> + Debug,
64    {
65        self.track_assertion();
66        let actual = *self.actual();
67        let expected = expected.as_ref();
68
69        let result = crate::util::slice::compare(actual, expected);
70
71        if !result.strictly_equal {
72            if !result.not_in_b.is_empty() {
73                self.add_detail_message(format!("Elements not expected: {:#?}", result.not_in_b));
74            }
75            if !result.not_in_a.is_empty() {
76                self.add_detail_message(format!("Elements not found: {:#?}", result.not_in_a));
77            }
78            if result.only_differing_in_order() {
79                self.add_detail_message("The order of elements does not match!".to_owned());
80            }
81
82            let actual = self.actual();
83
84            self.fail(format_args!(
85                "Actual: {actual:#?},\n\ndid not exactly match\n\nExpected: {expected:#?}\n",
86            ));
87        }
88        self
89    }
90
91    #[track_caller]
92    fn contains_exactly_in_any_order<E: AsRef<[T]>>(self, expected: E) -> Self
93    where
94        T: PartialEq + Debug,
95    {
96        self.track_assertion();
97        let actual: &[T] = self.actual();
98        let expected: &[T] = expected.as_ref();
99
100        let mut elements_found = Vec::new();
101        let mut elements_not_found: Vec<&T> = Vec::new();
102        let mut elements_not_expected = Vec::new();
103
104        for e in expected.iter() {
105            let found = actual.as_ref().iter().find(|it| *it == e);
106
107            match found {
108                Some(_e) => elements_found.push(e),
109                None => elements_not_found.push(e),
110            }
111        }
112
113        for e in actual.iter() {
114            match elements_found.iter().find(|it| **it == e) {
115                Some(_) => {}
116                None => elements_not_expected.push(e),
117            }
118        }
119
120        if !elements_not_found.is_empty() || !elements_not_expected.is_empty() {
121            self.fail(format_args!(
122                "Actual: {actual:#?},\n\nElements expected: {expected:#?}\n\nElements not found: {elements_not_found:#?}\n\nElements not expected: {elements_not_expected:#?}\n",
123                actual = actual,
124                expected = expected
125            ));
126        }
127        self
128    }
129
130    #[track_caller]
131    fn contains_exactly_matching_in_any_order<P>(self, expected: impl AsRef<[P]>) -> Self
132    where
133        T: Debug,
134        P: Fn(&T) -> bool,
135    {
136        self.track_assertion();
137        let actual = *self.actual();
138        let expected = expected.as_ref();
139
140        let result = crate::util::slice::test_matching_any(actual, expected);
141
142        if !result.not_matched.is_empty() {
143            if !result.not_matched.is_empty() {
144                self.add_detail_message(format!("Elements not matched: {:#?}", result.not_matched));
145            }
146
147            let actual = self.actual();
148
149            self.fail(format_args!(
150                "Actual: {actual:#?},\n\ndid not exactly match predicates in any order.\n",
151            ));
152        }
153        self
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    mod contains_exactly {
160        use indoc::formatdoc;
161
162        use crate::prelude::*;
163
164        #[test]
165        fn succeeds_when_exact_match() {
166            assert_that([1, 2, 3].as_slice()).contains_exactly([1, 2, 3]);
167        }
168
169        #[test]
170        fn compiles_for_different_type_combinations() {
171            assert_that(["foo".to_owned()].as_slice()).contains_exactly(["foo"]);
172            assert_that(["foo"].as_slice()).contains_exactly(["foo"]);
173            assert_that(["foo"].as_slice()).contains_exactly(["foo".to_owned()]);
174            assert_that(["foo"].as_slice()).contains_exactly(vec!["foo".to_owned()]);
175            assert_that(vec!["foo"].as_slice())
176                .contains_exactly(vec!["foo".to_owned()].into_iter());
177        }
178
179        #[test]
180        fn succeeds_when_exact_match_provided_as_slice() {
181            assert_that([1, 2, 3].as_slice()).contains_exactly(&[1, 2, 3]);
182        }
183
184        #[test]
185        fn panics_when_not_exact_match() {
186            assert_that_panic_by(|| {
187                assert_that([1, 2, 3].as_slice())
188                    .with_location(false)
189                    .contains_exactly([2, 3, 4])
190            })
191            .has_type::<String>()
192            .is_equal_to(formatdoc! {r#"
193                    -------- assertr --------
194                    Actual: [
195                        1,
196                        2,
197                        3,
198                    ],
199
200                    did not exactly match
201
202                    Expected: [
203                        2,
204                        3,
205                        4,
206                    ]
207
208                    Details: [
209                        Elements not expected: [
210                            1,
211                        ],
212                        Elements not found: [
213                            4,
214                        ],
215                    ]
216                    -------- assertr --------
217                "#});
218        }
219
220        #[test]
221        fn panics_with_detail_message_when_only_differing_in_order() {
222            assert_that_panic_by(|| {
223                assert_that([1, 2, 3].as_slice())
224                    .with_location(false)
225                    .contains_exactly([3, 2, 1])
226            })
227            .has_type::<String>()
228            .is_equal_to(formatdoc! {r#"
229                    -------- assertr --------
230                    Actual: [
231                        1,
232                        2,
233                        3,
234                    ],
235
236                    did not exactly match
237
238                    Expected: [
239                        3,
240                        2,
241                        1,
242                    ]
243
244                    Details: [
245                        The order of elements does not match!,
246                    ]
247                    -------- assertr --------
248                "#});
249        }
250    }
251
252    mod contains_exactly_in_any_order {
253        use indoc::formatdoc;
254
255        use crate::prelude::*;
256
257        #[test]
258        fn succeeds_when_slices_match() {
259            assert_that([1, 2, 3].as_slice()).contains_exactly_in_any_order([2, 3, 1]);
260        }
261
262        #[test]
263        fn panics_when_slice_contains_unknown_data() {
264            assert_that_panic_by(|| {
265                assert_that([1, 2, 3].as_slice())
266                    .with_location(false)
267                    .contains_exactly_in_any_order([2, 3, 4])
268            })
269            .has_type::<String>()
270            .is_equal_to(formatdoc! {"
271                    -------- assertr --------
272                    Actual: [
273                        1,
274                        2,
275                        3,
276                    ],
277
278                    Elements expected: [
279                        2,
280                        3,
281                        4,
282                    ]
283
284                    Elements not found: [
285                        4,
286                    ]
287
288                    Elements not expected: [
289                        1,
290                    ]
291                    -------- assertr --------
292                "});
293        }
294    }
295
296    mod contains_exactly_matching_in_any_order {
297        use indoc::formatdoc;
298
299        use crate::prelude::*;
300
301        #[test]
302        fn succeeds_when_slices_match() {
303            assert_that([1, 2, 3].as_slice()).contains_exactly_matching_in_any_order(
304                [
305                    move |it: &i32| *it == 1,
306                    move |it: &i32| *it == 2,
307                    move |it: &i32| *it == 3,
308                ]
309                .as_slice(),
310            );
311        }
312
313        #[test]
314        fn succeeds_when_slices_match_in_different_order() {
315            assert_that([1, 2, 3].as_slice()).contains_exactly_matching_in_any_order(
316                [
317                    move |it: &i32| *it == 3,
318                    move |it: &i32| *it == 1,
319                    move |it: &i32| *it == 2,
320                ]
321                .as_slice(),
322            );
323        }
324
325        #[test]
326        fn panics_when_slice_contains_non_matching_data() {
327            assert_that_panic_by(|| {
328                assert_that([1, 2, 3].as_slice())
329                    .with_location(false)
330                    .contains_exactly_matching_in_any_order(
331                        [
332                            move |it: &i32| *it == 2,
333                            move |it: &i32| *it == 3,
334                            move |it: &i32| *it == 4,
335                        ]
336                        .as_slice(),
337                    )
338            })
339            .has_type::<String>()
340            .is_equal_to(formatdoc! {"
341                    -------- assertr --------
342                    Actual: [
343                        1,
344                        2,
345                        3,
346                    ],
347
348                    did not exactly match predicates in any order.
349
350                    Details: [
351                        Elements not matched: [
352                            1,
353                        ],
354                    ]
355                    -------- assertr --------
356                "});
357        }
358    }
359}