diff options
author | Matthias Clasen <mclasen@redhat.com> | 2023-05-09 16:36:55 +0000 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2023-05-09 16:36:55 +0000 |
commit | 526ddfa8666cf31e26763f2bcd0c83b066714134 (patch) | |
tree | dde096f76dc75d8588e316786df78b6081d303b9 /testsuite | |
parent | a959fba18afe9d161205d13523205c91c5a23a15 (diff) | |
parent | c88ac79437f1a88b8ba56ff48e6d5fcab95c04ca (diff) | |
download | gtk+-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.c | 18 | ||||
-rw-r--r-- | testsuite/gtk/filterlistmodel.c | 91 | ||||
-rw-r--r-- | testsuite/gtk/listitemmanager.c | 497 | ||||
-rw-r--r-- | testsuite/gtk/meson.build | 1 | ||||
-rw-r--r-- | testsuite/gtk/sortlistmodel-exhaustive.c | 271 | ||||
-rw-r--r-- | testsuite/gtk/sortlistmodel.c | 63 |
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 (); } |