• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 // There are no visible documentation elements in this module.
16 #![doc(hidden)]
17 
18 use crate::{
19     description::Description,
20     matcher::{Matcher, MatcherBase, MatcherResult},
21 };
22 use std::fmt::Debug;
23 
24 /// Matcher created by [`Matcher::or`] and [`any!`].
25 ///
26 /// Both [`Matcher::or`] and [`any!`] nest on m1. In other words,
27 /// both `x.or(y).or(z)` and `any![x, y, z]` produce:
28 /// ```ignore
29 /// DisjunctionMatcher {
30 ///     m1: DisjunctionMatcher {
31 ///         m1: x, m2: y
32 ///     },
33 ///     m2: z
34 /// }
35 /// ```
36 /// **For internal use only. API stablility is not guaranteed!**
37 #[doc(hidden)]
38 #[derive(MatcherBase)]
39 pub struct DisjunctionMatcher<M1, M2> {
40     m1: M1,
41     m2: M2,
42 }
43 
44 impl<M1, M2> DisjunctionMatcher<M1, M2> {
new(m1: M1, m2: M2) -> Self45     pub fn new(m1: M1, m2: M2) -> Self {
46         Self { m1, m2 }
47     }
48 }
49 
50 impl<T: Debug + Copy, M1: Matcher<T>, M2: Matcher<T>> Matcher<T> for DisjunctionMatcher<M1, M2> {
matches(&self, actual: T) -> MatcherResult51     fn matches(&self, actual: T) -> MatcherResult {
52         match (self.m1.matches(actual), self.m2.matches(actual)) {
53             (MatcherResult::NoMatch, MatcherResult::NoMatch) => MatcherResult::NoMatch,
54             _ => MatcherResult::Match,
55         }
56     }
57 
explain_match(&self, actual: T) -> Description58     fn explain_match(&self, actual: T) -> Description {
59         match (self.m1.matches(actual), self.m2.matches(actual)) {
60             (MatcherResult::NoMatch, MatcherResult::Match) => self.m1.explain_match(actual),
61             (MatcherResult::Match, MatcherResult::NoMatch) => self.m2.explain_match(actual),
62             (_, _) => {
63                 let m1_description = self.m1.explain_match(actual);
64                 if m1_description.is_disjunction_description() {
65                     m1_description.nested(self.m2.explain_match(actual))
66                 } else {
67                     Description::new()
68                         .bullet_list()
69                         .collect([m1_description, self.m2.explain_match(actual)])
70                         .disjunction_description()
71                 }
72             }
73         }
74     }
75 
describe(&self, matcher_result: MatcherResult) -> Description76     fn describe(&self, matcher_result: MatcherResult) -> Description {
77         let m1_description = self.m1.describe(matcher_result);
78         if m1_description.is_disjunction_description() {
79             m1_description.push_in_last_nested(self.m2.describe(matcher_result))
80         } else {
81             let header = if matcher_result.into() {
82                 "has at least one of the following properties:"
83             } else {
84                 "has all of the following properties:"
85             };
86             Description::new()
87                 .text(header)
88                 .nested(
89                     Description::new()
90                         .bullet_list()
91                         .collect([m1_description, self.m2.describe(matcher_result)]),
92                 )
93                 .disjunction_description()
94         }
95     }
96 }
97 
98 #[cfg(test)]
99 mod tests {
100     use crate::prelude::*;
101     use indoc::indoc;
102 
103     #[test]
or_true_true_matches() -> Result<()>104     fn or_true_true_matches() -> Result<()> {
105         verify_that!(1, anything().or(anything()))
106     }
107 
108     #[test]
or_true_false_matches() -> Result<()>109     fn or_true_false_matches() -> Result<()> {
110         verify_that!(1, anything().or(not(anything())))
111     }
112 
113     #[test]
or_false_true_matches() -> Result<()>114     fn or_false_true_matches() -> Result<()> {
115         verify_that!(1, not(anything()).or(anything()))
116     }
117 
118     #[test]
or_false_false_does_not_match() -> Result<()>119     fn or_false_false_does_not_match() -> Result<()> {
120         let result = verify_that!(1, not(anything()).or(not(anything())));
121         verify_that!(
122             result,
123             err(displays_as(contains_substring(indoc!(
124                 "
125                 Value of: 1
126                 Expected: has at least one of the following properties:
127                   * never matches
128                   * never matches
129                 Actual: 1,
130                   * which is anything
131                   * which is anything
132                 "
133             ))))
134         )
135     }
136 
137     #[test]
chained_or_matches() -> Result<()>138     fn chained_or_matches() -> Result<()> {
139         verify_that!(10, eq(1).or(eq(5)).or(ge(9)))
140     }
141 
142     #[test]
works_with_str_slices() -> Result<()>143     fn works_with_str_slices() -> Result<()> {
144         verify_that!("A string", ends_with("A").or(ends_with("string")))
145     }
146 
147     #[test]
works_with_owned_strings() -> Result<()>148     fn works_with_owned_strings() -> Result<()> {
149         verify_that!("A string".to_string(), ends_with("A").or(ends_with("string")))
150     }
151 }
152