1use super::predicate::PredicateExpectation;
2use crate::{ExpectProjection, ExpectationBuilder};
3use std::fmt::Debug;
4
5pub trait StringExpectations<'e, T>
7where
8 T: Debug + 'e,
9{
10 fn to_contain(self, substring: &'e str) -> Self;
20
21 fn to_not_contain(self, substring: &'e str) -> Self;
31
32 fn to_have_length(self, length: usize) -> Self;
42
43 fn to_start_with(self, prefix: &'e str) -> Self;
53
54 fn to_end_with(self, suffix: &'e str) -> Self;
64
65 fn to_be_empty(self) -> Self;
75
76 fn to_be_all_whitespace(self) -> Self;
86
87 fn to_be_alphabetic(self) -> Self;
97
98 fn to_be_numeric(self) -> Self;
108
109 fn to_be_alphanumeric(self) -> Self;
119
120 fn length(self) -> impl ExpectationBuilder<'e, Value = usize>
132 where
133 Self: Sized;
134}
135
136impl<'e, T, B> StringExpectations<'e, T> for B
137where
138 T: AsRef<str> + Debug + 'e,
139 B: ExpectationBuilder<'e, Value = T>,
140{
141 fn to_contain(self, substring: &'e str) -> Self {
142 self.to_pass(PredicateExpectation::new(
143 substring,
144 |a: &T, b| a.as_ref().contains(b),
145 |a: &T, b| format!("Expected \"{}\" to contain \"{b}\"", a.as_ref()),
146 ))
147 }
148
149 fn to_not_contain(self, substring: &'e str) -> Self {
150 self.to_pass(PredicateExpectation::new(
151 substring,
152 |a: &T, b| !a.as_ref().contains(b),
153 |a: &T, b| format!("Expected \"{}\" to not contain \"{b}\"", a.as_ref()),
154 ))
155 }
156
157 fn to_have_length(self, length: usize) -> Self {
158 self.to_pass(PredicateExpectation::new(
159 length,
160 |a: &T, &b| a.as_ref().chars().count() == b,
161 |a: &T, &b| {
162 format!(
163 "Expected \"{}\" to have length {b}, but it has length {}",
164 a.as_ref(),
165 a.as_ref().chars().count()
166 )
167 },
168 ))
169 }
170
171 fn to_start_with(self, prefix: &'e str) -> Self {
172 self.to_pass(PredicateExpectation::new(
173 prefix,
174 |a: &T, b| a.as_ref().starts_with(b),
175 |a: &T, b| format!("Expected \"{}\" to start with \"{b}\"", a.as_ref()),
176 ))
177 }
178
179 fn to_end_with(self, suffix: &'e str) -> Self {
180 self.to_pass(PredicateExpectation::new(
181 suffix,
182 |a: &T, b| a.as_ref().ends_with(b),
183 |a: &T, b| format!("Expected \"{}\" to end with \"{b}\"", a.as_ref()),
184 ))
185 }
186
187 fn to_be_empty(self) -> Self {
188 self.to_pass(PredicateExpectation::new(
189 (),
190 |a: &T, _| a.as_ref().is_empty(),
191 |a: &T, _| format!("Expected \"{}\" to be empty", a.as_ref()),
192 ))
193 }
194
195 fn to_be_all_whitespace(self) -> Self {
196 self.to_pass(PredicateExpectation::new(
197 (),
198 |a: &T, _| a.as_ref().chars().all(|c| c.is_whitespace()),
199 |a: &T, _| format!("Expected \"{}\" to be all whitespace", a.as_ref()),
200 ))
201 }
202
203 fn to_be_alphabetic(self) -> Self {
204 self.to_pass(PredicateExpectation::new(
205 (),
206 |a: &T, _| !a.as_ref().is_empty() && a.as_ref().chars().all(|c| c.is_alphabetic()),
207 |a: &T, _| format!("Expected \"{}\" to be alphabetic", a.as_ref()),
208 ))
209 }
210
211 fn to_be_numeric(self) -> Self {
212 self.to_pass(PredicateExpectation::new(
213 (),
214 |a: &T, _| !a.as_ref().is_empty() && a.as_ref().chars().all(|c| c.is_numeric()),
215 |a: &T, _| format!("Expected \"{}\" to be numeric", a.as_ref()),
216 ))
217 }
218
219 fn to_be_alphanumeric(self) -> Self {
220 self.to_pass(PredicateExpectation::new(
221 (),
222 |a: &T, _| !a.as_ref().is_empty() && a.as_ref().chars().all(|c| c.is_alphanumeric()),
223 |a: &T, _| format!("Expected \"{}\" to be alphanumeric", a.as_ref()),
224 ))
225 }
226
227 fn length(self) -> impl ExpectationBuilder<'e, Value = usize>
228 where
229 Self: Sized,
230 {
231 self.projected_by(|it: &T| it.as_ref().chars().count())
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use crate::expect;
238 use crate::expectations::string::StringExpectations;
239 use rstest::rstest;
240
241 #[rstest]
242 #[case("", "")]
243 #[case("foobar", "foo")]
244 #[case("foobar", "bar")]
245 #[case("foobar", "oob")]
246 #[case("foobar", "")]
247 fn that_to_contain_passes_when_string_contains_the_substring(
248 #[case] actual: &str,
249 #[case] substring: &str,
250 ) {
251 expect(actual).to_contain(substring);
252 }
253
254 #[rstest]
255 #[case("", "foo")]
256 #[case("foobar", "rab")]
257 #[case("foobar", "oof")]
258 #[case("foobar", "boo")]
259 #[should_panic]
260 fn that_to_contain_does_not_pass_when_string_does_not_contain_the_substring(
261 #[case] actual: &str,
262 #[case] substring: &str,
263 ) {
264 expect(actual).to_contain(substring);
265 }
266
267 #[rstest]
268 #[case("", "foo")]
269 #[case("foobar", "rab")]
270 #[case("foobar", "oof")]
271 #[case("foobar", "boo")]
272 fn that_to_not_contain_passes_when_string_does_not_contain_the_substring(
273 #[case] actual: &str,
274 #[case] substring: &str,
275 ) {
276 expect(actual).to_not_contain(substring);
277 }
278
279 #[rstest]
280 #[case("", "")]
281 #[case("foobar", "foo")]
282 #[case("foobar", "bar")]
283 #[case("foobar", "oob")]
284 #[case("foobar", "")]
285 #[should_panic]
286 fn that_to_not_contain_does_not_pass_when_string_contains_the_substring(
287 #[case] actual: &str,
288 #[case] substring: &str,
289 ) {
290 expect(actual).to_not_contain(substring);
291 }
292
293 #[rstest]
294 #[case("", 0)]
295 #[case("a", 1)]
296 #[case("ab", 2)]
297 #[case("abc", 3)]
298 #[case("abcd", 4)]
299 fn that_to_have_length_passes_when_string_has_expected_length(
300 #[case] actual: &str,
301 #[case] length: usize,
302 ) {
303 expect(actual).to_have_length(length);
304 }
305
306 #[rstest]
307 #[case("", 1)]
308 #[case("a", 0)]
309 #[case("ab", 3)]
310 #[case("abc", 2)]
311 #[case("abcd", 5)]
312 #[should_panic]
313 fn that_to_have_length_does_not_pass_when_string_does_not_have_expected_length(
314 #[case] actual: &str,
315 #[case] length: usize,
316 ) {
317 expect(actual).to_have_length(length);
318 }
319
320 #[rstest]
321 #[case("Ä", 1)] #[case("中文", 2)] #[case("héllo", 5)] fn that_to_have_length_counts_characters_not_bytes(
325 #[case] actual: &str,
326 #[case] length: usize,
327 ) {
328 expect(actual).to_have_length(length);
329 }
330
331 #[rstest]
332 #[case("Ä", 1)] #[case("中文", 2)] #[case("héllo", 5)] fn that_length_projection_counts_characters_not_bytes(
336 #[case] actual: &str,
337 #[case] length: usize,
338 ) {
339 use crate::expectations::EqualityExpectations;
340 expect(actual).length().to_equal(length);
341 }
342
343 #[rstest]
344 #[case("", "")]
345 #[case("foobar", "foo")]
346 #[case("foobar", "f")]
347 #[case("foobar", "")]
348 fn that_to_start_with_passes_when_string_starts_with_prefix(
349 #[case] actual: &str,
350 #[case] prefix: &str,
351 ) {
352 expect(actual).to_start_with(prefix);
353 }
354
355 #[rstest]
356 #[case("", "foo")]
357 #[case("foobar", "bar")]
358 #[case("foobar", "oo")]
359 #[case("foobar", "foobar1")]
360 #[should_panic]
361 fn that_to_start_with_does_not_pass_when_string_does_not_start_with_prefix(
362 #[case] actual: &str,
363 #[case] prefix: &str,
364 ) {
365 expect(actual).to_start_with(prefix);
366 }
367
368 #[rstest]
369 #[case("", "")]
370 #[case("foobar", "bar")]
371 #[case("foobar", "r")]
372 #[case("foobar", "")]
373 fn that_to_end_with_passes_when_string_ends_with_suffix(
374 #[case] actual: &str,
375 #[case] suffix: &str,
376 ) {
377 expect(actual).to_end_with(suffix);
378 }
379
380 #[rstest]
381 #[case("", "foo")]
382 #[case("foobar", "foo")]
383 #[case("foobar", "ba")]
384 #[case("foobar", "1foobar")]
385 #[should_panic]
386 fn that_to_end_with_does_not_pass_when_string_does_not_end_with_suffix(
387 #[case] actual: &str,
388 #[case] suffix: &str,
389 ) {
390 expect(actual).to_end_with(suffix);
391 }
392
393 #[rstest]
394 #[case("")]
395 fn that_to_be_empty_passes_when_string_is_empty(#[case] actual: &str) {
396 expect(actual).to_be_empty();
397 }
398
399 #[rstest]
400 #[case("a")]
401 #[case("foo")]
402 #[case(" ")]
403 #[should_panic]
404 fn that_to_be_empty_does_not_pass_when_string_is_not_empty(#[case] actual: &str) {
405 expect(actual).to_be_empty();
406 }
407
408 #[rstest]
409 #[case("")]
410 #[case(" ")]
411 #[case(" ")]
412 #[case("\t")]
413 #[case("\n")]
414 #[case(" \t\n\r")]
415 fn that_to_be_all_whitespace_passes_when_string_is_all_whitespace(#[case] actual: &str) {
416 expect(actual).to_be_all_whitespace();
417 }
418
419 #[rstest]
420 #[case("a")]
421 #[case("foo")]
422 #[case(" a")]
423 #[case("a ")]
424 #[case(" a ")]
425 #[should_panic]
426 fn that_to_be_all_whitespace_does_not_pass_when_string_is_not_all_whitespace(
427 #[case] actual: &str,
428 ) {
429 expect(actual).to_be_all_whitespace();
430 }
431
432 #[rstest]
433 #[case("a")]
434 #[case("abc")]
435 #[case("Abc")]
436 #[case("ABC")]
437 #[case("абв")]
438 fn that_to_be_alphabetic_passes_when_string_is_alphabetic(#[case] actual: &str) {
439 expect(actual).to_be_alphabetic();
440 }
441
442 #[rstest]
443 #[case("")]
444 #[case("123")]
445 #[case("a1")]
446 #[case("1a")]
447 #[case("a b")]
448 #[case("a-b")]
449 #[should_panic]
450 fn that_to_be_alphabetic_does_not_pass_when_string_is_not_alphabetic(#[case] actual: &str) {
451 expect(actual).to_be_alphabetic();
452 }
453
454 #[rstest]
455 #[case("0")]
456 #[case("123")]
457 #[case("١٢٣")] fn that_to_be_numeric_passes_when_string_is_numeric(#[case] actual: &str) {
459 expect(actual).to_be_numeric();
460 }
461
462 #[rstest]
463 #[case("")]
464 #[case("abc")]
465 #[case("1a")]
466 #[case("a1")]
467 #[case("1 2")]
468 #[case("1-2")]
469 #[should_panic]
470 fn that_to_be_numeric_does_not_pass_when_string_is_not_numeric(#[case] actual: &str) {
471 expect(actual).to_be_numeric();
472 }
473}