]> Pileus Git - ~andy/gtk/blobdiff - gtk/tests/filtermodel.c
List work left to do in the filter model unit test
[~andy/gtk] / gtk / tests / filtermodel.c
index 2463601c6547303b2da9ae0df11c34198615383f..ab862cea02c9de0fdc33e38e12ec7deb2f372fd1 100644 (file)
 #include <gtk/gtk.h>
 
 
+/* Left to do:
+ *   - Proper coverage checking to see if the unit tests cover
+ *     all possible cases.
+ *   - Verify if the ref counting is done properly for both the
+ *     normal ref_count and the zero_ref_count.  One way to test
+ *     this area is by collapsing/expanding branches on the view
+ *     that is connected to the filter model.
+ *   - Check if the iterator stamp is incremented at the correct times.
+ */
+
+
 /*
  * Model creation
  */
@@ -74,6 +85,218 @@ create_tree_store (int      depth,
   return store;
 }
 
+/*
+ * Signal monitor
+ */
+
+typedef enum
+{
+  ROW_INSERTED,
+  ROW_DELETED,
+  ROW_CHANGED,
+  ROW_HAS_CHILD_TOGGLED,
+  ROWS_REORDERED,
+  LAST_SIGNAL
+}
+SignalName;
+
+typedef struct
+{
+  SignalName signal;
+  GtkTreePath *path;
+}
+Signal;
+
+
+static Signal *
+signal_new (SignalName signal, GtkTreePath *path)
+{
+  Signal *s;
+
+  s = g_new0 (Signal, 1);
+  s->signal = signal;
+  s->path = gtk_tree_path_copy (path);
+
+  return s;
+}
+
+static void
+signal_free (Signal *s)
+{
+  if (s->path)
+    gtk_tree_path_free (s->path);
+
+  g_free (s);
+}
+
+
+typedef struct
+{
+  GQueue *queue;
+  GtkTreeModel *client;
+  guint signal_ids[LAST_SIGNAL];
+}
+SignalMonitor;
+
+
+static void
+signal_monitor_generic_handler (SignalMonitor *m,
+                                SignalName     signal,
+                                GtkTreeModel  *model,
+                                GtkTreePath   *path)
+{
+  Signal *s;
+
+  g_return_if_fail (m->client == model);
+  g_return_if_fail (!g_queue_is_empty (m->queue));
+
+#if 0
+  /* For debugging: output signals that are coming in.  Leaks memory. */
+  g_print ("signal=%d  path=%s\n", signal, gtk_tree_path_to_string (path));
+#endif
+
+  s = g_queue_peek_tail (m->queue);
+
+  g_return_if_fail (s->signal == signal);
+
+  s = g_queue_pop_tail (m->queue);
+
+  g_return_if_fail (!gtk_tree_path_compare (path, s->path));
+
+  signal_free (s);
+}
+
+static void
+signal_monitor_row_inserted (GtkTreeModel *model,
+                             GtkTreePath  *path,
+                             GtkTreeIter  *iter,
+                             gpointer      data)
+{
+  signal_monitor_generic_handler (data, ROW_INSERTED,
+                                  model, path);
+}
+
+static void
+signal_monitor_row_deleted (GtkTreeModel *model,
+                            GtkTreePath  *path,
+                            gpointer      data)
+{
+  signal_monitor_generic_handler (data, ROW_DELETED,
+                                  model, path);
+}
+
+static void
+signal_monitor_row_changed (GtkTreeModel *model,
+                            GtkTreePath  *path,
+                            GtkTreeIter  *iter,
+                            gpointer      data)
+{
+  signal_monitor_generic_handler (data, ROW_CHANGED,
+                                  model, path);
+}
+
+static void
+signal_monitor_row_has_child_toggled (GtkTreeModel *model,
+                                      GtkTreePath  *path,
+                                      GtkTreeIter  *iter,
+                                      gpointer      data)
+{
+  signal_monitor_generic_handler (data, ROW_HAS_CHILD_TOGGLED,
+                                  model, path);
+}
+
+static void
+signal_monitor_rows_reordered (GtkTreeModel *model,
+                               GtkTreePath  *path,
+                               GtkTreeIter  *iter,
+                               gint         *new_order,
+                               gpointer      data)
+{
+  signal_monitor_generic_handler (data, ROWS_REORDERED,
+                                  model, path);
+}
+
+static SignalMonitor *
+signal_monitor_new (GtkTreeModel *client)
+{
+  SignalMonitor *m;
+
+  m = g_new0 (SignalMonitor, 1);
+  m->client = g_object_ref (client);
+  m->queue = g_queue_new ();
+
+  m->signal_ids[ROW_INSERTED] = g_signal_connect (client,
+                                                  "row-inserted",
+                                                  G_CALLBACK (signal_monitor_row_inserted),
+                                                  m);
+  m->signal_ids[ROW_DELETED] = g_signal_connect (client,
+                                                 "row-deleted",
+                                                 G_CALLBACK (signal_monitor_row_deleted),
+                                                 m);
+  m->signal_ids[ROW_CHANGED] = g_signal_connect (client,
+                                                 "row-changed",
+                                                 G_CALLBACK (signal_monitor_row_changed),
+                                                 m);
+  m->signal_ids[ROW_HAS_CHILD_TOGGLED] = g_signal_connect (client,
+                                                           "row-has-child-toggled",
+                                                           G_CALLBACK (signal_monitor_row_has_child_toggled),
+                                                           m);
+  m->signal_ids[ROWS_REORDERED] = g_signal_connect (client,
+                                                    "rows-reordered",
+                                                    G_CALLBACK (signal_monitor_rows_reordered),
+                                                    m);
+
+  return m;
+}
+
+static void
+signal_monitor_free (SignalMonitor *m)
+{
+  int i;
+
+  for (i = 0; i < LAST_SIGNAL; i++)
+    g_signal_handler_disconnect (m->client, m->signal_ids[i]);
+
+  g_object_unref (m->client);
+
+  if (m->queue)
+    g_queue_free (m->queue);
+
+  g_free (m);
+}
+
+static void
+signal_monitor_assert_is_empty (SignalMonitor *m)
+{
+  g_assert (g_queue_is_empty (m->queue));
+}
+
+static void
+signal_monitor_append_signal_path (SignalMonitor *m,
+                                   SignalName     signal,
+                                   GtkTreePath   *path)
+{
+  Signal *s;
+
+  s = signal_new (signal, path);
+  g_queue_push_head (m->queue, s);
+}
+
+static void
+signal_monitor_append_signal (SignalMonitor *m,
+                              SignalName     signal,
+                              const gchar   *path_string)
+{
+  Signal *s;
+  GtkTreePath *path;
+
+  path = gtk_tree_path_new_from_string (path_string);
+
+  s = signal_new (signal, path);
+  g_queue_push_head (m->queue, s);
+
+  gtk_tree_path_free (path);
+}
 
 /*
  * Fixture
@@ -85,75 +308,275 @@ typedef struct
 
   GtkTreeStore *store;
   GtkTreeModelFilter *filter;
+
+  SignalMonitor *monitor;
 } FilterTest;
 
 static void
-filter_test_setup (FilterTest    *fixture,
-                   gconstpointer  test_data)
+filter_test_setup_generic (FilterTest    *fixture,
+                           gconstpointer  test_data,
+                           int            depth,
+                           gboolean       empty,
+                           gboolean       unfiltered)
 {
   const GtkTreePath *vroot = test_data;
+  GtkTreeModel *filter;
+
+  fixture->store = create_tree_store (depth, !empty);
 
-  fixture->store = create_tree_store (3, TRUE);
   /* Please forgive me for casting const away. */
