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. */
26 static gpointer
list_model_get(GListModel * model,guint position)27 list_model_get (GListModel *model,
28 guint position)
29 {
30 GObject *item = g_list_model_get_item (model, position);
31 GObject *object = g_list_model_get_object (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. */
40 static void
test_store_properties(void)41 test_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 (store, "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. */
54 static void
test_store_non_gobjects(void)55 test_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, "item-type", G_TYPE_LONG, NULL);
62 return;
63 }
64
65 g_test_trap_subprocess (NULL, 0, 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
71 static void
test_store_boundaries(void)72 test_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, G_LOG_LEVEL_CRITICAL, "*g_sequence*");
83 g_list_store_remove (store, 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, G_LOG_LEVEL_CRITICAL, "*g_sequence*");
88 g_list_store_insert (store, 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, 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, G_LOG_LEVEL_CRITICAL, "*g_sequence*");
98 g_list_store_remove (store, 1);
99 g_test_assert_expected_messages ();
100
101 g_list_store_remove (store, 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, G_LOG_LEVEL_CRITICAL, "*position*");
106 g_list_store_splice (store, 1, 0, NULL, 0);
107 g_test_assert_expected_messages ();
108
109 /* remove items from an empty list */
110 g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*position*");
111 g_list_store_splice (store, 0, 1, NULL, 0);
112 g_test_assert_expected_messages ();
113
114 g_list_store_append (store, item);
115 g_list_store_splice (store, 0, 1, (gpointer *) &item, 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, G_LOG_LEVEL_CRITICAL, "*position*");
120 g_list_store_splice (store, 0, 5, NULL, 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 (store);
125 g_assert_finalize_object (item);
126 }
127
128 static void
test_store_refcounts(void)129 test_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]), (gpointer *) &items[i]);
147 g_list_store_append (store, items[i]);
148
149 g_object_unref (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 (G_LIST_MODEL (store), 3);
157 g_assert_true (tmp == items[3]);
158 g_object_unref (tmp);
159
160 g_list_store_remove (store, 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 (store);
167 for (i = 0; i < G_N_ELEMENTS (items); i++)
168 g_assert_null (items[i]);
169 }
170
171 static gchar *
make_random_string(void)172 make_random_string (void)
173 {
174 gchar *str = g_malloc (10);
175 gint i;
176
177 for (i = 0; i < 9; i++)
178 str[i] = g_test_rand_int_range ('a', 'z');
179 str[i] = '\0';
180
181 return str;
182 }
183
184 static gint
compare_items(gconstpointer a_p,gconstpointer b_p,gpointer user_data)185 compare_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 (a_o, "key");
193 gchar *b = g_object_get_data (b_o, "key");
194
195 g_assert (user_data == GUINT_TO_POINTER(0x1234u));
196
197 return strcmp (a, b);
198 }
199
200 static void
insert_string(GListStore * store,const gchar * str)201 insert_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 (obj, "key", g_strdup (str), g_free);
208
209 g_list_store_insert_sorted (store, obj, compare_items, GUINT_TO_POINTER(0x1234u));
210
211 g_object_unref (obj);
212 }
213
214 static void
test_store_sorted(void)215 test_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 (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 (G_LIST_MODEL (store), i * 2);
238 b = list_model_get (G_LIST_MODEL (store), 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 (G_LIST_MODEL (store), 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 (c);
255 }
256
257 g_object_unref (a);
258 g_object_unref (b);
259 }
260
261 g_object_unref (store);
262 }
263
264 /* Test that using splice() to replace the middle element in a list store works. */
265 static void
test_store_splice_replace_middle(void)266 test_store_splice_replace_middle (void)
267 {
268 GListStore *store;
269 GListModel *model;
270 GAction *item;
271 GPtrArray *array;
272
273 g_test_bug ("https://bugzilla.gnome.org/show_bug.cgi?id=795307");
274
275 store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
276 model = G_LIST_MODEL (store);
277
278 array = g_ptr_array_new_full (0, g_object_unref);
279 g_ptr_array_add (array, g_simple_action_new ("1", NULL));
280 g_ptr_array_add (array, g_simple_action_new ("2", NULL));
281 g_ptr_array_add (array, g_simple_action_new ("3", NULL));
282 g_ptr_array_add (array, g_simple_action_new ("4", NULL));
283 g_ptr_array_add (array, g_simple_action_new ("5", NULL));
284
285 /* Add three items through splice */
286 g_list_store_splice (store, 0, 0, array->pdata, 3);
287 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 3);
288
289 item = list_model_get (model, 0);
290 g_assert_cmpstr (g_action_get_name (item), ==, "1");
291 g_object_unref (item);
292 item = list_model_get (model, 1);
293 g_assert_cmpstr (g_action_get_name (item), ==, "2");
294 g_object_unref (item);
295 item = list_model_get (model, 2);
296 g_assert_cmpstr (g_action_get_name (item), ==, "3");
297 g_object_unref (item);
298
299 /* Replace the middle one with two new items */
300 g_list_store_splice (store, 1, 1, array->pdata + 3, 2);
301 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 4);
302
303 item = list_model_get (model, 0);
304 g_assert_cmpstr (g_action_get_name (item), ==, "1");
305 g_object_unref (item);
306 item = list_model_get (model, 1);
307 g_assert_cmpstr (g_action_get_name (item), ==, "4");
308 g_object_unref (item);
309 item = list_model_get (model, 2);
310 g_assert_cmpstr (g_action_get_name (item), ==, "5");
311 g_object_unref (item);
312 item = list_model_get (model, 3);
313 g_assert_cmpstr (g_action_get_name (item), ==, "3");
314 g_object_unref (item);
315
316 g_ptr_array_unref (array);
317 g_object_unref (store);
318 }
319
320 /* Test that using splice() to replace the whole list store works. */
321 static void
test_store_splice_replace_all(void)322 test_store_splice_replace_all (void)
323 {
324 GListStore *store;
325 GListModel *model;
326 GPtrArray *array;
327 GAction *item;
328
329 g_test_bug ("https://bugzilla.gnome.org/show_bug.cgi?id=795307");
330
331 store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
332 model = G_LIST_MODEL (store);
333
334 array = g_ptr_array_new_full (0, g_object_unref);
335 g_ptr_array_add (array, g_simple_action_new ("1", NULL));
336 g_ptr_array_add (array, g_simple_action_new ("2", NULL));
337 g_ptr_array_add (array, g_simple_action_new ("3", NULL));
338 g_ptr_array_add (array, g_simple_action_new ("4", NULL));
339
340 /* Add the first two */
341 g_list_store_splice (store, 0, 0, array->pdata, 2);
342
343 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 2);
344 item = list_model_get (model, 0);
345 g_assert_cmpstr (g_action_get_name (item), ==, "1");
346 g_object_unref (item);
347 item = list_model_get (model, 1);
348 g_assert_cmpstr (g_action_get_name (item), ==, "2");
349 g_object_unref (item);
350
351 /* Replace all with the last two */
352 g_list_store_splice (store, 0, 2, array->pdata + 2, 2);
353
354 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 2);
355 item = list_model_get (model, 0);
356 g_assert_cmpstr (g_action_get_name (item), ==, "3");
357 g_object_unref (item);
358 item = list_model_get (model, 1);
359 g_assert_cmpstr (g_action_get_name (item), ==, "4");
360 g_object_unref (item);
361
362 g_ptr_array_unref (array);
363 g_object_unref (store);
364 }
365
366 /* Test that using splice() without removing or adding anything works */
367 static void
test_store_splice_noop(void)368 test_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 (store);
376
377 /* splice noop with an empty list */
378 g_list_store_splice (store, 0, 0, NULL, 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 (item);
385
386 g_list_store_splice (store, 0, 0, NULL, 0);
387 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 1);
388
389 g_list_store_splice (store, 1, 0, NULL, 0);
390 g_assert_cmpuint (g_list_model_get_n_items (model), ==, 1);
391
392 item = list_model_get (model, 0);
393 g_assert_cmpstr (g_action_get_name (item), ==, "1");
394 g_object_unref (item);
395
396 g_object_unref (store);
397 }
398
399 static gboolean
model_array_equal(GListModel * model,GPtrArray * array)400 model_array_equal (GListModel *model, GPtrArray *array)
401 {
402 guint i;
403
404 if (g_list_model_get_n_items (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, i);
413 ptrs_equal = (g_ptr_array_index (array, i) == ptr);
414 g_object_unref (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 */
424 static void
test_store_splice_remove_multiple(void)425 test_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 (store);
433
434 array = g_ptr_array_new_full (0, g_object_unref);
435 g_ptr_array_add (array, g_simple_action_new ("1", NULL));
436 g_ptr_array_add (array, g_simple_action_new ("2", NULL));
437 g_ptr_array_add (array, g_simple_action_new ("3", NULL));
438 g_ptr_array_add (array, g_simple_action_new ("4", NULL));
439 g_ptr_array_add (array, g_simple_action_new ("5", NULL));
440 g_ptr_array_add (array, g_simple_action_new ("6", NULL));
441 g_ptr_array_add (array, g_simple_action_new ("7", NULL));
442 g_ptr_array_add (array, g_simple_action_new ("8", NULL));
443 g_ptr_array_add (array, g_simple_action_new ("9", NULL));
444 g_ptr_array_add (array, g_simple_action_new ("10", NULL));
445
446 /* Add all */
447 g_list_store_splice (store, 0, 0, array->pdata, array->len);
448 g_assert_true (model_array_equal (model, array));
449
450 /* Remove the first two */
451 g_list_store_splice (store, 0, 2, NULL, 0);
452 g_assert_false (model_array_equal (model, array));
453 g_ptr_array_remove_range (array, 0, 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, 2, 2, NULL, 0);
459 g_assert_false (model_array_equal (model, array));
460 g_ptr_array_remove_range (array, 2, 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, 4, 2, NULL, 0);
466 g_assert_false (model_array_equal (model, array));
467 g_ptr_array_remove_range (array, 4, 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 (store);
473 }
474
475 /* Test that using splice() to add multiple items at different
476 * positions works */
477 static void
test_store_splice_add_multiple(void)478 test_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 (store);
486
487 array = g_ptr_array_new_full (0, g_object_unref);
488 g_ptr_array_add (array, g_simple_action_new ("1", NULL));
489 g_ptr_array_add (array, g_simple_action_new ("2", NULL));
490 g_ptr_array_add (array, g_simple_action_new ("3", NULL));
491 g_ptr_array_add (array, g_simple_action_new ("4", NULL));
492 g_ptr_array_add (array, g_simple_action_new ("5", NULL));
493 g_ptr_array_add (array, g_simple_action_new ("6", NULL));
494
495 /* Add two at the beginning */
496 g_list_store_splice (store, 0, 0, array->pdata, 2);
497
498 /* Add two at the end */
499 g_list_store_splice (store, 2, 0, array->pdata + 4, 2);
500
501 /* Add two in the middle */
502 g_list_store_splice (store, 2, 0, array->pdata + 2, 2);
503
504 g_assert_true (model_array_equal (model, array));
505
506 g_ptr_array_unref (array);
507 g_object_unref (store);
508 }
509
510 /* Test that get_item_type() returns the right type */
511 static void
test_store_item_type(void)512 test_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 (G_LIST_MODEL (store));
519 g_assert (gtype == G_TYPE_SIMPLE_ACTION);
520
521 g_object_unref (store);
522 }
523
524 /* Test that remove_all() removes all items */
525 static void
test_store_remove_all(void)526 test_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 (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 ("42", NULL);
541 g_list_store_append (store, item);
542 g_list_store_append (store, item);
543 g_object_unref (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 (store);
549 }
550
551 /* Test that splice() logs an error when passed the wrong item type */
552 static void
test_store_splice_wrong_type(void)553 test_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 G_LOG_LEVEL_CRITICAL,
561 "*GListStore instead of a GSimpleAction*");
562 g_list_store_splice (store, 0, 0, (gpointer)&store, 1);
563
564 g_object_unref (store);
565 }
566
567 static gint
ptr_array_cmp_action_by_name(GAction ** a,GAction ** b)568 ptr_array_cmp_action_by_name (GAction **a, GAction **b)
569 {
570 return g_strcmp0 (g_action_get_name (*a), g_action_get_name (*b));
571 }
572
573 static gint
list_model_cmp_action_by_name(GAction * a,GAction * b,gpointer user_data)574 list_model_cmp_action_by_name (GAction *a, GAction *b, gpointer user_data)
575 {
576 return g_strcmp0 (g_action_get_name (a), g_action_get_name (b));
577 }
578
579 /* Test if sort() works */
580 static void
test_store_sort(void)581 test_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 (store);
589
590 array = g_ptr_array_new_full (0, g_object_unref);
591 g_ptr_array_add (array, g_simple_action_new ("2", NULL));
592 g_ptr_array_add (array, g_simple_action_new ("3", NULL));
593 g_ptr_array_add (array, g_simple_action_new ("9", NULL));
594 g_ptr_array_add (array, g_simple_action_new ("4", NULL));
595 g_ptr_array_add (array, g_simple_action_new ("5", NULL));
596 g_ptr_array_add (array, g_simple_action_new ("8", NULL));
597 g_ptr_array_add (array, g_simple_action_new ("6", NULL));
598 g_ptr_array_add (array, g_simple_action_new ("7", NULL));
599 g_ptr_array_add (array, g_simple_action_new ("1", NULL));
600
601 /* Sort an empty list */
602 g_list_store_sort (store, (GCompareDataFunc)list_model_cmp_action_by_name, NULL);
603
604 /* Add all */
605 g_list_store_splice (store, 0, 0, array->pdata, 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, (GCompareFunc)ptr_array_cmp_action_by_name);
610 g_assert_false (model_array_equal (model, array));
611 g_list_store_sort (store, (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 (store);
616 }
617
618 /* Test the cases where the item store tries to speed up item access by caching
619 * the last iter/position */
620 static void
test_store_get_item_cache(void)621 test_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 (store);
629
630 /* Add two */
631 item1 = g_simple_action_new ("1", NULL);
632 g_list_store_append (store, item1);
633 item2 = g_simple_action_new ("2", NULL);
634 g_list_store_append (store, 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, 1);
641 g_assert (temp == item2);
642 g_object_unref (temp);
643 temp = list_model_get (model, 1);
644 g_assert (temp == item2);
645 g_object_unref (temp);
646
647 g_assert_null (list_model_get (model, 42));
648
649 /* Access forwards */
650 temp = list_model_get (model, 0);
651 g_assert (temp == item1);
652 g_object_unref (temp);
653 temp = list_model_get (model, 1);
654 g_assert (temp == item2);
655 g_object_unref (temp);
656
657 g_assert_null (list_model_get (model, 42));
658
659 /* Access backwards */
660 temp = list_model_get (model, 1);
661 g_assert (temp == item2);
662 g_object_unref (temp);
663 temp = list_model_get (model, 0);
664 g_assert (temp == item1);
665 g_object_unref (temp);
666
667 g_object_unref (item1);
668 g_object_unref (item2);
669 g_object_unref (store);
670 }
671
672 struct ItemsChangedData
673 {
674 guint position;
675 guint removed;
676 guint added;
677 gboolean called;
678 };
679
680 static void
expect_items_changed(struct ItemsChangedData * expected,guint position,guint removed,guint added)681 expect_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
692 static void
on_items_changed(GListModel * model,guint position,guint removed,guint added,struct ItemsChangedData * expected)693 on_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 */
707 static void
test_store_signal_items_changed(void)708 test_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 (store);
717
718 g_object_connect (model, "signal::items-changed",
719 on_items_changed, &expected, NULL);
720
721 /* Emit the signal manually */
722 expect_items_changed (&expected, 0, 0, 0);
723 g_list_model_items_changed (model, 0, 0, 0);
724 g_assert_true (expected.called);
725
726 /* Append an item */
727 expect_items_changed (&expected, 0, 0, 1);
728 item = g_simple_action_new ("2", NULL);
729 g_list_store_append (store, item);
730 g_object_unref (item);
731 g_assert_true (expected.called);
732
733 /* Insert an item */
734 expect_items_changed (&expected, 1, 0, 1);
735 item = g_simple_action_new ("1", NULL);
736 g_list_store_insert (store, 1, item);
737 g_object_unref (item);
738 g_assert_true (expected.called);
739
740 /* Sort the list */
741 expect_items_changed (&expected, 0, 2, 2);
742 g_list_store_sort (store,
743 (GCompareDataFunc)list_model_cmp_action_by_name,
744 NULL);
745 g_assert_true (expected.called);
746
747 /* Insert sorted */
748 expect_items_changed (&expected, 2, 0, 1);
749 item = g_simple_action_new ("3", NULL);
750 g_list_store_insert_sorted (store,
751 item,
752 (GCompareDataFunc)list_model_cmp_action_by_name,
753 NULL);
754 g_object_unref (item);
755 g_assert_true (expected.called);
756
757 /* Remove an item */
758 expect_items_changed (&expected, 1, 1, 0);
759 g_list_store_remove (store, 1);
760 g_assert_true (expected.called);
761
762 /* Splice */
763 expect_items_changed (&expected, 0, 2, 1);
764 item = g_simple_action_new ("4", NULL);
765 g_assert_cmpuint (g_list_model_get_n_items (model), >=, 2);
766 g_list_store_splice (store, 0, 2, (gpointer)&item, 1);
767 g_object_unref (item);
768 g_assert_true (expected.called);
769
770 /* Remove all */
771 expect_items_changed (&expected, 0, 1, 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 (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 */
784 static void
test_store_past_end(void)785 test_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 (store);
793
794 item = g_simple_action_new ("2", NULL);
795 g_list_store_append (store, item);
796 g_object_unref (item);
797
798 g_assert_cmpint (g_list_model_get_n_items (model), ==, 1);
799 item = g_list_model_get_item (model, 0);
800 g_assert_nonnull (item);
801 g_object_unref (item);
802 item = g_list_model_get_item (model, G_MAXUINT);
803 g_assert_null (item);
804
805 g_object_unref (store);
806 }
807
808 static gboolean
list_model_casecmp_action_by_name(gconstpointer a,gconstpointer b)809 list_model_casecmp_action_by_name (gconstpointer a,
810 gconstpointer b)
811 {
812 return g_ascii_strcasecmp (g_action_get_name (G_ACTION (a)),
813 g_action_get_name (G_ACTION (b))) == 0;
814 }
815
816 /* Test if find() and find_with_equal_func() works */
817 static void
test_store_find(void)818 test_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 (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, 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 ("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, 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 ("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
main(int argc,char * argv[])874 int main (int argc, char *argv[])
875 {
876 g_test_init (&argc, &argv, NULL);
877
878 g_test_add_func ("/glistmodel/store/properties", test_store_properties);
879 g_test_add_func ("/glistmodel/store/non-gobjects", test_store_non_gobjects);
880 g_test_add_func ("/glistmodel/store/boundaries", test_store_boundaries);
881 g_test_add_func ("/glistmodel/store/refcounts", test_store_refcounts);
882 g_test_add_func ("/glistmodel/store/sorted", test_store_sorted);
883 g_test_add_func ("/glistmodel/store/splice-replace-middle",
884 test_store_splice_replace_middle);
885 g_test_add_func ("/glistmodel/store/splice-replace-all",
886 test_store_splice_replace_all);
887 g_test_add_func ("/glistmodel/store/splice-noop", test_store_splice_noop);
888 g_test_add_func ("/glistmodel/store/splice-remove-multiple",
889 test_store_splice_remove_multiple);
890 g_test_add_func ("/glistmodel/store/splice-add-multiple",
891 test_store_splice_add_multiple);
892 g_test_add_func ("/glistmodel/store/splice-wrong-type",
893 test_store_splice_wrong_type);
894 g_test_add_func ("/glistmodel/store/item-type",
895 test_store_item_type);
896 g_test_add_func ("/glistmodel/store/remove-all",
897 test_store_remove_all);
898 g_test_add_func ("/glistmodel/store/sort",
899 test_store_sort);
900 g_test_add_func ("/glistmodel/store/get-item-cache",
901 test_store_get_item_cache);
902 g_test_add_func ("/glistmodel/store/items-changed",
903 test_store_signal_items_changed);
904 g_test_add_func ("/glistmodel/store/past-end", test_store_past_end);
905 g_test_add_func ("/glistmodel/store/find", test_store_find);
906
907 return g_test_run ();
908 }
909