Поиск: case-insensitive сравнение в logic_search
- сопоставление имени/описания календарей приведено к lowercase - юнит-тесты: 20/20
This commit is contained in:
@@ -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) ->
|
||||
|
||||
@@ -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, []),
|
||||
|
||||
Reference in New Issue
Block a user