-  fixture->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL (fixture->store), (GtkTreePath *)vroot));
-  gtk_tree_model_filter_set_visible_column (fixture->filter, 1);
+  filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (fixture->store),
+                                      (GtkTreePath *)vroot);
+  fixture->filter = GTK_TREE_MODEL_FILTER (filter);
+
+  if (!unfiltered)
+    gtk_tree_model_filter_set_visible_column (fixture->filter, 1);
 
   /* We need a tree view that's listening to get ref counting from that
    * side.
    */
-  fixture->tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (fixture->filter));
+  fixture->tree_view = gtk_tree_view_new_with_model (filter);
+
+  fixture->monitor = signal_monitor_new (filter);
+}
+
+static void
+filter_test_setup (FilterTest    *fixture,
+                   gconstpointer  test_data)
+{
+  filter_test_setup_generic (fixture, test_data, 3, FALSE, FALSE);
 }
 
 static void
 filter_test_setup_empty (FilterTest    *fixture,
                          gconstpointer  test_data)
 {
-  fixture->store = create_tree_store (3, FALSE);
-  fixture->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL (fixture->store), NULL));
-  gtk_tree_model_filter_set_visible_column (fixture->filter, 1);
-
-  /* We need a tree view that's listening to get ref counting from that
-   * side.
-   */
-  fixture->tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (fixture->filter));
+  filter_test_setup_generic (fixture, test_data, 3, TRUE, FALSE);
 }
 
 static void
 filter_test_setup_unfiltered (FilterTest    *fixture,
                               gconstpointer  test_data)
 {
-  fixture->store = create_tree_store (3, TRUE);
-  fixture->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL (fixture->store), NULL));
-
-  /* We need a tree view that's listening to get ref counting from that
-   * side.
-   */
-  fixture->tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (fixture->filter));
+  filter_test_setup_generic (fixture, test_data, 3, FALSE, TRUE);
 }
 
 static void
 filter_test_setup_empty_unfiltered (FilterTest    *fixture,
                                     gconstpointer  test_data)
 {
-  fixture->store = create_tree_store (3, FALSE);
-  fixture->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL (fixture->store), NULL));
+  filter_test_setup_generic (fixture, test_data, 3, TRUE, TRUE);
+}
 
