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 |
27 | list_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. */ |
40 | static 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 (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. */ |
54 | static 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, 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 | |
71 | static 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, 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 | |
128 | static 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]), 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 | |
171 | static gchar * |
172 | make_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 | |
184 | static gint |
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 (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 | |
200 | static void |
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 (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 | |
214 | static 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 (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. */ |
265 | static 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 (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. */ |
321 | static 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 (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 */ |
367 | static 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 (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 | |
399 | static gboolean |
400 | model_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 */ |
424 | static 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 (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 */ |
477 | static 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 (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 */ |
511 | static 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 (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 */ |
525 | static 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 (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 */ |
552 | static 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 | 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 | |
567 | static gint |
568 | ptr_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 | |
573 | static gint |
574 | list_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 */ |
580 | static 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 (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 */ |
620 | static 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 (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 | |
672 | struct ItemsChangedData |
673 | { |
674 | guint position; |
675 | guint removed; |
676 | guint added; |
677 | gboolean called; |
678 | }; |
679 | |
680 | static void |
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 |
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 |
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 (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 | */ |
784 | static 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 (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 | |
808 | static gboolean |
809 | list_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 */ |
817 | static 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 (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 | |
874 | int 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 | |