Files
cproof/tests/unittests/tools/test_autocomplete.c
Michael Vetter e9c6b3b3d8 feat(tools): support UTF-8 characters in autocompletion
Autocompletion failed for nicknames using non Latin scripts. We used
`g_str_to_ascii`, which replaces characters it cannot transliterate with
'?', leading search failures and false matches between different
scripts.

Now we do:
Use `g_utf8_casefold` for case-insensitive UTF-8 comparison. This
ensures that like 'Σ' correctly match 'σ' across all Unicode scripts,
providing correct results for non English nicknames.

If we don't fine anything typing a base ASCII character matches an
accented one (typing `e` matches `è`). This pass uses `g_str_to_ascii`
followed by `g_ascii_strdown` for comparison. It is now restricted to
run if the search string itself is valid ASCII, preventing the
"everything matches '?'" bug in non Latin scripts.

Autocomplete items are sorted using `strcmp`. `g_utf8_collate` provides
linguistical ordering for a specific language. But its behavior is
locale dependent and undefined when comparing strings from different
scripts. `strcmp` does byte-wise ordering that correctly follows Unicode
code order for UTF-8 strings.
2026-03-19 08:41:50 +01:00

348 lines
8.2 KiB
C

#include <glib.h>
#include "prof_cmocka.h"
#include <stdlib.h>
#include "xmpp/contact.h"
#include "tools/autocomplete.h"
void
autocomplete_complete__returns__null_when_empty(void** state)
{
Autocomplete ac = autocomplete_new();
char* result = autocomplete_complete(ac, "test", TRUE, FALSE);
assert_null(result);
autocomplete_free(ac);
free(result);
}
void
autocomplete_reset__updates__after_create(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_reset(ac);
autocomplete_free(ac);
}
void
autocomplete_complete__returns__null_after_create(void** state)
{
Autocomplete ac = autocomplete_new();
char* result = autocomplete_complete(ac, "hello", TRUE, FALSE);
assert_null(result);
autocomplete_free(ac);
free(result);
}
void
autocomplete_create_list__returns__null_after_create(void** state)
{
Autocomplete ac = autocomplete_new();
GList* result = autocomplete_create_list(ac);
assert_null(result);
autocomplete_free(ac);
g_list_free_full(result, free);
}
void
autocomplete_add__updates__one_and_complete(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "Hello");
char* result = autocomplete_complete(ac, "Hel", TRUE, FALSE);
assert_string_equal("Hello", result);
autocomplete_free(ac);
free(result);
}
void
autocomplete_complete__returns__first_of_two(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "Hello");
autocomplete_add(ac, "Help");
char* result = autocomplete_complete(ac, "Hel", TRUE, FALSE);
assert_string_equal("Hello", result);
autocomplete_free(ac);
free(result);
}
void
autocomplete_complete__returns__second_of_two(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "Hello");
autocomplete_add(ac, "Help");
char* result1 = autocomplete_complete(ac, "Hel", TRUE, FALSE);
char* result2 = autocomplete_complete(ac, result1, TRUE, FALSE);
assert_string_equal("Help", result2);
autocomplete_free(ac);
free(result1);
free(result2);
}
void
autocomplete_add__updates__two_elements(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "Hello");
autocomplete_add(ac, "Help");
GList* result = autocomplete_create_list(ac);
assert_int_equal(2, g_list_length(result));
autocomplete_free(ac);
g_list_free_full(result, free);
}
void
autocomplete_add__updates__only_once_for_same_value(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "Hello");
autocomplete_add(ac, "Hello");
GList* result = autocomplete_create_list(ac);
assert_int_equal(1, g_list_length(result));
autocomplete_free(ac);
g_list_free_full(result, free);
}
void
autocomplete_add__updates__existing_value(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "Hello");
autocomplete_add(ac, "Hello");
GList* result = autocomplete_create_list(ac);
GList* first = g_list_nth(result, 0);
char* str = first->data;
assert_string_equal("Hello", str);
autocomplete_free(ac);
g_list_free_full(result, free);
}
void
autocomplete_complete__returns__accented_with_accented(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "èâîô");
char* result = autocomplete_complete(ac, "èâ", TRUE, FALSE);
assert_string_equal("èâîô", result);
autocomplete_free(ac);
free(result);
}
void
autocomplete_complete__returns__accented_with_base(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "èâîô");
char* result = autocomplete_complete(ac, "ea", TRUE, FALSE);
assert_string_equal("èâîô", result);
autocomplete_free(ac);
free(result);
}
void
autocomplete_complete__returns__both_with_accented(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "eaooooo");
autocomplete_add(ac, "èâîô");
char* result1 = autocomplete_complete(ac, "èâ", TRUE, FALSE);
char* result2 = autocomplete_complete(ac, result1, TRUE, FALSE);
assert_string_equal("èâîô", result2);
autocomplete_free(ac);
free(result1);
free(result2);
}
void
autocomplete_complete__returns__both_with_base(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "eaooooo");
autocomplete_add(ac, "èâîô");
char* result1 = autocomplete_complete(ac, "ea", TRUE, FALSE);
char* result2 = autocomplete_complete(ac, result1, TRUE, FALSE);
assert_string_equal("èâîô", result2);
autocomplete_free(ac);
free(result1);
free(result2);
}
void
autocomplete_complete__is__case_insensitive(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "MyBuddy");
char* result = autocomplete_complete(ac, "myb", TRUE, FALSE);
assert_string_equal("MyBuddy", result);
autocomplete_free(ac);
free(result);
}
void
autocomplete_complete__returns__previous(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "MyBuddy1");
autocomplete_add(ac, "MyBuddy2");
autocomplete_add(ac, "MyBuddy3");
char* result1 = autocomplete_complete(ac, "myb", TRUE, FALSE);
char* result2 = autocomplete_complete(ac, result1, TRUE, FALSE);
char* result3 = autocomplete_complete(ac, result2, TRUE, FALSE);
char* result4 = autocomplete_complete(ac, result3, TRUE, TRUE);
assert_string_equal("MyBuddy2", result4);
autocomplete_free(ac);
free(result1);
free(result2);
free(result3);
free(result4);
}
void
autocomplete_complete__returns__greek_false_match(void** state)
{
Autocomplete ac = autocomplete_new();
// Σωκράτης (Socrates) and Πλάτων (Plato)
autocomplete_add(ac, "Σωκράτης");
autocomplete_add(ac, "Πλάτων");
char* result = autocomplete_complete(ac, "Π", TRUE, FALSE);
assert_string_equal("Πλάτων", result);
autocomplete_free(ac);
free(result);
}
void
autocomplete_complete__returns__greek(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "Αριστοτέλης");
char* result = autocomplete_complete(ac, "Αριστ", TRUE, FALSE);
assert_non_null(result);
assert_string_equal("Αριστοτέλης", result);
autocomplete_free(ac);
free(result);
}
void
autocomplete_complete__returns__greek_case_insensitive(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "Σωκράτης");
// Case insensitive search for Socrates
// σω is the lowercase of Σω
char* result = autocomplete_complete(ac, "σω", TRUE, FALSE);
assert_non_null(result);
assert_string_equal("Σωκράτης", result);
autocomplete_free(ac);
free(result);
}
void
autocomplete_complete__returns__russian(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "Достоевский");
autocomplete_add(ac, "Толстой");
autocomplete_add(ac, "Пушкин");
char* result = autocomplete_complete(ac, "дост", TRUE, FALSE);
assert_non_null(result);
assert_string_equal("Достоевский", result);
autocomplete_free(ac);
free(result);
}
void
autocomplete_complete__returns__chinese(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "孙子");
autocomplete_add(ac, "诸葛亮");
char* result = autocomplete_complete(ac, "", TRUE, FALSE);
assert_non_null(result);
assert_string_equal("孙子", result);
autocomplete_free(ac);
free(result);
}
void
autocomplete_complete__returns__transliterated(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "München");
// Match 'ü' with 'u'
char* result = autocomplete_complete(ac, "mun", TRUE, FALSE);
assert_non_null(result);
assert_string_equal("München", result);
autocomplete_free(ac);
free(result);
}
void
autocomplete_complete__returns__regular_ascii(void** state)
{
Autocomplete ac = autocomplete_new();
autocomplete_add(ac, "London");
char* result = autocomplete_complete(ac, "lon", TRUE, FALSE);
assert_non_null(result);
assert_string_equal("London", result);
autocomplete_free(ac);
free(result);
}