-  /* We need a tree view that's listening to get ref counting from that
-   * side.
+static GtkTreePath *
+strip_virtual_root (GtkTreePath *path,
+                    GtkTreePath *root_path)
+{
+  GtkTreePath *real_path;
+
+  if (root_path)
+    {
+      int j;
+      int depth = gtk_tree_path_get_depth (path);
+      int root_depth = gtk_tree_path_get_depth (root_path);
+
+      real_path = gtk_tree_path_new ();
+
+      for (j = 0; j < depth - root_depth; j++)
+        gtk_tree_path_append_index (real_path,
+                                    gtk_tree_path_get_indices (path)[root_depth + j]);
+    }
+  else
+    real_path = gtk_tree_path_copy (path);
+
+  return real_path;
+}
+
+static void
+filter_test_append_refilter_signals_recurse (FilterTest  *fixture,
+                                             GtkTreePath *store_path,
+                                             GtkTreePath *filter_path,
+                                             int          depth,
+                                             GtkTreePath *root_path)
+{
+  int i;
+  int rows_deleted = 0;
+  GtkTreeIter iter;
+
+  gtk_tree_path_down (store_path);
+  gtk_tree_path_down (filter_path);
+
+  gtk_tree_model_get_iter (GTK_TREE_MODEL (fixture->store),
+                           &iter, store_path);
+
+  for (i = 0; i < LEVEL_LENGTH; i++)
+    {
+      gboolean visible;
+      GtkTreePath *real_path;
+
+      gtk_tree_model_get (GTK_TREE_MODEL (fixture->store), &iter,
+                          1, &visible,
+                          -1);
+
+      if (root_path &&
+          (!gtk_tree_path_is_descendant (store_path, root_path)
+           || !gtk_tree_path_compare (store_path, root_path)))
+        {
+          if (!gtk_tree_path_compare (store_path, root_path))
+            {
+              if (depth > 1
+                  && gtk_tree_model_iter_has_child (GTK_TREE_MODEL (fixture->store),
+                                                    &iter))
+                {
+                  GtkTreePath *store_copy;
+                  GtkTreePath *filter_copy;
+
+                  store_copy = gtk_tree_path_copy (store_path);
+                  filter_copy = gtk_tree_path_copy (filter_path);
+                  filter_test_append_refilter_signals_recurse (fixture,
+                                                               store_copy,
+                                                               filter_copy,
+                                                               depth - 1,
+                                                               root_path);
+                  gtk_tree_path_free (store_copy);
+                  gtk_tree_path_free (filter_copy);
+                }
+            }
+
+          gtk_tree_path_next (store_path);
+          gtk_tree_model_iter_next (GTK_TREE_MODEL (fixture->store), &iter);
+
+          if (visible)
+            gtk_tree_path_next (filter_path);
+
+          continue;
+        }
+
+      real_path = strip_virtual_root (filter_path, root_path);
+
+      if (visible)
+        {
+          /* This row will be inserted */
+          signal_monitor_append_signal_path (fixture->monitor, ROW_CHANGED,
+                                             real_path);
+          signal_monitor_append_signal_path (fixture->monitor,
+                                             ROW_HAS_CHILD_TOGGLED,
+                                             real_path);
+
+          if (depth > 1
+              && gtk_tree_model_iter_has_child (GTK_TREE_MODEL (fixture->store),
+                                                &iter))
+            {
+              GtkTreePath *store_copy;
+              GtkTreePath *filter_copy;
+
+              store_copy = gtk_tree_path_copy (store_path);
+              filter_copy = gtk_tree_path_copy (filter_path);
+              filter_test_append_refilter_signals_recurse (fixture,
+                                                           store_copy,
+                                                           filter_copy,
+                                                           depth - 1,
+                                                           root_path);
+              gtk_tree_path_free (store_copy);
+              gtk_tree_path_free (filter_copy);
+            }
+
+          gtk_tree_path_next (filter_path);
+        }
+      else
+        {
+          /* This row will be deleted */
+          rows_deleted++;
+          signal_monitor_append_signal_path (fixture->monitor, ROW_DELETED,
+                                             real_path);
+        }
+
+      gtk_tree_path_free (real_path);
+
+      gtk_tree_path_next (store_path);
+      gtk_tree_model_iter_next (GTK_TREE_MODEL (fixture->store), &iter);
+    }
+
+  if (rows_deleted == LEVEL_LENGTH
+      && gtk_tree_path_get_depth (filter_path) > 1)
+    {
+      GtkTreePath *real_path;
+
+      gtk_tree_path_up (store_path);
+      gtk_tree_path_up (filter_path);
+
+      /* A row-has-child-toggled will be emitted on the parent */
+      if (!root_path
+          || (root_path
+              && gtk_tree_path_is_descendant (store_path, root_path)
+              && gtk_tree_path_compare (store_path, root_path)))
+        {
+          real_path = strip_virtual_root (filter_path, root_path);
+          signal_monitor_append_signal_path (fixture->monitor,
+                                             ROW_HAS_CHILD_TOGGLED,
+                                             real_path);
+
+          gtk_tree_path_free (real_path);
+        }
+    }
+}
+
+static void
+filter_test_append_refilter_signals (FilterTest *fixture,
+                                     int         depth)
+{
+  /* A special function that walks the tree store like the
+   * model validation functions below.
    */
-  fixture->tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (fixture->filter));
+  GtkTreePath *path;
+  GtkTreePath *filter_path;
+
+  path = gtk_tree_path_new ();
+  filter_path = gtk_tree_path_new ();
+  filter_test_append_refilter_signals_recurse (fixture,
+                                               path,
+                                               filter_path,
+                                               depth,
+                                               NULL);
+  gtk_tree_path_free (path);
+  gtk_tree_path_free (filter_path);
+}
+
+static void
+filter_test_append_refilter_signals_with_vroot (FilterTest  *fixture,
+                                                int          depth,
+                                                GtkTreePath *root_path)
+{
+  /* A special function that walks the tree store like the
+   * model validation functions below.
+   */
+  GtkTreePath *path;
+  GtkTreePath *filter_path;
+
+  path = gtk_tree_path_new ();
+  filter_path = gtk_tree_path_new ();
+  filter_test_append_refilter_signals_recurse (fixture,
+                                               path,
+                                               filter_path,
+                                               depth,
+                                               root_path);
+  gtk_tree_path_free (path);
+  gtk_tree_path_free (filter_path);
 }
 
 static void
 filter_test_enable_filter (FilterTest *fixture)
 {
   gtk_tree_model_filter_set_visible_column (fixture->filter, 1);
+  gtk_tree_model_filter_refilter (fixture->filter);
 }
 
 static void
 filter_test_teardown (FilterTest    *fixture,
                       gconstpointer  test_data)
 {
+  signal_monitor_free (fixture->monitor);
+
   g_object_unref (fixture->filter);
   g_object_unref (fixture->store);
 }
@@ -249,6 +672,9 @@ check_filter_model (FilterTest *fixture)
 {
   GtkTreePath *path;
 
+  if (fixture->monitor)
+    signal_monitor_assert_is_empty (fixture->monitor);
+
   path = gtk_tree_path_new ();
 
   check_filter_model_recurse (fixture, path, gtk_tree_path_copy (path));
@@ -258,6 +684,9 @@ static void
 check_filter_model_with_root (FilterTest  *fixture,
                               GtkTreePath *path)
 {
+  if (fixture->monitor)
+    signal_monitor_assert_is_empty (fixture->monitor);
+
   check_filter_model_recurse (fixture,
                               gtk_tree_path_copy (path),
                               gtk_tree_path_new ());
@@ -278,10 +707,12 @@ check_level_length (GtkTreeModelFilter *filter,
   else
     {
       int l;
+      gboolean retrieved_iter = FALSE;
       GtkTreeIter iter;
 
-      gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (filter),
-                                           &iter, level);
+      retrieved_iter = gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (filter),
+                                                            &iter, level);
+      g_return_if_fail (retrieved_iter);
       l = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (filter), &iter);
       g_return_if_fail (l == length);
     }
@@ -345,20 +776,26 @@ static void
 filled_hide_root_level (FilterTest    *fixture,
                         gconstpointer  user_data)
 {
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "2");
   set_path_visibility (fixture, "2", FALSE);
   check_filter_model (fixture);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 1);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0");
   set_path_visibility (fixture, "0", FALSE);
   check_filter_model (fixture);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 2);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "2");
   set_path_visibility (fixture, "4", FALSE);
   check_filter_model (fixture);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 3);
 
 
   /* Hide remaining */
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0");
+
   set_path_visibility (fixture, "1", FALSE);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 4);
 
@@ -368,6 +805,11 @@ filled_hide_root_level (FilterTest    *fixture,
   check_filter_model (fixture);
 
   /* Show some */
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "1");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "1");
+
   set_path_visibility (fixture, "1", TRUE);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 4);
 
