• 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::and`] and [`all!`].
25 ///
26 /// Both [`Matcher::and`] and [`all!`] nest on m1. In other words,
27 /// both `x.and(y).and(z)` and `all![x, y, z]` produce:
28 /// ```ignore
29 /// ConjunctionMatcher {
30 ///     m1: ConjunctionMatcher {
31 ///         m1: x,
32 ///         m2: y
33 ///     },
34 ///     m2: z
35 /// }
36 /// ```
37 ///
38 /// This behavior must be respected
39 /// to ensure that [`Matcher::explain_match`] and [`Matcher::describe`] produce
40 /// useful descriptions.
41 ///
42 /// **For internal use only. API stablility is not guaranteed!**
43 #[doc(hidden)]
44 #[derive(MatcherBase)]
45 pub struct ConjunctionMatcher<M1, M2> {
46     m1: M1,
47     m2: M2,
48 }
49 
50 impl<M1, M2> ConjunctionMatcher<M1, M2> {
new(m1: M1, m2: M2) -> Self51     pub fn new(m1: M1, m2: M2) -> Self {
52         Self { m1, m2 }
53     }
54 }
55 
56 impl<T: Debug + Copy, M1: Matcher<T>, M2: Matcher<T>> Matcher<T> for ConjunctionMatcher<M1, M2> {
matches(&self, actual: T) -> MatcherResult57     fn matches(&self, actual: T) -> MatcherResult {
58         match (self.m1.matches(actual), self.m2.matches(actual)) {
59             (MatcherResult::Match, MatcherResult::Match) => MatcherResult::Match,
60             _ => MatcherResult::NoMatch,
61         }
62     }
63 
explain_match(&self, actual: T) -> Description64     fn explain_match(&self, actual: T) -> Description {
65         match (self.m1.matches(actual), self.m2.matches(actual)) {
66             (MatcherResult::NoMatch, MatcherResult::Match) => self.m1.explain_match(actual),
67             (MatcherResult::Match, MatcherResult::NoMatch) => self.m2.explain_match(actual),
68             (_, _) => {
69                 let m1_description = self.m1.explain_match(actual);
70                 if m1_description.is_conjunction_description() {
71                     m1_description.nested(self.m2.explain_match(actual))
72                 } else {
73                     Description::new()
74                         .bullet_list()
75                         .collect([m1_description, self.m2.explain_match(actual)])
76                         .conjunction_description()
77                 }
78             }
79         }
80     }
81 
describe(&self, matcher_result: MatcherResult) -> Description82     fn describe(&self, matcher_result: MatcherResult) -> Description {
83         let m1_description = self.m1.describe(matcher_result);
84         if m1_description.is_conjunction_description() {
85             m1_description.push_in_last_nested(self.m2.describe(matcher_result))
86         } else {
87             let header = if matcher_result.into() {
88                 "has all the following properties:"
89             } else {
90                 "has at least one of the following properties:"
91             };
92             Description::new()
93                 .text(header)
94                 .nested(
95                     Description::new()
96                         .bullet_list()
97                         .collect([m1_description, self.m2.describe(matcher_result)]),
98                 )
99                 .conjunction_description()
100         }
101     }
102 }
103 
104 #[cfg(test)]
105 mod tests {
106     use crate::prelude::*;
107     use indoc::indoc;
108 
109     #[test]
and_true_true_matches() -> Result<()>110     fn and_true_true_matches() -> Result<()> {
111         verify_that!(1, anything().and(anything()))
112     }
113 
114     #[test]
and_true_false_does_not_match() -> Result<()>115     fn and_true_false_does_not_match() -> Result<()> {
116         let result = verify_that!(1, anything().and(not(anything())));
117         verify_that!(
118             result,
119             err(displays_as(contains_substring(indoc!(
120                 "
121                 Value of: 1
122                 Expected: has all the following properties:
123                   * is anything
124                   * never matches
125                 Actual: 1,
126                   which is anything
127                 "
128             ))))
129         )
130     }
131 
132     #[test]
and_false_true_does_not_match() -> Result<()>133     fn and_false_true_does_not_match() -> Result<()> {
134         let result = verify_that!(1, not(anything()).and(anything()));
135         verify_that!(
136             result,
137             err(displays_as(contains_substring(indoc!(
138                 "
139                     Value of: 1
140                     Expected: has all the following properties:
141                       * never matches
142                       * is anything
143                     Actual: 1,
144                       which is anything
145                 "
146             ))))
147         )
148     }
149 
150     #[test]
and_false_false_does_not_match() -> Result<()>151     fn and_false_false_does_not_match() -> Result<()> {
152         let result = verify_that!(1, not(anything()).and(not(anything())));
153         verify_that!(
154             result,
155             err(displays_as(contains_substring(indoc!(
156                 "
157                 Value of: 1
158                 Expected: has all the following properties:
159                   * never matches
160                   * never matches
161                 Actual: 1,
162                   * which is anything
163                   * which is anything
164                 "
165             ))))
166         )
167     }
168 
169     #[test]
and_long_chain_of_matchers() -> Result<()>170     fn and_long_chain_of_matchers() -> Result<()> {
171         let result = verify_that!(
172             1,
173             anything().and(not(anything())).and(anything()).and(not(anything())).and(anything())
174         );
175         verify_that!(
176             result,
177             err(displays_as(contains_substring(indoc!(
178                 "
179                 Value of: 1
180                 Expected: has all the following properties:
181                   * is anything
182                   * never matches
183                   * is anything
184                   * never matches
185                   * is anything
186                 Actual: 1,
187                   * which is anything
188                   * which is anything
189                 "
190             ))))
191         )
192     }
193 
194     #[test]
chained_and_matches() -> Result<()>195     fn chained_and_matches() -> Result<()> {
196         #[derive(Debug, Clone, Copy)]
197         struct Struct {
198             a: i32,
199             b: i32,
200             c: i32,
201         }
202         verify_that!(
203             Struct { a: 1, b: 2, c: 3 },
204             field!(Struct.a, eq(1)).and(field!(Struct.b, eq(2))).and(field!(Struct.c, eq(3)))
205         )
206     }
207 
208     #[test]
works_with_str_slices() -> Result<()>209     fn works_with_str_slices() -> Result<()> {
210         verify_that!("A string", starts_with("A").and(ends_with("string")))
211     }
212 
213     #[test]
works_with_owned_strings() -> Result<()>214     fn works_with_owned_strings() -> Result<()> {
215         verify_that!("A string".to_string(), starts_with("A").and(ends_with("string")))
216     }
217 }
218