fluent_test/backend/matchers/
collection.rs

1use crate::backend::Assertion;
2use crate::backend::assertions::sentence::AssertionSentence;
3use std::fmt::Debug;
4
5/// Define the primary matcher trait for collections
6pub trait CollectionMatchers<T> {
7    fn to_be_empty(self) -> Self;
8    fn to_have_length(self, expected: usize) -> Self;
9    fn to_contain<U: PartialEq<T> + Debug>(self, expected: U) -> Self;
10    fn to_contain_all_of<U: PartialEq<T> + Debug>(self, expected: &[U]) -> Self;
11    fn to_equal_collection<U: PartialEq<T> + Debug>(self, expected: &[U]) -> Self;
12}
13
14/// Helper trait for types that can be examined as collections
15trait AsCollection {
16    type Item;
17
18    fn is_empty(&self) -> bool;
19    fn length(&self) -> usize;
20    fn contains_item<U>(&self, item: &U) -> bool
21    where
22        U: PartialEq<Self::Item>;
23    fn contains_all_items<U>(&self, items: &[U]) -> bool
24    where
25        U: PartialEq<Self::Item>;
26    fn equals_items<U>(&self, other: &[U]) -> bool
27    where
28        U: PartialEq<Self::Item>;
29}
30
31// Implement AsCollection for slice references
32impl<T: PartialEq> AsCollection for &[T] {
33    type Item = T;
34
35    fn is_empty(&self) -> bool {
36        <[T]>::is_empty(self)
37    }
38
39    fn length(&self) -> usize {
40        self.len()
41    }
42
43    fn contains_item<U>(&self, item: &U) -> bool
44    where
45        U: PartialEq<Self::Item>,
46    {
47        self.iter().any(|x| item == x)
48    }
49
50    fn contains_all_items<U>(&self, items: &[U]) -> bool
51    where
52        U: PartialEq<Self::Item>,
53    {
54        items.iter().all(|item| self.contains_item(item))
55    }
56
57    fn equals_items<U>(&self, other: &[U]) -> bool
58    where
59        U: PartialEq<Self::Item>,
60    {
61        if self.len() != other.len() {
62            return false;
63        }
64
65        self.iter().zip(other.iter()).all(|(a, b)| b == a)
66    }
67}
68
69// Implement AsCollection for Vec references
70impl<T: PartialEq> AsCollection for &Vec<T> {
71    type Item = T;
72
73    fn is_empty(&self) -> bool {
74        Vec::is_empty(self)
75    }
76
77    fn length(&self) -> usize {
78        self.len()
79    }
80
81    fn contains_item<U>(&self, item: &U) -> bool
82    where
83        U: PartialEq<Self::Item>,
84    {
85        self.iter().any(|x| item == x)
86    }
87
88    fn contains_all_items<U>(&self, items: &[U]) -> bool
89    where
90        U: PartialEq<Self::Item>,
91    {
92        items.iter().all(|item| self.contains_item(item))
93    }
94
95    fn equals_items<U>(&self, other: &[U]) -> bool
96    where
97        U: PartialEq<Self::Item>,
98    {
99        if self.len() != other.len() {
100            return false;
101        }
102
103        self.iter().zip(other.iter()).all(|(a, b)| b == a)
104    }
105}
106
107// Implement AsCollection for owned Vecs
108impl<T: PartialEq> AsCollection for Vec<T> {
109    type Item = T;
110
111    fn is_empty(&self) -> bool {
112        Vec::is_empty(self)
113    }
114
115    fn length(&self) -> usize {
116        self.len()
117    }
118
119    fn contains_item<U>(&self, item: &U) -> bool
120    where
121        U: PartialEq<Self::Item>,
122    {
123        self.iter().any(|x| item == x)
124    }
125
126    fn contains_all_items<U>(&self, items: &[U]) -> bool
127    where
128        U: PartialEq<Self::Item>,
129    {
130        items.iter().all(|item| self.contains_item(item))
131    }
132
133    fn equals_items<U>(&self, other: &[U]) -> bool
134    where
135        U: PartialEq<Self::Item>,
136    {
137        if self.len() != other.len() {
138            return false;
139        }
140
141        self.iter().zip(other.iter()).all(|(a, b)| b == a)
142    }
143}
144
145// Implement AsCollection for array references
146impl<T: PartialEq, const N: usize> AsCollection for &[T; N] {
147    type Item = T;
148
149    fn is_empty(&self) -> bool {
150        N == 0
151    }
152
153    fn length(&self) -> usize {
154        N
155    }
156
157    fn contains_item<U>(&self, item: &U) -> bool
158    where
159        U: PartialEq<Self::Item>,
160    {
161        self.iter().any(|x| item == x)
162    }
163
164    fn contains_all_items<U>(&self, items: &[U]) -> bool
165    where
166        U: PartialEq<Self::Item>,
167    {
168        items.iter().all(|item| self.contains_item(item))
169    }
170
171    fn equals_items<U>(&self, other: &[U]) -> bool
172    where
173        U: PartialEq<Self::Item>,
174    {
175        if N != other.len() {
176            return false;
177        }
178
179        self.iter().zip(other.iter()).all(|(a, b)| b == a)
180    }
181}
182
183// Implementation of CollectionMatchers that works with any type implementing AsCollection
184impl<T, V> CollectionMatchers<T> for Assertion<V>
185where
186    T: Debug + Clone + PartialEq,
187    V: AsCollection<Item = T> + Debug + Clone,
188{
189    fn to_be_empty(self) -> Self {
190        let result = self.value.is_empty();
191        let sentence = AssertionSentence::new("be", "empty");
192
193        return self.add_step(sentence, result);
194    }
195
196    fn to_have_length(self, expected: usize) -> Self {
197        let actual_length = self.value.length();
198        let result = actual_length == expected;
199        let sentence = AssertionSentence::new("have", format!("length {}", expected));
200
201        return self.add_step(sentence, result);
202    }
203
204    fn to_contain<U: PartialEq<T> + Debug>(self, expected: U) -> Self {
205        let result = self.value.contains_item(&expected);
206        let sentence = AssertionSentence::new("contain", format!("{:?}", expected));
207
208        return self.add_step(sentence, result);
209    }
210
211    fn to_contain_all_of<U: PartialEq<T> + Debug>(self, expected: &[U]) -> Self {
212        let result = self.value.contains_all_items(expected);
213        let sentence = AssertionSentence::new("contain", format!("all of {:?}", expected));
214
215        return self.add_step(sentence, result);
216    }
217
218    fn to_equal_collection<U: PartialEq<T> + Debug>(self, expected: &[U]) -> Self {
219        let result = self.value.equals_items(expected);
220
221        // Different message if lengths don't match
222        if self.value.length() != expected.len() {
223            let sentence = AssertionSentence::new("equal", format!("collection {:?} (different lengths)", expected));
224            return self.add_step(sentence, result);
225        }
226
227        let sentence = AssertionSentence::new("equal", format!("collection {:?}", expected));
228        return self.add_step(sentence, result);
229    }
230}
231
232/// Extension trait for adding helper methods to collections
233pub trait CollectionExtensions<T> {
234    fn first(&self) -> Option<&T>;
235    fn last(&self) -> Option<&T>;
236}
237
238impl<T> CollectionExtensions<T> for Vec<T> {
239    fn first(&self) -> Option<&T> {
240        return <[T]>::first(self);
241    }
242
243    fn last(&self) -> Option<&T> {
244        return <[T]>::last(self);
245    }
246}
247
248impl<T> CollectionExtensions<T> for &[T] {
249    fn first(&self) -> Option<&T> {
250        return <[T]>::first(self);
251    }
252
253    fn last(&self) -> Option<&T> {
254        return <[T]>::last(self);
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use crate::prelude::*;
261
262    #[test]
263    fn test_collection_length() {
264        // Disable deduplication for tests
265        crate::Reporter::disable_deduplication();
266
267        let collection = vec![1, 2, 3, 4, 5];
268        let slice = collection.as_slice();
269
270        expect!(slice).to_have_length(5);
271        expect!(&collection).to_have_length(5);
272    }
273
274    #[test]
275    #[should_panic(expected = "have length 6")]
276    fn test_wrong_length_fails() {
277        // This should fail
278        let collection = vec![1, 2, 3, 4, 5];
279        let slice = collection.as_slice();
280        expect!(slice).to_have_length(6);
281    }
282
283    #[test]
284    #[should_panic(expected = "not have length 5")]
285    fn test_right_length_not_fails() {
286        // This should fail
287        let collection = vec![1, 2, 3, 4, 5];
288        let slice = collection.as_slice();
289        expect!(slice).not().to_have_length(5);
290    }
291
292    #[test]
293    fn test_collection_contains() {
294        // Disable deduplication for tests
295        crate::Reporter::disable_deduplication();
296
297        let collection = vec![1, 2, 3, 4, 5];
298        let slice = collection.as_slice();
299
300        expect!(slice).to_contain(3);
301        expect!(&collection).to_contain(3);
302    }
303
304    #[test]
305    #[should_panic(expected = "contain 6")]
306    fn test_missing_value_fails() {
307        let collection = vec![1, 2, 3, 4, 5];
308        let slice = collection.as_slice();
309        expect!(slice).to_contain(6);
310    }
311
312    #[test]
313    #[should_panic(expected = "not contain 3")]
314    fn test_present_value_not_fails() {
315        let collection = vec![1, 2, 3, 4, 5];
316        let slice = collection.as_slice();
317        expect!(slice).not().to_contain(3);
318    }
319
320    #[test]
321    fn test_collection_contains_all() {
322        // Disable deduplication for tests
323        crate::Reporter::disable_deduplication();
324
325        let collection = vec![1, 2, 3, 4, 5];
326        let slice = collection.as_slice();
327
328        expect!(slice).to_contain_all_of(&[1, 3, 5]);
329        expect!(&collection).to_contain_all_of(&[1, 3, 5]);
330    }
331
332    #[test]
333    #[should_panic(expected = "contain all of")]
334    fn test_missing_values_fails() {
335        let collection = vec![1, 2, 3, 4, 5];
336        let slice = collection.as_slice();
337        expect!(slice).to_contain_all_of(&[1, 6, 7]);
338    }
339
340    #[test]
341    #[should_panic(expected = "not contain all of")]
342    fn test_present_values_not_fails() {
343        let collection = vec![1, 2, 3, 4, 5];
344        let slice = collection.as_slice();
345        expect!(slice).not().to_contain_all_of(&[1, 3, 5]);
346    }
347
348    #[test]
349    fn test_collection_equality() {
350        // Disable deduplication for tests
351        crate::Reporter::disable_deduplication();
352
353        let collection = vec![1, 2, 3, 4, 5];
354        let slice = collection.as_slice();
355
356        expect!(slice).to_equal_collection(&[1, 2, 3, 4, 5]);
357        expect!(&collection).to_equal_collection(&[1, 2, 3, 4, 5]);
358    }
359
360    #[test]
361    #[should_panic(expected = "equal collection")]
362    fn test_different_collection_fails() {
363        let collection = vec![1, 2, 3, 4, 5];
364        let slice = collection.as_slice();
365        expect!(slice).to_equal_collection(&[5, 4, 3, 2, 1]);
366    }
367
368    #[test]
369    #[should_panic(expected = "different lengths")]
370    fn test_shorter_collection_fails() {
371        let collection = vec![1, 2, 3, 4, 5];
372        let slice = collection.as_slice();
373        expect!(slice).to_equal_collection(&[1, 2, 3]);
374    }
375
376    #[test]
377    #[should_panic(expected = "not equal collection")]
378    fn test_same_collection_not_fails() {
379        let collection = vec![1, 2, 3, 4, 5];
380        let slice = collection.as_slice();
381        expect!(slice).not().to_equal_collection(&[1, 2, 3, 4, 5]);
382    }
383
384    #[test]
385    fn test_empty_collection() {
386        // Disable deduplication for tests
387        crate::Reporter::disable_deduplication();
388
389        let empty: Vec<i32> = vec![];
390        let slice = empty.as_slice();
391
392        expect!(slice).to_be_empty();
393        expect!(&empty).to_be_empty();
394    }
395
396    #[test]
397    #[should_panic(expected = "be empty")]
398    fn test_non_empty_to_be_empty_fails() {
399        let collection = vec![1, 2, 3];
400        let slice = collection.as_slice();
401        expect!(slice).to_be_empty();
402    }
403
404    #[test]
405    #[should_panic(expected = "not be empty")]
406    fn test_empty_not_to_be_empty_fails() {
407        let empty: Vec<i32> = vec![];
408        let slice = empty.as_slice();
409        expect!(slice).not().to_be_empty();
410    }
411}