@@ -381,11 +823,13 @@ static void
 filled_hide_child_levels (FilterTest    *fixture,
                           gconstpointer  user_data)
 {
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0:2");
   set_path_visibility (fixture, "0:2", FALSE);
   check_filter_model (fixture);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
   check_level_length (fixture->filter, "0", LEVEL_LENGTH - 1);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0:3");
   set_path_visibility (fixture, "0:4", FALSE);
   check_filter_model (fixture);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
@@ -404,19 +848,35 @@ filled_hide_child_levels (FilterTest    *fixture,
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
   check_level_length (fixture->filter, "0", LEVEL_LENGTH - 2);
 
-  set_path_visibility (fixture, "0:4", TRUE);
   /* Since "0:2" is hidden, "0:4" must be "0:3" in the filter model */
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:3");
+  /* FIXME: Actually, the filter model should not be emitted the
+   * row-has-child-toggled signal here.  *However* an extraneous emission
+   * of this signal does not hurt and is allowed.
+   */
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:3");
+  set_path_visibility (fixture, "0:4", TRUE);
   check_filter_model (fixture);
   check_level_length (fixture->filter, "0:3", 0);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:2");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:2");
   set_path_visibility (fixture, "0:2", TRUE);
   check_filter_model (fixture);
   check_level_length (fixture->filter, "0:2", LEVEL_LENGTH);
   check_level_length (fixture->filter, "0:3", LEVEL_LENGTH);
   check_level_length (fixture->filter, "0:4", 0);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:4:0");
+  /* Once 0:4:0 got inserted, 0:4 became a parent */
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:4");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:4:0");
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:4:1");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:4:1");
+
   set_path_visibility (fixture, "0:4:2", TRUE);
   set_path_visibility (fixture, "0:4:4", TRUE);
+  signal_monitor_assert_is_empty (fixture->monitor);
   check_level_length (fixture->filter, "0:4", 2);
 }
 
@@ -474,10 +934,12 @@ filled_vroot_hide_root_level (FilterTest    *fixture,
   check_filter_model_with_root (fixture, path);
 
   /* Now test changes in the virtual root level */
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "2");
   set_path_visibility (fixture, "2:2", FALSE);
   check_filter_model_with_root (fixture, path);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 1);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "3");
   set_path_visibility (fixture, "2:4", FALSE);
   check_filter_model_with_root (fixture, path);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 2);
@@ -486,6 +948,8 @@ filled_vroot_hide_root_level (FilterTest    *fixture,
   check_filter_model_with_root (fixture, path);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 2);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "3");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "3");
   set_path_visibility (fixture, "2:4", TRUE);
   check_filter_model_with_root (fixture, path);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 1);
@@ -494,6 +958,10 @@ filled_vroot_hide_root_level (FilterTest    *fixture,
   check_filter_model_with_root (fixture, path);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 1);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0");
   set_path_visibility (fixture, "2:0", FALSE);
   set_path_visibility (fixture, "2:1", FALSE);
   set_path_visibility (fixture, "2:2", FALSE);
@@ -510,10 +978,13 @@ filled_vroot_hide_root_level (FilterTest    *fixture,
   check_filter_model_with_root (fixture, path);
   check_level_length (fixture->filter, NULL, 0);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0");
   set_path_visibility (fixture, "2:4", TRUE);
   check_filter_model_with_root (fixture, path);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 4);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0");
   set_path_visibility (fixture, "2:4", FALSE);
   check_filter_model_with_root (fixture, path);
   check_level_length (fixture->filter, NULL, 0);
@@ -522,6 +993,12 @@ filled_vroot_hide_root_level (FilterTest    *fixture,
   check_filter_model_with_root (fixture, path);
   check_level_length (fixture->filter, NULL, 0);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "1");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "1");
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "2");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2");
   set_path_visibility (fixture, "2:0", TRUE);
   set_path_visibility (fixture, "2:1", TRUE);
   set_path_visibility (fixture, "2:2", TRUE);
