test_that/matchers/containers/
contains_matcher.rs1use crate::{
17 description::Description,
18 matcher::{Describable, Matcher, MatcherResult},
19 matchers::containers::{OwnedItems, RefItems},
20};
21use alloc::boxed::Box;
22use core::{fmt::Debug, marker::PhantomData};
23
24pub fn contains<InnerMatcherT, ModeT>(
68 inner: InnerMatcherT,
69) -> ContainsMatcher<InnerMatcherT, ModeT> {
70 ContainsMatcher { inner, count: None, phantom: Default::default() }
71}
72
73pub struct ContainsMatcher<InnerMatcherT, ModeT> {
76 inner: InnerMatcherT,
77 count: Option<Box<dyn Matcher<usize>>>,
78 phantom: PhantomData<ModeT>,
79}
80
81impl<InnerMatcherT, ModeT> ContainsMatcher<InnerMatcherT, ModeT> {
82 pub fn times(mut self, count: impl Matcher<usize> + 'static) -> Self {
95 self.count = Some(Box::new(count));
96 self
97 }
98}
99
100impl<T: Debug, InnerMatcherT: Matcher<T>, ContainerT: Debug + ?Sized> Matcher<ContainerT>
105 for ContainsMatcher<InnerMatcherT, OwnedItems>
106where
107 for<'a> &'a ContainerT: IntoIterator<Item = T>,
108{
109 fn matches(&self, actual: &ContainerT) -> MatcherResult {
110 if let Some(count) = &self.count {
111 count.matches(&self.count_matches_owned(actual))
112 } else {
113 for v in actual.into_iter() {
114 if self.inner.matches(&v).into() {
115 return MatcherResult::Match;
116 }
117 }
118 MatcherResult::NoMatch
119 }
120 }
121
122 fn explain_match(&self, actual: &ContainerT) -> Description {
123 let count = self.count_matches_owned(actual);
124 match (count, &self.count) {
125 (_, Some(_)) => format!("which contains {} matching elements", count).into(),
126 (0, None) => "which does not contain a matching element".into(),
127 (_, None) => "which contains a matching element".into(),
128 }
129 }
130}
131
132impl<T: Debug, InnerMatcherT: Matcher<T>, ContainerT: Debug + ?Sized> Matcher<ContainerT>
143 for ContainsMatcher<InnerMatcherT, RefItems>
144where
145 for<'a> &'a ContainerT: IntoIterator<Item = &'a T>,
146{
147 fn matches(&self, actual: &ContainerT) -> MatcherResult {
148 if let Some(count) = &self.count {
149 count.matches(&self.count_matches_ref(actual))
150 } else {
151 for v in actual.into_iter() {
152 if self.inner.matches(v).into() {
153 return MatcherResult::Match;
154 }
155 }
156 MatcherResult::NoMatch
157 }
158 }
159
160 fn explain_match(&self, actual: &ContainerT) -> Description {
161 let count = self.count_matches_ref(actual);
162 match (count, &self.count) {
163 (_, Some(_)) => format!("which contains {} matching elements", count).into(),
164 (0, None) => "which does not contain a matching element".into(),
165 (_, None) => "which contains a matching element".into(),
166 }
167 }
168}
169
170impl<InnerMatcherT: Describable, ModeT> Describable for ContainsMatcher<InnerMatcherT, ModeT> {
171 fn describe(&self, matcher_result: MatcherResult) -> Description {
172 match (matcher_result, &self.count) {
173 (MatcherResult::Match, Some(count)) => format!(
174 "contains n elements which {}\n where n {}",
175 self.inner.describe(MatcherResult::Match),
176 count.describe(MatcherResult::Match)
177 )
178 .into(),
179 (MatcherResult::NoMatch, Some(count)) => format!(
180 "doesn't contain n elements which {}\n where n {}",
181 self.inner.describe(MatcherResult::Match),
182 count.describe(MatcherResult::Match)
183 )
184 .into(),
185 (MatcherResult::Match, None) => format!(
186 "contains at least one element which {}",
187 self.inner.describe(MatcherResult::Match)
188 )
189 .into(),
190 (MatcherResult::NoMatch, None) => {
191 format!("contains no element which {}", self.inner.describe(MatcherResult::Match))
192 .into()
193 }
194 }
195 }
196}
197
198impl<InnerMatcherT, ModeT> ContainsMatcher<InnerMatcherT, ModeT> {
199 fn count_matches_ref<T: Debug, ContainerT: ?Sized>(&self, actual: &ContainerT) -> usize
200 where
201 for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
202 InnerMatcherT: Matcher<T>,
203 {
204 let mut count = 0;
205 for v in actual.into_iter() {
206 if self.inner.matches(v).into() {
207 count += 1;
208 }
209 }
210 count
211 }
212
213 fn count_matches_owned<T: Debug, ContainerT: ?Sized>(&self, actual: &ContainerT) -> usize
214 where
215 for<'b> &'b ContainerT: IntoIterator<Item = T>,
216 InnerMatcherT: Matcher<T>,
217 {
218 let mut count = 0;
219 for v in actual.into_iter() {
220 if self.inner.matches(&v).into() {
221 count += 1;
222 }
223 }
224 count
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::{ContainsMatcher, contains};
231 use crate::matcher::{Describable as _, Matcher, MatcherResult};
232 use crate::matchers::containers::RefItems;
233 use crate::prelude::*;
234 use alloc::{string::String, vec::Vec};
235
236 #[test]
237 fn contains_matches_singleton_slice_with_value() -> TestResult<()> {
238 let matcher = contains(eq(1));
239
240 let result = matcher.matches(&vec![1]);
241
242 verify_that!(result, eq(MatcherResult::Match))
243 }
244
245 #[test]
246 fn contains_matches_singleton_vec_with_value() -> TestResult<()> {
247 let matcher = contains(eq(1));
248
249 let result = matcher.matches(&vec![1]);
250
251 verify_that!(result, eq(MatcherResult::Match))
252 }
253
254 #[test]
255 fn contains_matches_two_element_slice_with_value() -> TestResult<()> {
256 let matcher = contains(eq(1));
257
258 let result = matcher.matches(&[0, 1]);
259
260 verify_that!(result, eq(MatcherResult::Match))
261 }
262
263 #[test]
264 fn contains_does_not_match_singleton_slice_with_wrong_value() -> TestResult<()> {
265 let matcher = contains(eq(1));
266
267 let result = matcher.matches(&[0]);
268
269 verify_that!(result, eq(MatcherResult::NoMatch))
270 }
271
272 #[test]
273 fn contains_does_not_match_empty_slice() -> TestResult<()> {
274 let value = Vec::<i32>::new();
275 verify_that!(value.as_slice(), not(points_to(contains(eq(1)))))
276 }
277
278 #[test]
279 fn contains_matches_slice_with_repeated_value() -> TestResult<()> {
280 let matcher = contains(eq(1)).times(eq(2));
281
282 let result = matcher.matches(&[1, 1]);
283
284 verify_that!(result, eq(MatcherResult::Match))
285 }
286
287 #[test]
288 fn contains_does_not_match_slice_with_too_few_of_value() -> TestResult<()> {
289 let matcher = contains(eq(1)).times(eq(2));
290
291 let result = matcher.matches(&[0, 1]);
292
293 verify_that!(result, eq(MatcherResult::NoMatch))
294 }
295
296 #[test]
297 fn contains_does_not_match_slice_with_too_many_of_value() -> TestResult<()> {
298 let matcher = contains(eq(1)).times(eq(1));
299
300 let result = matcher.matches(&[1, 1]);
301
302 verify_that!(result, eq(MatcherResult::NoMatch))
303 }
304
305 #[test]
306 fn contains_matches_on_vec_of_values() -> TestResult<()> {
307 verify_that!(vec![1, 2, 3], contains(eq(1)))
308 }
309
310 #[test]
311 fn contains_matches_on_array_of_values() -> TestResult<()> {
312 verify_that!([1, 2, 3], contains(eq(1)))
313 }
314
315 #[test]
316 fn contains_matches_on_slice_of_values_with_points_to_slice() -> TestResult<()> {
317 verify_that!(&[1, 2, 3], points_to(contains(eq(1))))
318 }
319
320 #[test]
321 fn contains_matches_on_slice_of_values_with_deref_notation_on_macro() -> TestResult<()> {
322 let slice = &[1, 2, 3];
323 verify_that!(*slice, contains(eq(1)))
324 }
325
326 #[derive(Debug)]
327 struct OwnedItemContainer(Vec<i32>);
328
329 impl<'a> IntoIterator for &'a OwnedItemContainer {
330 type Item = i32;
331 type IntoIter = core::iter::Copied<core::slice::Iter<'a, i32>>;
332 fn into_iter(self) -> Self::IntoIter {
333 self.0.iter().copied()
334 }
335 }
336
337 #[test]
338 fn contains_matches_on_container_when_ref_to_container_has_into_iterator_producing_owned_values()
339 -> TestResult<()> {
340 verify_that!(OwnedItemContainer(vec![1, 2, 3]), contains(eq(1)))
341 }
342
343 #[test]
344 fn contains_matches_vec_of_string_slices() -> TestResult<()> {
345 verify_that!(vec!["String 1", "String 2", "String 3"], contains(contains_substring("1")))
346 }
347
348 #[test]
349 fn contains_matches_vec_of_string_slices_with_non_static_lifetime() -> TestResult<()> {
350 let string_1 = String::from("String 1");
351 let string_2 = String::from("String 2");
352 let string_3 = String::from("String 3");
353 verify_that!(
354 vec![string_1.as_str(), string_2.as_str(), string_3.as_str()],
355 contains(contains_substring("1"))
356 )
357 }
358
359 #[test]
360 fn contains_matches_slice_of_string_slices() -> TestResult<()> {
361 let value = vec!["String 1", "String 2", "String 3"];
362 verify_that!(value.as_slice(), points_to(contains(contains_substring("1"))))
363 }
364
365 #[test]
366 fn contains_matches_slice_of_string_slices_with_non_static_lifetime() -> TestResult<()> {
367 let string_1 = String::from("String 1");
368 let string_2 = String::from("String 2");
369 let string_3 = String::from("String 3");
370 let value = vec![string_1.as_str(), string_2.as_str(), string_3.as_str()];
371 verify_that!(value.as_slice(), points_to(contains(contains_substring("1"))))
372 }
373
374 #[test]
375 fn contains_formats_without_multiplicity_by_default() -> TestResult<()> {
376 let matcher: ContainsMatcher<_, RefItems> = contains(eq(1));
377
378 verify_that!(
379 matcher.describe(MatcherResult::Match),
380 displays_as(eq("contains at least one element which is equal to 1"))
381 )
382 }
383
384 #[test]
385 fn contains_formats_with_multiplicity_when_specified() -> TestResult<()> {
386 let matcher: ContainsMatcher<_, RefItems> = contains(eq(1)).times(eq(2));
387
388 verify_that!(
389 matcher.describe(MatcherResult::Match),
390 displays_as(eq("contains n elements which is equal to 1\n where n is equal to 2"))
391 )
392 }
393
394 #[test]
395 fn contains_mismatch_shows_number_of_times_element_was_found() -> TestResult<()> {
396 verify_that!(
397 contains(eq(3)).times(eq(1)).explain_match(&vec![1, 2, 3, 3]),
398 displays_as(eq("which contains 2 matching elements"))
399 )
400 }
401
402 #[test]
403 fn contains_mismatch_shows_when_matches() -> TestResult<()> {
404 verify_that!(
405 contains(eq(3)).explain_match(&vec![1, 2, 3, 3]),
406 displays_as(eq("which contains a matching element"))
407 )
408 }
409
410 #[test]
411 fn contains_mismatch_shows_when_no_matches() -> TestResult<()> {
412 verify_that!(
413 contains(eq(3)).explain_match(&vec![1, 2]),
414 displays_as(eq("which does not contain a matching element"))
415 )
416 }
417}