summaryrefslogtreecommitdiff
path: root/testsuite
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2023-05-09 16:36:55 +0000
committerMatthias Clasen <mclasen@redhat.com>2023-05-09 16:36:55 +0000
commit526ddfa8666cf31e26763f2bcd0c83b066714134 (patch)
treedde096f76dc75d8588e316786df78b6081d303b9 /testsuite
parenta959fba18afe9d161205d13523205c91c5a23a15 (diff)
parentc88ac79437f1a88b8ba56ff48e6d5fcab95c04ca (diff)
downloadgtk+-526ddfa8666cf31e26763f2bcd0c83b066714134.tar.gz
Merge branch 'wip/otte/sections' into 'main'
Add GtkSectionModel See merge request GNOME/gtk!5818
Diffstat (limited to 'testsuite')
-rw-r--r--testsuite/gtk/filterlistmodel-exhaustive.c18
-rw-r--r--testsuite/gtk/filterlistmodel.c91
-rw-r--r--testsuite/gtk/listitemmanager.c497
-rw-r--r--testsuite/gtk/meson.build1
-rw-r--r--testsuite/gtk/sortlistmodel-exhaustive.c271
-rw-r--r--testsuite/gtk/sortlistmodel.c63
6 files changed, 934 insertions, 7 deletions
diff --git a/testsuite/gtk/filterlistmodel-exhaustive.c b/testsuite/gtk/filterlistmodel-exhaustive.c
index 084f1d301d..2af32072f5 100644
--- a/testsuite/gtk/filterlistmodel-exhaustive.c
+++ b/testsuite/gtk/filterlistmodel-exhaustive.c
@@ -43,6 +43,23 @@
} \
}G_STMT_END
+#define assert_sections_equal(model1, model2) G_STMT_START{ \
+ guint _i, _n, _start1, _end1, _start2, _end2; \
+ g_assert_cmpint (g_list_model_get_n_items (G_LIST_MODEL (model1)), ==, g_list_model_get_n_items (G_LIST_MODEL (model2))); \
+ _n = g_list_model_get_n_items (G_LIST_MODEL (model1)); \
+ for (_i = 0; _i < _n; _i = _end1) \
+ { \
+ gtk_section_model_get_section (model1, _i, &_start1, &_end1); \
+ gtk_section_model_get_section (model2, _i, &_start2, &_end2); \
+ g_assert_cmpint (_start1, <, _end1); \
+ g_assert_cmpint (_start2, <, _end2); \
+ g_assert_cmpint (_start1, ==, _start2); \
+ g_assert_cmpint (_end1, ==, _end2); \
+ g_assert_cmpint (_i, ==, _start1); \
+ g_assert_cmpint (_end1, <=, _n); \
+ } \
+}G_STMT_END
+
G_GNUC_UNUSED static char *
model_to_string (GListModel *model)
{
@@ -469,6 +486,7 @@ test_model_changes (gconstpointer model_id)
{
ensure_updated ();
assert_model_equal (G_LIST_MODEL (flatten1), G_LIST_MODEL (model2));
+ assert_sections_equal (GTK_SECTION_MODEL (flatten1), GTK_SECTION_MODEL (model2));
}
}
diff --git a/testsuite/gtk/filterlistmodel.c b/testsuite/gtk/filterlistmodel.c
index b3c8303daa..6f1465b3d1 100644
--- a/testsuite/gtk/filterlistmodel.c
+++ b/testsuite/gtk/filterlistmodel.c
@@ -323,7 +323,7 @@ test_change_filter (void)
{
GtkFilterListModel *filter;
GtkFilter *custom;
-
+
filter = new_model (10, is_not_near, GUINT_TO_POINTER (5));
assert_model (filter, "1 2 8 9 10");
assert_changes (filter, "");
@@ -457,6 +457,94 @@ test_add_remove_item (void)
g_object_unref (filter);
}
+static int
+sort_func (gconstpointer p1,
+ gconstpointer p2,
+ gpointer data)
+{
+ const char *s1 = gtk_string_object_get_string ((GtkStringObject *)p1);
+ const char *s2 = gtk_string_object_get_string ((GtkStringObject *)p2);
+
+ /* compare just the first byte */
+ return (int)(s1[0]) - (int)(s2[0]);
+}
+
+static gboolean
+filter_func (gpointer item,
+ gpointer data)
+{
+ const char *s = gtk_string_object_get_string ((GtkStringObject *)item);
+
+ return s[0] == s[1];
+}
+
+static void
+test_sections (void)
+{
+ GtkStringList *list;
+ const char *strings[] = {
+ "aaa",
+ "aab",
+ "abc",
+ "bbb",
+ "bq1",
+ "bq2",
+ "cc",
+ "cx",
+ NULL
+ };
+ GtkSorter *sorter;
+ GtkSortListModel *sorted;
+ GtkSorter *section_sorter;
+ guint s, e;
+ GtkFilterListModel *filtered;
+ GtkFilter *filter;
+
+ list = gtk_string_list_new (strings);
+ sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
+ sorted = gtk_sort_list_model_new (G_LIST_MODEL (list), sorter);
+ section_sorter = GTK_SORTER (gtk_custom_sorter_new (sort_func, NULL, NULL));
+ gtk_sort_list_model_set_section_sorter (GTK_SORT_LIST_MODEL (sorted), section_sorter);
+ g_object_unref (section_sorter);
+
+ gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 0, &s, &e);
+ g_assert_cmpint (s, ==, 0);
+ g_assert_cmpint (e, ==, 3);
+ gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 3, &s, &e);
+ g_assert_cmpint (s, ==, 3);
+ g_assert_cmpint (e, ==, 6);
+ gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 6, &s, &e);
+ g_assert_cmpint (s, ==, 6);
+ g_assert_cmpint (e, ==, 8);
+
+ filtered = gtk_filter_list_model_new (NULL, NULL);
+ gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 0, &s, &e);
+ g_assert_cmpint (s, ==, 0);
+ g_assert_cmpint (e, ==, G_MAXUINT);
+
+ gtk_filter_list_model_set_model (filtered, G_LIST_MODEL (sorted));
+ gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 0, &s, &e);
+ g_assert_cmpint (s, ==, 0);
+ g_assert_cmpint (e, ==, 3);
+
+ filter = GTK_FILTER (gtk_custom_filter_new (filter_func, NULL, NULL));
+ gtk_filter_list_model_set_filter (filtered, filter);
+ g_object_unref (filter);
+
+ gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 0, &s, &e);
+ g_assert_cmpint (s, ==, 0);
+ g_assert_cmpint (e, ==, 2);
+ gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 2, &s, &e);
+ g_assert_cmpint (s, ==, 2);
+ g_assert_cmpint (e, ==, 3);
+ gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 3, &s, &e);
+ g_assert_cmpint (s, ==, 3);
+ g_assert_cmpint (e, ==, 4);
+
+ g_object_unref (filtered);
+ g_object_unref (sorted);
+}
+
int
main (int argc, char *argv[])
{
@@ -472,6 +560,7 @@ main (int argc, char *argv[])
g_test_add_func ("/filterlistmodel/incremental", test_incremental);
g_test_add_func ("/filterlistmodel/empty", test_empty);
g_test_add_func ("/filterlistmodel/add_remove_item", test_add_remove_item);
+ g_test_add_func ("/filterlistmodel/sections", test_sections);
return g_test_run ();
}
diff --git a/testsuite/gtk/listitemmanager.c b/testsuite/gtk/listitemmanager.c
new file mode 100644
index 0000000000..ba5fbd54ba
--- /dev/null
+++ b/testsuite/gtk/listitemmanager.c
@@ -0,0 +1,497 @@
+/*
+ * Copyright © 2023 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <locale.h>
+
+#include <gtk/gtk.h>
+#include "gtk/gtklistitemmanagerprivate.h"
+#include "gtk/gtklistbaseprivate.h"
+
+static GListModel *
+create_source_model (guint min_size, guint max_size)
+{
+ GtkStringList *list;
+ guint i, size;
+
+ size = g_test_rand_int_range (min_size, max_size + 1);
+ list = gtk_string_list_new (NULL);
+
+ for (i = 0; i < size; i++)
+ gtk_string_list_append (list, g_test_rand_bit () ? "A" : "B");
+
+ return G_LIST_MODEL (list);
+}
+
+void
+print_list_item_manager_tiles (GtkListItemManager *items)
+{
+ GString *string;
+ GtkListTile *tile;
+
+ string = g_string_new ("");
+
+ for (tile = gtk_list_item_manager_get_first (items);
+ tile != NULL;
+ tile = gtk_rb_tree_node_get_next (tile))
+ {
+ switch (tile->type)
+ {
+ case GTK_LIST_TILE_ITEM:
+ if (tile->widget)
+ g_string_append_c (string, 'W');
+ else if (tile->n_items == 1)
+ g_string_append_c (string, 'x');
+ else
+ g_string_append_printf (string, "%u,", tile->n_items);
+ break;
+ case GTK_LIST_TILE_HEADER:
+ g_string_append_c (string, '[');
+ break;
+ case GTK_LIST_TILE_UNMATCHED_HEADER:
+ g_string_append_c (string, '(');
+ break;
+ case GTK_LIST_TILE_FOOTER:
+ g_string_append_c (string, ']');
+ break;
+ case GTK_LIST_TILE_UNMATCHED_FOOTER:
+ g_string_append_c (string, ')');
+ break;
+
+ case GTK_LIST_TILE_FILLER:
+ g_string_append_c (string, '_');
+ break;
+ case GTK_LIST_TILE_REMOVED:
+ g_string_append_c (string, '.');
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+
+ g_print ("%s\n", string->str);
+
+ g_string_free (string, TRUE);
+}
+
+static void
+check_list_item_manager (GtkListItemManager *items,
+ GtkListItemTracker **trackers,
+ gsize n_trackers)
+{
+ GListModel *model = G_LIST_MODEL (gtk_list_item_manager_get_model (items));
+ GtkListTile *tile;
+ guint n_items = 0;
+ guint i;
+ gboolean has_sections;
+ enum {
+ NO_SECTION,
+ MATCHED_SECTION,
+ UNMATCHED_SECTION
+ } section_state = NO_SECTION;
+
+ has_sections = gtk_list_item_manager_get_has_sections (items);
+
+ for (tile = gtk_list_item_manager_get_first (items);
+ tile != NULL;
+ tile = gtk_rb_tree_node_get_next (tile))
+ {
+ switch (tile->type)
+ {
+ case GTK_LIST_TILE_HEADER:
+ g_assert_cmpint (section_state, ==, NO_SECTION);
+ g_assert_cmpint (tile->n_items, ==, 0);
+ g_assert_true (has_sections);
+ g_assert_true (tile->widget);
+ section_state = MATCHED_SECTION;
+ break;
+
+ case GTK_LIST_TILE_UNMATCHED_HEADER:
+ g_assert_cmpint (section_state, ==, NO_SECTION);
+ g_assert_cmpint (tile->n_items, ==, 0);
+ g_assert_false (tile->widget);
+ section_state = UNMATCHED_SECTION;
+ break;
+
+ case GTK_LIST_TILE_FOOTER:
+ g_assert_cmpint (section_state, ==, MATCHED_SECTION);
+ g_assert_cmpint (tile->n_items, ==, 0);
+ g_assert_true (has_sections);
+ g_assert_false (tile->widget);
+ section_state = NO_SECTION;
+ break;
+
+ case GTK_LIST_TILE_UNMATCHED_FOOTER:
+ g_assert_cmpint (section_state, ==, UNMATCHED_SECTION);
+ g_assert_cmpint (tile->n_items, ==, 0);
+ g_assert_false (tile->widget);
+ section_state = NO_SECTION;
+ break;
+
+ case GTK_LIST_TILE_ITEM:
+ g_assert_cmpint (section_state, !=, NO_SECTION);
+ if (tile->widget)
+ {
+ GObject *item = g_list_model_get_item (model, n_items);
+ if (has_sections)
+ g_assert_cmpint (section_state, ==, MATCHED_SECTION);
+ else
+ g_assert_cmpint (section_state, ==, UNMATCHED_SECTION);
+ g_assert_cmphex (GPOINTER_TO_SIZE (item), ==, GPOINTER_TO_SIZE (gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (tile->widget))));
+ g_object_unref (item);
+ g_assert_cmpint (n_items, ==, gtk_list_item_base_get_position (GTK_LIST_ITEM_BASE (tile->widget)));
+ g_assert_cmpint (tile->n_items, ==, 1);
+ }
+ if (tile->n_items)
+ n_items += tile->n_items;
+ break;
+
+ case GTK_LIST_TILE_FILLER:
+ /* We don't add fillers */
+ g_assert_not_reached ();
+ break;
+
+ case GTK_LIST_TILE_REMOVED:
+ g_assert_cmpint (tile->n_items, ==, 0);
+ g_assert_false (tile->widget);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+
+ g_assert_cmpint (section_state, ==, NO_SECTION);
+ g_assert_cmpint (n_items, ==, g_list_model_get_n_items (model));
+
+ for (i = 0; i < n_trackers; i++)
+ {
+ guint pos, offset;
+
+ pos = gtk_list_item_tracker_get_position (items, trackers[i]);
+ if (pos == GTK_INVALID_LIST_POSITION)
+ continue;
+ tile = gtk_list_item_manager_get_nth (items, pos, &offset);
+ g_assert_cmpint (tile->n_items, ==, 1);
+ g_assert_cmpint (offset, ==, 0);
+ g_assert_true (tile->widget);
+ }
+
+ for (tile = gtk_list_tile_gc (items, gtk_list_item_manager_get_first (items));
+ tile != NULL;
+ tile = gtk_list_tile_gc (items, gtk_rb_tree_node_get_next (tile)))
+ ;
+
+ n_items = 0;
+
+ for (tile = gtk_list_item_manager_get_first (items);
+ tile != NULL;
+ tile = gtk_rb_tree_node_get_next (tile))
+ {
+ switch (tile->type)
+ {
+ case GTK_LIST_TILE_HEADER:
+ g_assert_cmpint (section_state, ==, NO_SECTION);
+ g_assert_cmpint (tile->n_items, ==, 0);
+ g_assert_true (has_sections);
+ g_assert_true (tile->widget);
+ section_state = MATCHED_SECTION;
+ break;
+
+ case GTK_LIST_TILE_UNMATCHED_HEADER:
+ g_assert_cmpint (section_state, ==, NO_SECTION);
+ g_assert_cmpint (tile->n_items, ==, 0);
+ g_assert_false (tile->widget);
+ section_state = UNMATCHED_SECTION;
+ break;
+
+ case GTK_LIST_TILE_FOOTER:
+ g_assert_cmpint (section_state, ==, MATCHED_SECTION);
+ g_assert_cmpint (tile->n_items, ==, 0);
+ g_assert_true (has_sections);
+ g_assert_false (tile->widget);
+ section_state = NO_SECTION;
+ break;
+
+ case GTK_LIST_TILE_UNMATCHED_FOOTER:
+ g_assert_cmpint (section_state, ==, UNMATCHED_SECTION);
+ g_assert_cmpint (tile->n_items, ==, 0);
+ g_assert_false (tile->widget);
+ section_state = NO_SECTION;
+ break;
+
+ case GTK_LIST_TILE_ITEM:
+ g_assert_cmpint (section_state, !=, NO_SECTION);
+ if (tile->widget)
+ {
+ g_assert_cmpint (tile->n_items, ==, 1);
+ }
+ if (tile->n_items)
+ n_items += tile->n_items;
+ break;
+
+ case GTK_LIST_TILE_FILLER:
+ case GTK_LIST_TILE_REMOVED:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+
+ g_assert_cmpint (section_state, ==, NO_SECTION);
+ g_assert_cmpint (n_items, ==, g_list_model_get_n_items (model));
+
+ for (i = 0; i < n_trackers; i++)
+ {
+ guint pos, offset;
+
+ pos = gtk_list_item_tracker_get_position (items, trackers[i]);
+ if (pos == GTK_INVALID_LIST_POSITION)
+ continue;
+ tile = gtk_list_item_manager_get_nth (items, pos, &offset);
+ g_assert_cmpint (tile->n_items, ==, 1);
+ g_assert_cmpint (offset, ==, 0);
+ g_assert_true (tile->widget);
+ }
+}
+
+static GtkListTile *
+split_simple (GtkWidget *widget,
+ GtkListTile *tile,
+ guint n_items)
+{
+ GtkListItemManager *items = g_object_get_data (G_OBJECT (widget), "the-items");
+
+ return gtk_list_tile_split (items, tile, n_items);
+}
+
+static void
+prepare_simple (GtkWidget *widget,
+ GtkListTile *tile,
+ guint n_items)
+{
+}
+
+static GtkListItemBase *
+create_simple_item (GtkWidget *widget)
+{
+ return g_object_new (GTK_TYPE_LIST_ITEM_BASE, NULL);
+}
+
+static GtkListHeaderBase *
+create_simple_header (GtkWidget *widget)
+{
+ return g_object_new (GTK_TYPE_LIST_HEADER_BASE, NULL);
+}
+
+static void
+test_create (void)
+{
+ GtkListItemManager *items;
+ GtkWidget *widget;
+
+ widget = gtk_window_new ();
+ items = gtk_list_item_manager_new (widget,
+ split_simple,
+ create_simple_item,
+ prepare_simple,
+ create_simple_header);
+ g_object_set_data_full (G_OBJECT (widget), "the-items", items, g_object_unref);
+
+ gtk_window_destroy (GTK_WINDOW (widget));
+}
+
+static void
+test_create_with_items (void)
+{
+ GListModel *source;
+ GtkNoSelection *selection;
+ GtkListItemManager *items;
+ GtkWidget *widget;
+
+ widget = gtk_window_new ();
+ items = gtk_list_item_manager_new (widget,
+ split_simple,
+ create_simple_item,
+ prepare_simple,
+ create_simple_header);
+ g_object_set_data_full (G_OBJECT (widget), "the-items", items, g_object_unref);
+
+ source = create_source_model (1, 50);
+ selection = gtk_no_selection_new (G_LIST_MODEL (source));
+ gtk_list_item_manager_set_model (items, GTK_SELECTION_MODEL (selection));
+ check_list_item_manager (items, NULL, 0);
+ gtk_list_item_manager_set_model (items, GTK_SELECTION_MODEL (selection));
+ check_list_item_manager (items, NULL, 0);
+
+ g_object_unref (selection);
+ gtk_window_destroy (GTK_WINDOW (widget));
+}
+
+#define N_TRACKERS 3
+#define N_WIDGETS_PER_TRACKER 10
+#define N_RUNS 500
+
+static void
+print_changes_cb (GListModel *model,
+ guint position,
+ guint removed,
+ guint added,
+ gpointer unused)
+{
+ if (!g_test_verbose ())
+ return;
+
+ if (removed == 0)
+ g_test_message ("%u/%u: adding %u items", position, g_list_model_get_n_items (model), added);
+ else if (added == 0)
+ g_test_message ("%u/%u: removing %u items", position, g_list_model_get_n_items (model), removed);
+ else
+ g_test_message ("%u/%u: removing %u and adding %u items", position, g_list_model_get_n_items (model), removed, added);
+}
+
+static void
+test_exhaustive (void)
+{
+ GtkListItemTracker *trackers[N_TRACKERS];
+ GListStore *store;
+ GtkFlattenListModel *flatten;
+ GtkNoSelection *selection;
+ GtkListItemManager *items;
+ GtkWidget *widget;
+ gsize i;
+
+ widget = gtk_window_new ();
+ items = gtk_list_item_manager_new (widget,
+ split_simple,
+ create_simple_item,
+ prepare_simple,
+ create_simple_header);
+ for (i = 0; i < N_TRACKERS; i++)
+ trackers[i] = gtk_list_item_tracker_new (items);
+
+ g_object_set_data_full (G_OBJECT (widget), "the-items", items, g_object_unref);
+
+ store = g_list_store_new (G_TYPE_OBJECT);
+ flatten = gtk_flatten_list_model_new (G_LIST_MODEL (store));
+ selection = gtk_no_selection_new (G_LIST_MODEL (flatten));
+ g_signal_connect (selection, "items-changed", G_CALLBACK (print_changes_cb), NULL);
+ gtk_list_item_manager_set_model (items, GTK_SELECTION_MODEL (selection));
+
+ for (i = 0; i < N_RUNS; i++)
+ {
+ gboolean add = FALSE, remove = FALSE;
+ guint position, n_items;
+
+ if (g_test_verbose ())
+ print_list_item_manager_tiles (items);
+
+ switch (g_test_rand_int_range (0, 6))
+ {
+ case 0:
+ if (g_test_verbose ())
+ g_test_message ("GC and checking");
+ check_list_item_manager (items, trackers, N_TRACKERS);
+ break;
+
+ case 1:
+ /* remove a model */
+ remove = TRUE;
+ break;
+
+ case 2:
+ /* add a model */
+ add = TRUE;
+ break;
+
+ case 3:
+ /* replace a model */
+ remove = TRUE;
+ add = TRUE;
+ break;
+
+ case 4:
+ n_items = g_list_model_get_n_items (G_LIST_MODEL (selection));
+ if (n_items > 0)
+ {
+ guint tracker_id = g_test_rand_int_range (0, N_TRACKERS);
+ guint pos = g_test_rand_int_range (0, n_items);
+ guint n_before = g_test_rand_int_range (0, N_WIDGETS_PER_TRACKER / 2);
+ guint n_after = g_test_rand_int_range (0, N_WIDGETS_PER_TRACKER / 2);
+ if (g_test_verbose ())
+ g_test_message ("setting tracker %u to %u -%u + %u", tracker_id, pos, n_before, n_after);
+ gtk_list_item_tracker_set_position (items,
+ trackers [tracker_id],
+ pos,
+ n_before, n_after);
+ }
+ break;
+
+ case 5:
+ {
+ gboolean has_sections = g_test_rand_bit ();
+ if (g_test_verbose ())
+ g_test_message ("Setting has_sections to %s", has_sections ? "true" : "false");
+ gtk_list_item_manager_set_has_sections (items, has_sections);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ position = g_test_rand_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1);
+ if (g_list_model_get_n_items (G_LIST_MODEL (store)) == position)
+ remove = FALSE;
+
+ if (add)
+ {
+ /* We want at least one element, otherwise the filters will see no changes */
+ GListModel *source = create_source_model (1, 50);
+ g_list_store_splice (store,
+ position,
+ remove ? 1 : 0,
+ (gpointer *) &source, 1);
+ g_object_unref (source);
+ }
+ else if (remove)
+ {
+ g_list_store_remove (store, position);
+ }
+ }
+
+ check_list_item_manager (items, trackers, N_TRACKERS);
+
+ for (i = 0; i < N_TRACKERS; i++)
+ gtk_list_item_tracker_free (items, trackers[i]);
+ g_object_unref (selection);
+ gtk_window_destroy (GTK_WINDOW (widget));
+}
+
+int
+main (int argc, char *argv[])
+{
+ gtk_test_init (&argc, &argv);
+
+ g_test_add_func ("/listitemmanager/create", test_create);
+ g_test_add_func ("/listitemmanager/create_with_items", test_create_with_items);
+ g_test_add_func ("/listitemmanager/exhaustive", test_exhaustive);
+
+ return g_test_run ();
+}
diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build
index 11560a95f4..a3f2be7172 100644
--- a/testsuite/gtk/meson.build
+++ b/testsuite/gtk/meson.build
@@ -126,6 +126,7 @@ internal_tests = [
{ 'name': 'texthistory' },
{ 'name': 'fnmatch' },
{ 'name': 'a11y' },
+ { 'name': 'listitemmanager' },
]
is_debug = get_option('buildtype').startswith('debug')
diff --git a/testsuite/gtk/sortlistmodel-exhaustive.c b/testsuite/gtk/sortlistmodel-exhaustive.c
index 0ca2c68f5e..e06bed8195 100644
--- a/testsuite/gtk/sortlistmodel-exhaustive.c
+++ b/testsuite/gtk/sortlistmodel-exhaustive.c
@@ -36,6 +36,8 @@
if (o1 != o2) \
{ \
char *_s = g_strdup_printf ("Objects differ at index %u out of %u", _i, _n); \
+ g_print ("%s\n", model_to_string (model1)); \
+ g_print ("%s\n", model_to_string (model2)); \
g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, _s); \
g_free (_s); \
} \
@@ -45,6 +47,19 @@
} \
}G_STMT_END
+#define assert_model_sections(model) G_STMT_START{ \
+ guint _i, _start, _end; \
+ _start = 0; \
+ _end = 0; \
+ for (_i = 0; _i < G_MAXUINT; _i = _end) \
+ { \
+ gtk_section_model_get_section (GTK_SECTION_MODEL (model), _i, &_start, &_end); \
+\
+ g_assert_cmpint (_start, ==, _i); \
+ g_assert_cmpint (_end, >, _i); \
+ } \
+}G_STMT_END
+
G_GNUC_UNUSED static char *
model_to_string (GListModel *model)
{
@@ -287,7 +302,7 @@ create_sorter (gsize id)
/* match all As, Bs and nothing */
sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
if (id == 1)
- gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), TRUE);
+ gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), FALSE);
return sorter;
default:
@@ -463,6 +478,258 @@ test_stability (gconstpointer model_id)
g_object_unref (flatten);
}
+static gboolean
+string_is_lowercase (GtkStringObject *o)
+{
+ return g_ascii_islower (*gtk_string_object_get_string (o));
+}
+
+/* Run:
+ * source => section-sorter
+ * source => sorter
+ * and set a section sorter on the section sorter that is a subsort of
+ * the real sorter.
+ *
+ * And then randomly add/remove sources and change the sorters and
+ * see if the two sorters stay identical
+ */
+static void
+test_section_sorters (gconstpointer model_id)
+{
+ GListStore *store;
+ GtkFlattenListModel *flatten;
+ GtkSortListModel *sort1, *sort2;
+ GtkSorter *sorter;
+ gsize i;
+
+ store = g_list_store_new (G_TYPE_OBJECT);
+ flatten = gtk_flatten_list_model_new (G_LIST_MODEL (store));
+ sort1 = create_sort_list_model (model_id, FALSE, G_LIST_MODEL (flatten), NULL);
+ sort2 = create_sort_list_model (model_id, FALSE, G_LIST_MODEL (flatten), NULL);
+
+ for (i = 0; i < 500; i++)
+ {
+ gboolean add = FALSE, remove = FALSE;
+ guint position;
+
+ switch (g_test_rand_int_range (0, 4))
+ {
+ case 0:
+ /* set the same sorter, once as section sorter, once as sorter */
+ sorter = create_random_sorter (TRUE);
+ gtk_sort_list_model_set_section_sorter (sort1, sorter);
+ gtk_sort_list_model_set_sorter (sort1, NULL);
+ gtk_sort_list_model_set_sorter (sort2, sorter);
+ g_clear_object (&sorter);
+ break;
+
+ case 1:
+ /* use a section sorter that is a more generic version of the sorter */
+ sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
+ gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), FALSE);
+ gtk_sort_list_model_set_sorter (sort1, sorter);
+ gtk_sort_list_model_set_sorter (sort2, sorter);
+ g_clear_object (&sorter);
+ sorter = GTK_SORTER (gtk_numeric_sorter_new (gtk_cclosure_expression_new (G_TYPE_BOOLEAN, NULL, 0, NULL, G_CALLBACK (string_is_lowercase), NULL, NULL)));
+ gtk_sort_list_model_set_section_sorter (sort1, sorter);
+ g_clear_object (&sorter);
+ break;
+
+ case 2:
+ /* remove a model */
+ remove = TRUE;
+ break;
+
+ case 3:
+ /* add a model */
+ add = TRUE;
+ break;
+
+ case 4:
+ /* replace a model */
+ remove = TRUE;
+ add = TRUE;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ position = g_test_rand_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1);
+ if (g_list_model_get_n_items (G_LIST_MODEL (store)) == position)
+ remove = FALSE;
+
+ if (add)
+ {
+ /* We want at least one element, otherwise the sorters will see no changes */
+ GListModel *source = create_source_model (1, 50);
+ g_list_store_splice (store,
+ position,
+ remove ? 1 : 0,
+ (gpointer *) &source, 1);
+ g_object_unref (source);
+ }
+ else if (remove)
+ {
+ g_list_store_remove (store, position);
+ }
+
+ if (g_test_rand_bit ())
+ {
+ ensure_updated ();
+ assert_model_equal (G_LIST_MODEL (sort1), G_LIST_MODEL (sort2));
+ }
+
+ if (g_test_rand_bit ())
+ assert_model_sections (G_LIST_MODEL (sort1));
+ }
+
+ g_object_unref (sort2);
+ g_object_unref (sort1);
+ g_object_unref (flatten);
+}
+
+/* Run:
+ * source => sorter
+ * And then randomly add/remove sources and change the sorters and
+ * see if the invariants for sections keep correct.
+ */
+static void
+test_sections (gconstpointer model_id)
+{
+ GListStore *store;
+ GtkFlattenListModel *flatten;
+ GtkSortListModel *sort;
+ GtkSorter *sorter;
+ gsize i;
+
+ store = g_list_store_new (G_TYPE_OBJECT);
+ flatten = gtk_flatten_list_model_new (G_LIST_MODEL (store));
+ sort = create_sort_list_model (model_id, FALSE, G_LIST_MODEL (flatten), NULL);
+
+ for (i = 0; i < 500; i++)
+ {
+ gboolean add = FALSE, remove = FALSE;
+ guint position;
+
+ switch (g_test_rand_int_range (0, 4))
+ {
+ case 0:
+ /* set the same sorter, once as section sorter, once as sorter */
+ sorter = create_random_sorter (TRUE);
+ gtk_sort_list_model_set_sorter (sort, sorter);
+ g_clear_object (&sorter);
+ break;
+
+ case 1:
+ /* set the same sorter, once as section sorter, once as sorter */
+ sorter = create_random_sorter (TRUE);
+ gtk_sort_list_model_set_section_sorter (sort, sorter);
+ g_clear_object (&sorter);
+ break;
+
+ case 2:
+ /* remove a model */
+ remove = TRUE;
+ break;
+
+ case 3:
+ /* add a model */
+ add = TRUE;
+ break;
+
+ case 4:
+ /* replace a model */
+ remove = TRUE;
+ add = TRUE;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ position = g_test_rand_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1);
+ if (g_list_model_get_n_items (G_LIST_MODEL (store)) == position)
+ remove = FALSE;
+
+ if (add)
+ {
+ /* We want at least one element, otherwise the sorters will see no changes */
+ GListModel *source = create_source_model (1, 50);
+ g_list_store_splice (store,
+ position,
+ remove ? 1 : 0,
+ (gpointer *) &source, 1);
+ g_object_unref (source);
+ }
+ else if (remove)
+ {
+ g_list_store_remove (store, position);
+ }
+
+ if (g_test_rand_bit ())
+ ensure_updated ();
+
+ if (g_test_rand_bit ())
+ {
+ guint start, end, pos, n, sec_start, sec_end;
+ gpointer prev_item, item;
+
+ n = g_list_model_get_n_items (G_LIST_MODEL (sort));
+ sorter = gtk_sort_list_model_get_section_sorter (sort);
+ start = end = 0;
+ prev_item = item = NULL;
+
+ for (pos = 0; pos < n; pos++)
+ {
+ gtk_section_model_get_section (GTK_SECTION_MODEL (sort), pos, &sec_start, &sec_end);
+ prev_item = item;
+ item = g_list_model_get_item (G_LIST_MODEL (sort), pos);
+ if (end <= pos)
+ {
+ g_assert_cmpint (pos, ==, end);
+ /* there should be a new section */
+ g_assert_cmpint (sec_start, ==, end);
+ g_assert_cmpint (sec_end, >, sec_start);
+ g_assert_cmpint (sec_end, <=, n);
+ start = sec_start;
+ end = sec_end;
+ if (prev_item)
+ {
+ g_assert_nonnull (sorter);
+ g_assert_cmpint (gtk_sorter_compare (sorter, prev_item, item), !=, GTK_ORDERING_EQUAL);
+ }
+ }
+ else
+ {
+ /* the old section keeps on going */
+ g_assert_cmpint (sec_start, ==, start);
+ g_assert_cmpint (sec_end, ==, end);
+ if (prev_item && sorter)
+ g_assert_cmpint (gtk_sorter_compare (sorter, prev_item, item), ==, GTK_ORDERING_EQUAL);
+ }
+ g_clear_object (&prev_item);
+ }
+
+ g_clear_object (&item);
+
+ /* for good measure, check the error condition */
+ if (n < G_MAXINT32)
+ {
+ gtk_section_model_get_section (GTK_SECTION_MODEL (sort), g_test_rand_int_range (n, G_MAXINT32), &sec_start, &sec_end);
+ g_assert_cmpint (sec_start, ==, n);
+ g_assert_cmpint (sec_end, ==, G_MAXUINT);
+ }
+ sorter = NULL;
+ }
+ }
+
+ g_object_unref (sort);
+ g_object_unref (flatten);
+}
+
static void
add_test_for_all_models (const char *name,
GTestDataFunc test_func)
@@ -488,6 +755,8 @@ main (int argc, char *argv[])
add_test_for_all_models ("two-sorters", test_two_sorters);
add_test_for_all_models ("stability", test_stability);
+ add_test_for_all_models ("section-sorters", test_section_sorters);
+ add_test_for_all_models ("sections", test_sections);
return g_test_run ();
}
diff --git a/testsuite/gtk/sortlistmodel.c b/testsuite/gtk/sortlistmodel.c
index 6a1753373c..d2cd699269 100644
--- a/testsuite/gtk/sortlistmodel.c
+++ b/testsuite/gtk/sortlistmodel.c
@@ -258,7 +258,7 @@ test_create (void)
{
GtkSortListModel *sort;
GListStore *store;
-
+
store = new_store ((guint[]) { 4, 8, 2, 6, 10, 0 });
sort = new_model (store);
assert_model (sort, "2 4 6 8 10");
@@ -280,7 +280,7 @@ test_set_model (void)
{
GtkSortListModel *sort;
GListStore *store;
-
+
sort = new_model (NULL);
assert_model (sort, "");
assert_changes (sort, "");
@@ -319,7 +319,7 @@ test_set_sorter (void)
GtkSortListModel *sort;
GtkSorter *sorter;
GListStore *store;
-
+
store = new_store ((guint[]) { 4, 8, 2, 6, 10, 0 });
sort = new_model (store);
assert_model (sort, "2 4 6 8 10");
@@ -350,7 +350,7 @@ test_add_items (void)
{
GtkSortListModel *sort;
GListStore *store;
-
+
/* add beginning */
store = new_store ((guint[]) { 51, 99, 100, 49, 50, 0 });
sort = new_model (store);
@@ -390,7 +390,7 @@ test_remove_items (void)
{
GtkSortListModel *sort;
GListStore *store;
-
+
/* remove beginning */
store = new_store ((guint[]) { 51, 99, 100, 49, 1, 2, 50, 0 });
sort = new_model (store);
@@ -570,6 +570,58 @@ test_add_remove_item (void)
g_object_unref (sort);
}
+static int
+sort_func (gconstpointer p1,
+ gconstpointer p2,
+ gpointer data)
+{
+ const char *s1 = gtk_string_object_get_string ((GtkStringObject *)p1);
+ const char *s2 = gtk_string_object_get_string ((GtkStringObject *)p2);
+
+ /* compare just the first byte */
+ return (int)(s1[0]) - (int)(s2[0]);
+}
+
+static void
+test_sections (void)
+{
+ GtkStringList *list;
+ const char *strings[] = {
+ "aaa",
+ "aab",
+ "abc",
+ "bbb",
+ "bq1",
+ "bq2",
+ "cc",
+ "cx",
+ NULL
+ };
+ GtkSorter *sorter;
+ GtkSortListModel *sorted;
+ GtkSorter *section_sorter;
+ guint s, e;
+
+ list = gtk_string_list_new (strings);
+ sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
+ sorted = gtk_sort_list_model_new (G_LIST_MODEL (list), sorter);
+ section_sorter = GTK_SORTER (gtk_custom_sorter_new (sort_func, NULL, NULL));
+ gtk_sort_list_model_set_section_sorter (GTK_SORT_LIST_MODEL (sorted), section_sorter);
+ g_object_unref (section_sorter);
+
+ gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 0, &s, &e);
+ g_assert_cmpint (s, ==, 0);
+ g_assert_cmpint (e, ==, 3);
+ gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 3, &s, &e);
+ g_assert_cmpint (s, ==, 3);
+ g_assert_cmpint (e, ==, 6);
+ gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 6, &s, &e);
+ g_assert_cmpint (s, ==, 6);
+ g_assert_cmpint (e, ==, 8);
+
+ g_object_unref (sorted);
+}
+
int
main (int argc, char *argv[])
{
@@ -589,6 +641,7 @@ main (int argc, char *argv[])
g_test_add_func ("/sortlistmodel/incremental/remove", test_incremental_remove);
g_test_add_func ("/sortlistmodel/oob-access", test_out_of_bounds_access);
g_test_add_func ("/sortlistmodel/add-remove-item", test_add_remove_item);
+ g_test_add_func ("/sortlistmodel/sections", test_sections);
return g_test_run ();
}