@@ -539,11 +1016,13 @@ filled_vroot_hide_child_levels (FilterTest    *fixture,
 {
   GtkTreePath *path = (GtkTreePath *)user_data;
 
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0:2");
   set_path_visibility (fixture, "2:0:2", FALSE);
   check_filter_model_with_root (fixture, path);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
   check_level_length (fixture->filter, "0", LEVEL_LENGTH - 1);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0:3");
   set_path_visibility (fixture, "2:0:4", FALSE);
   check_filter_model_with_root (fixture, path);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
@@ -562,17 +1041,32 @@ filled_vroot_hide_child_levels (FilterTest    *fixture,
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
   check_level_length (fixture->filter, "0", LEVEL_LENGTH - 2);
 
-  set_path_visibility (fixture, "2:0:4", TRUE);
   /* Since "0:2" is hidden, "0:4" must be "0:3" in the filter model */
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:3");
+  /* FIXME: Actually, the filter model should not be emitted the
+   * row-has-child-toggled signal here.  *However* an extraneous emission
+   * of this signal does not hurt and is allowed.
+   */
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:3");
+  set_path_visibility (fixture, "2:0:4", TRUE);
   check_filter_model_with_root (fixture, path);
   check_level_length (fixture->filter, "0:3", 0);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:2");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:2");
   set_path_visibility (fixture, "2:0:2", TRUE);
   check_filter_model_with_root (fixture, path);
   check_level_length (fixture->filter, "0:2", LEVEL_LENGTH);
   check_level_length (fixture->filter, "0:3", LEVEL_LENGTH);
   check_level_length (fixture->filter, "0:4", 0);
 
+  /* FIXME: Inconsistency!  For the non-vroot case we also receive two
+   * row-has-child-toggled signals here.
+   */
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:4:0");
+  /* Once 0:4:0 got inserted, 0:4 became a parent */
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:4");
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:4:1");
   set_path_visibility (fixture, "2:0:4:2", TRUE);
   set_path_visibility (fixture, "2:0:4:4", TRUE);
   check_level_length (fixture->filter, "0:4", 2);
@@ -586,6 +1080,8 @@ empty_show_nodes (FilterTest    *fixture,
   check_filter_model (fixture);
   check_level_length (fixture->filter, NULL, 0);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0");
   set_path_visibility (fixture, "3", TRUE);
   check_filter_model (fixture);
   check_level_length (fixture->filter, NULL, 1);
@@ -596,6 +1092,9 @@ empty_show_nodes (FilterTest    *fixture,
   check_level_length (fixture->filter, NULL, 1);
   check_level_length (fixture->filter, "0", 0);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:0");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:0");
   set_path_visibility (fixture, "3:2", TRUE);
   check_filter_model (fixture);
   check_level_length (fixture->filter, NULL, 1);
@@ -603,10 +1102,13 @@ empty_show_nodes (FilterTest    *fixture,
   check_level_length (fixture->filter, "0:0", 1);
   check_level_length (fixture->filter, "0:0:0", 0);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0");
   set_path_visibility (fixture, "3", FALSE);
   check_filter_model (fixture);
   check_level_length (fixture->filter, NULL, 0);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0");
   set_path_visibility (fixture, "3:2:1", TRUE);
   set_path_visibility (fixture, "3", TRUE);
   check_filter_model (fixture);
@@ -631,8 +1133,10 @@ empty_vroot_show_nodes (FilterTest    *fixture,
 
   set_path_visibility (fixture, "2:2:2", TRUE);
   check_filter_model_with_root (fixture, path);
-  check_level_length (fixture->filter, "0", 0);
+  check_level_length (fixture->filter, NULL, 0);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0");
   set_path_visibility (fixture, "2:2", TRUE);
   check_filter_model_with_root (fixture, path);
   check_level_length (fixture->filter, NULL, 1);
@@ -643,10 +1147,13 @@ empty_vroot_show_nodes (FilterTest    *fixture,
   check_filter_model_with_root (fixture, path);
   check_level_length (fixture->filter, NULL, 1);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0");
   set_path_visibility (fixture, "2:2", FALSE);
   check_filter_model_with_root (fixture, path);
   check_level_length (fixture->filter, NULL, 0);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0");
   set_path_visibility (fixture, "2:2:1", TRUE);
   set_path_visibility (fixture, "2:2", TRUE);
   check_filter_model_with_root (fixture, path);
@@ -661,10 +1168,17 @@ unfiltered_hide_single (FilterTest    *fixture,
                         gconstpointer  user_data)
 
 {
+  signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2");
   set_path_visibility (fixture, "2", FALSE);
 
+  signal_monitor_assert_is_empty (fixture->monitor);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
 
+  /* The view only shows the root level, so the filter model only has
+   * the first two levels cached.
+   */
+  filter_test_append_refilter_signals (fixture, 2);
   filter_test_enable_filter (fixture);
 
   check_filter_model (fixture);
@@ -676,11 +1190,18 @@ unfiltered_hide_single_child (FilterTest    *fixture,
                               gconstpointer  user_data)
 
 {
+  signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2:2");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2:2");
   set_path_visibility (fixture, "2:2", FALSE);
 
+  signal_monitor_assert_is_empty (fixture->monitor);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
   check_level_length (fixture->filter, "2", LEVEL_LENGTH);
 
+  /* The view only shows the root level, so the filter model only has
+   * the first two levels cached.
+   */
+  filter_test_append_refilter_signals (fixture, 2);
   filter_test_enable_filter (fixture);
 
   check_filter_model (fixture);
@@ -693,19 +1214,30 @@ unfiltered_hide_single_multi_level (FilterTest    *fixture,
                                     gconstpointer  user_data)
 
 {
+  /* This row is not shown, so its signal is not propagated */
   set_path_visibility (fixture, "2:2:2", FALSE);
+
+  signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2:2");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2:2");
   set_path_visibility (fixture, "2:2", FALSE);
 
+  signal_monitor_assert_is_empty (fixture->monitor);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
   check_level_length (fixture->filter, "2", LEVEL_LENGTH);
   check_level_length (fixture->filter, "2:2", LEVEL_LENGTH);
 
+  /* The view only shows the root level, so the filter model only has
+   * the first two levels cached.
+   */
+  filter_test_append_refilter_signals (fixture, 2);
   filter_test_enable_filter (fixture);
 
   check_filter_model (fixture);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
   check_level_length (fixture->filter, "2", LEVEL_LENGTH - 1);
 
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "2:2");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2:2");
   set_path_visibility (fixture, "2:2", TRUE);
 
   check_filter_model (fixture);
@@ -715,15 +1247,115 @@ unfiltered_hide_single_multi_level (FilterTest    *fixture,
 }
 
 
+static void
+unfiltered_vroot_hide_single (FilterTest    *fixture,
+                              gconstpointer  user_data)
+
+{
+  GtkTreePath *path = (GtkTreePath *)user_data;
+
+  signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2");
+  set_path_visibility (fixture, "2:2", FALSE);
+
+  signal_monitor_assert_is_empty (fixture->monitor);
+  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
+
+  /* The view only shows the root level, so the filter model only has
+   * the first two levels cached.  (We add an additional level to
+   * take the virtual root into account).
+   */
+  filter_test_append_refilter_signals_with_vroot (fixture, 3, path);
+  filter_test_enable_filter (fixture);
+
+  check_filter_model_with_root (fixture, path);
+  check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 1);
+}
+
+static void
+unfiltered_vroot_hide_single_child (FilterTest    *fixture,
+                                    gconstpointer  user_data)
+
+{
+  GtkTreePath *path = (GtkTreePath *)user_data;
+
+  signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2:2");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2:2");
+  set_path_visibility (fixture, "2:2:2", FALSE);
+
+  signal_monitor_assert_is_empty (fixture->monitor);
+  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
+  check_level_length (fixture->filter, "2", LEVEL_LENGTH);
+
+  /* The view only shows the root level, so the filter model only has
+   * the first two levels cached.  (We add an additional level to take
+   * the virtual root into account).
+   */
+  filter_test_append_refilter_signals_with_vroot (fixture, 3, path);
+  filter_test_enable_filter (fixture);
+
+  check_filter_model_with_root (fixture, path);
+  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
+  check_level_length (fixture->filter, "2", LEVEL_LENGTH - 1);
+}
+
+static void
+unfiltered_vroot_hide_single_multi_level (FilterTest    *fixture,
+                                          gconstpointer  user_data)
+
+{
+  GtkTreePath *path = (GtkTreePath *)user_data;
+
+  /* This row is not shown, so its signal is not propagated */
+  set_path_visibility (fixture, "2:2:2:2", FALSE);
+
+  signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2:2");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2:2");
+  set_path_visibility (fixture, "2:2:2", FALSE);
+
+  signal_monitor_assert_is_empty (fixture->monitor);
+  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
+  check_level_length (fixture->filter, "2", LEVEL_LENGTH);
+  check_level_length (fixture->filter, "2:2", LEVEL_LENGTH);
+
+  /* The view only shows the root level, so the filter model only has
+   * the first two levels cached.
+   */
+  filter_test_append_refilter_signals_with_vroot (fixture, 3, path);
+  filter_test_enable_filter (fixture);
+
+  check_filter_model_with_root (fixture, path);
+  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
+  check_level_length (fixture->filter, "2", LEVEL_LENGTH - 1);
+
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "2:2");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2:2");
+  set_path_visibility (fixture, "2:2:2", TRUE);
+
+  check_filter_model_with_root (fixture, path);
+  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
+  check_level_length (fixture->filter, "2", LEVEL_LENGTH);
+  check_level_length (fixture->filter, "2:2", LEVEL_LENGTH - 1);
+}
+
+
+
 static void
 unfiltered_show_single (FilterTest    *fixture,
                         gconstpointer  user_data)
 
 {
+  signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2");
   set_path_visibility (fixture, "2", TRUE);
 
+  signal_monitor_assert_is_empty (fixture->monitor);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
 
+  /* The view only shows the root level, so the filter model only has
+   * the first two levels cached.
+   */
+  filter_test_append_refilter_signals (fixture, 2);
   filter_test_enable_filter (fixture);
 
   check_filter_model (fixture);
@@ -735,19 +1367,32 @@ unfiltered_show_single_child (FilterTest    *fixture,
                               gconstpointer  user_data)
 
 {
+  signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2:2");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2:2");
   set_path_visibility (fixture, "2:2", TRUE);
 
+  signal_monitor_assert_is_empty (fixture->monitor);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
   check_level_length (fixture->filter, "2", LEVEL_LENGTH);
 
+  /* The view only shows the root level, so the filter model only has
+   * the first two levels cached.
+   */
+  filter_test_append_refilter_signals (fixture, 3);
   filter_test_enable_filter (fixture);
 
   check_filter_model (fixture);
   check_level_length (fixture->filter, NULL, 0);
 
+  /* From here we are filtered, "2" in the real model is "0" in the filter
+   * model.
+   */
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0");
   set_path_visibility (fixture, "2", TRUE);
+  signal_monitor_assert_is_empty (fixture->monitor);
   check_level_length (fixture->filter, NULL, 1);
-  check_level_length (fixture->filter, "2", 1);
+  check_level_length (fixture->filter, "0", 1);
 }
 
 static void
@@ -755,23 +1400,141 @@ unfiltered_show_single_multi_level (FilterTest    *fixture,
                                     gconstpointer  user_data)
 
 {
+  /* The view is not showing this row (collapsed state), so it is not
+   * referenced.  The signal should not go through.
+   */
   set_path_visibility (fixture, "2:2:2", TRUE);
+
+  signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2:2");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2:2");
   set_path_visibility (fixture, "2:2", TRUE);
 
+  signal_monitor_assert_is_empty (fixture->monitor);
   check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
   check_level_length (fixture->filter, "2", LEVEL_LENGTH);
   check_level_length (fixture->filter, "2:2", LEVEL_LENGTH);
 
+  /* The view only shows the root level, so the filter model only has
+   * the first two levels cached.
+   */
+  filter_test_append_refilter_signals (fixture, 3);
   filter_test_enable_filter (fixture);
 
   check_filter_model (fixture);
   check_level_length (fixture->filter, NULL, 0);
 
+  /* From here we are filtered, "2" in the real model is "0" in the filter
+   * model.
+   */
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0");
   set_path_visibility (fixture, "2", TRUE);
   check_filter_model (fixture);
   check_level_length (fixture->filter, NULL, 1);
-  check_level_length (fixture->filter, "2", 1);
-  check_level_length (fixture->filter, "2:2", 1);
+  check_level_length (fixture->filter, "0", 1);
+  check_level_length (fixture->filter, "0:0", 1);
+}
+
+
+static void
+unfiltered_vroot_show_single (FilterTest    *fixture,
+                              gconstpointer  user_data)
+
+{
+  GtkTreePath *path = (GtkTreePath *)user_data;
+
+  signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2");
+  set_path_visibility (fixture, "2:2", TRUE);
+
+  signal_monitor_assert_is_empty (fixture->monitor);
+  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
+
+  /* The view only shows the root level, so the filter model only has
+   * the first two levels cached.
+   */
+  filter_test_append_refilter_signals_with_vroot (fixture, 3, path);
+  filter_test_enable_filter (fixture);
+
+  check_filter_model_with_root (fixture, path);
+  check_level_length (fixture->filter, NULL, 1);
+}
+
+static void
+unfiltered_vroot_show_single_child (FilterTest    *fixture,
+                                    gconstpointer  user_data)
+
+{
+  GtkTreePath *path = (GtkTreePath *)user_data;
+
+  signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2:2");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2:2");
+  set_path_visibility (fixture, "2:2:2", TRUE);
+
+  signal_monitor_assert_is_empty (fixture->monitor);
+  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
+  check_level_length (fixture->filter, "2", LEVEL_LENGTH);
+
+  /* The view only shows the root level, so the filter model only has
+   * the first two levels cached.
+   */
+  filter_test_append_refilter_signals_with_vroot (fixture, 2, path);
+  filter_test_enable_filter (fixture);
+
+  check_filter_model_with_root (fixture, path);
+  check_level_length (fixture->filter, NULL, 0);
+
+  /* From here we are filtered, "2" in the real model is "0" in the filter
+   * model.
+   */
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0");
+  set_path_visibility (fixture, "2:2", TRUE);
+  signal_monitor_assert_is_empty (fixture->monitor);
+  check_level_length (fixture->filter, NULL, 1);
+  check_level_length (fixture->filter, "0", 1);
+}
+
+static void
+unfiltered_vroot_show_single_multi_level (FilterTest    *fixture,
+                                          gconstpointer  user_data)
+
+{
+  GtkTreePath *path = (GtkTreePath *)user_data;
+
+  /* The view is not showing this row (collapsed state), so it is not
+   * referenced.  The signal should not go through.
+   */
+  set_path_visibility (fixture, "2:2:2:2", TRUE);
+
+  signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2:2");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2:2");
+  set_path_visibility (fixture, "2:2:2", TRUE);
+
+  signal_monitor_assert_is_empty (fixture->monitor);
+  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
+  check_level_length (fixture->filter, "2", LEVEL_LENGTH);
+  check_level_length (fixture->filter, "2:2", LEVEL_LENGTH);
+
+  /* The view only shows the root level, so the filter model only has
+   * the first two levels cached.
+   */
+  filter_test_append_refilter_signals_with_vroot (fixture, 4, path);
+  filter_test_enable_filter (fixture);
+
+  check_filter_model_with_root (fixture, path);
+  check_level_length (fixture->filter, NULL, 0);
+
+  /* From here we are filtered, "2" in the real model is "0" in the filter
+   * model.
+   */
+  signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0");
+  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0");
+  set_path_visibility (fixture, "2:2", TRUE);
+  check_filter_model_with_root (fixture, path);
+  check_level_length (fixture->filter, NULL, 1);
+  check_level_length (fixture->filter, "0", 1);
+  check_level_length (fixture->filter, "0:0", 1);
 }
 
 
