fluent_test/backend/matchers/
hashmap.rs

1use crate::backend::Assertion;
2use crate::backend::assertions::sentence::AssertionSentence;
3use std::borrow::Borrow;
4use std::collections::HashMap;
5use std::fmt::Debug;
6use std::hash::Hash;
7
8pub trait HashMapMatchers<K, V> {
9    fn to_be_empty(self) -> Self;
10    fn to_have_length(self, expected: usize) -> Self;
11    fn to_contain_key<Q>(self, key: &Q) -> Self
12    where
13        K: Borrow<Q>,
14        Q: Hash + Eq + Debug + ?Sized;
15    fn to_contain_entry<Q, R>(self, key: &Q, value: &R) -> Self
16    where
17        K: Borrow<Q>,
18        V: Borrow<R>,
19        Q: Hash + Eq + Debug + ?Sized,
20        R: PartialEq + Debug + ?Sized;
21}
22
23/// Helper trait for HashMap-like types
24trait AsHashMap<K, V> {
25    fn is_map_empty(&self) -> bool;
26    fn map_length(&self) -> usize;
27    fn map_contains_key<Q>(&self, key: &Q) -> bool
28    where
29        K: Borrow<Q>,
30        Q: Hash + Eq + ?Sized;
31    fn map_contains_entry<Q, R>(&self, key: &Q, value: &R) -> bool
32    where
33        K: Borrow<Q>,
34        V: Borrow<R>,
35        Q: Hash + Eq + ?Sized,
36        R: PartialEq + ?Sized;
37}
38
39// Implementation for &HashMap<K, V>
40impl<K, V> AsHashMap<K, V> for &HashMap<K, V>
41where
42    K: Hash + Eq,
43    V: Clone,
44{
45    fn is_map_empty(&self) -> bool {
46        self.is_empty()
47    }
48
49    fn map_length(&self) -> usize {
50        self.len()
51    }
52
53    fn map_contains_key<Q>(&self, key: &Q) -> bool
54    where
55        K: Borrow<Q>,
56        Q: Hash + Eq + ?Sized,
57    {
58        self.contains_key(key)
59    }
60
61    fn map_contains_entry<Q, R>(&self, key: &Q, value: &R) -> bool
62    where
63        K: Borrow<Q>,
64        V: Borrow<R>,
65        Q: Hash + Eq + ?Sized,
66        R: PartialEq + ?Sized,
67    {
68        self.get(key).is_some_and(|v| v.borrow() == value)
69    }
70}
71
72// Implementation for HashMap<K, V>
73impl<K, V> AsHashMap<K, V> for HashMap<K, V>
74where
75    K: Hash + Eq,
76    V: Clone,
77{
78    fn is_map_empty(&self) -> bool {
79        self.is_empty()
80    }
81
82    fn map_length(&self) -> usize {
83        self.len()
84    }
85
86    fn map_contains_key<Q>(&self, key: &Q) -> bool
87    where
88        K: Borrow<Q>,
89        Q: Hash + Eq + ?Sized,
90    {
91        self.contains_key(key)
92    }
93
94    fn map_contains_entry<Q, R>(&self, key: &Q, value: &R) -> bool
95    where
96        K: Borrow<Q>,
97        V: Borrow<R>,
98        Q: Hash + Eq + ?Sized,
99        R: PartialEq + ?Sized,
100    {
101        self.get(key).is_some_and(|v| v.borrow() == value)
102    }
103}
104
105// Single implementation for any type that implements AsHashMap
106impl<M, K, V> HashMapMatchers<K, V> for Assertion<M>
107where
108    K: Hash + Eq + Debug + Clone,
109    V: Debug + Clone,
110    M: AsHashMap<K, V> + Debug + Clone,
111{
112    fn to_be_empty(self) -> Self {
113        let result = self.value.is_map_empty();
114        let sentence = AssertionSentence::new("be", "empty");
115
116        return self.add_step(sentence, result);
117    }
118
119    fn to_have_length(self, expected: usize) -> Self {
120        let actual_length = self.value.map_length();
121        let result = actual_length == expected;
122        let sentence = AssertionSentence::new("have", format!("length {}", expected));
123
124        return self.add_step(sentence, result);
125    }
126
127    fn to_contain_key<Q>(self, key: &Q) -> Self
128    where
129        K: Borrow<Q>,
130        Q: Hash + Eq + Debug + ?Sized,
131    {
132        let result = self.value.map_contains_key(key);
133        let sentence = AssertionSentence::new("contain", format!("key {:?}", key));
134
135        return self.add_step(sentence, result);
136    }
137
138    fn to_contain_entry<Q, R>(self, key: &Q, value: &R) -> Self
139    where
140        K: Borrow<Q>,
141        V: Borrow<R>,
142        Q: Hash + Eq + Debug + ?Sized,
143        R: PartialEq + Debug + ?Sized,
144    {
145        let result = self.value.map_contains_entry(key, value);
146        let sentence = AssertionSentence::new("contain", format!("entry ({:?}, {:?})", key, value));
147
148        return self.add_step(sentence, result);
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use crate::prelude::*;
155    use std::collections::HashMap;
156
157    #[test]
158    fn test_hashmap_to_be_empty() {
159        // Disable deduplication for tests
160        crate::Reporter::disable_deduplication();
161
162        let empty: HashMap<i32, i32> = HashMap::new();
163        let not_empty: HashMap<i32, i32> = [(1, 2)].iter().cloned().collect();
164
165        // These should pass
166        expect!(&empty).to_be_empty();
167        expect!(&not_empty).not().to_be_empty();
168    }
169
170    #[test]
171    #[should_panic(expected = "be empty")]
172    fn test_non_empty_to_be_empty_fails() {
173        let map: HashMap<i32, i32> = [(1, 2)].iter().cloned().collect();
174        let _assertion = expect!(&map).to_be_empty();
175        std::hint::black_box(_assertion);
176    }
177
178    #[test]
179    #[should_panic(expected = "not be empty")]
180    fn test_empty_not_to_be_empty_fails() {
181        let map: HashMap<i32, i32> = HashMap::new();
182        let _assertion = expect!(&map).not().to_be_empty();
183        std::hint::black_box(_assertion);
184    }
185
186    #[test]
187    fn test_hashmap_to_have_length() {
188        // Disable deduplication for tests
189        crate::Reporter::disable_deduplication();
190
191        let map: HashMap<i32, i32> = [(1, 2), (3, 4)].iter().cloned().collect();
192
193        // These should pass
194        expect!(&map).to_have_length(2);
195        expect!(&map).not().to_have_length(3);
196    }
197
198    #[test]
199    #[should_panic(expected = "have length")]
200    fn test_wrong_length_fails() {
201        let map: HashMap<i32, i32> = [(1, 2)].iter().cloned().collect();
202        let _assertion = expect!(&map).to_have_length(2);
203        std::hint::black_box(_assertion);
204    }
205
206    #[test]
207    #[should_panic(expected = "not have length")]
208    fn test_right_length_not_fails() {
209        let map: HashMap<i32, i32> = [(1, 2)].iter().cloned().collect();
210        let _assertion = expect!(&map).not().to_have_length(1);
211        std::hint::black_box(_assertion);
212    }
213
214    #[test]
215    fn test_hashmap_to_contain_key() {
216        // Disable deduplication for tests
217        crate::Reporter::disable_deduplication();
218
219        let map: HashMap<i32, i32> = [(1, 2), (3, 4)].iter().cloned().collect();
220
221        // These should pass
222        expect!(&map).to_contain_key(&1);
223        expect!(&map).not().to_contain_key(&2);
224    }
225
226    #[test]
227    #[should_panic(expected = "not contain key")]
228    fn test_present_key_not_fails() {
229        let map: HashMap<i32, i32> = [(1, 2)].iter().cloned().collect();
230        let _assertion = expect!(&map).not().to_contain_key(&1);
231        std::hint::black_box(_assertion);
232    }
233
234    #[test]
235    #[should_panic(expected = "contain key")]
236    fn test_missing_key_fails() {
237        let map: HashMap<i32, i32> = [(1, 2)].iter().cloned().collect();
238        let _assertion = expect!(&map).to_contain_key(&2);
239        std::hint::black_box(_assertion);
240    }
241
242    #[test]
243    fn test_hashmap_to_contain_entry() {
244        // Disable deduplication for tests
245        crate::Reporter::disable_deduplication();
246
247        let map: HashMap<i32, i32> = [(1, 2), (3, 4)].iter().cloned().collect();
248
249        // These should pass
250        expect!(&map).to_contain_entry(&1, &2);
251        expect!(&map).not().to_contain_entry(&1, &3);
252        expect!(&map).not().to_contain_entry(&2, &3);
253    }
254
255    #[test]
256    #[should_panic(expected = "not contain entry")]
257    fn test_right_entry_not_fails() {
258        let map: HashMap<i32, i32> = [(1, 2)].iter().cloned().collect();
259        let _assertion = expect!(&map).not().to_contain_entry(&1, &2);
260        std::hint::black_box(_assertion);
261    }
262
263    #[test]
264    #[should_panic(expected = "contain entry")]
265    fn test_missing_key_entry_fails() {
266        let map: HashMap<i32, i32> = [(1, 2)].iter().cloned().collect();
267        let _assertion = expect!(&map).to_contain_entry(&2, &3);
268        std::hint::black_box(_assertion);
269    }
270
271    #[test]
272    #[should_panic(expected = "contain entry")]
273    fn test_wrong_value_fails() {
274        let map: HashMap<i32, i32> = [(1, 2)].iter().cloned().collect();
275        let _assertion = expect!(&map).to_contain_entry(&1, &3);
276        std::hint::black_box(_assertion);
277    }
278}