@@ -56,8 +56,9 @@ use std::ops::Deref;
56
56
/// > Note on memory use: In most cases, this matcher does not allocate memory
57
57
/// > when matching strings. However, it must allocate copies of both the actual
58
58
/// > and expected values when matching strings while
59
- /// > [`ignoring_ascii_case`][StrMatcherConfigurator::ignoring_ascii_case] is
60
- /// > set.
59
+ /// > [`ignoring_ascii_case`][StrMatcherConfigurator::ignoring_ascii_case] or
60
+ /// > [`ignoring_unicode_case`][StrMatcherConfigurator::ignoring_unicode_case]
61
+ /// > are set.
61
62
pub fn contains_substring < T > ( expected : T ) -> StrMatcher < T > {
62
63
StrMatcher {
63
64
configuration : Configuration { mode : MatchMode :: Contains , ..Default :: default ( ) } ,
@@ -235,6 +236,25 @@ pub trait StrMatcherConfigurator<ExpectedT> {
235
236
/// case characters outside of the codepoints 0-127 covered by ASCII.
236
237
fn ignoring_ascii_case ( self ) -> StrMatcher < ExpectedT > ;
237
238
239
+ /// Configures the matcher to ignore Unicode case when comparing values.
240
+ ///
241
+ /// This uses the same rules for case as [`str::to_lowercase`].
242
+ ///
243
+ /// ```
244
+ /// # use googletest::prelude::*;
245
+ /// # fn should_pass() -> Result<()> {
246
+ /// verify_that!("ὈΔΥΣΣΕΎΣ", eq("ὀδυσσεύς").ignoring_unicode_case())?; // Passes
247
+ /// # Ok(())
248
+ /// # }
249
+ /// # fn should_fail() -> Result<()> {
250
+ /// verify_that!("secret", eq("비밀").ignoring_unicode_case())?; // Fails
251
+ /// # Ok(())
252
+ /// # }
253
+ /// # should_pass().unwrap();
254
+ /// # should_fail().unwrap_err();
255
+ /// ```
256
+ fn ignoring_unicode_case ( self ) -> StrMatcher < ExpectedT > ;
257
+
238
258
/// Configures the matcher to match only strings which otherwise satisfy the
239
259
/// conditions a number times matched by the matcher `times`.
240
260
///
@@ -333,6 +353,11 @@ impl<ExpectedT, MatcherT: Into<StrMatcher<ExpectedT>>> StrMatcherConfigurator<Ex
333
353
StrMatcher { configuration : existing. configuration . ignoring_ascii_case ( ) , ..existing }
334
354
}
335
355
356
+ fn ignoring_unicode_case ( self ) -> StrMatcher < ExpectedT > {
357
+ let existing = self . into ( ) ;
358
+ StrMatcher { configuration : existing. configuration . ignoring_unicode_case ( ) , ..existing }
359
+ }
360
+
336
361
fn times ( self , times : impl Matcher < usize > + ' static ) -> StrMatcher < ExpectedT > {
337
362
let existing = self . into ( ) ;
338
363
if !matches ! ( existing. configuration. mode, MatchMode :: Contains ) {
@@ -394,6 +419,7 @@ impl MatchMode {
394
419
enum CasePolicy {
395
420
Respect ,
396
421
IgnoreAscii ,
422
+ IgnoreUnicode ,
397
423
}
398
424
399
425
impl Configuration {
@@ -411,27 +437,38 @@ impl Configuration {
411
437
MatchMode :: Equals => match self . case_policy {
412
438
CasePolicy :: Respect => expected == actual,
413
439
CasePolicy :: IgnoreAscii => expected. eq_ignore_ascii_case ( actual) ,
440
+ CasePolicy :: IgnoreUnicode => expected. to_lowercase ( ) == actual. to_lowercase ( ) ,
414
441
} ,
415
442
MatchMode :: Contains => match self . case_policy {
416
443
CasePolicy :: Respect => self . does_containment_match ( actual, expected) ,
417
444
CasePolicy :: IgnoreAscii => self . does_containment_match (
418
445
actual. to_ascii_lowercase ( ) . as_str ( ) ,
419
446
expected. to_ascii_lowercase ( ) . as_str ( ) ,
420
447
) ,
448
+ CasePolicy :: IgnoreUnicode => self . does_containment_match (
449
+ actual. to_lowercase ( ) . as_str ( ) ,
450
+ expected. to_lowercase ( ) . as_str ( ) ,
451
+ ) ,
421
452
} ,
422
453
MatchMode :: StartsWith => match self . case_policy {
423
454
CasePolicy :: Respect => actual. starts_with ( expected) ,
424
455
CasePolicy :: IgnoreAscii => {
425
456
actual. len ( ) >= expected. len ( )
426
457
&& actual[ ..expected. len ( ) ] . eq_ignore_ascii_case ( expected)
427
458
}
459
+ CasePolicy :: IgnoreUnicode => {
460
+ actual. to_lowercase ( ) . starts_with ( & expected. to_lowercase ( ) )
461
+ }
428
462
} ,
429
463
MatchMode :: EndsWith => match self . case_policy {
430
464
CasePolicy :: Respect => actual. ends_with ( expected) ,
431
465
CasePolicy :: IgnoreAscii => {
432
466
actual. len ( ) >= expected. len ( )
433
467
&& actual[ actual. len ( ) - expected. len ( ) ..] . eq_ignore_ascii_case ( expected)
434
468
}
469
+ CasePolicy :: IgnoreUnicode => {
470
+ actual. to_lowercase ( ) . ends_with ( & expected. to_lowercase ( ) )
471
+ }
435
472
} ,
436
473
}
437
474
}
@@ -461,6 +498,7 @@ impl Configuration {
461
498
match self . case_policy {
462
499
CasePolicy :: Respect => { }
463
500
CasePolicy :: IgnoreAscii => addenda. push ( "ignoring ASCII case" . into ( ) ) ,
501
+ CasePolicy :: IgnoreUnicode => addenda. push ( "ignoring Unicode case" . into ( ) ) ,
464
502
}
465
503
if let Some ( times) = self . times . as_ref ( ) {
466
504
addenda. push ( format ! ( "count {}" , times. describe( matcher_result) ) . into ( ) ) ;
@@ -516,6 +554,10 @@ impl Configuration {
516
554
// TODO - b/283448414 : Support StrMatcher with ignore ascii case policy.
517
555
return default_explanation;
518
556
}
557
+ if matches ! ( self . case_policy, CasePolicy :: IgnoreUnicode ) {
558
+ // TODO - b/283448414 : Support StrMatcher with ignore unicode case policy.
559
+ return default_explanation;
560
+ }
519
561
if self . do_strings_match ( expected, actual) {
520
562
// TODO - b/283448414 : Consider supporting debug difference if the
521
563
// strings match. This can be useful when a small contains is found
@@ -556,6 +598,10 @@ impl Configuration {
556
598
Self { case_policy : CasePolicy :: IgnoreAscii , ..self }
557
599
}
558
600
601
+ fn ignoring_unicode_case ( self ) -> Self {
602
+ Self { case_policy : CasePolicy :: IgnoreUnicode , ..self }
603
+ }
604
+
559
605
fn times ( self , times : impl Matcher < usize > + ' static ) -> Self {
560
606
Self { times : Some ( Box :: new ( times) ) , ..self }
561
607
}
@@ -677,6 +723,12 @@ mod tests {
677
723
verify_that ! ( "A STRING" , matcher. ignoring_ascii_case( ) )
678
724
}
679
725
726
+ #[ test]
727
+ fn ignores_unicode_case_when_requested ( ) -> Result < ( ) > {
728
+ let matcher = StrMatcher :: with_default_config ( "ὈΔΥΣΣΕΎΣ" ) ;
729
+ verify_that ! ( "ὀδυσσεύς" , matcher. ignoring_unicode_case( ) )
730
+ }
731
+
680
732
#[ test]
681
733
fn allows_ignoring_leading_whitespace_from_eq ( ) -> Result < ( ) > {
682
734
verify_that ! ( "A string" , eq( " \n \t A string" ) . ignoring_leading_whitespace( ) )
@@ -697,6 +749,16 @@ mod tests {
697
749
verify_that ! ( "A string" , eq( "A STRING" ) . ignoring_ascii_case( ) )
698
750
}
699
751
752
+ #[ test]
753
+ fn allows_ignoring_unicode_case_from_eq ( ) -> Result < ( ) > {
754
+ verify_that ! ( "ὈΔΥΣΣΕΎΣ" , eq( "ὀδυσσεύς" ) . ignoring_unicode_case( ) )
755
+ }
756
+
757
+ #[ test]
758
+ fn unicode_case_sensitive_from_eq ( ) -> Result < ( ) > {
759
+ verify_that ! ( "ὈΔΥΣΣΕΎΣ" , not( eq( "ὀδυσσεύς" ) ) )
760
+ }
761
+
700
762
#[ test]
701
763
fn matches_string_containing_expected_value_in_contains_mode ( ) -> Result < ( ) > {
702
764
verify_that ! ( "Some string" , contains_substring( "str" ) )
@@ -708,6 +770,12 @@ mod tests {
708
770
verify_that ! ( "Some string" , contains_substring( "STR" ) . ignoring_ascii_case( ) )
709
771
}
710
772
773
+ #[ test]
774
+ fn matches_string_containing_expected_value_in_contains_mode_while_ignoring_unicode_case (
775
+ ) -> Result < ( ) > {
776
+ verify_that ! ( "Some σpsilon" , contains_substring( "Σps" ) . ignoring_unicode_case( ) )
777
+ }
778
+
711
779
#[ test]
712
780
fn contains_substring_matches_correct_number_of_substrings ( ) -> Result < ( ) > {
713
781
verify_that ! ( "Some string" , contains_substring( "str" ) . times( eq( 1 ) ) )
@@ -739,10 +807,25 @@ mod tests {
739
807
}
740
808
741
809
#[ test]
742
- fn ends_with_does_not_match_short_string_ignoring_ascii_case ( ) -> Result < ( ) > {
810
+ fn starts_with_does_not_match_short_string_ignoring_ascii_case ( ) -> Result < ( ) > {
743
811
verify_that ! ( "Some" , not( starts_with( "OTHER" ) . ignoring_ascii_case( ) ) )
744
812
}
745
813
814
+ #[ test]
815
+ fn starts_with_matches_string_reference_with_prefix_ignoring_unicode_case ( ) -> Result < ( ) > {
816
+ verify_that ! ( "비밀 santa" , starts_with( "비밀" ) . ignoring_unicode_case( ) )
817
+ }
818
+
819
+ #[ test]
820
+ fn starts_with_does_not_match_wrong_prefix_ignoring_unicode_case ( ) -> Result < ( ) > {
821
+ verify_that ! ( "secret santa" , not( starts_with( "비밀" ) . ignoring_unicode_case( ) ) )
822
+ }
823
+
824
+ #[ test]
825
+ fn starts_with_does_not_match_short_string_ignoring_unicode_case ( ) -> Result < ( ) > {
826
+ verify_that ! ( "비밀" , not( starts_with( "秘密" ) . ignoring_unicode_case( ) ) )
827
+ }
828
+
746
829
#[ test]
747
830
fn starts_with_does_not_match_string_without_prefix ( ) -> Result < ( ) > {
748
831
verify_that ! ( "Some value" , not( starts_with( "Another" ) ) )
@@ -783,6 +866,21 @@ mod tests {
783
866
verify_that ! ( "Some value" , not( ends_with( "Some" ) ) )
784
867
}
785
868
869
+ #[ test]
870
+ fn ends_with_matches_string_reference_with_suffix_ignoring_unicode_case ( ) -> Result < ( ) > {
871
+ verify_that ! ( "santa 비밀" , ends_with( "비밀" ) . ignoring_unicode_case( ) )
872
+ }
873
+
874
+ #[ test]
875
+ fn ends_with_does_not_match_wrong_suffix_ignoring_unicode_case ( ) -> Result < ( ) > {
876
+ verify_that ! ( "secret santa" , not( ends_with( "비밀" ) . ignoring_unicode_case( ) ) )
877
+ }
878
+
879
+ #[ test]
880
+ fn ends_with_does_not_match_short_string_ignoring_unicode_case ( ) -> Result < ( ) > {
881
+ verify_that ! ( "비밀" , not( ends_with( "秘密" ) . ignoring_unicode_case( ) ) )
882
+ }
883
+
786
884
#[ test]
787
885
fn describes_itself_for_matching_result ( ) -> Result < ( ) > {
788
886
let matcher = StrMatcher :: with_default_config ( "A string" ) ;
0 commit comments