52d71d91747cd3e6afaa3efd57e41c473afd54e4
[platform/upstream/glib.git] / gio / tests / gmenumodel.c
1 #include <gio/gio.h>
2
3 /* TestItem {{{1 */
4
5 /* This utility struct is used by both the RandomMenu and MirrorMenu
6  * class implementations below.
7  */
8 typedef struct {
9   GHashTable *attributes;
10   GHashTable *links;
11 } TestItem;
12
13 static TestItem *
14 test_item_new (GHashTable *attributes,
15                GHashTable *links)
16 {
17   TestItem *item;
18
19   item = g_slice_new (TestItem);
20   item->attributes = g_hash_table_ref (attributes);
21   item->links = g_hash_table_ref (links);
22
23   return item;
24 }
25
26 static void
27 test_item_free (gpointer data)
28 {
29   TestItem *item = data;
30
31   g_hash_table_unref (item->attributes);
32   g_hash_table_unref (item->links);
33
34   g_slice_free (TestItem, item);
35 }
36
37 /* RandomMenu {{{1 */
38 #define MAX_ITEMS 5
39 #define TOP_ORDER 4
40
41 typedef struct {
42   GMenuModel parent_instance;
43
44   GSequence *items;
45   gint order;
46 } RandomMenu;
47
48 typedef GMenuModelClass RandomMenuClass;
49
50 static GType random_menu_get_type (void);
51 G_DEFINE_TYPE (RandomMenu, random_menu, G_TYPE_MENU_MODEL);
52
53 static gboolean
54 random_menu_is_mutable (GMenuModel *model)
55 {
56   return TRUE;
57 }
58
59 static gint
60 random_menu_get_n_items (GMenuModel *model)
61 {
62   RandomMenu *menu = (RandomMenu *) model;
63
64   return g_sequence_get_length (menu->items);
65 }
66
67 static void
68 random_menu_get_item_attributes (GMenuModel  *model,
69                                  gint         position,
70                                  GHashTable **table)
71 {
72   RandomMenu *menu = (RandomMenu *) model;
73   TestItem *item;
74
75   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
76   *table = g_hash_table_ref (item->attributes);
77 }
78
79 static void
80 random_menu_get_item_links (GMenuModel  *model,
81                             gint         position,
82                             GHashTable **table)
83 {
84   RandomMenu *menu = (RandomMenu *) model;
85   TestItem *item;
86
87   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
88   *table = g_hash_table_ref (item->links);
89 }
90
91 static void
92 random_menu_finalize (GObject *object)
93 {
94   RandomMenu *menu = (RandomMenu *) object;
95
96   g_sequence_free (menu->items);
97
98   G_OBJECT_CLASS (random_menu_parent_class)
99     ->finalize (object);
100 }
101
102 static void
103 random_menu_init (RandomMenu *menu)
104 {
105 }
106
107 static void
108 random_menu_class_init (GMenuModelClass *class)
109 {
110   GObjectClass *object_class = G_OBJECT_CLASS (class);
111
112   class->is_mutable = random_menu_is_mutable;
113   class->get_n_items = random_menu_get_n_items;
114   class->get_item_attributes = random_menu_get_item_attributes;
115   class->get_item_links = random_menu_get_item_links;
116
117   object_class->finalize = random_menu_finalize;
118 }
119
120 static RandomMenu * random_menu_new (GRand *rand, gint order);
121
122 static void
123 random_menu_change (RandomMenu *menu,
124                     GRand      *rand)
125 {
126   gint position, removes, adds;
127   GSequenceIter *point;
128   gint n_items;
129   gint i;
130
131   n_items = g_sequence_get_length (menu->items);
132
133   do
134     {
135       position = g_rand_int_range (rand, 0, n_items + 1);
136       removes = g_rand_int_range (rand, 0, n_items - position + 1);
137       adds = g_rand_int_range (rand, 0, MAX_ITEMS - (n_items - removes) + 1);
138     }
139   while (removes == 0 && adds == 0);
140
141   point = g_sequence_get_iter_at_pos (menu->items, position + removes);
142
143   if (removes)
144     {
145       GSequenceIter *start;
146
147       start = g_sequence_get_iter_at_pos (menu->items, position);
148       g_sequence_remove_range (start, point);
149     }
150
151   for (i = 0; i < adds; i++)
152     {
153       const gchar *label;
154       GHashTable *links;
155       GHashTable *attributes;
156
157       attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
158       links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
159
160       if (menu->order > 0 && g_rand_boolean (rand))
161         {
162           RandomMenu *child;
163           const gchar *subtype;
164
165           child = random_menu_new (rand, menu->order - 1);
166
167           if (g_rand_boolean (rand))
168             {
169               subtype = G_MENU_LINK_SECTION;
170               /* label some section headers */
171               if (g_rand_boolean (rand))
172                 label = "Section";
173               else
174                 label = NULL;
175             }
176           else
177             {
178               /* label all submenus */
179               subtype = G_MENU_LINK_SUBMENU;
180               label = "Submenu";
181             }
182
183           g_hash_table_insert (links, g_strdup (subtype), child);
184         }
185       else
186         /* label all terminals */
187         label = "Menu Item";
188
189       if (label)
190         g_hash_table_insert (attributes, g_strdup ("label"), g_variant_ref_sink (g_variant_new_string (label)));
191
192       g_sequence_insert_before (point, test_item_new (attributes, links));
193       g_hash_table_unref (links);
194       g_hash_table_unref (attributes);
195     }
196
197   g_menu_model_items_changed (G_MENU_MODEL (menu), position, removes, adds);
198 }
199
200 static RandomMenu *
201 random_menu_new (GRand *rand,
202                  gint   order)
203 {
204   RandomMenu *menu;
205
206   menu = g_object_new (random_menu_get_type (), NULL);
207   menu->items = g_sequence_new (test_item_free);
208   menu->order = order;
209
210   random_menu_change (menu, rand);
211
212   return menu;
213 }
214
215 /* MirrorMenu {{{1 */
216 typedef struct {
217   GMenuModel parent_instance;
218
219   GMenuModel *clone_of;
220   GSequence *items;
221   gulong handler_id;
222 } MirrorMenu;
223
224 typedef GMenuModelClass MirrorMenuClass;
225
226 static GType mirror_menu_get_type (void);
227 G_DEFINE_TYPE (MirrorMenu, mirror_menu, G_TYPE_MENU_MODEL);
228
229 static gboolean
230 mirror_menu_is_mutable (GMenuModel *model)
231 {
232   MirrorMenu *menu = (MirrorMenu *) model;
233
234   return menu->handler_id != 0;
235 }
236
237 static gint
238 mirror_menu_get_n_items (GMenuModel *model)
239 {
240   MirrorMenu *menu = (MirrorMenu *) model;
241
242   return g_sequence_get_length (menu->items);
243 }
244
245 static void
246 mirror_menu_get_item_attributes (GMenuModel  *model,
247                                  gint         position,
248                                  GHashTable **table)
249 {
250   MirrorMenu *menu = (MirrorMenu *) model;
251   TestItem *item;
252
253   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
254   *table = g_hash_table_ref (item->attributes);
255 }
256
257 static void
258 mirror_menu_get_item_links (GMenuModel  *model,
259                             gint         position,
260                             GHashTable **table)
261 {
262   MirrorMenu *menu = (MirrorMenu *) model;
263   TestItem *item;
264
265   item = g_sequence_get (g_sequence_get_iter_at_pos (menu->items, position));
266   *table = g_hash_table_ref (item->links);
267 }
268
269 static void
270 mirror_menu_finalize (GObject *object)
271 {
272   MirrorMenu *menu = (MirrorMenu *) object;
273
274   if (menu->handler_id)
275     g_signal_handler_disconnect (menu->clone_of, menu->handler_id);
276
277   g_sequence_free (menu->items);
278   g_object_unref (menu->clone_of);
279
280   G_OBJECT_CLASS (mirror_menu_parent_class)
281     ->finalize (object);
282 }
283
284 static void
285 mirror_menu_init (MirrorMenu *menu)
286 {
287 }
288
289 static void
290 mirror_menu_class_init (GMenuModelClass *class)
291 {
292   GObjectClass *object_class = G_OBJECT_CLASS (class);
293
294   class->is_mutable = mirror_menu_is_mutable;
295   class->get_n_items = mirror_menu_get_n_items;
296   class->get_item_attributes = mirror_menu_get_item_attributes;
297   class->get_item_links = mirror_menu_get_item_links;
298
299   object_class->finalize = mirror_menu_finalize;
300 }
301
302 static MirrorMenu * mirror_menu_new (GMenuModel *clone_of);
303
304 static void
305 mirror_menu_changed (GMenuModel *model,
306                      gint        position,
307                      gint        removed,
308                      gint        added,
309                      gpointer    user_data)
310 {
311   MirrorMenu *menu = user_data;
312   GSequenceIter *point;
313   gint i;
314
315   g_assert (model == menu->clone_of);
316
317   point = g_sequence_get_iter_at_pos (menu->items, position + removed);
318
319   if (removed)
320     {
321       GSequenceIter *start;
322
323       start = g_sequence_get_iter_at_pos (menu->items, position);
324       g_sequence_remove_range (start, point);
325     }
326
327   for (i = position; i < position + added; i++)
328     {
329       GMenuAttributeIter *attr_iter;
330       GMenuLinkIter *link_iter;
331       GHashTable *links;
332       GHashTable *attributes;
333       const gchar *name;
334       GMenuModel *child;
335       GVariant *value;
336
337       attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
338       links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
339
340       attr_iter = g_menu_model_iterate_item_attributes (model, i);
341       while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
342         {
343           g_hash_table_insert (attributes, g_strdup (name), value);
344         }
345       g_object_unref (attr_iter);
346
347       link_iter = g_menu_model_iterate_item_links (model, i);
348       while (g_menu_link_iter_get_next (link_iter, &name, &child))
349         {
350           g_hash_table_insert (links, g_strdup (name), mirror_menu_new (child));
351           g_object_unref (child);
352         }
353       g_object_unref (link_iter);
354
355       g_sequence_insert_before (point, test_item_new (attributes, links));
356       g_hash_table_unref (attributes);
357       g_hash_table_unref (links);
358     }
359
360   g_menu_model_items_changed (G_MENU_MODEL (menu), position, removed, added);
361 }
362
363 static MirrorMenu *
364 mirror_menu_new (GMenuModel *clone_of)
365 {
366   MirrorMenu *menu;
367
368   menu = g_object_new (mirror_menu_get_type (), NULL);
369   menu->items = g_sequence_new (test_item_free);
370   menu->clone_of = g_object_ref (clone_of);
371
372   if (g_menu_model_is_mutable (clone_of))
373     menu->handler_id = g_signal_connect (clone_of, "items-changed", G_CALLBACK (mirror_menu_changed), menu);
374   mirror_menu_changed (clone_of, 0, 0, g_menu_model_get_n_items (clone_of), menu);
375
376   return menu;
377 }
378
379 /* check_menus_equal(), assert_menus_equal() {{{1 */
380 static gboolean
381 check_menus_equal (GMenuModel *a,
382                    GMenuModel *b)
383 {
384   gboolean equal = TRUE;
385   gint a_n, b_n;
386   gint i;
387
388   a_n = g_menu_model_get_n_items (a);
389   b_n = g_menu_model_get_n_items (b);
390
391   if (a_n != b_n)
392     return FALSE;
393
394   for (i = 0; i < a_n; i++)
395     {
396       GMenuAttributeIter *attr_iter;
397       GVariant *a_value, *b_value;
398       GMenuLinkIter *link_iter;
399       GMenuModel *a_menu, *b_menu;
400       const gchar *name;
401
402       attr_iter = g_menu_model_iterate_item_attributes (a, i);
403       while (g_menu_attribute_iter_get_next (attr_iter, &name, &a_value))
404         {
405           b_value = g_menu_model_get_item_attribute_value (b, i, name, NULL);
406           equal &= b_value && g_variant_equal (a_value, b_value);
407           if (b_value)
408             g_variant_unref (b_value);
409           g_variant_unref (a_value);
410         }
411       g_object_unref (attr_iter);
412
413       attr_iter = g_menu_model_iterate_item_attributes (b, i);
414       while (g_menu_attribute_iter_get_next (attr_iter, &name, &b_value))
415         {
416           a_value = g_menu_model_get_item_attribute_value (a, i, name, NULL);
417           equal &= a_value && g_variant_equal (a_value, b_value);
418           if (a_value)
419             g_variant_unref (a_value);
420           g_variant_unref (b_value);
421         }
422       g_object_unref (attr_iter);
423
424       link_iter = g_menu_model_iterate_item_links (a, i);
425       while (g_menu_link_iter_get_next (link_iter, &name, &a_menu))
426         {
427           b_menu = g_menu_model_get_item_link (b, i, name);
428           equal &= b_menu && check_menus_equal (a_menu, b_menu);
429           if (b_menu)
430             g_object_unref (b_menu);
431           g_object_unref (a_menu);
432         }
433       g_object_unref (link_iter);
434
435       link_iter = g_menu_model_iterate_item_links (b, i);
436       while (g_menu_link_iter_get_next (link_iter, &name, &b_menu))
437         {
438           a_menu = g_menu_model_get_item_link (a, i, name);
439           equal &= a_menu && check_menus_equal (a_menu, b_menu);
440           if (a_menu)
441             g_object_unref (a_menu);
442           g_object_unref (b_menu);
443         }
444       g_object_unref (link_iter);
445     }
446
447   return equal;
448 }
449
450 static void
451 assert_menus_equal (GMenuModel *a,
452                     GMenuModel *b)
453 {
454   if (!check_menus_equal (a, b))
455     {
456       GString *string;
457
458       string = g_string_new ("\n  <a>\n");
459       g_menu_markup_print_string (string, G_MENU_MODEL (a), 4, 2);
460       g_string_append (string, "  </a>\n\n-------------\n  <b>\n");
461       g_menu_markup_print_string (string, G_MENU_MODEL (b), 4, 2);
462       g_string_append (string, "  </b>\n");
463       g_error ("%s", string->str);
464     }
465 }
466
467 /* Test cases {{{1 */
468 static void
469 test_equality (void)
470 {
471   GRand *randa, *randb;
472   guint32 seed;
473   gint i;
474
475   seed = g_test_rand_int ();
476
477   randa = g_rand_new_with_seed (seed);
478   randb = g_rand_new_with_seed (seed);
479
480   for (i = 0; i < 500; i++)
481     {
482       RandomMenu *a, *b;
483
484       a = random_menu_new (randa, TOP_ORDER);
485       b = random_menu_new (randb, TOP_ORDER);
486       assert_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b));
487       g_object_unref (b);
488       g_object_unref (a);
489     }
490
491   g_rand_int (randa);
492
493   for (i = 0; i < 500;)
494     {
495       RandomMenu *a, *b;
496
497       a = random_menu_new (randa, TOP_ORDER);
498       b = random_menu_new (randb, TOP_ORDER);
499       if (check_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b)))
500         {
501           /* by chance, they may really be equal.  double check. */
502           GString *as, *bs;
503
504           as = g_menu_markup_print_string (NULL, G_MENU_MODEL (a), 4, 2);
505           bs = g_menu_markup_print_string (NULL, G_MENU_MODEL (b), 4, 2);
506           g_assert_cmpstr (as->str, ==, bs->str);
507           g_string_free (bs, TRUE);
508           g_string_free (as, TRUE);
509         }
510       else
511         /* make sure we get enough unequals (ie: no GRand failure) */
512         i++;
513
514       g_object_unref (b);
515       g_object_unref (a);
516     }
517
518   g_rand_free (randb);
519   g_rand_free (randa);
520 }
521
522 static void
523 test_random (void)
524 {
525   RandomMenu *random;
526   MirrorMenu *mirror;
527   GRand *rand;
528   gint i;
529
530   rand = g_rand_new_with_seed (g_test_rand_int ());
531   random = random_menu_new (rand, TOP_ORDER);
532   mirror = mirror_menu_new (G_MENU_MODEL (random));
533
534   for (i = 0; i < 500; i++)
535     {
536       assert_menus_equal (G_MENU_MODEL (random), G_MENU_MODEL (mirror));
537       random_menu_change (random, rand);
538     }
539
540   g_object_unref (mirror);
541   g_object_unref (random);
542
543   g_rand_free (rand);
544 }
545
546 struct roundtrip_state
547 {
548   RandomMenu *random;
549   GMenuProxy *proxy;
550   GMainLoop *loop;
551   GRand *rand;
552   gint success;
553   gint count;
554 };
555
556 static gboolean
557 roundtrip_step (gpointer data)
558 {
559   struct roundtrip_state *state = data;
560
561   if (check_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy)))
562     {
563       state->success++;
564       state->count = 0;
565
566       if (state->success < 100)
567         random_menu_change (state->random, state->rand);
568       else
569         g_main_loop_quit (state->loop);
570     }
571   else if (state->count == 100)
572     {
573       assert_menus_equal (G_MENU_MODEL (state->random), G_MENU_MODEL (state->proxy));
574       g_assert_not_reached ();
575     }
576   else
577     state->count++;
578
579   return G_SOURCE_CONTINUE;
580 }
581
582 static void
583 test_dbus_roundtrip (void)
584 {
585   struct roundtrip_state state;
586   GDBusConnection *bus;
587
588   bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
589
590   state.rand = g_rand_new_with_seed (g_test_rand_int ());
591
592   state.random = random_menu_new (state.rand, TOP_ORDER);
593   g_menu_model_dbus_export_start (bus, "/", G_MENU_MODEL (state.random), NULL);
594   state.proxy = g_menu_proxy_get (bus, g_dbus_connection_get_unique_name (bus), "/");
595   state.count = 0;
596   state.success = 0;
597
598   g_timeout_add (10, roundtrip_step, &state);
599
600   state.loop = g_main_loop_new (NULL, FALSE);
601   g_main_loop_run (state.loop);
602
603   g_main_loop_unref (state.loop);
604   g_object_unref (state.proxy);
605   g_menu_model_dbus_export_stop (G_MENU_MODEL (state.random));
606   g_object_unref (state.random);
607   g_rand_free (state.rand);
608   g_object_unref (bus);
609 }
610
611 static void
612 start_element (GMarkupParseContext *context,
613                const gchar         *element_name,
614                const gchar        **attribute_names,
615                const gchar        **attribute_values,
616                gpointer             user_data,
617                GError             **error)
618 {
619   if (g_strcmp0 (element_name, "menu") == 0)
620     g_menu_markup_parser_start_menu (context, "domain", NULL);
621 }
622
623 static void
624 end_element (GMarkupParseContext *context,
625              const gchar         *element_name,
626              gpointer             user_data,
627              GError             **error)
628 {
629   GMenu **menu = user_data;
630
631   if (g_strcmp0 (element_name, "menu") == 0)
632     *menu = g_menu_markup_parser_end_menu (context);
633 }
634
635 static GMenuModel *
636 parse_menu_string (const gchar *string, GError **error)
637 {
638   const GMarkupParser parser = {
639     start_element, end_element, NULL, NULL, NULL
640   };
641   GMarkupParseContext *context;
642   GMenuModel *menu = NULL;
643
644   context = g_markup_parse_context_new (&parser, 0, &menu, NULL);
645   g_markup_parse_context_parse (context, string, -1, error);
646   g_markup_parse_context_free (context);
647
648   return menu;
649 }
650
651 static gchar *
652 menu_to_string (GMenuModel *menu)
653 {
654   GString *s;
655
656   s = g_string_new ("<menu>\n");
657   g_menu_markup_print_string (s, menu, 2, 2);
658   g_string_append (s, "</menu>\n");
659
660   return g_string_free (s, FALSE);
661 }
662
663 static void
664 test_markup_roundtrip (void)
665 {
666   const gchar data[] =
667   "<menu id='edit-menu'>\n"
668   "  <section>\n"
669   "    <item action='undo'>\n"
670   "      <attribute name='label' translatable='yes' context='Stock label'>'_Undo'</attribute>\n"
671   "    </item>\n"
672   "    <item label='Redo' action='redo'/>\n"
673   "  </section>\n"
674   "  <section></section>\n"
675   "  <section label='Copy &amp; Paste'>\n"
676   "    <item label='Cut' action='cut'/>\n"
677   "    <item label='Copy' action='copy'/>\n"
678   "    <item label='Paste' action='paste'/>\n"
679   "  </section>\n"
680   "  <section>\n"
681   "    <item label='Bold' action='bold'/>\n"
682   "    <submenu label='Language'>\n"
683   "      <item label='Latin' action='lang' target='latin'/>\n"
684   "      <item label='Greek' action='lang' target='greek'/>\n"
685   "      <item label='Urdu'  action='lang' target='urdu'/>\n"
686   "    </submenu>\n"
687   "    <item name='test unusual attributes'>\n"
688   "      <attribute name='action' type='s'>'quite-some-action'</attribute>\n"
689   "      <attribute name='target' type='i'>36</attribute>\n"
690   "      <attribute name='chocolate-thunda' type='as'>['a','b']</attribute>\n"
691   "      <attribute name='thing1' type='g'>'s(uu)'</attribute>\n"
692   "      <attribute name='icon' type='s'>'small blue thing'</attribute>\n"
693   "   </item>\n"
694   "  </section>\n"
695   "</menu>\n";
696   GError *error = NULL;
697   GMenuModel *a;
698   GMenuModel *b;
699   gchar *s;
700   gchar *s2;
701
702   a = parse_menu_string (data, &error);
703   g_assert_no_error (error);
704   g_assert (G_IS_MENU_MODEL (a));
705
706   /* normalized representation */
707   s = menu_to_string (a);
708
709   b = parse_menu_string (s, &error);
710   g_assert_no_error (error);
711   g_assert (G_IS_MENU_MODEL (b));
712
713   assert_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b));
714
715   s2 = menu_to_string (b);
716
717   g_assert_cmpstr (s, ==, s2);
718
719   g_object_unref (a);
720   g_object_unref (b);
721   g_free (s);
722   g_free (s2);
723 }
724
725 static void
726 test_attributes (void)
727 {
728   GMenu *menu;
729   GMenuItem *item;
730   GVariant *v;
731
732   menu = g_menu_new ();
733
734   item = g_menu_item_new ("test", NULL);
735   g_menu_item_set_attribute_value (item, "boolean", g_variant_new_boolean (FALSE));
736   g_menu_item_set_attribute_value (item, "string", g_variant_new_string ("bla"));
737   g_menu_item_set_attribute_value (item, "double", g_variant_new_double (1.5));
738   v = g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three");
739   g_menu_item_set_attribute_value (item, "complex", v);
740   g_menu_item_set_attribute_value (item, "test-123", g_variant_new_string ("test-123"));
741
742   g_menu_append_item (menu, item);
743
744   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 1);
745
746   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "boolean", NULL);
747   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN));
748   g_variant_unref (v);
749
750   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "string", NULL);
751   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
752   g_variant_unref (v);
753
754   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "double", NULL);
755   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_DOUBLE));
756   g_variant_unref (v);
757
758   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "complex", NULL);
759   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE("a(si)")));
760   g_variant_unref (v);
761
762   g_object_unref (menu);
763 }
764
765 static void
766 test_links (void)
767 {
768   GMenu *menu;
769   GMenuModel *m;
770   GMenuModel *x;
771   GMenuItem *item;
772
773   m = G_MENU_MODEL (g_menu_new ());
774   g_menu_append (G_MENU (m), "test", NULL);
775
776   menu = g_menu_new ();
777
778   item = g_menu_item_new ("test1", NULL);
779   g_menu_item_set_link (item, "section", m);
780   g_menu_append_item (menu, item);
781
782   item = g_menu_item_new ("test2", NULL);
783   g_menu_item_set_link (item, "submenu", m);
784   g_menu_append_item (menu, item);
785
786   item = g_menu_item_new ("test3", NULL);
787   g_menu_item_set_link (item, "wallet", m);
788   g_menu_append_item (menu, item);
789
790   item = g_menu_item_new ("test4", NULL);
791   g_menu_item_set_link (item, "purse", m);
792   g_menu_item_set_link (item, "purse", NULL);
793   g_menu_append_item (menu, item);
794
795   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 4);
796
797   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 0, "section");
798   g_assert (x == m);
799   g_object_unref (x);
800
801   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 1, "submenu");
802   g_assert (x == m);
803   g_object_unref (x);
804
805   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 2, "wallet");
806   g_assert (x == m);
807   g_object_unref (x);
808
809   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 3, "purse");
810   g_assert (x == NULL);
811
812   g_object_unref (m);
813   g_object_unref (menu);
814 }
815
816 static void
817 test_mutable (void)
818 {
819   GMenu *menu;
820
821   menu = g_menu_new ();
822   g_menu_append (menu, "test", "test");
823
824   g_assert (g_menu_model_is_mutable (G_MENU_MODEL (menu)));
825   g_menu_freeze (menu);
826   g_assert (!g_menu_model_is_mutable (G_MENU_MODEL (menu)));
827
828   g_object_unref (menu);
829 }
830
831 /* Epilogue {{{1 */
832 int
833 main (int argc, char **argv)
834 {
835   g_test_init (&argc, &argv, NULL);
836
837   g_type_init ();
838
839   g_test_add_func ("/gmenu/equality", test_equality);
840   g_test_add_func ("/gmenu/random", test_random);
841   g_test_add_func ("/gmenu/bus/roundtrip", test_dbus_roundtrip);
842   g_test_add_func ("/gmenu/markup/roundtrip", test_markup_roundtrip);
843   g_test_add_func ("/gmenu/attributes", test_attributes);
844   g_test_add_func ("/gmenu/links", test_links);
845   g_test_add_func ("/gmenu/mutable", test_mutable);
846
847   return g_test_run ();
848 }
849 /* vim:set foldmethod=marker: */