@@ -1047,6 +1810,225 @@ specific_sort_filter_remove_root (void)
 }
 
 
+static void
+specific_root_mixed_visibility (void)
+{
+  int i;
+  GtkTreeModel *filter;
+  /* A bit nasty, apologies */
+  FilterTest fixture;
+
+  fixture.store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN);
+
+  for (i = 0; i < LEVEL_LENGTH; i++)
+    {
+      GtkTreeIter iter;
+
+      gtk_tree_store_insert (fixture.store, &iter, NULL, i);
+      if (i % 2 == 0)
+        create_tree_store_set_values (fixture.store, &iter, TRUE);
+      else
+        create_tree_store_set_values (fixture.store, &iter, FALSE);
+    }
+
+  filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (fixture.store), NULL);
+  fixture.filter = GTK_TREE_MODEL_FILTER (filter);
+  fixture.monitor = NULL;
+
+  gtk_tree_model_filter_set_visible_column (fixture.filter, 1);
+
+  /* In order to trigger the potential bug, we should not access
+   * the filter model here (so don't call the check functions).
+   */
+
+  /* Change visibility of an odd row to TRUE */
+  set_path_visibility (&fixture, "3", TRUE);
+  check_filter_model (&fixture);
+  check_level_length (fixture.filter, NULL, 4);
+}
+
+
+
+static gboolean
+specific_has_child_filter_filter_func (GtkTreeModel *model,
+                                       GtkTreeIter  *iter,
+                                       gpointer      data)
+{
+  return gtk_tree_model_iter_has_child (model, iter);
+}
+
+static void
+specific_has_child_filter (void)
+{
+  GtkTreeModel *filter;
+  GtkTreeIter iter, root;
+  /* A bit nasty, apologies */
+  FilterTest fixture;
+
+  fixture.store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN);
+  filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (fixture.store), NULL);
+  fixture.filter = GTK_TREE_MODEL_FILTER (filter);
+  fixture.monitor = NULL;
+
+  /* We will filter on parent state using a filter function.  We will
+   * manually keep the boolean column in sync, so that we can use
+   * check_filter_model() to check the consistency of the model.
+   */
+  /* FIXME: We need a check_filter_model() that is not tied to LEVEL_LENGTH
+   * to be able to check the structure here.  We keep the calls to
+   * check_filter_model() commented out until then.
+   */
+  gtk_tree_model_filter_set_visible_func (fixture.filter,
+                                          specific_has_child_filter_filter_func,
+                                          NULL, NULL);
+
+  gtk_tree_store_append (fixture.store, &root, NULL);
+  create_tree_store_set_values (fixture.store, &root, FALSE);
+
+  /* check_filter_model (&fixture); */
+  check_level_length (fixture.filter, NULL, 0);
+
+  gtk_tree_store_append (fixture.store, &iter, &root);
+  create_tree_store_set_values (fixture.store, &iter, TRUE);
+
+  /* Parent must now be visible.  Do the level length check first,
+   * to avoid modifying the child model triggering a row-changed to
+   * the filter model.
+   */
+  check_level_length (fixture.filter, NULL, 1);
+  check_level_length (fixture.filter, "0", 0);
+
+  set_path_visibility (&fixture, "0", TRUE);
+  /* check_filter_model (&fixture); */
+
+  gtk_tree_store_append (fixture.store, &root, NULL);
+  check_level_length (fixture.filter, NULL, 1);
+
+  gtk_tree_store_append (fixture.store, &iter, &root);
+  check_level_length (fixture.filter, NULL, 2);
+  check_level_length (fixture.filter, "1", 0);
+
+  create_tree_store_set_values (fixture.store, &root, TRUE);
+  create_tree_store_set_values (fixture.store, &iter, TRUE);
+
+  /* check_filter_model (&fixture); */
+
+  gtk_tree_store_append (fixture.store, &iter, &root);
+  create_tree_store_set_values (fixture.store, &iter, TRUE);
+  check_level_length (fixture.filter, NULL, 2);
+  check_level_length (fixture.filter, "0", 0);
+  check_level_length (fixture.filter, "1", 0);
+
+  /* Now remove one of the remaining child rows */
+  gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture.store),
+                                       &iter, "0:0");
+  gtk_tree_store_remove (fixture.store, &iter);
+
+  check_level_length (fixture.filter, NULL, 1);
+  check_level_length (fixture.filter, "0", 0);
+
+  set_path_visibility (&fixture, "0", FALSE);
+  /* check_filter_model (&fixture); */
+}
+
+
+static gboolean
+specific_root_has_child_filter_filter_func (GtkTreeModel *model,
+                                            GtkTreeIter  *iter,
+                                            gpointer      data)
+{
+  int depth;
+  GtkTreePath *path;
+
+  path = gtk_tree_model_get_path (model, iter);
+  depth = gtk_tree_path_get_depth (path);
+  gtk_tree_path_free (path);
+
+  if (depth > 1)
+    return TRUE;
+  /* else */
+  return gtk_tree_model_iter_has_child (model, iter);
+}
+
+static void
+specific_root_has_child_filter (void)
+{
+  GtkTreeModel *filter;
+  GtkTreeIter iter, root;
+  /* A bit nasty, apologies */
+  FilterTest fixture;
+
+  /* This is a variation on the above test case wherein the has-child
+   * check for visibility only applies to root level nodes.
+   */
+
+  fixture.store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN);
+  filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (fixture.store), NULL);
+  fixture.filter = GTK_TREE_MODEL_FILTER (filter);
+  fixture.monitor = NULL;
+
+  /* We will filter on parent state using a filter function.  We will
+   * manually keep the boolean column in sync, so that we can use
+   * check_filter_model() to check the consistency of the model.
+   */
+  /* FIXME: We need a check_filter_model() that is not tied to LEVEL_LENGTH
+   * to be able to check the structure here.  We keep the calls to
+   * check_filter_model() commented out until then.
+   */
+  gtk_tree_model_filter_set_visible_func (fixture.filter,
+                                          specific_root_has_child_filter_filter_func,
+                                          NULL, NULL);
+
+  gtk_tree_store_append (fixture.store, &root, NULL);
+  create_tree_store_set_values (fixture.store, &root, FALSE);
+
+  /* check_filter_model (&fixture); */
+  check_level_length (fixture.filter, NULL, 0);
+
+  gtk_tree_store_append (fixture.store, &iter, &root);
+  create_tree_store_set_values (fixture.store, &iter, TRUE);
+
+  /* Parent must now be visible.  Do the level length check first,
+   * to avoid modifying the child model triggering a row-changed to
+   * the filter model.
+   */
+  check_level_length (fixture.filter, NULL, 1);
+  check_level_length (fixture.filter, "0", 1);
+
+  set_path_visibility (&fixture, "0", TRUE);
+  /* check_filter_model (&fixture); */
+
+  gtk_tree_store_append (fixture.store, &root, NULL);
+  check_level_length (fixture.filter, NULL, 1);
+
+  gtk_tree_store_append (fixture.store, &iter, &root);
+  check_level_length (fixture.filter, NULL, 2);
+  check_level_length (fixture.filter, "1", 1);
+
+  create_tree_store_set_values (fixture.store, &root, TRUE);
+  create_tree_store_set_values (fixture.store, &iter, TRUE);
+
+  /* check_filter_model (&fixture); */
+
+  gtk_tree_store_append (fixture.store, &iter, &root);
+  create_tree_store_set_values (fixture.store, &iter, TRUE);
+  check_level_length (fixture.filter, NULL, 2);
+  check_level_length (fixture.filter, "0", 1);
+  check_level_length (fixture.filter, "1", 2);
+
+  /* Now remove one of the remaining child rows */
+  gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture.store),
+                                       &iter, "0:0");
+  gtk_tree_store_remove (fixture.store, &iter);
+
+  check_level_length (fixture.filter, NULL, 1);
+  check_level_length (fixture.filter, "0", 2);
+
+  set_path_visibility (&fixture, "0", FALSE);
+  /* check_filter_model (&fixture); */
+}
+
+
 static void
 specific_filter_add_child (void)
 {
@@ -1618,6 +2600,24 @@ main (int    argc,
               unfiltered_hide_single_multi_level,
               filter_test_teardown);
 
+  g_test_add ("/FilterModel/unfiltered/hide-single/vroot",
+              FilterTest, gtk_tree_path_new_from_indices (2, -1),
+              filter_test_setup_unfiltered,
+              unfiltered_vroot_hide_single,
+              filter_test_teardown);
+  g_test_add ("/FilterModel/unfiltered/hide-single-child/vroot",
+              FilterTest, gtk_tree_path_new_from_indices (2, -1),
+              filter_test_setup_unfiltered,
+              unfiltered_vroot_hide_single_child,
+              filter_test_teardown);
+  g_test_add ("/FilterModel/unfiltered/hide-single-multi-level/vroot",
+              FilterTest, gtk_tree_path_new_from_indices (2, -1),
+              filter_test_setup_unfiltered,
+              unfiltered_vroot_hide_single_multi_level,
+              filter_test_teardown);
+
+
+
   g_test_add ("/FilterModel/unfiltered/show-single",
               FilterTest, NULL,
               filter_test_setup_empty_unfiltered,
@@ -1634,6 +2634,22 @@ main (int    argc,
               unfiltered_show_single_multi_level,
               filter_test_teardown);
 
+  g_test_add ("/FilterModel/unfiltered/show-single/vroot",
+              FilterTest, gtk_tree_path_new_from_indices (2, -1),
+              filter_test_setup_empty_unfiltered,
+              unfiltered_vroot_show_single,
+              filter_test_teardown);
+  g_test_add ("/FilterModel/unfiltered/show-single-child/vroot",
+              FilterTest, gtk_tree_path_new_from_indices (2, -1),
+              filter_test_setup_empty_unfiltered,
+              unfiltered_vroot_show_single_child,
+              filter_test_teardown);
+  g_test_add ("/FilterModel/unfiltered/show-single-multi-level/vroot",
+              FilterTest, gtk_tree_path_new_from_indices (2, -1),
+              filter_test_setup_empty_unfiltered,
+              unfiltered_vroot_show_single_multi_level,
+              filter_test_teardown);
+
 
   g_test_add_func ("/FilterModel/specific/path-dependent-filter",
                    specific_path_dependent_filter);
@@ -1643,6 +2659,12 @@ main (int    argc,
                    specific_sort_filter_remove_node);
   g_test_add_func ("/FilterModel/specific/sort-filter-remove-root",
                    specific_sort_filter_remove_root);
+  g_test_add_func ("/FilterModel/specific/root-mixed-visibility",
+                   specific_root_mixed_visibility);
+  g_test_add_func ("/FilterModel/specific/has-child-filter",
+                   specific_has_child_filter);
+  g_test_add_func ("/FilterModel/specific/root-has-child-filter",
+                   specific_root_has_child_filter);
   g_test_add_func ("/FilterModel/specific/filter-add-child",
                    specific_filter_add_child);