1  /*
       2   * Copyright 2015 Lars Uebernickel
       3   *
       4   * SPDX-License-Identifier: LGPL-2.1-or-later
       5   *
       6   * This library is free software; you can redistribute it and/or
       7   * modify it under the terms of the GNU Lesser General Public
       8   * License as published by the Free Software Foundation; either
       9   * version 2.1 of the License, or (at your option) any later version.
      10   *
      11   * This library is distributed in the hope that it will be useful,
      12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
      13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      14   * Lesser General Public License for more details.
      15   *
      16   * You should have received a copy of the GNU Lesser General
      17   * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
      18   *
      19   * Authors: Lars Uebernickel <lars@uebernic.de>
      20   */
      21  
      22  #include <gio/gio.h>
      23  
      24  #include <string.h>
      25  
      26  /* Wrapper around g_list_model_get_item() and g_list_model_get_object() which
      27   * checks they return the same thing. */
      28  static gpointer
      29  list_model_get (GListModel *model,
      30                  guint       position)
      31  {
      32    GObject *item = g_list_model_get_item (model, position);
      33    GObject *object = g_list_model_get_object (model, position);
      34  
      35    g_assert_true (item == object);
      36  
      37    g_clear_object (&object);
      38    return g_steal_pointer (&item);
      39  }
      40  
      41  #define assert_cmpitems(store, cmp, n_items) G_STMT_START{ \
      42    guint tmp; \
      43    g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (store)), cmp, n_items); \
      44    g_object_get (store, "n-items", &tmp, NULL); \
      45    g_assert_cmpuint (tmp, cmp, n_items); \
      46  }G_STMT_END
      47  
      48  /* Test that constructing/getting/setting properties on a #GListStore works. */
      49  static void
      50  test_store_properties (void)
      51  {
      52    GListStore *store = NULL;
      53    GType item_type;
      54  
      55    store = g_list_store_new (G_TYPE_MENU_ITEM);
      56    g_object_get (store, "item-type", &item_type, NULL);
      57    g_assert_cmpint (item_type, ==, G_TYPE_MENU_ITEM);
      58  
      59    g_clear_object (&store);
      60  }
      61  
      62  /* Test that #GListStore rejects non-GObject item types. */
      63  static void
      64  test_store_non_gobjects (void)
      65  {
      66    if (g_test_subprocess ())
      67      {
      68        /* We have to use g_object_new() since g_list_store_new() checks the item
      69         * type. We want to check the property setter code works properly. */
      70        g_object_new (G_TYPE_LIST_STORE, "item-type", G_TYPE_LONG, NULL);
      71        return;
      72      }
      73  
      74    g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_DEFAULT);
      75    g_test_trap_assert_failed ();
      76    g_test_trap_assert_stderr ("*CRITICAL*value * of type 'GType' is invalid or "
      77                               "out of range for property 'item-type'*");
      78  }
      79  
      80  static void
      81  test_store_boundaries (void)
      82  {
      83    GListStore *store;
      84    GMenuItem *item;
      85  
      86    store = g_list_store_new (G_TYPE_MENU_ITEM);
      87  
      88    item = g_menu_item_new (NULL, NULL);
      89  
      90    /* remove an item from an empty list */
      91    g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*g_sequence*");
      92    g_list_store_remove (store, 0);
      93    g_test_assert_expected_messages ();
      94  
      95    /* don't allow inserting an item past the end ... */
      96    g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*g_sequence*");
      97    g_list_store_insert (store, 1, item);
      98    assert_cmpitems (store, ==, 0);
      99    g_test_assert_expected_messages ();
     100  
     101    /* ... except exactly at the end */
     102    g_list_store_insert (store, 0, item);
     103    assert_cmpitems (store, ==, 1);
     104  
     105    /* remove a non-existing item at exactly the end of the list */
     106    g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*g_sequence*");
     107    g_list_store_remove (store, 1);
     108    g_test_assert_expected_messages ();
     109  
     110    g_list_store_remove (store, 0);
     111    assert_cmpitems (store, ==, 0);
     112  
     113    /* splice beyond the end of the list */
     114    g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*position*");
     115    g_list_store_splice (store, 1, 0, NULL, 0);
     116    g_test_assert_expected_messages ();
     117  
     118    /* remove items from an empty list */
     119    g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*position*");
     120    g_list_store_splice (store, 0, 1, NULL, 0);
     121    g_test_assert_expected_messages ();
     122  
     123    g_list_store_append (store, item);
     124    g_list_store_splice (store, 0, 1, (gpointer *) &item, 1);
     125    assert_cmpitems (store, ==, 1);
     126  
     127    /* remove more items than exist */
     128    g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*position*");
     129    g_list_store_splice (store, 0, 5, NULL, 0);
     130    g_test_assert_expected_messages ();
     131    assert_cmpitems (store, ==, 1);
     132  
     133    g_object_unref (store);
     134    g_assert_finalize_object (item);
     135  }
     136  
     137  static void
     138  test_store_refcounts (void)
     139  {
     140    GListStore *store;
     141    GMenuItem *items[10];
     142    GMenuItem *tmp;
     143    guint i;
     144    guint n_items;
     145  
     146    store = g_list_store_new (G_TYPE_MENU_ITEM);
     147  
     148    assert_cmpitems (store, ==, 0);
     149    g_assert_null (list_model_get (G_LIST_MODEL (store), 0));
     150  
     151    n_items = G_N_ELEMENTS (items);
     152    for (i = 0; i < n_items; i++)
     153      {
     154        items[i] = g_menu_item_new (NULL, NULL);
     155        g_object_add_weak_pointer (G_OBJECT (items[i]), (gpointer *) &items[i]);
     156        g_list_store_append (store, items[i]);
     157  
     158        g_object_unref (items[i]);
     159        g_assert_nonnull (items[i]);
     160      }
     161  
     162    assert_cmpitems (store, ==, n_items);
     163    g_assert_null (list_model_get (G_LIST_MODEL (store), n_items));
     164  
     165    tmp = list_model_get (G_LIST_MODEL (store), 3);
     166    g_assert_true (tmp == items[3]);
     167    g_object_unref (tmp);
     168  
     169    g_list_store_remove (store, 4);
     170    g_assert_null (items[4]);
     171    n_items--;
     172    assert_cmpitems (store, ==, n_items);
     173    g_assert_null (list_model_get (G_LIST_MODEL (store), n_items));
     174  
     175    g_object_unref (store);
     176    for (i = 0; i < G_N_ELEMENTS (items); i++)
     177      g_assert_null (items[i]);
     178  }
     179  
     180  static gchar *
     181  make_random_string (void)
     182  {
     183    gchar *str = g_malloc (10);
     184    gint i;
     185  
     186    for (i = 0; i < 9; i++)
     187      str[i] = g_test_rand_int_range ('a', 'z');
     188    str[i] = '\0';
     189  
     190    return str;
     191  }
     192  
     193  static gint
     194  compare_items (gconstpointer a_p,
     195                 gconstpointer b_p,
     196                 gpointer      user_data)
     197  {
     198    GObject *a_o = (GObject *) a_p;
     199    GObject *b_o = (GObject *) b_p;
     200  
     201    gchar *a = g_object_get_data (a_o, "key");
     202    gchar *b = g_object_get_data (b_o, "key");
     203  
     204    g_assert (user_data == GUINT_TO_POINTER(0x1234u));
     205  
     206    return strcmp (a, b);
     207  }
     208  
     209  static void
     210  insert_string (GListStore  *store,
     211                 const gchar *str)
     212  {
     213    GObject *obj;
     214  
     215    obj = g_object_new (G_TYPE_OBJECT, NULL);
     216    g_object_set_data_full (obj, "key", g_strdup (str), g_free);
     217  
     218    g_list_store_insert_sorted (store, obj, compare_items, GUINT_TO_POINTER(0x1234u));
     219  
     220    g_object_unref (obj);
     221  }
     222  
     223  static void
     224  test_store_sorted (void)
     225  {
     226    GListStore *store;
     227    guint i;
     228  
     229    store = g_list_store_new (G_TYPE_OBJECT);
     230  
     231    for (i = 0; i < 1000; i++)
     232      {
     233        gchar *str = make_random_string ();
     234        insert_string (store, str);
     235        insert_string (store, str); /* multiple copies of the same are OK */
     236        g_free (str);
     237      }
     238  
     239    assert_cmpitems (store, ==, 2000);
     240  
     241    for (i = 0; i < 1000; i++)
     242      {
     243        GObject *a, *b;
     244  
     245        /* should see our two copies */
     246        a = list_model_get (G_LIST_MODEL (store), i * 2);
     247        b = list_model_get (G_LIST_MODEL (store), i * 2 + 1);
     248  
     249        g_assert (compare_items (a, b, GUINT_TO_POINTER(0x1234)) == 0);
     250        g_assert (a != b);
     251  
     252        if (i)
     253          {
     254            GObject *c;
     255  
     256            c = list_model_get (G_LIST_MODEL (store), i * 2 - 1);
     257            g_assert (c != a);
     258            g_assert (c != b);
     259  
     260            g_assert (compare_items (b, c, GUINT_TO_POINTER(0x1234)) > 0);
     261            g_assert (compare_items (a, c, GUINT_TO_POINTER(0x1234)) > 0);
     262  
     263            g_object_unref (c);
     264          }
     265  
     266        g_object_unref (a);
     267        g_object_unref (b);
     268      }
     269  
     270    g_object_unref (store);
     271  }
     272  
     273  /* Test that using splice() to replace the middle element in a list store works. */
     274  static void
     275  test_store_splice_replace_middle (void)
     276  {
     277    GListStore *store;
     278    GListModel *model;
     279    GAction *item;
     280    GPtrArray *array;
     281  
     282    g_test_bug ("https://bugzilla.gnome.org/show_bug.cgi?id=795307");
     283  
     284    store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
     285    model = G_LIST_MODEL (store);
     286  
     287    array = g_ptr_array_new_full (0, g_object_unref);
     288    g_ptr_array_add (array, g_simple_action_new ("1", NULL));
     289    g_ptr_array_add (array, g_simple_action_new ("2", NULL));
     290    g_ptr_array_add (array, g_simple_action_new ("3", NULL));
     291    g_ptr_array_add (array, g_simple_action_new ("4", NULL));
     292    g_ptr_array_add (array, g_simple_action_new ("5", NULL));
     293  
     294    /* Add three items through splice */
     295    g_list_store_splice (store, 0, 0, array->pdata, 3);
     296    assert_cmpitems (store, ==, 3);
     297  
     298    item = list_model_get (model, 0);
     299    g_assert_cmpstr (g_action_get_name (item), ==, "1");
     300    g_object_unref (item);
     301    item = list_model_get (model, 1);
     302    g_assert_cmpstr (g_action_get_name (item), ==, "2");
     303    g_object_unref (item);
     304    item = list_model_get (model, 2);
     305    g_assert_cmpstr (g_action_get_name (item), ==, "3");
     306    g_object_unref (item);
     307  
     308    /* Replace the middle one with two new items */
     309    g_list_store_splice (store, 1, 1, array->pdata + 3, 2);
     310    assert_cmpitems (store, ==, 4);
     311  
     312    item = list_model_get (model, 0);
     313    g_assert_cmpstr (g_action_get_name (item), ==, "1");
     314    g_object_unref (item);
     315    item = list_model_get (model, 1);
     316    g_assert_cmpstr (g_action_get_name (item), ==, "4");
     317    g_object_unref (item);
     318    item = list_model_get (model, 2);
     319    g_assert_cmpstr (g_action_get_name (item), ==, "5");
     320    g_object_unref (item);
     321    item = list_model_get (model, 3);
     322    g_assert_cmpstr (g_action_get_name (item), ==, "3");
     323    g_object_unref (item);
     324  
     325    g_ptr_array_unref (array);
     326    g_object_unref (store);
     327  }
     328  
     329  /* Test that using splice() to replace the whole list store works. */
     330  static void
     331  test_store_splice_replace_all (void)
     332  {
     333    GListStore *store;
     334    GListModel *model;
     335    GPtrArray *array;
     336    GAction *item;
     337  
     338    g_test_bug ("https://bugzilla.gnome.org/show_bug.cgi?id=795307");
     339  
     340    store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
     341    model = G_LIST_MODEL (store);
     342  
     343    array = g_ptr_array_new_full (0, g_object_unref);
     344    g_ptr_array_add (array, g_simple_action_new ("1", NULL));
     345    g_ptr_array_add (array, g_simple_action_new ("2", NULL));
     346    g_ptr_array_add (array, g_simple_action_new ("3", NULL));
     347    g_ptr_array_add (array, g_simple_action_new ("4", NULL));
     348  
     349    /* Add the first two */
     350    g_list_store_splice (store, 0, 0, array->pdata, 2);
     351  
     352    assert_cmpitems (store, ==, 2);
     353    item = list_model_get (model, 0);
     354    g_assert_cmpstr (g_action_get_name (item), ==, "1");
     355    g_object_unref (item);
     356    item = list_model_get (model, 1);
     357    g_assert_cmpstr (g_action_get_name (item), ==, "2");
     358    g_object_unref (item);
     359  
     360    /* Replace all with the last two */
     361    g_list_store_splice (store, 0, 2, array->pdata + 2, 2);
     362  
     363    assert_cmpitems (store, ==, 2);
     364    item = list_model_get (model, 0);
     365    g_assert_cmpstr (g_action_get_name (item), ==, "3");
     366    g_object_unref (item);
     367    item = list_model_get (model, 1);
     368    g_assert_cmpstr (g_action_get_name (item), ==, "4");
     369    g_object_unref (item);
     370  
     371    g_ptr_array_unref (array);
     372    g_object_unref (store);
     373  }
     374  
     375  /* Test that using splice() without removing or adding anything works */
     376  static void
     377  test_store_splice_noop (void)
     378  {
     379    GListStore *store;
     380    GListModel *model;
     381    GAction *item;
     382  
     383    store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
     384    model = G_LIST_MODEL (store);
     385  
     386    /* splice noop with an empty list */
     387    g_list_store_splice (store, 0, 0, NULL, 0);
     388    assert_cmpitems (store, ==, 0);
     389  
     390    /* splice noop with a non-empty list */
     391    item = G_ACTION (g_simple_action_new ("1", NULL));
     392    g_list_store_append (store, item);
     393    g_object_unref (item);
     394  
     395    g_list_store_splice (store, 0, 0, NULL, 0);
     396    assert_cmpitems (store, ==, 1);
     397  
     398    g_list_store_splice (store, 1, 0, NULL, 0);
     399    assert_cmpitems (store, ==, 1);
     400  
     401    item = list_model_get (model, 0);
     402    g_assert_cmpstr (g_action_get_name (item), ==, "1");
     403    g_object_unref (item);
     404  
     405    g_object_unref (store);
     406  }
     407  
     408  static gboolean
     409  model_array_equal (GListModel *model, GPtrArray *array)
     410  {
     411    guint i;
     412  
     413    if (g_list_model_get_n_items (model) != array->len)
     414      return FALSE;
     415  
     416    for (i = 0; i < array->len; i++)
     417      {
     418        GObject *ptr;
     419        gboolean ptrs_equal;
     420  
     421        ptr = list_model_get (model, i);
     422        ptrs_equal = (g_ptr_array_index (array, i) == ptr);
     423        g_object_unref (ptr);
     424        if (!ptrs_equal)
     425          return FALSE;
     426      }
     427  
     428    return TRUE;
     429  }
     430  
     431  /* Test that using splice() to remove multiple items at different
     432   * positions works */
     433  static void
     434  test_store_splice_remove_multiple (void)
     435  {
     436    GListStore *store;
     437    GListModel *model;
     438    GPtrArray *array;
     439  
     440    store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
     441    model = G_LIST_MODEL (store);
     442  
     443    array = g_ptr_array_new_full (0, g_object_unref);
     444    g_ptr_array_add (array, g_simple_action_new ("1", NULL));
     445    g_ptr_array_add (array, g_simple_action_new ("2", NULL));
     446    g_ptr_array_add (array, g_simple_action_new ("3", NULL));
     447    g_ptr_array_add (array, g_simple_action_new ("4", NULL));
     448    g_ptr_array_add (array, g_simple_action_new ("5", NULL));
     449    g_ptr_array_add (array, g_simple_action_new ("6", NULL));
     450    g_ptr_array_add (array, g_simple_action_new ("7", NULL));
     451    g_ptr_array_add (array, g_simple_action_new ("8", NULL));
     452    g_ptr_array_add (array, g_simple_action_new ("9", NULL));
     453    g_ptr_array_add (array, g_simple_action_new ("10", NULL));
     454  
     455    /* Add all */
     456    g_list_store_splice (store, 0, 0, array->pdata, array->len);
     457    g_assert_true (model_array_equal (model, array));
     458  
     459    /* Remove the first two */
     460    g_list_store_splice (store, 0, 2, NULL, 0);
     461    g_assert_false (model_array_equal (model, array));
     462    g_ptr_array_remove_range (array, 0, 2);
     463    g_assert_true (model_array_equal (model, array));
     464    assert_cmpitems (store, ==, 8);
     465  
     466    /* Remove two in the middle */
     467    g_list_store_splice (store, 2, 2, NULL, 0);
     468    g_assert_false (model_array_equal (model, array));
     469    g_ptr_array_remove_range (array, 2, 2);
     470    g_assert_true (model_array_equal (model, array));
     471    assert_cmpitems (store, ==, 6);
     472  
     473    /* Remove two at the end */
     474    g_list_store_splice (store, 4, 2, NULL, 0);
     475    g_assert_false (model_array_equal (model, array));
     476    g_ptr_array_remove_range (array, 4, 2);
     477    g_assert_true (model_array_equal (model, array));
     478    assert_cmpitems (store, ==, 4);
     479  
     480    g_ptr_array_unref (array);
     481    g_object_unref (store);
     482  }
     483  
     484  /* Test that using splice() to add multiple items at different
     485   * positions works */
     486  static void
     487  test_store_splice_add_multiple (void)
     488  {
     489    GListStore *store;
     490    GListModel *model;
     491    GPtrArray *array;
     492  
     493    store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
     494    model = G_LIST_MODEL (store);
     495  
     496    array = g_ptr_array_new_full (0, g_object_unref);
     497    g_ptr_array_add (array, g_simple_action_new ("1", NULL));
     498    g_ptr_array_add (array, g_simple_action_new ("2", NULL));
     499    g_ptr_array_add (array, g_simple_action_new ("3", NULL));
     500    g_ptr_array_add (array, g_simple_action_new ("4", NULL));
     501    g_ptr_array_add (array, g_simple_action_new ("5", NULL));
     502    g_ptr_array_add (array, g_simple_action_new ("6", NULL));
     503  
     504    /* Add two at the beginning */
     505    g_list_store_splice (store, 0, 0, array->pdata, 2);
     506  
     507    /* Add two at the end */
     508    g_list_store_splice (store, 2, 0, array->pdata + 4, 2);
     509  
     510    /* Add two in the middle */
     511    g_list_store_splice (store, 2, 0, array->pdata + 2, 2);
     512  
     513    g_assert_true (model_array_equal (model, array));
     514  
     515    g_ptr_array_unref (array);
     516    g_object_unref (store);
     517  }
     518  
     519  /* Test that get_item_type() returns the right type */
     520  static void
     521  test_store_item_type (void)
     522  {
     523    GListStore *store;
     524    GType gtype;
     525  
     526    store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
     527    gtype = g_list_model_get_item_type (G_LIST_MODEL (store));
     528    g_assert (gtype == G_TYPE_SIMPLE_ACTION);
     529  
     530    g_object_unref (store);
     531  }
     532  
     533  /* Test that remove_all() removes all items */
     534  static void
     535  test_store_remove_all (void)
     536  {
     537    GListStore *store;
     538    GSimpleAction *item;
     539  
     540    store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
     541  
     542    /* Test with an empty list */
     543    g_list_store_remove_all (store);
     544    assert_cmpitems (store, ==, 0);
     545  
     546    /* Test with a non-empty list */
     547    item = g_simple_action_new ("42", NULL);
     548    g_list_store_append (store, item);
     549    g_list_store_append (store, item);
     550    g_object_unref (item);
     551    assert_cmpitems (store, ==, 2);
     552    g_list_store_remove_all (store);
     553    assert_cmpitems (store, ==, 0);
     554  
     555    g_object_unref (store);
     556  }
     557  
     558  /* Test that splice() logs an error when passed the wrong item type */
     559  static void
     560  test_store_splice_wrong_type (void)
     561  {
     562    GListStore *store;
     563  
     564    store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
     565  
     566    g_test_expect_message (G_LOG_DOMAIN,
     567                           G_LOG_LEVEL_CRITICAL,
     568                           "*GListStore instead of a GSimpleAction*");
     569    g_list_store_splice (store, 0, 0, (gpointer)&store, 1);
     570  
     571    g_object_unref (store);
     572  }
     573  
     574  static gint
     575  cmp_action_by_name (GAction *a, GAction *b, gpointer user_data)
     576  {
     577    return g_strcmp0 (g_action_get_name (a), g_action_get_name (b));
     578  }
     579  
     580  /* Test if sort() works */
     581  static void
     582  test_store_sort (void)
     583  {
     584    GListStore *store;
     585    GListModel *model;
     586    GPtrArray *array;
     587  
     588    store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
     589    model = G_LIST_MODEL (store);
     590  
     591    array = g_ptr_array_new_full (0, g_object_unref);
     592    g_ptr_array_add (array, g_simple_action_new ("2", NULL));
     593    g_ptr_array_add (array, g_simple_action_new ("3", NULL));
     594    g_ptr_array_add (array, g_simple_action_new ("9", NULL));
     595    g_ptr_array_add (array, g_simple_action_new ("4", NULL));
     596    g_ptr_array_add (array, g_simple_action_new ("5", NULL));
     597    g_ptr_array_add (array, g_simple_action_new ("8", NULL));
     598    g_ptr_array_add (array, g_simple_action_new ("6", NULL));
     599    g_ptr_array_add (array, g_simple_action_new ("7", NULL));
     600    g_ptr_array_add (array, g_simple_action_new ("1", NULL));
     601  
     602    /* Sort an empty list */
     603    g_list_store_sort (store, (GCompareDataFunc) cmp_action_by_name, NULL);
     604  
     605    /* Add all */
     606    g_list_store_splice (store, 0, 0, array->pdata, array->len);
     607    g_assert_true (model_array_equal (model, array));
     608  
     609    /* Sort both and check if the result is the same */
     610    g_ptr_array_sort_values (array, (GCompareFunc)cmp_action_by_name);
     611    g_assert_false (model_array_equal (model, array));
     612    g_list_store_sort (store, (GCompareDataFunc) cmp_action_by_name, NULL);
     613    g_assert_true (model_array_equal (model, array));
     614  
     615    g_ptr_array_unref (array);
     616    g_object_unref (store);
     617  }
     618  
     619  /* Test the cases where the item store tries to speed up item access by caching
     620   * the last iter/position */
     621  static void
     622  test_store_get_item_cache (void)
     623  {
     624    GListStore *store;
     625    GListModel *model;
     626    GSimpleAction *item1, *item2, *temp;
     627  
     628    store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
     629    model = G_LIST_MODEL (store);
     630  
     631    /* Add two */
     632    item1 = g_simple_action_new ("1", NULL);
     633    g_list_store_append (store, item1);
     634    item2 = g_simple_action_new ("2", NULL);
     635    g_list_store_append (store, item2);
     636  
     637    /* Clear the cache */
     638    g_assert_null (list_model_get (model, 42));
     639  
     640    /* Access the same position twice */
     641    temp = list_model_get (model, 1);
     642    g_assert (temp == item2);
     643    g_object_unref (temp);
     644    temp = list_model_get (model, 1);
     645    g_assert (temp == item2);
     646    g_object_unref (temp);
     647  
     648    g_assert_null (list_model_get (model, 42));
     649  
     650    /* Access forwards */
     651    temp = list_model_get (model, 0);
     652    g_assert (temp == item1);
     653    g_object_unref (temp);
     654    temp = list_model_get (model, 1);
     655    g_assert (temp == item2);
     656    g_object_unref (temp);
     657  
     658    g_assert_null (list_model_get (model, 42));
     659  
     660    /* Access backwards */
     661    temp = list_model_get (model, 1);
     662    g_assert (temp == item2);
     663    g_object_unref (temp);
     664    temp = list_model_get (model, 0);
     665    g_assert (temp == item1);
     666    g_object_unref (temp);
     667  
     668    g_object_unref (item1);
     669    g_object_unref (item2);
     670    g_object_unref (store);
     671  }
     672  
     673  struct ItemsChangedData
     674  {
     675    guint position;
     676    guint removed;
     677    guint added;
     678    gboolean called;
     679    gboolean notified;
     680  };
     681  
     682  static void
     683  expect_items_changed (struct ItemsChangedData *expected,
     684                        guint position,
     685                        guint removed,
     686                        guint added)
     687  {
     688    expected->position = position;
     689    expected->removed = removed;
     690    expected->added = added;
     691    expected->called = FALSE;
     692    expected->notified = FALSE;
     693  }
     694  
     695  static void
     696  on_items_changed (GListModel *model,
     697                    guint position,
     698                    guint removed,
     699                    guint added,
     700                    struct ItemsChangedData *expected)
     701  {
     702    g_assert_false (expected->called);
     703    g_assert_cmpuint (expected->position, ==, position);
     704    g_assert_cmpuint (expected->removed, ==, removed);
     705    g_assert_cmpuint (expected->added, ==, added);
     706    expected->called = TRUE;
     707  }
     708  
     709  static void
     710  on_notify_n_items (GListModel *model,
     711                     GParamSpec *pspec,
     712                     struct ItemsChangedData *expected)
     713  {
     714    g_assert_false (expected->notified);
     715    expected->notified = TRUE;
     716  }
     717  
     718  /* Test that all operations on the list emit the items-changed signal */
     719  static void
     720  test_store_signal_items_changed (void)
     721  {
     722    GListStore *store;
     723    GListModel *model;
     724    GSimpleAction *item;
     725    struct ItemsChangedData expected = {0};
     726  
     727    store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
     728    model = G_LIST_MODEL (store);
     729  
     730    g_object_connect (model, "signal::items-changed",
     731                      on_items_changed, &expected, NULL);
     732    g_object_connect (model, "signal::notify::n-items",
     733                      on_notify_n_items, &expected, NULL);
     734  
     735    /* Emit the signal manually */
     736    expect_items_changed (&expected, 0, 0, 0);
     737    g_list_model_items_changed (model, 0, 0, 0);
     738    g_assert_true (expected.called);
     739    g_assert_false (expected.notified);
     740  
     741    /* Append an item */
     742    expect_items_changed (&expected, 0, 0, 1);
     743    item = g_simple_action_new ("2", NULL);
     744    g_list_store_append (store, item);
     745    g_object_unref (item);
     746    g_assert_true (expected.called);
     747    g_assert_true (expected.notified);
     748  
     749    /* Insert an item */
     750    expect_items_changed (&expected, 1, 0, 1);
     751    item = g_simple_action_new ("1", NULL);
     752    g_list_store_insert (store, 1, item);
     753    g_object_unref (item);
     754    g_assert_true (expected.called);
     755    g_assert_true (expected.notified);
     756  
     757    /* Sort the list */
     758    expect_items_changed (&expected, 0, 2, 2);
     759    g_list_store_sort (store,
     760                       (GCompareDataFunc) cmp_action_by_name,
     761                       NULL);
     762    g_assert_true (expected.called);
     763    g_assert_false (expected.notified);
     764  
     765    /* Insert sorted */
     766    expect_items_changed (&expected, 2, 0, 1);
     767    item = g_simple_action_new ("3", NULL);
     768    g_list_store_insert_sorted (store,
     769                                item,
     770                                (GCompareDataFunc) cmp_action_by_name,
     771                                NULL);
     772    g_object_unref (item);
     773    g_assert_true (expected.called);
     774    g_assert_true (expected.notified);
     775  
     776    /* Remove an item */
     777    expect_items_changed (&expected, 1, 1, 0);
     778    g_list_store_remove (store, 1);
     779    g_assert_true (expected.called);
     780    g_assert_true (expected.notified);
     781  
     782    /* Splice */
     783    expect_items_changed (&expected, 0, 2, 1);
     784    item = g_simple_action_new ("4", NULL);
     785    assert_cmpitems (store, >=, 2);
     786    g_list_store_splice (store, 0, 2, (gpointer)&item, 1);
     787    g_object_unref (item);
     788    g_assert_true (expected.called);
     789    g_assert_true (expected.notified);
     790  
     791    /* Splice to replace */
     792    expect_items_changed (&expected, 0, 1, 1);
     793    item = g_simple_action_new ("5", NULL);
     794    assert_cmpitems (store, >=, 1);
     795    g_list_store_splice (store, 0, 1, (gpointer)&item, 1);
     796    g_object_unref (item);
     797    g_assert_true (expected.called);
     798    g_assert_false (expected.notified);
     799  
     800    /* Remove all */
     801    expect_items_changed (&expected, 0, 1, 0);
     802    assert_cmpitems (store, ==, 1);
     803    g_list_store_remove_all (store);
     804    g_assert_true (expected.called);
     805    g_assert_true (expected.notified);
     806  
     807    g_object_unref (store);
     808  }
     809  
     810  /* Due to an overflow in the list store last-iter optimization,
     811   * the sequence 'lookup 0; lookup MAXUINT' was returning the
     812   * same item twice, and not NULL for the second lookup.
     813   * See #1639.
     814   */
     815  static void
     816  test_store_past_end (void)
     817  {
     818    GListStore *store;
     819    GListModel *model;
     820    GSimpleAction *item;
     821  
     822    store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
     823    model = G_LIST_MODEL (store);
     824  
     825    item = g_simple_action_new ("2", NULL);
     826    g_list_store_append (store, item);
     827    g_object_unref (item);
     828  
     829    assert_cmpitems (store, ==, 1);
     830    item = g_list_model_get_item (model, 0);
     831    g_assert_nonnull (item);
     832    g_object_unref (item);
     833    item = g_list_model_get_item (model, G_MAXUINT);
     834    g_assert_null (item);
     835  
     836    g_object_unref (store);
     837  }
     838  
     839  static gboolean
     840  list_model_casecmp_action_by_name (gconstpointer a,
     841                                     gconstpointer b)
     842  {
     843    return g_ascii_strcasecmp (g_action_get_name (G_ACTION (a)),
     844                               g_action_get_name (G_ACTION (b))) == 0;
     845  }
     846  
     847  static gboolean
     848  list_model_casecmp_action_by_name_full (gconstpointer a,
     849                                          gconstpointer b,
     850                                          gpointer      user_data)
     851  {
     852    char buf[4];
     853    const char *suffix = user_data;
     854  
     855    g_snprintf (buf, sizeof buf, "%s%s", g_action_get_name (G_ACTION (b)), suffix);
     856    return g_strcmp0 (g_action_get_name (G_ACTION (a)), buf) == 0;
     857  }
     858  
     859  /* Test if find() and find_with_equal_func() works */
     860  static void
     861  test_store_find (void)
     862  {
     863    GListStore *store;
     864    guint position = 100;
     865    const gchar *item_strs[4] = { "aaa", "bbb", "xxx", "ccc" };
     866    GSimpleAction *items[4] = { NULL, };
     867    GSimpleAction *other_item;
     868    guint i;
     869  
     870    store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
     871  
     872    for (i = 0; i < G_N_ELEMENTS (item_strs); i++)
     873      items[i] = g_simple_action_new (item_strs[i], NULL);
     874  
     875    /* Shouldn't crash on an empty list, or change the position pointer */
     876    g_assert_false (g_list_store_find (store, items[0], NULL));
     877    g_assert_false (g_list_store_find (store, items[0], &position));
     878    g_assert_cmpint (position, ==, 100);
     879  
     880    for (i = 0; i < G_N_ELEMENTS (item_strs); i++)
     881      g_list_store_append (store, items[i]);
     882  
     883    /* Check whether it could still find the the elements */
     884    for (i = 0; i < G_N_ELEMENTS (item_strs); i++)
     885      {
     886        g_assert_true (g_list_store_find (store, items[i], &position));
     887        g_assert_cmpint (position, ==, i);
     888        /* Shouldn't try to write to position pointer if NULL given */
     889        g_assert_true (g_list_store_find (store, items[i], NULL));
     890      }
     891  
     892    /* try to find element not part of the list */
     893    other_item = g_simple_action_new ("111", NULL);
     894    g_assert_false (g_list_store_find (store, other_item, NULL));
     895    g_clear_object (&other_item);
     896  
     897    /* Re-add item; find() should only return the first position */
     898    g_list_store_append (store, items[0]);
     899    g_assert_true (g_list_store_find (store, items[0], &position));
     900    g_assert_cmpint (position, ==, 0);
     901  
     902    /* try to find element which should only work with custom equality check */
     903    other_item = g_simple_action_new ("XXX", NULL);
     904    g_assert_false (g_list_store_find (store, other_item, NULL));
     905    g_assert_true (g_list_store_find_with_equal_func (store,
     906                                                      other_item,
     907                                                      list_model_casecmp_action_by_name,
     908                                                      &position));
     909    g_assert_cmpint (position, ==, 2);
     910    g_clear_object (&other_item);
     911  
     912    /* try to find element which should only work with custom equality check and string concat */
     913    other_item = g_simple_action_new ("c", NULL);
     914    g_assert_false (g_list_store_find (store, other_item, NULL));
     915    g_assert_true (g_list_store_find_with_equal_func_full (store,
     916                                                           other_item,
     917                                                           list_model_casecmp_action_by_name_full,
     918                                                           "cc",
     919                                                           &position));
     920    g_assert_cmpint (position, ==, 3);
     921    g_clear_object (&other_item);
     922  
     923    for (i = 0; i < G_N_ELEMENTS (item_strs); i++)
     924      g_clear_object(&items[i]);
     925    g_clear_object (&store);
     926  }
     927  
     928  int main (int argc, char *argv[])
     929  {
     930    g_test_init (&argc, &argv, NULL);
     931  
     932    g_test_add_func ("/glistmodel/store/properties", test_store_properties);
     933    g_test_add_func ("/glistmodel/store/non-gobjects", test_store_non_gobjects);
     934    g_test_add_func ("/glistmodel/store/boundaries", test_store_boundaries);
     935    g_test_add_func ("/glistmodel/store/refcounts", test_store_refcounts);
     936    g_test_add_func ("/glistmodel/store/sorted", test_store_sorted);
     937    g_test_add_func ("/glistmodel/store/splice-replace-middle",
     938                     test_store_splice_replace_middle);
     939    g_test_add_func ("/glistmodel/store/splice-replace-all",
     940                     test_store_splice_replace_all);
     941    g_test_add_func ("/glistmodel/store/splice-noop", test_store_splice_noop);
     942    g_test_add_func ("/glistmodel/store/splice-remove-multiple",
     943                     test_store_splice_remove_multiple);
     944    g_test_add_func ("/glistmodel/store/splice-add-multiple",
     945                     test_store_splice_add_multiple);
     946    g_test_add_func ("/glistmodel/store/splice-wrong-type",
     947                     test_store_splice_wrong_type);
     948    g_test_add_func ("/glistmodel/store/item-type",
     949                     test_store_item_type);
     950    g_test_add_func ("/glistmodel/store/remove-all",
     951                     test_store_remove_all);
     952    g_test_add_func ("/glistmodel/store/sort",
     953                     test_store_sort);
     954    g_test_add_func ("/glistmodel/store/get-item-cache",
     955                     test_store_get_item_cache);
     956    g_test_add_func ("/glistmodel/store/items-changed",
     957                     test_store_signal_items_changed);
     958    g_test_add_func ("/glistmodel/store/past-end", test_store_past_end);
     959    g_test_add_func ("/glistmodel/store/find", test_store_find);
     960  
     961    return g_test_run ();
     962  }