@@ -504,6 +504,139 @@ def test_search_user_dir_start_of_user_id(self) -> None:
504504 {"user_id" : BELA , "display_name" : "Bela" , "avatar_url" : None },
505505 )
506506
507+ @override_config ({"user_directory" : {"search_all_users" : True }})
508+ def test_search_user_dir_ascii_case_insensitivity (self ) -> None :
509+ """Tests that a user can look up another user by searching for their name in a
510+ different case.
511+ """
512+ CHARLIE = "@someuser:example.org"
513+ self .get_success (
514+ self .store .update_profile_in_user_dir (CHARLIE , "Charlie" , None )
515+ )
516+
517+ r = self .get_success (self .store .search_user_dir (ALICE , "cHARLIE" , 10 ))
518+ self .assertFalse (r ["limited" ])
519+ self .assertEqual (1 , len (r ["results" ]))
520+ self .assertDictEqual (
521+ r ["results" ][0 ],
522+ {"user_id" : CHARLIE , "display_name" : "Charlie" , "avatar_url" : None },
523+ )
524+
525+ @override_config ({"user_directory" : {"search_all_users" : True }})
526+ def test_search_user_dir_unicode_case_insensitivity (self ) -> None :
527+ """Tests that a user can look up another user by searching for their name in a
528+ different case.
529+ """
530+ IVAN = "@someuser:example.org"
531+ self .get_success (self .store .update_profile_in_user_dir (IVAN , "Иван" , None ))
532+
533+ r = self .get_success (self .store .search_user_dir (ALICE , "иВАН" , 10 ))
534+ self .assertFalse (r ["limited" ])
535+ self .assertEqual (1 , len (r ["results" ]))
536+ self .assertDictEqual (
537+ r ["results" ][0 ],
538+ {"user_id" : IVAN , "display_name" : "Иван" , "avatar_url" : None },
539+ )
540+
541+ @override_config ({"user_directory" : {"search_all_users" : True }})
542+ def test_search_user_dir_dotted_dotless_i_case_insensitivity (self ) -> None :
543+ """Tests that a user can look up another user by searching for their name in a
544+ different case, when their name contains dotted or dotless "i"s.
545+
546+ Some languages have dotted and dotless versions of "i", which are considered to
547+ be different letters: i <-> İ, ı <-> I. To make things difficult, they reuse the
548+ ASCII "i" and "I" code points, despite having different lowercase / uppercase
549+ forms.
550+ """
551+ USER = "@someuser:example.org"
552+
553+ expected_matches = [
554+ # (search_term, display_name)
555+ # A search for "i" should match "İ".
556+ ("iiiii" , "İİİİİ" ),
557+ # A search for "I" should match "ı".
558+ ("IIIII" , "ııııı" ),
559+ # A search for "ı" should match "I".
560+ ("ııııı" , "IIIII" ),
561+ # A search for "İ" should match "i".
562+ ("İİİİİ" , "iiiii" ),
563+ ]
564+
565+ for search_term , display_name in expected_matches :
566+ self .get_success (
567+ self .store .update_profile_in_user_dir (USER , display_name , None )
568+ )
569+
570+ r = self .get_success (self .store .search_user_dir (ALICE , search_term , 10 ))
571+ self .assertFalse (r ["limited" ])
572+ self .assertEqual (
573+ 1 ,
574+ len (r ["results" ]),
575+ f"searching for { search_term !r} did not match { display_name !r} " ,
576+ )
577+ self .assertDictEqual (
578+ r ["results" ][0 ],
579+ {"user_id" : USER , "display_name" : display_name , "avatar_url" : None },
580+ )
581+
582+ # We don't test for negative matches, to allow implementations that consider all
583+ # the i variants to be the same.
584+
585+ test_search_user_dir_dotted_dotless_i_case_insensitivity .skip = "not supported" # type: ignore
586+
587+ @override_config ({"user_directory" : {"search_all_users" : True }})
588+ def test_search_user_dir_unicode_normalization (self ) -> None :
589+ """Tests that a user can look up another user by searching for their name with
590+ either composed or decomposed accents.
591+ """
592+ AMELIE = "@someuser:example.org"
593+
594+ expected_matches = [
595+ # (search_term, display_name)
596+ ("Ame\u0301 lie" , "Amélie" ),
597+ ("Amélie" , "Ame\u0301 lie" ),
598+ ]
599+
600+ for search_term , display_name in expected_matches :
601+ self .get_success (
602+ self .store .update_profile_in_user_dir (AMELIE , display_name , None )
603+ )
604+
605+ r = self .get_success (self .store .search_user_dir (ALICE , search_term , 10 ))
606+ self .assertFalse (r ["limited" ])
607+ self .assertEqual (
608+ 1 ,
609+ len (r ["results" ]),
610+ f"searching for { search_term !r} did not match { display_name !r} " ,
611+ )
612+ self .assertDictEqual (
613+ r ["results" ][0 ],
614+ {"user_id" : AMELIE , "display_name" : display_name , "avatar_url" : None },
615+ )
616+
617+ @override_config ({"user_directory" : {"search_all_users" : True }})
618+ def test_search_user_dir_accent_insensitivity (self ) -> None :
619+ """Tests that a user can look up another user by searching for their name
620+ without any accents.
621+ """
622+ AMELIE = "@someuser:example.org"
623+ self .get_success (self .store .update_profile_in_user_dir (AMELIE , "Amélie" , None ))
624+
625+ r = self .get_success (self .store .search_user_dir (ALICE , "amelie" , 10 ))
626+ self .assertFalse (r ["limited" ])
627+ self .assertEqual (1 , len (r ["results" ]))
628+ self .assertDictEqual (
629+ r ["results" ][0 ],
630+ {"user_id" : AMELIE , "display_name" : "Amélie" , "avatar_url" : None },
631+ )
632+
633+ # It may be desirable for "é"s in search terms to not match plain "e"s and we
634+ # really don't want "é"s in search terms to match "e"s with different accents.
635+ # But we don't test for this to allow implementations that consider all
636+ # "e"-lookalikes to be the same.
637+
638+ test_search_user_dir_accent_insensitivity .skip = "not supported yet" # type: ignore
639+
507640
508641class UserDirectoryStoreTestCaseWithIcu (UserDirectoryStoreTestCase ):
509642 use_icu = True
0 commit comments