easy_assert/
list_assertions.rs

1//! ## Usage
2//!
3//! ``` rust
4//! use easy_assert::{actual_vec, expected_vec};
5//! use easy_assert::list_assertions::ListAssert;
6//!
7//!  ListAssert::assert_that(actual_vec(vec![1, 2, 3]))
8//!  .with_element_matcher(|a, b| a.eq(b))
9//!  .is_equal_to(expected_vec(vec![3, 2, 1]))
10//!  .in_any_order();
11//!
12//!  ListAssert::assert_that(actual_vec(vec![1, 2, 3]))
13//!  .with_element_matcher(|a, b| a.eq(b))
14//!  .is_equal_to(expected_vec(vec![1, 2, 3]))
15//!  .in_order();
16//!
17//!  ListAssert::assert_that(actual_vec(vec![2, 3, 1, 2, 3, 4, 5]))
18//!  .with_element_matcher(|a, b| a.eq(b))
19//!  .contains(expected_vec(vec![2, 3, 4]))
20//!  .in_exact_order();
21//!
22//!  ListAssert::assert_that(actual_vec(vec![2, 1, 3, 5, 4]))
23//!  .with_element_matcher(|a, b| a.eq(b))
24//!  .contains(expected_vec(vec![2, 3, 4]))
25//!  .just_in_order();
26//!
27//!  ListAssert::assert_that(actual_vec(vec![2, 1, 3, 5, 4]))
28//!  .with_element_matcher(|a, b| a.eq(b))
29//!  .contains(expected_vec(vec![5, 1, 2]))
30//!  .in_any_order();
31//!
32//!  ListAssert::assert_that(actual_vec(vec![2, 1, 3, 5, 4]))
33//!  .with_element_matcher(|a, b| a.eq(b))
34//!  .does_not_contain(expected_vec(vec![5, 1, 6]))
35//!  .at_least_one();
36//!
37//!  ListAssert::assert_that(actual_vec(vec![2, 1, 3, 5, 4]))
38//!  .with_element_matcher(|a, b| a.eq(b))
39//!  .does_not_contain(expected_vec(vec![12, 14, 16]))
40//!  .all();
41//!
42//!  ListAssert::assert_that(actual_vec(vec![2, 1, 3, 5, 4]))
43//!  .with_element_matcher(|a, b| a.eq(b))
44//!  .is_not_equal_to(expected_vec(vec![1, 2, 3, 5, 4]))
45//!  .in_order();
46//!
47//!  ListAssert::assert_that(actual_vec(vec![2, 1, 3, 5, 4]))
48//!  .with_element_matcher(|a, b| a.eq(b))
49//!  .is_not_equal_to(expected_vec(vec![2, 1, 3, 5, 6]))
50//!  .in_any_order();
51//! ```
52//!
53//! ### Custom objects
54//!
55//! ``` rust
56//! use easy_assert::{actual_vec_with, expected_vec_with};
57//! use easy_assert::list_assertions::ListAssert;
58//!
59//! #[derive(Clone)]
60//! struct CustomObject {
61//!     a: String,
62//!     b: u32,
63//!     c: bool,
64//! }
65//!
66//! fn custom_match(value1: &CustomObject, value2: &CustomObject) -> bool {
67//!     value1.a.eq(&value2.a) && value1.b == value2.b && value1.c == value2.c
68//! }
69//!
70//! fn custom_describe(val: &CustomObject) -> String {
71//!     format!(
72//!         "CustomObject: \n a: {} \n b: {} \n c: {}",
73//!         val.a, val.b, val.c
74//!     )
75//! }
76//!
77//! #[test]
78//! fn my_test() {
79//!     let obj1 = CustomObject {a: String::from("a"),b: 1, c: true};
80//!     let obj2 = CustomObject {a: String::from("b"),b: 2, c: true};
81//!     let obj3 = CustomObject {a: String::from("c"),b: 3, c: false};
82//!
83//!      ListAssert::assert_that(actual_vec_with(
84//!          vec![obj1.clone(), obj2.clone(), obj3.clone()],
85//!          custom_describe,
86//!      ))
87//!      .with_element_matcher(custom_match)
88//!      .is_equal_to(expected_vec_with(vec![obj1, obj2, obj3], custom_describe))
89//!      .in_order()
90//! }
91//! ```
92
93use crate::assertions::{
94    CollectionContains, CollectionEqual, CollectionNotContain, CollectionNotEqual, Length,
95};
96use crate::{test_failed, Actual, Expected};
97use std::collections::HashMap;
98
99pub struct ListAssert<T> {
100    actual: Vec<Actual<T>>,
101}
102
103impl<T> ListAssert<T>
104where
105    T: 'static,
106{
107    pub fn assert_that(actual: Vec<Actual<T>>) -> ListAssert<T> {
108        ListAssert { actual }
109    }
110
111    pub fn with_element_matcher(self, element_matcher: fn(&T, &T) -> bool) -> ListElementAssert<T> {
112        ListElementAssert {
113            actual: self.actual,
114            element_matcher,
115        }
116    }
117
118    pub fn length(self) -> Box<dyn Length> {
119        Box::new(self)
120    }
121}
122
123pub struct ListElementAssert<T> {
124    actual: Vec<Actual<T>>,
125    element_matcher: fn(&T, &T) -> bool,
126}
127
128impl<T> ListElementAssert<T>
129where
130    T: 'static,
131{
132    pub fn contains(self, expected: Vec<Expected<T>>) -> Box<dyn CollectionContains<T>> {
133        Box::new(ListFinalAssert {
134            actual: self.actual,
135            expected,
136            element_matcher: self.element_matcher,
137        })
138    }
139
140    pub fn does_not_contain(self, expected: Vec<Expected<T>>) -> Box<dyn CollectionNotContain<T>> {
141        Box::new(ListFinalAssert {
142            actual: self.actual,
143            expected,
144            element_matcher: self.element_matcher,
145        })
146    }
147
148    pub fn is_equal_to(self, expected: Vec<Expected<T>>) -> Box<dyn CollectionEqual<T>> {
149        Box::new(ListFinalAssert {
150            actual: self.actual,
151            expected,
152            element_matcher: self.element_matcher,
153        })
154    }
155
156    pub fn is_not_equal_to(self, expected: Vec<Expected<T>>) -> Box<dyn CollectionNotEqual<T>> {
157        Box::new(ListFinalAssert {
158            actual: self.actual,
159            expected,
160            element_matcher: self.element_matcher,
161        })
162    }
163}
164
165struct ListFinalAssert<T> {
166    actual: Vec<Actual<T>>,
167    expected: Vec<Expected<T>>,
168    element_matcher: fn(&T, &T) -> bool,
169}
170
171type ActualIndex = usize;
172type ExpectedIndex = usize;
173
174impl<T> ListFinalAssert<T> {
175    fn vec_to_string(vec: &[Actual<T>]) -> String {
176        vec.iter()
177            .map(|val| val.to_string())
178            .collect::<Vec<String>>()
179            .join(",")
180    }
181
182    fn contains_in_any_order(&self) -> Result<(), String> {
183        //[1,2,3,4,5].contains[5,3,1]
184
185        let mut errors: Vec<String> = vec![];
186        let expected_diff = self.only_in_expected();
187        for expected_index in expected_diff {
188            errors.push(format!(
189                "   - Expected element: ({}) - Not found",
190                self.expected[expected_index]
191            ));
192        }
193
194        if errors.is_empty() {
195            return Ok(());
196        }
197        Err(errors.join("\n"))
198    }
199
200    fn get_unchecked_index(
201        &self,
202        expected_elem: &Expected<T>,
203        checked_actual_indexes: &[usize],
204    ) -> Option<usize> {
205        for (actual_index, actual_elem) in self.actual.iter().enumerate() {
206            let actual_index_was_checked = checked_actual_indexes.get(actual_index).is_some();
207            if actual_index_was_checked {
208                continue;
209            }
210            if (self.element_matcher)(&actual_elem.value, &expected_elem.value) {
211                return Some(actual_index);
212            }
213        }
214
215        None
216    }
217
218    fn actual_contains_exact_expected_slice(&self, starting_position: usize) -> bool {
219        let mut matches_length = 0;
220        for (expected_index, expected_elem) in self.expected.iter().enumerate() {
221            let actual_index = expected_index + starting_position;
222            if actual_index >= self.actual.len() {
223                return false;
224            }
225            let actual_elem = &self.actual[actual_index];
226
227            if (self.element_matcher)(&actual_elem.value, &expected_elem.value) {
228                matches_length += 1;
229            } else {
230                break;
231            }
232        }
233
234        matches_length == self.expected.len()
235    }
236
237    fn actual_len_ge_expected(&self) -> Result<(), String> {
238        if self.actual.len() < self.expected.len() {
239            return Err(format!(
240                "\n The actual vec size ({}) is LESS then expected vec ({})\n",
241                self.actual.len(),
242                self.expected.len()
243            ));
244        };
245        Ok(())
246    }
247
248    fn list_test_failed(&self, error_message: &str) {
249        let list_error_message = format!(
250            "Actual vec: [{}] \n Expected vec: [{}] \n Detailed error: \n {}",
251            Self::vec_to_string(&self.actual),
252            Self::vec_to_string(&self.expected),
253            error_message
254        );
255        test_failed(&list_error_message);
256    }
257
258    fn only_in_expected(&self) -> Vec<ExpectedIndex> {
259        let (_, only_in_expected) = self.difference_ignoring_position();
260
261        only_in_expected
262    }
263
264    fn difference_ignoring_position(&self) -> (Vec<ActualIndex>, Vec<ExpectedIndex>) {
265        let intersection = self.intersection_indexes();
266        if intersection.len() == self.actual.len() && intersection.len() == self.expected.len() {
267            return (vec![], vec![]);
268        }
269
270        let mut actual_existing: Vec<bool> = vec![false; self.actual.len()];
271        let mut expected_existing: Vec<bool> = vec![false; self.expected.len()];
272        for (actual_index, expected_index) in intersection {
273            actual_existing[actual_index] = true;
274            expected_existing[expected_index] = true;
275        }
276
277        let mut only_in_actual = vec![];
278        for (index, exist) in actual_existing.iter().enumerate() {
279            if !exist {
280                only_in_actual.push(index);
281            }
282        }
283
284        let mut only_in_expected = vec![];
285        for (index, exist) in expected_existing.iter().enumerate() {
286            if !exist {
287                only_in_expected.push(index);
288            }
289        }
290
291        (only_in_actual, only_in_expected)
292    }
293
294    fn intersection_indexes(&self) -> HashMap<ActualIndex, ExpectedIndex> {
295        let mut equals_indexes: HashMap<usize, usize> = HashMap::new();
296
297        for (expected_index, expected_elem) in self.expected.iter().enumerate() {
298            for (actual_index, actual_elem) in self.actual.iter().enumerate() {
299                let actual_index_was_matched = equals_indexes.get(&actual_index).is_some();
300                if actual_index_was_matched {
301                    continue;
302                }
303
304                let elements_matches =
305                    (self.element_matcher)(&actual_elem.value, &expected_elem.value);
306                if elements_matches {
307                    equals_indexes.insert(actual_index, expected_index);
308                    break;
309                }
310            }
311        }
312
313        equals_indexes
314    }
315}
316
317impl<T> CollectionEqual<T> for ListFinalAssert<T> {
318    fn in_any_order(&self) {
319        let error = length_check(self.actual.len(), self.expected.len()).err();
320        if let Some(error_message) = error {
321            self.list_test_failed(&error_message);
322        }
323        let result = self.contains_in_any_order();
324        if let Some(error) = result.err() {
325            self.list_test_failed(&error);
326        }
327    }
328
329    fn in_order(&self) {
330        let error = length_check(self.actual.len(), self.expected.len()).err();
331        if let Some(error_message) = error {
332            self.list_test_failed(&error_message);
333        }
334        let mut errors: Vec<String> = vec![];
335
336        for index in 0..self.actual.len() {
337            let actual_elem = &self.actual[index];
338            let expected_elem = &self.expected[index];
339            if !(self.element_matcher)(&actual_elem.value, &expected_elem.value) {
340                errors.push(format!(
341                    "Actual element: ({}) not equal to Expected: ({}) in position ({})\n",
342                    actual_elem, expected_elem, index
343                ));
344            }
345        }
346
347        if !errors.is_empty() {
348            let error_message = format!("\n {}", errors.join("\n"));
349            self.list_test_failed(&error_message);
350        }
351    }
352}
353
354impl<T> CollectionNotEqual<T> for ListFinalAssert<T> {
355    fn in_any_order(&self) {
356        if self.actual.len() != self.expected.len() {
357            return;
358        }
359        let result = self.contains_in_any_order();
360        if result.is_ok() {
361            self.list_test_failed("Collections are equals, but should not. Collections considered equals if they has same elements in any order.");
362        }
363    }
364
365    fn in_order(&self) {
366        if self.actual.len() != self.expected.len() {
367            return;
368        }
369
370        for index in 0..self.actual.len() {
371            let actual_elem = &self.actual[index];
372            let expected_elem = &self.expected[index];
373            if !(self.element_matcher)(&actual_elem.value, &expected_elem.value) {
374                return;
375            }
376        }
377
378        self.list_test_failed("Collections has same elements in same order, they are equal");
379    }
380}
381
382impl<T> CollectionContains<T> for ListFinalAssert<T> {
383    fn in_any_order(&self) {
384        let result = self.contains_in_any_order();
385        if let Some(error) = result.err() {
386            self.list_test_failed(&error);
387        }
388    }
389
390    fn in_exact_order(&self) {
391        //[1,2,2,2,5,2,3,4,5].contains[2,3,4]
392        let error = self.actual_len_ge_expected().err();
393        if let Some(error_message) = error {
394            self.list_test_failed(&error_message);
395        }
396
397        let first_expected_elem = &self.expected[0];
398        let mut checked_actual_indexes: Vec<usize> = Vec::new();
399        let mut matched = false;
400        loop {
401            let starting_position =
402                self.get_unchecked_index(first_expected_elem, &checked_actual_indexes);
403            if starting_position.is_none() {
404                break;
405            }
406            let starting_position = starting_position.unwrap();
407            checked_actual_indexes.push(starting_position);
408
409            matched = self.actual_contains_exact_expected_slice(starting_position);
410            if matched {
411                break;
412            }
413        }
414
415        if !matched {
416            self.list_test_failed(
417                "\n the Actual vec  doesn't contains Expected vec in Exact order",
418            );
419        }
420    }
421
422    fn just_in_order(&self) {
423        //[1,2,8,9,5,7,3,1,4].contains[2,3,4]
424        let result = self.actual_len_ge_expected();
425        if let Some(error_message) = result.err() {
426            self.list_test_failed(&error_message);
427        }
428        let mut prev_number_index: usize = 0;
429        let mut matched_length: usize = 0;
430        for expected_elem in &self.expected {
431            let start_index = prev_number_index;
432            for actual_index in start_index..self.actual.len() {
433                let actual_elem = &self.actual[actual_index];
434                if (self.element_matcher)(&actual_elem.value, &expected_elem.value) {
435                    prev_number_index = actual_index;
436                    matched_length += 1;
437                    break;
438                }
439            }
440        }
441
442        if matched_length != self.expected.len() {
443            let error_message = format!("\n the Actual vec doesn't contains Expected vec Just in order. Matched only {} elements",
444                   matched_length
445            );
446            self.list_test_failed(&error_message);
447        }
448    }
449}
450
451impl<T> CollectionNotContain<T> for ListFinalAssert<T> {
452    fn all(&self) {
453        if self.actual.len() < self.expected.len() {
454            return;
455        }
456
457        let intersection = self.intersection_indexes();
458        if intersection.is_empty() {
459            return;
460        }
461
462        let mut errors: Vec<String> = vec![];
463        for (actual_index, expected_index) in intersection {
464            errors.push(format!(
465                "   - Element was found ({}). Actual index ({}), Expected index ({})",
466                &self.actual[actual_index], actual_index, expected_index,
467            ));
468        }
469        self.list_test_failed(&errors.join("\n"));
470    }
471
472    fn at_least_one(&self) {
473        let intersection = self.intersection_indexes();
474        if intersection.len() == self.expected.len() {
475            self.list_test_failed("All expected elements were found in Actual vec");
476        }
477    }
478}
479
480impl<T> Length for ListAssert<T> {
481    fn is(&self, expected: Expected<usize>) {
482        let error = length_check(self.actual.len(), expected.value).err();
483        if let Some(error_message) = error {
484            test_failed(&error_message);
485        }
486    }
487}
488
489fn length_check(actual: usize, expected: usize) -> Result<(), String> {
490    if actual != expected {
491        return Err(format!(
492            "\n Actual collection size: {} \n          not equal to \n Expected: {} \n",
493            actual, expected,
494        ));
495    };
496    Ok(())
497}