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