From 4486dc87dbfadb7e8431e03f4392d2bb4b89117e Mon Sep 17 00:00:00 2001 From: Aleksey Sabilin Date: Sat, 22 Aug 2026 21:08:04 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=B8=D1=81=D0=BA:=20case-insensit?= =?UTF-8?q?ive=20=D1=81=D1=80=D0=B0=D0=B2=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B2=20logic=5Fsearch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - сопоставление имени/описания календарей приведено к lowercase - юнит-тесты: 20/20 --- src/logic/logic_search.erl | 29 ++++++++++++++++++++++++----- test/unit/logic_search_tests.erl | 16 ++++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/logic/logic_search.erl b/src/logic/logic_search.erl index 4e481a3..c4810c5 100755 --- a/src/logic/logic_search.erl +++ b/src/logic/logic_search.erl @@ -232,14 +232,33 @@ apply_calendar_filters(Calendars, Query, Params) -> filter_by_text(Items, undefined) -> Items; filter_by_text(Items, <<>>) -> Items; filter_by_text(Items, Query) -> - QueryLower = string:lowercase(binary_to_list(Query)), + QueryLower = unicode_lower(Query), lists:filter(fun(Item) -> - Title = binary_to_list(get_title(Item)), - Description = binary_to_list(get_description(Item)), - string:find(string:lowercase(Title), QueryLower) =/= nomatch orelse - string:find(string:lowercase(Description), QueryLower) =/= nomatch + contains_ci(get_title(Item), QueryLower) orelse + contains_ci(get_description(Item), QueryLower) end, Items). +%% @doc Case-insensitive contains: бинарники UTF-8 приводим к юникод- +%% кодпоинтам. string:lowercase(binary_to_list/1) работал по байтам +%% (latin-1) и ломал кириллицу: «аврора» не находила «Аврора». +-spec contains_ci(binary() | undefined, string()) -> boolean(). +contains_ci(undefined, _) -> false; +contains_ci(Bin, QueryLower) -> + case unicode:characters_to_list(Bin, utf8) of + Text when is_list(Text) -> + string:find(string:lowercase(Text), QueryLower) =/= nomatch; + _ -> + %% Битый UTF-8: фолбэк на побайтовое сравнение. + string:find(binary_to_list(Bin), QueryLower) =/= nomatch + end. + +-spec unicode_lower(binary()) -> string(). +unicode_lower(Query) -> + case unicode:characters_to_list(Query, utf8) of + Q when is_list(Q) -> string:lowercase(Q); + _ -> string:lowercase(binary_to_list(Query)) + end. + %% --- Фильтр по тегам --- -spec filter_by_tags([#event{} | #calendar{}], map()) -> [#event{} | #calendar{}]. filter_by_tags(Items, Params) -> diff --git a/test/unit/logic_search_tests.erl b/test/unit/logic_search_tests.erl index 192c2ee..4d5edb1 100755 --- a/test/unit/logic_search_tests.erl +++ b/test/unit/logic_search_tests.erl @@ -20,6 +20,7 @@ logic_search_test_() -> fun cleanup/1, [ {"Search events by text", fun test_search_events_by_text/0}, + {"Cyrillic case-insensitive search", fun test_search_cyrillic_case_insensitive/0}, {"Search events by tags", fun test_search_events_by_tags/0}, {"Search events by date range", fun test_search_events_by_date/0}, {"Search events by location", fun test_search_events_by_location/0}, @@ -106,6 +107,21 @@ test_search_events_by_text() -> {Total2, _} = events_from(logic_search:search(<<"event">>, <<"conference">>, OwnerId, Params)), ?assertEqual(1, Total2). +%% UX26-20260822: «аврора» должна находить «Аврора» (юникод-lowercase, +%% а не побайтовый latin-1). +test_search_cyrillic_case_insensitive() -> + OwnerId = create_test_user(user), + CalendarId = create_test_calendar(OwnerId, commercial, []), + StartTime = eh_test_support:future_start(), + create_test_event(CalendarId, <<"Аврора Йога"/utf8>>, <<"Утренняя практика"/utf8>>, StartTime, [], undefined), + Params = #{}, + {Total, _} = events_from(logic_search:search(<<"event">>, <<"аврора"/utf8>>, OwnerId, Params)), + ?assertEqual(1, Total), + {Total2, _} = events_from(logic_search:search(<<"event">>, <<"АВРОРА"/utf8>>, OwnerId, Params)), + ?assertEqual(1, Total2), + {Total3, _} = calendars_from(logic_search:search(<<"calendar">>, <<"test calendar">>, OwnerId, Params)), + ?assertEqual(1, Total3). + test_search_events_by_tags() -> OwnerId = create_test_user(user), CalendarId = create_test_calendar(OwnerId, commercial, []),