Skip to content

Commit edf566d

Browse files
committed
Introduce the option::match_some matcher
1 parent 46eb42f commit edf566d

File tree

1 file changed

+77
-2
lines changed

1 file changed

+77
-2
lines changed

src/matchers/option.rs

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,51 @@ impl<T> Matcher<Option<T>> for SomeMatcher {
3030
}
3131
}
3232

33+
/// Matches if `actual` is an [`Option::Some`] *and* the contained value matches the inner matcher.
34+
///
35+
/// [`Option::Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some
36+
///
37+
/// # Examples
38+
///
39+
/// ```
40+
/// # use expect::{expect, matchers::{equal, collection::contain, option::match_some,
41+
/// string::match_regex}};
42+
/// expect(&Some(vec![1, 2, 3])).to(match_some(contain(2)));
43+
/// expect(&Some("foo")).not_to(match_some(match_regex("\\d+")));
44+
/// expect(&None::<&str>).not_to(match_some(equal("foo")));
45+
/// ```
46+
pub fn match_some<I>(inner: I) -> MatchSomeMatcher<I> {
47+
MatchSomeMatcher { inner }
48+
}
49+
50+
pub struct MatchSomeMatcher<I> {
51+
inner: I,
52+
}
53+
54+
impl<T: std::fmt::Debug, M: Matcher<T>> Matcher<Option<T>> for MatchSomeMatcher<M> {
55+
fn match_value(&self, actual: &Option<T>) -> bool {
56+
if let Some(value) = actual {
57+
return self.inner.match_value(value);
58+
}
59+
false
60+
}
61+
62+
fn description(&self, actual: &Option<T>) -> Description {
63+
if let Some(value) = actual {
64+
let inner_desc = self.inner.description(value);
65+
Description {
66+
verb: format!("be a Some and {}", inner_desc.verb),
67+
object: inner_desc.object,
68+
}
69+
} else {
70+
Description {
71+
verb: String::from("be a Some"),
72+
object: None,
73+
}
74+
}
75+
}
76+
}
77+
3378
/// Matches if `actual` is an [`Option::None`].
3479
///
3580
/// [`Option::None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None
@@ -62,8 +107,8 @@ impl<T> Matcher<Option<T>> for NoneMatcher {
62107

63108
#[cfg(test)]
64109
mod tests {
65-
use super::{be_none, be_some};
66-
use crate::Matcher;
110+
use super::{be_none, be_some, match_some};
111+
use crate::{matchers::equal, Matcher};
67112

68113
#[test]
69114
fn some_matcher_should_match_if_actual_is_some() {
@@ -98,4 +143,34 @@ mod tests {
98143
assert_eq!(description.verb, String::from("be None"));
99144
assert_eq!(description.object, None);
100145
}
146+
147+
#[test]
148+
fn match_some_matcher_should_match_if_actual_is_some_and_inner_value_matches_inner_matcher() {
149+
assert!(match_some(equal("foo")).match_value(&Some("foo")))
150+
}
151+
152+
#[test]
153+
fn match_some_matcher_should_not_match_if_actual_is_some_but_inner_value_does_not_match_inner_matcher(
154+
) {
155+
assert!(!match_some(equal("foo")).match_value(&Some("bar")))
156+
}
157+
158+
#[test]
159+
fn match_some_matcher_should_not_match_if_actual_is_not_some() {
160+
assert!(!match_some(equal("foo")).match_value(&None::<&str>))
161+
}
162+
163+
#[test]
164+
fn match_some_matcher_should_describe_itself_when_actual_is_not_some() {
165+
let description = match_some(equal("foo")).description(&None::<&str>);
166+
assert_eq!(description.verb, String::from("be a Some"));
167+
assert_eq!(description.object, None)
168+
}
169+
170+
#[test]
171+
fn match_some_matcher_should_describe_itself_and_its_inner_matcher_when_actual_is_some() {
172+
let description = match_some(equal("foo")).description(&Some("foo"));
173+
assert_eq!(description.verb, String::from("be a Some and equal"));
174+
assert_eq!(description.object, Some(String::from("\"foo\"")))
175+
}
101176
}

0 commit comments

Comments
 (0)