Add a test for menu subscriptions
[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 gint items_changed_count;
612
613 static void
614 items_changed (GMenuModel *model,
615                gint        position,
616                gint        removed,
617                gint        added,
618                gpointer    data)
619 {
620   items_changed_count++;
621 }
622
623 static gboolean
624 stop_loop (gpointer data)
625 {
626   GMainLoop *loop = data;
627
628   g_main_loop_quit (loop);
629
630   return FALSE;
631 }
632
633 static void
634 test_dbus_subscriptions (void)
635 {
636   GDBusConnection *bus;
637   GMenu *menu;
638   GMenuProxy *proxy;
639   GVariant *v;
640   GMainLoop *loop;
641
642   bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
643
644   menu = g_menu_new ();
645
646   g_menu_model_dbus_export_start (bus, "/", G_MENU_MODEL (menu), NULL);
647
648   proxy = g_menu_proxy_get (bus, g_dbus_connection_get_unique_name (bus), "/");
649   items_changed_count = 0;
650   g_signal_connect (proxy, "items-changed",
651                     G_CALLBACK (items_changed), NULL);
652
653   g_menu_append (menu, "item1", NULL);
654   g_menu_append (menu, "item2", NULL);
655   g_menu_append (menu, "item3", NULL);
656
657   g_assert_cmpint (items_changed_count, ==, 0);
658
659   loop = g_main_loop_new (NULL, FALSE);
660   g_timeout_add (100, stop_loop, loop);
661   g_main_loop_run (loop);
662
663   g_menu_model_get_n_items (G_MENU_MODEL (proxy));
664
665   g_timeout_add (100, stop_loop, loop);
666   g_main_loop_run (loop);
667
668   g_assert_cmpint (items_changed_count, ==, 1);
669
670   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 3);
671
672   g_timeout_add (100, stop_loop, loop);
673   g_main_loop_run (loop);
674
675   g_menu_append (menu, "item4", NULL);
676   g_menu_append (menu, "item5", NULL);
677   g_menu_append (menu, "item6", NULL);
678   g_menu_remove (menu, 0);
679   g_menu_remove (menu, 0);
680
681   g_timeout_add (100, stop_loop, loop);
682   g_main_loop_run (loop);
683
684   g_assert_cmpint (items_changed_count, ==, 6);
685
686   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (proxy)), ==, 4);
687   g_object_unref (proxy);
688
689   g_timeout_add (100, stop_loop, loop);
690   g_main_loop_run (loop);
691
692   g_menu_remove (menu, 0);
693   g_menu_remove (menu, 0);
694
695   g_timeout_add (100, stop_loop, loop);
696   g_main_loop_run (loop);
697
698   g_assert_cmpint (items_changed_count, ==, 6);
699
700   g_menu_model_dbus_export_stop (G_MENU_MODEL (menu));
701   g_object_unref (menu);
702 }
703
704 static void
705 start_element (GMarkupParseContext *context,
706                const gchar         *element_name,
707                const gchar        **attribute_names,
708                const gchar        **attribute_values,
709                gpointer             user_data,
710                GError             **error)
711 {
712   if (g_strcmp0 (element_name, "menu") == 0)
713     g_menu_markup_parser_start_menu (context, "domain", NULL);
714 }
715
716 static void
717 end_element (GMarkupParseContext *context,
718              const gchar         *element_name,
719              gpointer             user_data,
720              GError             **error)
721 {
722   GMenu **menu = user_data;
723
724   if (g_strcmp0 (element_name, "menu") == 0)
725     *menu = g_menu_markup_parser_end_menu (context);
726 }
727
728 static GMenuModel *
729 parse_menu_string (const gchar *string, GError **error)
730 {
731   const GMarkupParser parser = {
732     start_element, end_element, NULL, NULL, NULL
733   };
734   GMarkupParseContext *context;
735   GMenuModel *menu = NULL;
736
737   context = g_markup_parse_context_new (&parser, 0, &menu, NULL);
738   g_markup_parse_context_parse (context, string, -1, error);
739   g_markup_parse_context_free (context);
740
741   return menu;
742 }
743
744 static gchar *
745 menu_to_string (GMenuModel *menu)
746 {
747   GString *s;
748
749   s = g_string_new ("<menu>\n");
750   g_menu_markup_print_string (s, menu, 2, 2);
751   g_string_append (s, "</menu>\n");
752
753   return g_string_free (s, FALSE);
754 }
755
756 static void
757 test_markup_roundtrip (void)
758 {
759   const gchar data[] =
760   "<menu id='edit-menu'>\n"
761   "  <section>\n"
762   "    <item action='undo'>\n"
763   "      <attribute name='label' translatable='yes' context='Stock label'>'_Undo'</attribute>\n"
764   "    </item>\n"
765   "    <item label='Redo' action='redo'/>\n"
766   "  </section>\n"
767   "  <section></section>\n"
768   "  <section label='Copy &amp; Paste'>\n"
769   "    <item label='Cut' action='cut'/>\n"
770   "    <item label='Copy' action='copy'/>\n"
771   "    <item label='Paste' action='paste'/>\n"
772   "  </section>\n"
773   "  <section>\n"
774   "    <item label='Bold' action='bold'/>\n"
775   "    <submenu label='Language'>\n"
776   "      <item label='Latin' action='lang' target='latin'/>\n"
777   "      <item label='Greek' action='lang' target='greek'/>\n"
778   "      <item label='Urdu'  action='lang' target='urdu'/>\n"
779   "    </submenu>\n"
780   "    <item name='test unusual attributes'>\n"
781   "      <attribute name='action' type='s'>'quite-some-action'</attribute>\n"
782   "      <attribute name='target' type='i'>36</attribute>\n"
783   "      <attribute name='chocolate-thunda' type='as'>['a','b']</attribute>\n"
784   "      <attribute name='thing1' type='g'>'s(uu)'</attribute>\n"
785   "      <attribute name='icon' type='s'>'small blue thing'</attribute>\n"
786   "   </item>\n"
787   "  </section>\n"
788   "</menu>\n";
789   GError *error = NULL;
790   GMenuModel *a;
791   GMenuModel *b;
792   gchar *s;
793   gchar *s2;
794
795   a = parse_menu_string (data, &error);
796   g_assert_no_error (error);
797   g_assert (G_IS_MENU_MODEL (a));
798
799   /* normalized representation */
800   s = menu_to_string (a);
801
802   b = parse_menu_string (s, &error);
803   g_assert_no_error (error);
804   g_assert (G_IS_MENU_MODEL (b));
805
806   assert_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b));
807
808   s2 = menu_to_string (b);
809
810   g_assert_cmpstr (s, ==, s2);
811
812   g_object_unref (a);
813   g_object_unref (b);
814   g_free (s);
815   g_free (s2);
816 }
817
818 static void
819 test_attributes (void)
820 {
821   GMenu *menu;
822   GMenuItem *item;
823   GVariant *v;
824
825   menu = g_menu_new ();
826
827   item = g_menu_item_new ("test", NULL);
828   g_menu_item_set_attribute_value (item, "boolean", g_variant_new_boolean (FALSE));
829   g_menu_item_set_attribute_value (item, "string", g_variant_new_string ("bla"));
830   g_menu_item_set_attribute_value (item, "double", g_variant_new_double (1.5));
831   v = g_variant_new_parsed ("[('one', 1), ('two', %i), (%s, 3)]", 2, "three");
832   g_menu_item_set_attribute_value (item, "complex", v);
833   g_menu_item_set_attribute_value (item, "test-123", g_variant_new_string ("test-123"));
834
835   g_menu_append_item (menu, item);
836
837   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 1);
838
839   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "boolean", NULL);
840   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN));
841   g_variant_unref (v);
842
843   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "string", NULL);
844   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING));
845   g_variant_unref (v);
846
847   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "double", NULL);
848   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE_DOUBLE));
849   g_variant_unref (v);
850
851   v = g_menu_model_get_item_attribute_value (G_MENU_MODEL (menu), 0, "complex", NULL);
852   g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE("a(si)")));
853   g_variant_unref (v);
854
855   g_object_unref (menu);
856 }
857
858 static void
859 test_links (void)
860 {
861   GMenu *menu;
862   GMenuModel *m;
863   GMenuModel *x;
864   GMenuItem *item;
865
866   m = G_MENU_MODEL (g_menu_new ());
867   g_menu_append (G_MENU (m), "test", NULL);
868
869   menu = g_menu_new ();
870
871   item = g_menu_item_new ("test1", NULL);
872   g_menu_item_set_link (item, "section", m);
873   g_menu_append_item (menu, item);
874
875   item = g_menu_item_new ("test2", NULL);
876   g_menu_item_set_link (item, "submenu", m);
877   g_menu_append_item (menu, item);
878
879   item = g_menu_item_new ("test3", NULL);
880   g_menu_item_set_link (item, "wallet", m);
881   g_menu_append_item (menu, item);
882
883   item = g_menu_item_new ("test4", NULL);
884   g_menu_item_set_link (item, "purse", m);
885   g_menu_item_set_link (item, "purse", NULL);
886   g_menu_append_item (menu, item);
887
888   g_assert_cmpint (g_menu_model_get_n_items (G_MENU_MODEL (menu)), ==, 4);
889
890   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 0, "section");
891   g_assert (x == m);
892   g_object_unref (x);
893
894   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 1, "submenu");
895   g_assert (x == m);
896   g_object_unref (x);
897
898   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 2, "wallet");
899   g_assert (x == m);
900   g_object_unref (x);
901
902   x = g_menu_model_get_item_link (G_MENU_MODEL (menu), 3, "purse");
903   g_assert (x == NULL);
904
905   g_object_unref (m);
906   g_object_unref (menu);
907 }
908
909 static void
910 test_mutable (void)
911 {
912   GMenu *menu;
913
914   menu = g_menu_new ();
915   g_menu_append (menu, "test", "test");
916
917   g_assert (g_menu_model_is_mutable (G_MENU_MODEL (menu)));
918   g_menu_freeze (menu);
919   g_assert (!g_menu_model_is_mutable (G_MENU_MODEL (menu)));
920
921   g_object_unref (menu);
922 }
923
924 static void
925 test_misc (void)
926 {
927   /* trying to use most of the GMenu api for constructing the
928    * same menu two different ways
929    */
930   GMenu *a, *m, *m2;
931   GMenuModel *b;
932   GMenuItem *item;
933   const gchar *s;
934
935   a = g_menu_new ();
936   item = g_menu_item_new ("test1", "action1::target1");
937   g_menu_prepend_item (a, item);
938   g_object_unref (item);
939
940   m = g_menu_new ();
941   g_menu_prepend (m, "test2a", "action2");
942   g_menu_append (m, "test2c", NULL);
943   g_menu_insert (m, 1, "test2b", NULL);
944
945   item = g_menu_item_new_submenu ("test2", G_MENU_MODEL (m));
946   g_menu_append_item (a, item);
947   g_object_unref (item);
948   g_object_unref (m);
949
950   m = g_menu_new ();
951
952   m2 = g_menu_new ();
953   g_menu_append (m2, "x", NULL);
954   g_menu_prepend_section (m, "test3a", G_MENU_MODEL (m2));
955   g_object_unref (m2);
956
957   item = g_menu_item_new_section ("test3", G_MENU_MODEL (m));
958   g_menu_insert_item (a, -1, item);
959   g_object_unref (item);
960   g_object_unref (m);
961
962   s = ""
963 "<menu>"
964 "  <item target='target1' action='action1' label='test1'/>"
965 "  <item label='test2'>"
966 "    <link name='submenu'>"
967 "      <item action='action2' label='test2a'/>"
968 "      <item label='test2b'/>"
969 "      <item label='test2c'/>"
970 "    </link>"
971 "  </item>"
972 "  <item label='test3'>"
973 "    <link name='section'>"
974 "      <item label='test3a'>"
975 "        <link name='section'>"
976 "          <item label='x'/>"
977 "        </link>"
978 "      </item>"
979 "    </link>"
980 "  </item>"
981 "</menu>";
982
983   b = parse_menu_string (s, NULL);
984
985   assert_menus_equal (G_MENU_MODEL (a), G_MENU_MODEL (b));
986   g_object_unref (a);
987   g_object_unref (b);
988 }
989
990  /* Epilogue {{{1 */
991 int
992 main (int argc, char **argv)
993 {
994   g_test_init (&argc, &argv, NULL);
995
996   g_type_init ();
997
998   g_test_add_func ("/gmenu/equality", test_equality);
999   g_test_add_func ("/gmenu/random", test_random);
1000   g_test_add_func ("/gmenu/dbus/roundtrip", test_dbus_roundtrip);
1001   g_test_add_func ("/gmenu/dbus/subscriptions", test_dbus_subscriptions);
1002   g_test_add_func ("/gmenu/markup/roundtrip", test_markup_roundtrip);
1003   g_test_add_func ("/gmenu/attributes", test_attributes);
1004   g_test_add_func ("/gmenu/links", test_links);
1005   g_test_add_func ("/gmenu/mutable", test_mutable);
1006   g_test_add_func ("/gmenu/misc", test_misc);
1007
1008   return g_test_run ();
1009 }
1010 /* vim:set foldmethod=marker: */