1 // Copyright 2023 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 use crate::{
16 description::Description,
17 matcher::{Matcher, MatcherBase, MatcherResult},
18 };
19 use std::fmt::Debug;
20
21 /// Matches a string whose number of Unicode scalars matches `expected`.
22 ///
23 /// In other words, the argument must match the output of
24 /// [`actual_string.chars().count()`][std::str::Chars].
25 ///
26 /// This can have surprising effects when what appears to be a single character
27 /// is composed of multiple Unicode scalars. See [Rust documentation on
28 /// character
29 /// representation](https://doc.rust-lang.org/std/primitive.char.html#representation)
30 /// for more information.
31 ///
32 /// This matches against owned strings and string slices.
33 ///
34 /// ```
35 /// # use googletest::prelude::*;
36 /// # fn should_pass() -> Result<()> {
37 /// let string_slice = "A string";
38 /// verify_that!(string_slice, char_count(eq(8)))?;
39 /// let non_ascii_string_slice = "Ä ſtřiɲğ";
40 /// verify_that!(non_ascii_string_slice, char_count(eq(8)))?;
41 /// let owned_string = String::from("A string");
42 /// verify_that!(owned_string, char_count(eq(8)))?;
43 /// # Ok(())
44 /// # }
45 /// # should_pass().unwrap();
46 /// ```
47 ///
48 /// The parameter `expected` can be any integer numeric matcher.
49 ///
50 /// ```
51 /// # use googletest::prelude::*;
52 /// # fn should_pass() -> Result<()> {
53 /// let string_slice = "A string";
54 /// verify_that!(string_slice, char_count(gt(4)))?;
55 /// # Ok(())
56 /// # }
57 /// # should_pass().unwrap();
58 /// ```
char_count<E: Matcher<usize>>(expected: E) -> CharLenMatcher<E>59 pub fn char_count<E: Matcher<usize>>(expected: E) -> CharLenMatcher<E> {
60 CharLenMatcher { expected }
61 }
62
63 #[derive(MatcherBase)]
64 pub struct CharLenMatcher<E> {
65 expected: E,
66 }
67
68 impl<T: Debug + Copy + AsRef<str>, E: Matcher<usize>> Matcher<T> for CharLenMatcher<E> {
matches(&self, actual: T) -> MatcherResult69 fn matches(&self, actual: T) -> MatcherResult {
70 self.expected.matches(actual.as_ref().chars().count())
71 }
72
describe(&self, matcher_result: MatcherResult) -> Description73 fn describe(&self, matcher_result: MatcherResult) -> Description {
74 match matcher_result {
75 MatcherResult::Match => format!(
76 "has character count, which {}",
77 self.expected.describe(MatcherResult::Match)
78 )
79 .into(),
80 MatcherResult::NoMatch => format!(
81 "has character count, which {}",
82 self.expected.describe(MatcherResult::NoMatch)
83 )
84 .into(),
85 }
86 }
87
explain_match(&self, actual: T) -> Description88 fn explain_match(&self, actual: T) -> Description {
89 let actual_size = actual.as_ref().chars().count();
90 format!(
91 "which has character count {}, {}",
92 actual_size,
93 self.expected.explain_match(actual_size)
94 )
95 .into()
96 }
97 }
98
99 #[cfg(test)]
100 mod tests {
101 use crate::description::Description;
102 use crate::matcher::MatcherResult;
103 use crate::prelude::*;
104 use indoc::indoc;
105 use std::fmt::Debug;
106
107 #[test]
char_count_matches_string_slice() -> Result<()>108 fn char_count_matches_string_slice() -> Result<()> {
109 let value = "abcd";
110 verify_that!(value, char_count(eq(4)))
111 }
112
113 #[test]
char_count_matches_owned_string() -> Result<()>114 fn char_count_matches_owned_string() -> Result<()> {
115 let value = String::from("abcd");
116 verify_that!(value, char_count(eq(4)))
117 }
118
119 #[test]
char_count_counts_non_ascii_characters_correctly() -> Result<()>120 fn char_count_counts_non_ascii_characters_correctly() -> Result<()> {
121 let value = "äöüß";
122 verify_that!(value, char_count(eq(4)))
123 }
124
125 #[test]
char_count_explains_match() -> Result<()>126 fn char_count_explains_match() -> Result<()> {
127 #[derive(MatcherBase)]
128 struct TestMatcher;
129
130 impl<T: Debug + Copy> Matcher<T> for TestMatcher {
131 fn matches(&self, _: T) -> MatcherResult {
132 false.into()
133 }
134
135 fn describe(&self, _: MatcherResult) -> Description {
136 "called described".into()
137 }
138
139 fn explain_match(&self, _: T) -> Description {
140 "called explain_match".into()
141 }
142 }
143 verify_that!(
144 char_count(TestMatcher).explain_match("A string"),
145 displays_as(eq("which has character count 8, called explain_match"))
146 )
147 }
148
149 #[test]
char_count_has_correct_failure_message() -> Result<()>150 fn char_count_has_correct_failure_message() -> Result<()> {
151 let result = verify_that!("äöüß", char_count(eq(3)));
152 verify_that!(
153 result,
154 err(displays_as(contains_substring(indoc!(
155 r#"
156 Value of: "äöüß"
157 Expected: has character count, which is equal to 3
158 Actual: "äöüß",
159 which has character count 4, which isn't equal to 3"#
160 ))))
161 )
162 }
163 }
164