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