fluent_test/backend/matchers/
string.rs

1use crate::backend::Assertion;
2use crate::backend::assertions::sentence::AssertionSentence;
3use std::fmt::Debug;
4
5/// Trait for string assertions
6pub trait StringMatchers {
7    fn to_be_empty(self) -> Self;
8    fn to_have_length(self, expected: usize) -> Self;
9
10    /// Check if the string contains a substring
11    fn to_contain(self, substring: &str) -> Self;
12
13    /// Type-specific version of to_contain to avoid trait conflicts
14    fn to_contain_substring(self, substring: &str) -> Self;
15
16    fn to_start_with(self, prefix: &str) -> Self;
17    fn to_end_with(self, suffix: &str) -> Self;
18    fn to_match(self, pattern: &str) -> Self;
19}
20
21/// Helper trait for string-like types
22trait AsString {
23    fn is_empty_string(&self) -> bool;
24    fn length_string(&self) -> usize;
25    fn contains_substring(&self, substring: &str) -> bool;
26    fn starts_with_substring(&self, prefix: &str) -> bool;
27    fn ends_with_substring(&self, suffix: &str) -> bool;
28    fn matches_pattern(&self, pattern: &str) -> bool;
29}
30
31// Implementation for String
32impl AsString for String {
33    fn is_empty_string(&self) -> bool {
34        self.is_empty()
35    }
36
37    fn length_string(&self) -> usize {
38        self.len()
39    }
40
41    fn contains_substring(&self, substring: &str) -> bool {
42        self.contains(substring)
43    }
44
45    fn starts_with_substring(&self, prefix: &str) -> bool {
46        self.starts_with(prefix)
47    }
48
49    fn ends_with_substring(&self, suffix: &str) -> bool {
50        self.ends_with(suffix)
51    }
52
53    fn matches_pattern(&self, pattern: &str) -> bool {
54        // Simple implementation - in a real regex impl this would be different
55        self.contains(pattern)
56    }
57}
58
59// Implementation for &str
60impl AsString for &str {
61    fn is_empty_string(&self) -> bool {
62        self.is_empty()
63    }
64
65    fn length_string(&self) -> usize {
66        self.len()
67    }
68
69    fn contains_substring(&self, substring: &str) -> bool {
70        self.contains(substring)
71    }
72
73    fn starts_with_substring(&self, prefix: &str) -> bool {
74        self.starts_with(prefix)
75    }
76
77    fn ends_with_substring(&self, suffix: &str) -> bool {
78        self.ends_with(suffix)
79    }
80
81    fn matches_pattern(&self, pattern: &str) -> bool {
82        // Simple implementation - in a real regex impl this would be different
83        self.contains(pattern)
84    }
85}
86
87// Single implementation for any type that implements AsString
88impl<V> StringMatchers for Assertion<V>
89where
90    V: AsString + Debug + Clone,
91{
92    fn to_be_empty(self) -> Self {
93        let result = self.value.is_empty_string();
94        let sentence = AssertionSentence::new("be", "empty");
95
96        return self.add_step(sentence, result);
97    }
98
99    fn to_have_length(self, expected: usize) -> Self {
100        let actual_length = self.value.length_string();
101        let result = actual_length == expected;
102        let sentence = AssertionSentence::new("have", format!("length {}", expected));
103
104        return self.add_step(sentence, result);
105    }
106
107    fn to_contain(self, substring: &str) -> Self {
108        return self.to_contain_substring(substring);
109    }
110
111    fn to_contain_substring(self, substring: &str) -> Self {
112        let result = self.value.contains_substring(substring);
113        let sentence = AssertionSentence::new("contain", format!("\"{}\"", substring));
114
115        return self.add_step(sentence, result);
116    }
117
118    fn to_start_with(self, prefix: &str) -> Self {
119        let result = self.value.starts_with_substring(prefix);
120        let sentence = AssertionSentence::new("start with", format!("\"{}\"", prefix));
121
122        return self.add_step(sentence, result);
123    }
124
125    fn to_end_with(self, suffix: &str) -> Self {
126        let result = self.value.ends_with_substring(suffix);
127        let sentence = AssertionSentence::new("end with", format!("\"{}\"", suffix));
128
129        return self.add_step(sentence, result);
130    }
131
132    fn to_match(self, pattern: &str) -> Self {
133        // This is a simplified implementation since we can't easily include a regex library
134        let result = self.value.matches_pattern(pattern);
135        let sentence = AssertionSentence::new("match", format!("pattern \"{}\"", pattern));
136
137        return self.add_step(sentence, result);
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use crate::prelude::*;
144
145    #[test]
146    fn test_string_to_be_empty() {
147        // Disable deduplication for tests
148        crate::Reporter::disable_deduplication();
149
150        // These should pass
151        expect!("").to_be_empty();
152        expect!("hello").not().to_be_empty();
153        expect!(String::new()).to_be_empty();
154        expect!(String::from("hello")).not().to_be_empty();
155    }
156
157    #[test]
158    #[should_panic(expected = "be empty")]
159    fn test_non_empty_to_be_empty_fails() {
160        let _assertion = expect!("hello").to_be_empty();
161        std::hint::black_box(_assertion);
162    }
163
164    #[test]
165    #[should_panic(expected = "not be empty")]
166    fn test_empty_not_to_be_empty_fails() {
167        let _assertion = expect!("").not().to_be_empty();
168        std::hint::black_box(_assertion);
169    }
170
171    #[test]
172    fn test_string_to_have_length() {
173        // Disable deduplication for tests
174        crate::Reporter::disable_deduplication();
175
176        // These should pass
177        expect!("hello").to_have_length(5);
178        expect!("hello").not().to_have_length(4);
179        expect!(String::from("hello")).to_have_length(5);
180    }
181
182    #[test]
183    #[should_panic(expected = "have length")]
184    fn test_wrong_length_fails() {
185        let _assertion = expect!("hello").to_have_length(4);
186        std::hint::black_box(_assertion);
187    }
188
189    #[test]
190    #[should_panic(expected = "not have length")]
191    fn test_right_length_not_fails() {
192        let _assertion = expect!("hello").not().to_have_length(5);
193        std::hint::black_box(_assertion);
194    }
195
196    #[test]
197    fn test_string_to_contain() {
198        // Disable deduplication for tests
199        crate::Reporter::disable_deduplication();
200
201        // These should pass
202        expect!("hello world").to_contain("hello");
203        expect!("hello world").not().to_contain("goodbye");
204        expect!(String::from("hello world")).to_contain("world");
205    }
206
207    #[test]
208    #[should_panic(expected = "not contain")]
209    fn test_not_contains_when_it_does_fails() {
210        let _assertion = expect!("hello world").not().to_contain("hello");
211        std::hint::black_box(_assertion);
212    }
213
214    #[test]
215    #[should_panic(expected = "contain")]
216    fn test_contains_when_it_doesnt_fails() {
217        let _assertion = expect!("hello world").to_contain("goodbye");
218        std::hint::black_box(_assertion);
219    }
220
221    #[test]
222    fn test_string_to_start_with() {
223        // Disable deduplication for tests
224        crate::Reporter::disable_deduplication();
225
226        // These should pass
227        expect!("hello world").to_start_with("hello");
228        expect!("hello world").not().to_start_with("world");
229        expect!(String::from("hello world")).to_start_with("hello");
230    }
231
232    #[test]
233    #[should_panic(expected = "not start with")]
234    fn test_not_starts_with_when_it_does_fails() {
235        let _assertion = expect!("hello world").not().to_start_with("hello");
236        std::hint::black_box(_assertion);
237    }
238
239    #[test]
240    #[should_panic(expected = "start with")]
241    fn test_starts_with_when_it_doesnt_fails() {
242        let _assertion = expect!("hello world").to_start_with("world");
243        std::hint::black_box(_assertion);
244    }
245
246    #[test]
247    fn test_string_to_end_with() {
248        // Disable deduplication for tests
249        crate::Reporter::disable_deduplication();
250
251        // These should pass
252        expect!("hello world").to_end_with("world");
253        expect!("hello world").not().to_end_with("hello");
254        expect!(String::from("hello world")).to_end_with("world");
255    }
256
257    #[test]
258    #[should_panic(expected = "not end with")]
259    fn test_not_ends_with_when_it_does_fails() {
260        let _assertion = expect!("hello world").not().to_end_with("world");
261        std::hint::black_box(_assertion);
262    }
263
264    #[test]
265    #[should_panic(expected = "end with")]
266    fn test_ends_with_when_it_doesnt_fails() {
267        let _assertion = expect!("hello world").to_end_with("hello");
268        std::hint::black_box(_assertion);
269    }
270
271    #[test]
272    fn test_string_to_match() {
273        // Disable deduplication for tests
274        crate::Reporter::disable_deduplication();
275
276        // These should pass (simplified implementation)
277        expect!("hello world").to_match("world");
278        expect!("hello world").not().to_match("goodbye");
279        expect!(String::from("hello world")).to_match("hello");
280    }
281
282    #[test]
283    #[should_panic(expected = "not match")]
284    fn test_not_matches_when_it_does_fails() {
285        let _assertion = expect!("hello world").not().to_match("hello");
286        std::hint::black_box(_assertion);
287    }
288
289    #[test]
290    #[should_panic(expected = "match")]
291    fn test_matches_when_it_doesnt_fails() {
292        let _assertion = expect!("hello world").to_match("goodbye");
293        std::hint::black_box(_assertion);
294    }
295}