Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
test_tagging_completion.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
3 Copyright (C) 2026 Paolo SANTUCCI.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
20
21#include <stdarg.h>
22#include <stddef.h>
23#include <setjmp.h> // NOLINT(misc-include-cleaner)
24#include <stdint.h>
25#include <cmocka.h>
26
28{
30 GtkEntry *entry;
31 GtkListStore *store;
32 GtkEntryCompletion *completion;
34
40
41static void _append_completion(dt_tagging_completion_test_t *test, const char *text)
42{
43 GtkTreeIter iter;
44 gtk_list_store_append(test->store, &iter);
45 gtk_list_store_set(test->store, &iter, 0, text, -1);
46}
47
48static void _drain_gtk_events()
49{
50 while(gtk_events_pending()) gtk_main_iteration();
51}
52
53static gboolean _quit_main_loop(GMainLoop *loop)
54{
55 g_main_loop_quit(loop);
56 return G_SOURCE_REMOVE;
57}
58
60{
61 GMainLoop *loop = g_main_loop_new(NULL, FALSE);
62 g_timeout_add(250, (GSourceFunc)_quit_main_loop, loop);
63 g_main_loop_run(loop);
64 g_main_loop_unref(loop);
65}
66
67static gboolean _completion_popup_is_mapped(GtkWidget *entry_window)
68{
69 gboolean mapped = FALSE;
70 GList *toplevels = gtk_window_list_toplevels();
71 for(GList *toplevel = toplevels; toplevel; toplevel = g_list_next(toplevel))
72 {
73 GtkWidget *widget = GTK_WIDGET(toplevel->data);
74 if(widget != entry_window && gtk_widget_get_mapped(widget)) mapped = TRUE;
75 }
76 g_list_free(toplevels);
77 return mapped;
78}
79
80static gboolean _send_key(GtkWidget *widget, const guint keyval)
81{
82 GdkEventKey event = { 0 };
83 event.type = GDK_KEY_PRESS;
84 event.window = g_object_ref(gtk_widget_get_window(widget));
85 event.send_event = TRUE;
86 event.time = GDK_CURRENT_TIME;
87 event.keyval = keyval;
88 gboolean handled = FALSE;
89 g_signal_emit_by_name(widget, "key-press-event", &event, &handled);
90 g_object_unref(event.window);
91 return handled;
92}
93
94static gboolean _match_last_hierarchical_token(GtkEntryCompletion *completion, const gchar *key,
95 GtkTreeIter *iter, gpointer user_data)
96{
97 gchar *candidate = NULL;
98 gtk_tree_model_get(gtk_entry_completion_get_model(completion), iter, 0, &candidate, -1);
99 const gchar *token = g_strrstr(key, ",");
100 token = token ? token + 1 : key;
101 while(*token == ' ') token++;
102 const gboolean matches = g_str_has_prefix(candidate, token);
103 g_free(candidate);
104 return matches;
105}
106
107static int _setup_completion(void **state)
108{
109 dt_tagging_completion_test_t *test = g_malloc0(sizeof(*test));
110 test->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
111 test->entry = GTK_ENTRY(gtk_entry_new());
112 test->store = gtk_list_store_new(1, G_TYPE_STRING);
113 _append_completion(test, "subjects|animals");
114 _append_completion(test, "places|france|paris");
115 test->completion = gtk_entry_completion_new();
116 gtk_entry_completion_set_model(test->completion, GTK_TREE_MODEL(test->store));
117 gtk_entry_completion_set_text_column(test->completion, 0);
118 gtk_entry_completion_set_inline_completion(test->completion, TRUE);
119 gtk_entry_completion_set_popup_completion(test->completion, TRUE);
120 gtk_entry_completion_set_minimum_key_length(test->completion, 1);
121 gtk_entry_completion_set_match_func(test->completion, _match_last_hierarchical_token, NULL, NULL);
122 g_signal_connect(test->completion, "match-selected",
123 G_CALLBACK(dt_tagging_completion_match_selected), NULL);
124 gtk_entry_set_completion(test->entry, test->completion);
125 gtk_container_add(GTK_CONTAINER(test->window), GTK_WIDGET(test->entry));
126 gtk_widget_show_all(test->window);
127 gtk_window_set_focus(GTK_WINDOW(test->window), GTK_WIDGET(test->entry));
128 gtk_window_present(GTK_WINDOW(test->window));
129 gdk_window_focus(gtk_widget_get_window(test->window), GDK_CURRENT_TIME);
131 GdkEvent *focus_event = gdk_event_new(GDK_FOCUS_CHANGE);
132 focus_event->focus_change.window = g_object_ref(gtk_widget_get_window(GTK_WIDGET(test->entry)));
133 focus_event->focus_change.in = TRUE;
134 gtk_widget_send_focus_change(GTK_WIDGET(test->entry), focus_event);
135 gdk_event_free(focus_event);
136 assert_true(gtk_widget_has_focus(GTK_WIDGET(test->entry)));
137 *state = test;
138 return 0;
139}
140
141static int _teardown_completion(void **state)
142{
144 gtk_entry_set_completion(test->entry, NULL);
145 gtk_widget_destroy(test->window);
147 g_object_unref(test->completion);
148 g_object_unref(test->store);
149 g_free(test);
150 return 0;
151}
152
153static gboolean _record_plain_return(GtkWidget *entry, GdkEventKey *event, dt_return_handler_state_t *state)
154{
155 if(event->keyval != GDK_KEY_Return) return FALSE;
156 state->entry_calls++;
157 g_free(state->text);
158 state->text = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
159 return FALSE;
160}
161
163{
165
166 gtk_entry_set_text(test->entry, "subjects");
168 gtk_list_store_clear(test->store);
169 _append_completion(test, "subjects|animals");
170 _append_completion(test, "subjects|architecture");
172
173 assert_string_equal(gtk_entry_get_text(test->entry), "");
174 assert_ptr_equal(gtk_entry_get_completion(test->entry), test->completion);
175 assert_false(_completion_popup_is_mapped(test->window));
176}
177
179{
181
182 gtk_entry_set_text(test->entry, "subjects");
185 assert_string_equal(gtk_entry_get_text(test->entry), "");
186 assert_false(_completion_popup_is_mapped(test->window));
187
188 gtk_entry_set_text(test->entry, "subjects|animals, places");
191 assert_string_equal(gtk_entry_get_text(test->entry), "");
192 assert_false(_completion_popup_is_mapped(test->window));
193
194 gint position = 0;
195 gtk_editable_insert_text(GTK_EDITABLE(test->entry), "places", -1, &position);
197 gtk_entry_completion_insert_prefix(test->completion);
199
200 assert_ptr_equal(gtk_entry_get_completion(test->entry), test->completion);
201 assert_string_equal(gtk_entry_get_text(test->entry), "places|france|paris");
202}
203
205{
206 GtkEntry *entry = GTK_ENTRY(gtk_entry_new());
207 gtk_entry_set_text(entry, "places|france");
208
210
211 assert_string_equal(gtk_entry_get_text(entry), "");
212 assert_null(gtk_entry_get_completion(entry));
213 gtk_widget_destroy(GTK_WIDGET(entry));
214}
215
217{
219 gtk_entry_completion_set_inline_completion(test->completion, FALSE);
220 gtk_entry_set_text(test->entry, "subjects|animals, pla");
221 gtk_editable_set_position(GTK_EDITABLE(test->entry), -1);
222 gtk_entry_completion_set_inline_completion(test->completion, TRUE);
223 GtkTreeIter iter;
224 assert_true(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(test->store), &iter));
225 assert_true(gtk_tree_model_iter_next(GTK_TREE_MODEL(test->store), &iter));
226
227 gboolean handled = FALSE;
228 g_signal_emit_by_name(test->completion, "match-selected",
229 GTK_TREE_MODEL(test->store), &iter, &handled);
231
232 assert_true(handled);
233 assert_string_equal(gtk_entry_get_text(test->entry),
234 "subjects|animals,places|france|paris");
235 assert_true(gtk_entry_completion_get_inline_completion(test->completion));
236 assert_ptr_equal(gtk_entry_get_completion(test->entry), test->completion);
237 assert_false(_completion_popup_is_mapped(test->window));
238}
239
241{
243 dt_return_handler_state_t handler_state = { 0 };
244 if(!gtk_window_is_active(GTK_WINDOW(test->window))) skip();
245
246 gulong handler_id = g_signal_connect(test->entry, "key-press-event",
247 G_CALLBACK(_record_plain_return), &handler_state);
248
249 g_signal_handler_disconnect(test->entry, handler_id);
251 handler_id = g_signal_connect(test->entry, "key-press-event",
252 G_CALLBACK(_record_plain_return), &handler_state);
253 gint position = 0;
254 gtk_editable_insert_text(GTK_EDITABLE(test->entry), "subjects|animals, pla", -1, &position);
257 {
258 g_signal_handler_disconnect(test->entry, handler_id);
259 skip();
260 }
261 assert_true(_send_key(GTK_WIDGET(test->entry), GDK_KEY_Down));
262 assert_true(_send_key(GTK_WIDGET(test->entry), GDK_KEY_Return));
264
265 assert_string_equal(gtk_entry_get_text(test->entry),
266 "subjects|animals,places|france|paris");
267 assert_int_equal(handler_state.entry_calls, 0);
268 assert_false(_completion_popup_is_mapped(test->window));
269 assert_true(gtk_entry_completion_get_inline_completion(test->completion));
270
271 _send_key(GTK_WIDGET(test->entry), GDK_KEY_Return);
272 assert_int_equal(handler_state.entry_calls, 1);
273 assert_string_equal(handler_state.text, "subjects|animals,places|france|paris");
274
275 g_signal_handler_disconnect(test->entry, handler_id);
276 g_free(handler_state.text);
277}
278
279int main(int argc, char **argv)
280{
281 if(!gtk_init_check(&argc, &argv)) return 77;
282 const struct CMUnitTest tests[] = {
283 cmocka_unit_test_setup_teardown(test_clear_cancels_completion_before_model_refresh,
288 cmocka_unit_test_setup_teardown(test_selected_completion_replaces_only_the_active_token,
290 cmocka_unit_test_setup_teardown(test_completion_keeps_return_priority_after_reattach,
292 };
293 return cmocka_run_group_tests(tests, NULL, NULL);
294}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int position()
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
char * key
int main()
Definition prova.c:47
const float uint32_t state[4]
void dt_tagging_entry_clear(GtkEntry *entry)
gboolean dt_tagging_completion_match_selected(GtkEntryCompletion *completion, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
static void test_selected_completion_replaces_only_the_active_token(void **state)
static int _teardown_completion(void **state)
static void test_completion_survives_repeated_single_and_hierarchical_clears(void **state)
static void test_clear_cancels_completion_before_model_refresh(void **state)
static gboolean _send_key(GtkWidget *widget, const guint keyval)
static gboolean _quit_main_loop(GMainLoop *loop)
static void _append_completion(dt_tagging_completion_test_t *test, const char *text)
static void _drain_gtk_events()
static gboolean _match_last_hierarchical_token(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter, gpointer user_data)
static gboolean _record_plain_return(GtkWidget *entry, GdkEventKey *event, dt_return_handler_state_t *state)
static void test_clear_supports_entry_without_completion(void **state)
static void test_completion_keeps_return_priority_after_reattach(void **state)
static void _wait_for_completion_sources()
static int _setup_completion(void **state)
static gboolean _completion_popup_is_mapped(GtkWidget *entry_window)