1/*
2 * Copyright 2015 Lars Uebernickel
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General
15 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Lars Uebernickel <lars@uebernic.de>
18 */
19
20#include <gio/gio.h>
21
22#include <string.h>
23
24/* Wrapper around g_list_model_get_item() and g_list_model_get_object() which
25 * checks they return the same thing. */
26static gpointer
27list_model_get (GListModel *model,
28 guint position)
29{
30 GObject *item = g_list_model_get_item (list: model, position);
31 GObject *object = g_list_model_get_object (list: model, position);
32
33 g_assert_true (item == object);
34
35 g_clear_object (&object);
36 return g_steal_pointer (&item);
37}
38
39/* Test that constructing/getting/setting properties on a #GListStore works. */
40static void
41test_store_properties (void)
42{
43 GListStore *store = NULL;
44 GType item_type;
45
46 store = g_list_store_new (G_TYPE_MENU_ITEM);
47 g_object_get (object: store, first_property_name: "item-type", &item_type, NULL);
48 g_assert_cmpint (item_type, ==, G_TYPE_MENU_ITEM);
49
50 g_clear_object (&store);
51}
52
53/* Test that #GListStore rejects non-GObject item types. */
54static void
55test_store_non_gobjects (void)
56{
57 if (g_test_subprocess ())
58 {
59 /* We have to use g_object_new() since g_list_store_new() checks the item
60 * type. We want to check the property setter code works properly. */
61 g_object_new (G_TYPE_LIST_STORE, first_property_name: "item-type", G_TYPE_LONG, NULL);
62 return;
63 }
64
65 g_test_trap_subprocess (NULL, usec_timeout: 0, test_flags: 0);
66 g_test_trap_assert_failed ();
67 g_test_trap_assert_stderr ("*WARNING*value * of type 'GType' is invalid or "
68 "out of range for property 'item-type'*");
69}
70
71static void
72test_store_boundaries (void)
73{
74 GListStore *store;
75 GMenuItem *item;
76
77 store = g_list_store_new (G_TYPE_MENU_ITEM);
78
79 item = g_menu_item_new (NULL, NULL);
80
81 /* remove an item from an empty list */
82 g_test_expect_message (G_LOG_DOMAIN, log_level: G_LOG_LEVEL_CRITICAL, pattern: "*g_sequence*");
83 g_list_store_remove (store, position: 0);
84 g_test_assert_expected_messages ();
85
86 /* don't allow inserting an item past the end ... */
87 g_test_expect_message (G_LOG_DOMAIN, log_level: G_LOG_LEVEL_CRITICAL, pattern: "*g_sequence*");
88 g_list_store_insert (store, position: 1, item);
89 g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, 0);
90 g_test_assert_expected_messages ();
91
92 /* ... except exactly at the end */
93 g_list_store_insert (store, position: 0, item);
94 g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, 1);
95
96 /* remove a non-existing item at exactly the end of the list */
97 g_test_expect_message (G_LOG_DOMAIN, log_level: G_LOG_LEVEL_CRITICAL, pattern: "*g_sequence*");
98 g_list_store_remove (store, position: 1);
99 g_test_assert_expected_messages ();
100
101 g_list_store_remove (store, position: 0);
102 g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, 0);
103
104 /* splice beyond the end of the list */
105 g_test_expect_message (G_LOG_DOMAIN, log_level: G_LOG_LEVEL_CRITICAL, pattern: "*position*");
106 g_list_store_splice (store, position: 1, n_removals: 0, NULL, n_additions: 0);
107 g_test_assert_expected_messages ();
108
109 /* remove items from an empty list */
110 g_test_expect_message (G_LOG_DOMAIN, log_level: G_LOG_LEVEL_CRITICAL, pattern: "*position*");
111 g_list_store_splice (store, position: 0, n_removals: 1, NULL, n_additions: 0);
112 g_test_assert_expected_messages ();
113
114 g_list_store_append (store, item);
115 g_list_store_splice (store, position: 0, n_removals: 1, additions: (gpointer *) &item, n_additions: 1);
116 g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, 1);
117
118 /* remove more items than exist */
119 g_test_expect_message (G_LOG_DOMAIN, log_level: G_LOG_LEVEL_CRITICAL, pattern: "*position*");
120 g_list_store_splice (store, position: 0, n_removals: 5, NULL, n_additions: 0);
121 g_test_assert_expected_messages ();
122 g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, 1);
123
124 g_object_unref (object: store);
125 g_assert_finalize_object (item);
126}
127
128static void
129test_store_refcounts (void)
130{
131 GListStore *store;
132 GMenuItem *items[10];
133 GMenuItem *tmp;
134 guint i;
135 guint n_items;
136
137 store = g_list_store_new (G_TYPE_MENU_ITEM);
138
139 g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, 0);
140 g_assert_null (list_model_get (G_LIST_MODEL (store), 0));
141
142 n_items = G_N_ELEMENTS (items);
143 for (i = 0; i < n_items; i++)
144 {
145 items[i] = g_menu_item_new (NULL, NULL);
146 g_object_add_weak_pointer (G_OBJECT (items[i]), weak_pointer_location: (gpointer *) &items[i]);
147 g_list_store_append (store, item: items[i]);
148
149 g_object_unref (object: items[i]);
150 g_assert_nonnull (items[i]);
151 }
152
153 g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, n_items);
154 g_assert_null (list_model_get (G_LIST_MODEL (store), n_items));
155
156 tmp = list_model_get (model: G_LIST_MODEL (ptr: store), position: 3);
157 g_assert_true (tmp == items[3]);
158 g_object_unref (object: tmp);
159
160 g_list_store_remove (store, position: 4);
161 g_assert_null (items[4]);
162 n_items--;
163 g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, n_items);
164 g_assert_null (list_model_get (G_LIST_MODEL (store), n_items));
165
166 g_object_unref (object: store);
167 for (i = 0; i < G_N_ELEMENTS (items); i++)
168 g_assert_null (items[i]);
169}
170
171static gchar *
172make_random_string (void)
173{
174 gchar *str = g_malloc (n_bytes: 10);
175 gint i;
176
177 for (i = 0; i < 9; i++)
178 str[i] = g_test_rand_int_range (begin: 'a', end: 'z');
179 str[i] = '\0';
180
181 return str;
182}
183
184static gint
185compare_items (gconstpointer a_p,
186 gconstpointer b_p,
187 gpointer user_data)
188{
189 GObject *a_o = (GObject *) a_p;
190 GObject *b_o = (GObject *) b_p;
191
192 gchar *a = g_object_get_data (object: a_o, key: "key");
193 gchar *b = g_object_get_data (object: b_o, key: "key");
194
195 g_assert (user_data == GUINT_TO_POINTER(0x1234u));
196
197 return strcmp (s1: a, s2: b);
198}
199
200static void
201insert_string (GListStore *store,
202 const gchar *str)
203{
204 GObject *obj;
205
206 obj = g_object_new (G_TYPE_OBJECT, NULL);
207 g_object_set_data_full (object: obj, key: "key", data: g_strdup (str), destroy: g_free);
208
209 g_list_store_insert_sorted (store, item: obj, compare_func: compare_items, GUINT_TO_POINTER(0x1234u));
210
211 g_object_unref (object: obj);
212}
213
214static void
215test_store_sorted (void)
216{
217 GListStore *store;
218 guint i;
219
220 store = g_list_store_new (G_TYPE_OBJECT);
221
222 for (i = 0; i < 1000; i++)
223 {
224 gchar *str = make_random_string ();
225 insert_string (store, str);
226 insert_string (store, str); /* multiple copies of the same are OK */
227 g_free (mem: str);
228 }
229
230 g_assert_cmpint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, 2000);
231
232 for (i = 0; i < 1000; i++)
233 {
234 GObject *a, *b;
235
236 /* should see our two copies */
237 a = list_model_get (model: G_LIST_MODEL (ptr: store), position: i * 2);
238 b = list_model_get (model: G_LIST_MODEL (ptr: store), position: i * 2 + 1);
239
240 g_assert (compare_items (a, b, GUINT_TO_POINTER(0x1234)) == 0);
241 g_assert (a != b);
242
243 if (i)
244 {
245 GObject *c;
246
247 c = list_model_get (model: G_LIST_MODEL (ptr: store), position: i * 2 - 1);
248 g_assert (c != a);
249 g_assert (c != b);
250
251 g_assert (compare_items (b, c, GUINT_TO_POINTER(0x1234)) > 0);
252 g_assert (compare_items (a, c, GUINT_TO_POINTER(0x1234)) > 0);
253
254 g_object_unref (object: c);
255 }
256
257 g_object_unref (object: a);
258 g_object_unref (object: b);
259 }
260
261 g_object_unref (object: store);
262}
263
264/* Test that using splice() to replace the middle element in a list store works. */
265static void
266test_store_splice_replace_middle (void)
267{
268 GListStore *store;
269 GListModel *model;
270 GAction *item;
271 GPtrArray *array;
272
273 g_test_bug (bug_uri_snippet: "795307");
274
275 store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
276 model = G_LIST_MODEL (ptr: store);
277
278 array = g_ptr_array_new_full (reserved_size: 0, element_free_func: g_object_unref);
279 g_ptr_array_add (array, data: g_simple_action_new (name: "1", NULL));
280 g_ptr_array_add (array, data: g_simple_action_new (name: "2", NULL));
281 g_ptr_array_add (array, data: g_simple_action_new (name: "3", NULL));
282 g_ptr_array_add (array, data: g_simple_action_new (name: "4", NULL));
283 g_ptr_array_add (array, data: g_simple_action_new (name: "5", NULL));
284
285 /* Add three items through splice */
286 g_list_store_splice (store, position: 0, n_removals: 0, additions: array->pdata, n_additions: 3);
287 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 3);
288
289 item = list_model_get (model, position: 0);
290 g_assert_cmpstr (g_action_get_name (item), ==, "1");
291 g_object_unref (object: item);
292 item = list_model_get (model, position: 1);
293 g_assert_cmpstr (g_action_get_name (item), ==, "2");
294 g_object_unref (object: item);
295 item = list_model_get (model, position: 2);
296 g_assert_cmpstr (g_action_get_name (item), ==, "3");
297 g_object_unref (object: item);
298
299 /* Replace the middle one with two new items */
300 g_list_store_splice (store, position: 1, n_removals: 1, additions: array->pdata + 3, n_additions: 2);
301 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 4);
302
303 item = list_model_get (model, position: 0);
304 g_assert_cmpstr (g_action_get_name (item), ==, "1");
305 g_object_unref (object: item);
306 item = list_model_get (model, position: 1);
307 g_assert_cmpstr (g_action_get_name (item), ==, "4");
308 g_object_unref (object: item);
309 item = list_model_get (model, position: 2);
310 g_assert_cmpstr (g_action_get_name (item), ==, "5");
311 g_object_unref (object: item);
312 item = list_model_get (model, position: 3);
313 g_assert_cmpstr (g_action_get_name (item), ==, "3");
314 g_object_unref (object: item);
315
316 g_ptr_array_unref (array);
317 g_object_unref (object: store);
318}
319
320/* Test that using splice() to replace the whole list store works. */
321static void
322test_store_splice_replace_all (void)
323{
324 GListStore *store;
325 GListModel *model;
326 GPtrArray *array;
327 GAction *item;
328
329 g_test_bug (bug_uri_snippet: "795307");
330
331 store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
332 model = G_LIST_MODEL (ptr: store);
333
334 array = g_ptr_array_new_full (reserved_size: 0, element_free_func: g_object_unref);
335 g_ptr_array_add (array, data: g_simple_action_new (name: "1", NULL));
336 g_ptr_array_add (array, data: g_simple_action_new (name: "2", NULL));
337 g_ptr_array_add (array, data: g_simple_action_new (name: "3", NULL));
338 g_ptr_array_add (array, data: g_simple_action_new (name: "4", NULL));
339
340 /* Add the first two */
341 g_list_store_splice (store, position: 0, n_removals: 0, additions: array->pdata, n_additions: 2);
342
343 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 2);
344 item = list_model_get (model, position: 0);
345 g_assert_cmpstr (g_action_get_name (item), ==, "1");
346 g_object_unref (object: item);
347 item = list_model_get (model, position: 1);
348 g_assert_cmpstr (g_action_get_name (item), ==, "2");
349 g_object_unref (object: item);
350
351 /* Replace all with the last two */
352 g_list_store_splice (store, position: 0, n_removals: 2, additions: array->pdata + 2, n_additions: 2);
353
354 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 2);
355 item = list_model_get (model, position: 0);
356 g_assert_cmpstr (g_action_get_name (item), ==, "3");
357 g_object_unref (object: item);
358 item = list_model_get (model, position: 1);
359 g_assert_cmpstr (g_action_get_name (item), ==, "4");
360 g_object_unref (object: item);
361
362 g_ptr_array_unref (array);
363 g_object_unref (object: store);
364}
365
366/* Test that using splice() without removing or adding anything works */
367static void
368test_store_splice_noop (void)
369{
370 GListStore *store;
371 GListModel *model;
372 GAction *item;
373
374 store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
375 model = G_LIST_MODEL (ptr: store);
376
377 /* splice noop with an empty list */
378 g_list_store_splice (store, position: 0, n_removals: 0, NULL, n_additions: 0);
379 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 0);
380
381 /* splice noop with a non-empty list */
382 item = G_ACTION (g_simple_action_new ("1", NULL));
383 g_list_store_append (store, item);
384 g_object_unref (object: item);
385
386 g_list_store_splice (store, position: 0, n_removals: 0, NULL, n_additions: 0);
387 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 1);
388
389 g_list_store_splice (store, position: 1, n_removals: 0, NULL, n_additions: 0);
390 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 1);
391
392 item = list_model_get (model, position: 0);
393 g_assert_cmpstr (g_action_get_name (item), ==, "1");
394 g_object_unref (object: item);
395
396 g_object_unref (object: store);
397}
398
399static gboolean
400model_array_equal (GListModel *model, GPtrArray *array)
401{
402 guint i;
403
404 if (g_list_model_get_n_items (list: model) != array->len)
405 return FALSE;
406
407 for (i = 0; i < array->len; i++)
408 {
409 GObject *ptr;
410 gboolean ptrs_equal;
411
412 ptr = list_model_get (model, position: i);
413 ptrs_equal = (g_ptr_array_index (array, i) == ptr);
414 g_object_unref (object: ptr);
415 if (!ptrs_equal)
416 return FALSE;
417 }
418
419 return TRUE;
420}
421
422/* Test that using splice() to remove multiple items at different
423 * positions works */
424static void
425test_store_splice_remove_multiple (void)
426{
427 GListStore *store;
428 GListModel *model;
429 GPtrArray *array;
430
431 store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
432 model = G_LIST_MODEL (ptr: store);
433
434 array = g_ptr_array_new_full (reserved_size: 0, element_free_func: g_object_unref);
435 g_ptr_array_add (array, data: g_simple_action_new (name: "1", NULL));
436 g_ptr_array_add (array, data: g_simple_action_new (name: "2", NULL));
437 g_ptr_array_add (array, data: g_simple_action_new (name: "3", NULL));
438 g_ptr_array_add (array, data: g_simple_action_new (name: "4", NULL));
439 g_ptr_array_add (array, data: g_simple_action_new (name: "5", NULL));
440 g_ptr_array_add (array, data: g_simple_action_new (name: "6", NULL));
441 g_ptr_array_add (array, data: g_simple_action_new (name: "7", NULL));
442 g_ptr_array_add (array, data: g_simple_action_new (name: "8", NULL));
443 g_ptr_array_add (array, data: g_simple_action_new (name: "9", NULL));
444 g_ptr_array_add (array, data: g_simple_action_new (name: "10", NULL));
445
446 /* Add all */
447 g_list_store_splice (store, position: 0, n_removals: 0, additions: array->pdata, n_additions: array->len);
448 g_assert_true (model_array_equal (model, array));
449
450 /* Remove the first two */
451 g_list_store_splice (store, position: 0, n_removals: 2, NULL, n_additions: 0);
452 g_assert_false (model_array_equal (model, array));
453 g_ptr_array_remove_range (array, index_: 0, length: 2);
454 g_assert_true (model_array_equal (model, array));
455 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 8);
456
457 /* Remove two in the middle */
458 g_list_store_splice (store, position: 2, n_removals: 2, NULL, n_additions: 0);
459 g_assert_false (model_array_equal (model, array));
460 g_ptr_array_remove_range (array, index_: 2, length: 2);
461 g_assert_true (model_array_equal (model, array));
462 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 6);
463
464 /* Remove two at the end */
465 g_list_store_splice (store, position: 4, n_removals: 2, NULL, n_additions: 0);
466 g_assert_false (model_array_equal (model, array));
467 g_ptr_array_remove_range (array, index_: 4, length: 2);
468 g_assert_true (model_array_equal (model, array));
469 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 4);
470
471 g_ptr_array_unref (array);
472 g_object_unref (object: store);
473}
474
475/* Test that using splice() to add multiple items at different
476 * positions works */
477static void
478test_store_splice_add_multiple (void)
479{
480 GListStore *store;
481 GListModel *model;
482 GPtrArray *array;
483
484 store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
485 model = G_LIST_MODEL (ptr: store);
486
487 array = g_ptr_array_new_full (reserved_size: 0, element_free_func: g_object_unref);
488 g_ptr_array_add (array, data: g_simple_action_new (name: "1", NULL));
489 g_ptr_array_add (array, data: g_simple_action_new (name: "2", NULL));
490 g_ptr_array_add (array, data: g_simple_action_new (name: "3", NULL));
491 g_ptr_array_add (array, data: g_simple_action_new (name: "4", NULL));
492 g_ptr_array_add (array, data: g_simple_action_new (name: "5", NULL));
493 g_ptr_array_add (array, data: g_simple_action_new (name: "6", NULL));
494
495 /* Add two at the beginning */
496 g_list_store_splice (store, position: 0, n_removals: 0, additions: array->pdata, n_additions: 2);
497
498 /* Add two at the end */
499 g_list_store_splice (store, position: 2, n_removals: 0, additions: array->pdata + 4, n_additions: 2);
500
501 /* Add two in the middle */
502 g_list_store_splice (store, position: 2, n_removals: 0, additions: array->pdata + 2, n_additions: 2);
503
504 g_assert_true (model_array_equal (model, array));
505
506 g_ptr_array_unref (array);
507 g_object_unref (object: store);
508}
509
510/* Test that get_item_type() returns the right type */
511static void
512test_store_item_type (void)
513{
514 GListStore *store;
515 GType gtype;
516
517 store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
518 gtype = g_list_model_get_item_type (list: G_LIST_MODEL (ptr: store));
519 g_assert (gtype == G_TYPE_SIMPLE_ACTION);
520
521 g_object_unref (object: store);
522}
523
524/* Test that remove_all() removes all items */
525static void
526test_store_remove_all (void)
527{
528 GListStore *store;
529 GListModel *model;
530 GSimpleAction *item;
531
532 store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
533 model = G_LIST_MODEL (ptr: store);
534
535 /* Test with an empty list */
536 g_list_store_remove_all (store);
537 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 0);
538
539 /* Test with a non-empty list */
540 item = g_simple_action_new (name: "42", NULL);
541 g_list_store_append (store, item);
542 g_list_store_append (store, item);
543 g_object_unref (object: item);
544 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 2);
545 g_list_store_remove_all (store);
546 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 0);
547
548 g_object_unref (object: store);
549}
550
551/* Test that splice() logs an error when passed the wrong item type */
552static void
553test_store_splice_wrong_type (void)
554{
555 GListStore *store;
556
557 store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
558
559 g_test_expect_message (G_LOG_DOMAIN,
560 log_level: G_LOG_LEVEL_CRITICAL,
561 pattern: "*GListStore instead of a GSimpleAction*");
562 g_list_store_splice (store, position: 0, n_removals: 0, additions: (gpointer)&store, n_additions: 1);
563
564 g_object_unref (object: store);
565}
566
567static gint
568ptr_array_cmp_action_by_name (GAction **a, GAction **b)
569{
570 return g_strcmp0 (str1: g_action_get_name (action: *a), str2: g_action_get_name (action: *b));
571}
572
573static gint
574list_model_cmp_action_by_name (GAction *a, GAction *b, gpointer user_data)
575{
576 return g_strcmp0 (str1: g_action_get_name (action: a), str2: g_action_get_name (action: b));
577}
578
579/* Test if sort() works */
580static void
581test_store_sort (void)
582{
583 GListStore *store;
584 GListModel *model;
585 GPtrArray *array;
586
587 store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
588 model = G_LIST_MODEL (ptr: store);
589
590 array = g_ptr_array_new_full (reserved_size: 0, element_free_func: g_object_unref);
591 g_ptr_array_add (array, data: g_simple_action_new (name: "2", NULL));
592 g_ptr_array_add (array, data: g_simple_action_new (name: "3", NULL));
593 g_ptr_array_add (array, data: g_simple_action_new (name: "9", NULL));
594 g_ptr_array_add (array, data: g_simple_action_new (name: "4", NULL));
595 g_ptr_array_add (array, data: g_simple_action_new (name: "5", NULL));
596 g_ptr_array_add (array, data: g_simple_action_new (name: "8", NULL));
597 g_ptr_array_add (array, data: g_simple_action_new (name: "6", NULL));
598 g_ptr_array_add (array, data: g_simple_action_new (name: "7", NULL));
599 g_ptr_array_add (array, data: g_simple_action_new (name: "1", NULL));
600
601 /* Sort an empty list */
602 g_list_store_sort (store, compare_func: (GCompareDataFunc)list_model_cmp_action_by_name, NULL);
603
604 /* Add all */
605 g_list_store_splice (store, position: 0, n_removals: 0, additions: array->pdata, n_additions: array->len);
606 g_assert_true (model_array_equal (model, array));
607
608 /* Sort both and check if the result is the same */
609 g_ptr_array_sort (array, compare_func: (GCompareFunc)ptr_array_cmp_action_by_name);
610 g_assert_false (model_array_equal (model, array));
611 g_list_store_sort (store, compare_func: (GCompareDataFunc)list_model_cmp_action_by_name, NULL);
612 g_assert_true (model_array_equal (model, array));
613
614 g_ptr_array_unref (array);
615 g_object_unref (object: store);
616}
617
618/* Test the cases where the item store tries to speed up item access by caching
619 * the last iter/position */
620static void
621test_store_get_item_cache (void)
622{
623 GListStore *store;
624 GListModel *model;
625 GSimpleAction *item1, *item2, *temp;
626
627 store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
628 model = G_LIST_MODEL (ptr: store);
629
630 /* Add two */
631 item1 = g_simple_action_new (name: "1", NULL);
632 g_list_store_append (store, item: item1);
633 item2 = g_simple_action_new (name: "2", NULL);
634 g_list_store_append (store, item: item2);
635
636 /* Clear the cache */
637 g_assert_null (list_model_get (model, 42));
638
639 /* Access the same position twice */
640 temp = list_model_get (model, position: 1);
641 g_assert (temp == item2);
642 g_object_unref (object: temp);
643 temp = list_model_get (model, position: 1);
644 g_assert (temp == item2);
645 g_object_unref (object: temp);
646
647 g_assert_null (list_model_get (model, 42));
648
649 /* Access forwards */
650 temp = list_model_get (model, position: 0);
651 g_assert (temp == item1);
652 g_object_unref (object: temp);
653 temp = list_model_get (model, position: 1);
654 g_assert (temp == item2);
655 g_object_unref (object: temp);
656
657 g_assert_null (list_model_get (model, 42));
658
659 /* Access backwards */
660 temp = list_model_get (model, position: 1);
661 g_assert (temp == item2);
662 g_object_unref (object: temp);
663 temp = list_model_get (model, position: 0);
664 g_assert (temp == item1);
665 g_object_unref (object: temp);
666
667 g_object_unref (object: item1);
668 g_object_unref (object: item2);
669 g_object_unref (object: store);
670}
671
672struct ItemsChangedData
673{
674 guint position;
675 guint removed;
676 guint added;
677 gboolean called;
678};
679
680static void
681expect_items_changed (struct ItemsChangedData *expected,
682 guint position,
683 guint removed,
684 guint added)
685{
686 expected->position = position;
687 expected->removed = removed;
688 expected->added = added;
689 expected->called = FALSE;
690}
691
692static void
693on_items_changed (GListModel *model,
694 guint position,
695 guint removed,
696 guint added,
697 struct ItemsChangedData *expected)
698{
699 g_assert_false (expected->called);
700 g_assert_cmpuint (expected->position, ==, position);
701 g_assert_cmpuint (expected->removed, ==, removed);
702 g_assert_cmpuint (expected->added, ==, added);
703 expected->called = TRUE;
704}
705
706/* Test that all operations on the list emit the items-changed signal */
707static void
708test_store_signal_items_changed (void)
709{
710 GListStore *store;
711 GListModel *model;
712 GSimpleAction *item;
713 struct ItemsChangedData expected = {0};
714
715 store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
716 model = G_LIST_MODEL (ptr: store);
717
718 g_object_connect (object: model, signal_spec: "signal::items-changed",
719 on_items_changed, &expected, NULL);
720
721 /* Emit the signal manually */
722 expect_items_changed (expected: &expected, position: 0, removed: 0, added: 0);
723 g_list_model_items_changed (list: model, position: 0, removed: 0, added: 0);
724 g_assert_true (expected.called);
725
726 /* Append an item */
727 expect_items_changed (expected: &expected, position: 0, removed: 0, added: 1);
728 item = g_simple_action_new (name: "2", NULL);
729 g_list_store_append (store, item);
730 g_object_unref (object: item);
731 g_assert_true (expected.called);
732
733 /* Insert an item */
734 expect_items_changed (expected: &expected, position: 1, removed: 0, added: 1);
735 item = g_simple_action_new (name: "1", NULL);
736 g_list_store_insert (store, position: 1, item);
737 g_object_unref (object: item);
738 g_assert_true (expected.called);
739
740 /* Sort the list */
741 expect_items_changed (expected: &expected, position: 0, removed: 2, added: 2);
742 g_list_store_sort (store,
743 compare_func: (GCompareDataFunc)list_model_cmp_action_by_name,
744 NULL);
745 g_assert_true (expected.called);
746
747 /* Insert sorted */
748 expect_items_changed (expected: &expected, position: 2, removed: 0, added: 1);
749 item = g_simple_action_new (name: "3", NULL);
750 g_list_store_insert_sorted (store,
751 item,
752 compare_func: (GCompareDataFunc)list_model_cmp_action_by_name,
753 NULL);
754 g_object_unref (object: item);
755 g_assert_true (expected.called);
756
757 /* Remove an item */
758 expect_items_changed (expected: &expected, position: 1, removed: 1, added: 0);
759 g_list_store_remove (store, position: 1);
760 g_assert_true (expected.called);
761
762 /* Splice */
763 expect_items_changed (expected: &expected, position: 0, removed: 2, added: 1);
764 item = g_simple_action_new (name: "4", NULL);
765 g_assert_cmpuint (g_list_model_get_n_items (model), >=, 2);
766 g_list_store_splice (store, position: 0, n_removals: 2, additions: (gpointer)&item, n_additions: 1);
767 g_object_unref (object: item);
768 g_assert_true (expected.called);
769
770 /* Remove all */
771 expect_items_changed (expected: &expected, position: 0, removed: 1, added: 0);
772 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 1);
773 g_list_store_remove_all (store);
774 g_assert_true (expected.called);
775
776 g_object_unref (object: store);
777}
778
779/* Due to an overflow in the list store last-iter optimization,
780 * the sequence 'lookup 0; lookup MAXUINT' was returning the
781 * same item twice, and not NULL for the second lookup.
782 * See #1639.
783 */
784static void
785test_store_past_end (void)
786{
787 GListStore *store;
788 GListModel *model;
789 GSimpleAction *item;
790
791 store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
792 model = G_LIST_MODEL (ptr: store);
793
794 item = g_simple_action_new (name: "2", NULL);
795 g_list_store_append (store, item);
796 g_object_unref (object: item);
797
798 g_assert_cmpint (g_list_model_get_n_items (model), ==, 1);
799 item = g_list_model_get_item (list: model, position: 0);
800 g_assert_nonnull (item);
801 g_object_unref (object: item);
802 item = g_list_model_get_item (list: model, G_MAXUINT);
803 g_assert_null (item);
804
805 g_object_unref (object: store);
806}
807
808static gboolean
809list_model_casecmp_action_by_name (gconstpointer a,
810 gconstpointer b)
811{
812 return g_ascii_strcasecmp (s1: g_action_get_name (G_ACTION (a)),
813 s2: g_action_get_name (G_ACTION (b))) == 0;
814}
815
816/* Test if find() and find_with_equal_func() works */
817static void
818test_store_find (void)
819{
820 GListStore *store;
821 guint position = 100;
822 const gchar *item_strs[4] = { "aaa", "bbb", "xxx", "ccc" };
823 GSimpleAction *items[4] = { NULL, };
824 GSimpleAction *other_item;
825 guint i;
826
827 store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
828
829 for (i = 0; i < G_N_ELEMENTS (item_strs); i++)
830 items[i] = g_simple_action_new (name: item_strs[i], NULL);
831
832 /* Shouldn't crash on an empty list, or change the position pointer */
833 g_assert_false (g_list_store_find (store, items[0], NULL));
834 g_assert_false (g_list_store_find (store, items[0], &position));
835 g_assert_cmpint (position, ==, 100);
836
837 for (i = 0; i < G_N_ELEMENTS (item_strs); i++)
838 g_list_store_append (store, item: items[i]);
839
840 /* Check whether it could still find the the elements */
841 for (i = 0; i < G_N_ELEMENTS (item_strs); i++)
842 {
843 g_assert_true (g_list_store_find (store, items[i], &position));
844 g_assert_cmpint (position, ==, i);
845 /* Shouldn't try to write to position pointer if NULL given */
846 g_assert_true (g_list_store_find (store, items[i], NULL));
847 }
848
849 /* try to find element not part of the list */
850 other_item = g_simple_action_new (name: "111", NULL);
851 g_assert_false (g_list_store_find (store, other_item, NULL));
852 g_clear_object (&other_item);
853
854 /* Re-add item; find() should only return the first position */
855 g_list_store_append (store, item: items[0]);
856 g_assert_true (g_list_store_find (store, items[0], &position));
857 g_assert_cmpint (position, ==, 0);
858
859 /* try to find element which should only work with custom equality check */
860 other_item = g_simple_action_new (name: "XXX", NULL);
861 g_assert_false (g_list_store_find (store, other_item, NULL));
862 g_assert_true (g_list_store_find_with_equal_func (store,
863 other_item,
864 list_model_casecmp_action_by_name,
865 &position));
866 g_assert_cmpint (position, ==, 2);
867 g_clear_object (&other_item);
868
869 for (i = 0; i < G_N_ELEMENTS (item_strs); i++)
870 g_clear_object(&items[i]);
871 g_clear_object (&store);
872}
873
874int main (int argc, char *argv[])
875{
876 g_test_init (argc: &argc, argv: &argv, NULL);
877 g_test_bug_base (uri_pattern: "https://bugzilla.gnome.org/");
878
879 g_test_add_func (testpath: "/glistmodel/store/properties", test_func: test_store_properties);
880 g_test_add_func (testpath: "/glistmodel/store/non-gobjects", test_func: test_store_non_gobjects);
881 g_test_add_func (testpath: "/glistmodel/store/boundaries", test_func: test_store_boundaries);
882 g_test_add_func (testpath: "/glistmodel/store/refcounts", test_func: test_store_refcounts);
883 g_test_add_func (testpath: "/glistmodel/store/sorted", test_func: test_store_sorted);
884 g_test_add_func (testpath: "/glistmodel/store/splice-replace-middle",
885 test_func: test_store_splice_replace_middle);
886 g_test_add_func (testpath: "/glistmodel/store/splice-replace-all",
887 test_func: test_store_splice_replace_all);
888 g_test_add_func (testpath: "/glistmodel/store/splice-noop", test_func: test_store_splice_noop);
889 g_test_add_func (testpath: "/glistmodel/store/splice-remove-multiple",
890 test_func: test_store_splice_remove_multiple);
891 g_test_add_func (testpath: "/glistmodel/store/splice-add-multiple",
892 test_func: test_store_splice_add_multiple);
893 g_test_add_func (testpath: "/glistmodel/store/splice-wrong-type",
894 test_func: test_store_splice_wrong_type);
895 g_test_add_func (testpath: "/glistmodel/store/item-type",
896 test_func: test_store_item_type);
897 g_test_add_func (testpath: "/glistmodel/store/remove-all",
898 test_func: test_store_remove_all);
899 g_test_add_func (testpath: "/glistmodel/store/sort",
900 test_func: test_store_sort);
901 g_test_add_func (testpath: "/glistmodel/store/get-item-cache",
902 test_func: test_store_get_item_cache);
903 g_test_add_func (testpath: "/glistmodel/store/items-changed",
904 test_func: test_store_signal_items_changed);
905 g_test_add_func (testpath: "/glistmodel/store/past-end", test_func: test_store_past_end);
906 g_test_add_func (testpath: "/glistmodel/store/find", test_func: test_store_find);
907
908 return g_test_run ();
909}
910

source code of gtk/subprojects/glib/gio/tests/glistmodel.c