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