/* Extensive GtkTreeModelFilter tests.
 * Copyright (C) 2009  Kristian Rietveld  <kris@gtk.org>
 *
 * 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, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#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
 */

#define LEVEL_LENGTH 5

static void
create_tree_store_set_values (GtkTreeStore *store,
                              GtkTreeIter  *iter,
                              gboolean      visible)
{
  GtkTreePath *path;
  gchar *path_string;

  path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter);
  path_string = gtk_tree_path_to_string (path);

  gtk_tree_store_set (store, iter,
                      0, path_string,
                      1, visible,
                      -1);

  gtk_tree_path_free (path);
  g_free (path_string);
}

static void
create_tree_store_recurse (int           depth,
                           GtkTreeStore *store,
                           GtkTreeIter  *parent,
                           gboolean      visible)
{
  int i;

  for (i = 0; i < LEVEL_LENGTH; i++)
    {
      GtkTreeIter iter;

      gtk_tree_store_insert (store, &iter, parent, i);
      create_tree_store_set_values (store, &iter, visible);

      if (depth > 0)
        create_tree_store_recurse (depth - 1, store, &iter, visible);
    }
}

static GtkTreeStore *
create_tree_store (int      depth,
                   gboolean visible)
{
  GtkTreeStore *store;

  store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN);

  create_tree_store_recurse (depth, store, NULL, visible);

  return store;
}

/*
 * Signal monitor
 */

typedef enum
{
  ROW_INSERTED,
  ROW_DELETED,
  ROW_CHANGED,
  ROW_HAS_CHILD_TOGGLED,
  ROWS_REORDERED,
  LAST_SIGNAL
}
SignalName;

static const char *
signal_name_to_string (SignalName signal)
{
  switch (signal)
    {
      case ROW_INSERTED:
          return "row-inserted";

      case ROW_DELETED:
          return "row-deleted";

      case ROW_CHANGED:
          return "row-changed";

      case ROW_HAS_CHILD_TOGGLED:
          return "row-has-child-toggled";

      case ROWS_REORDERED:
          return "rows-reordered";

      default:
          /* Fall through */
          break;
    }

  return "(unknown)";
}

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;
  gulong signal_ids[LAST_SIGNAL];
}
SignalMonitor;


static void
signal_monitor_generic_handler (SignalMonitor *m,
                                SignalName     signal,
                                GtkTreeModel  *model,
                                GtkTreePath   *path)
{
  Signal *s;

  if (g_queue_is_empty (m->queue))
    {
      g_error ("Signal queue empty\n");
      g_assert_not_reached ();
    }

  if (m->client != model)
    {
      g_error ("Model mismatch; expected %p, got %p\n",
               m->client, model);
      g_assert_not_reached ();
    }

  s = g_queue_peek_tail (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

  if (s->signal != signal
      || gtk_tree_path_compare (s->path, path) != 0)
    {
      gchar *path_str, *s_path_str;

      s_path_str = gtk_tree_path_to_string (s->path);
      path_str = gtk_tree_path_to_string (path);

      g_error ("Signals don't match; expected signal %s path %s, got signal %s path %s\n",
               signal_name_to_string (s->signal), s_path_str,
               signal_name_to_string (signal), path_str);

      g_free (s_path_str);
      g_free (path_str);

      g_assert_not_reached ();
    }

  s = g_queue_pop_tail (m->queue);

  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
 */

typedef struct
{
  GtkWidget *tree_view;

  GtkTreeStore *store;
  GtkTreeModelFilter *filter;

  SignalMonitor *monitor;

  guint block_signals : 1;
} FilterTest;


static void
filter_test_store_signal (FilterTest *fixture)
{
  if (fixture->block_signals)
    g_signal_stop_emission_by_name (fixture->store, "row-changed");
}


static void
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);

  g_signal_connect_swapped (fixture->store, "row-changed",
                            G_CALLBACK (filter_test_store_signal), fixture);

  /* Please forgive me for casting const away. */
  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 (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)
{
  filter_test_setup_generic (fixture, test_data, 3, TRUE, FALSE);
}

static void
filter_test_setup_unfiltered (FilterTest    *fixture,
                              gconstpointer  test_data)
{
  filter_test_setup_generic (fixture, test_data, 3, FALSE, TRUE);
}

static void
filter_test_setup_empty_unfiltered (FilterTest    *fixture,
                                    gconstpointer  test_data)
{
  filter_test_setup_generic (fixture, test_data, 3, TRUE, TRUE);
}

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.
   */
  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_block_signals (FilterTest *fixture)
{
  fixture->block_signals = TRUE;
}

static void
filter_test_unblock_signals (FilterTest *fixture)
{
  fixture->block_signals = FALSE;
}

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);
}

/*
 * Model structure validation
 */

static void
check_filter_model_recurse (FilterTest  *fixture,
                            GtkTreePath *store_parent_path,
                            GtkTreePath *filter_parent_path)
{
  int i;
  GtkTreeIter store_iter;
  GtkTreeIter filter_iter;
  gboolean store_has_next, filter_has_next;

  gtk_tree_path_down (store_parent_path);
  gtk_tree_path_down (filter_parent_path);

  store_has_next = gtk_tree_model_get_iter (GTK_TREE_MODEL (fixture->store),
                                            &store_iter, store_parent_path);
  filter_has_next = gtk_tree_model_get_iter (GTK_TREE_MODEL (fixture->filter),
                                             &filter_iter, filter_parent_path);

  for (i = 0; i < LEVEL_LENGTH; i++)
    {
      gboolean visible;

      g_return_if_fail (store_has_next == TRUE);

      gtk_tree_model_get (GTK_TREE_MODEL (fixture->store),
                          &store_iter,
                          1, &visible,
                          -1);

      if (visible)
        {
          GtkTreePath *tmp;
          gchar *filter_str, *store_str;

          g_return_if_fail (filter_has_next == TRUE);

          /* Verify path */
          tmp = gtk_tree_model_get_path (GTK_TREE_MODEL (fixture->filter),
                                         &filter_iter);
          g_return_if_fail (gtk_tree_path_compare (tmp, filter_parent_path) == 0);

          /* Verify model content */
          gtk_tree_model_get (GTK_TREE_MODEL (fixture->store),
                              &store_iter,
                              0, &store_str,
                              -1);
          gtk_tree_model_get (GTK_TREE_MODEL (fixture->filter),
                              &filter_iter,
                              0, &filter_str,
                              -1);

          g_return_if_fail (g_strcmp0 (store_str, filter_str) == 0);

          g_free (store_str);
          g_free (filter_str);

          if (gtk_tree_model_iter_has_child (GTK_TREE_MODEL (fixture->filter),
                                             &filter_iter))
            {
              g_return_if_fail (gtk_tree_model_iter_has_child (GTK_TREE_MODEL (fixture->store), &store_iter));

              check_filter_model_recurse (fixture,
                                          gtk_tree_path_copy (store_parent_path),
                                          tmp);
            }

          gtk_tree_path_next (filter_parent_path);
          filter_has_next = gtk_tree_model_iter_next (GTK_TREE_MODEL (fixture->filter), &filter_iter);
        }

      gtk_tree_path_next (store_parent_path);
      store_has_next = gtk_tree_model_iter_next (GTK_TREE_MODEL (fixture->store), &store_iter);
    }

  /* Both models should have no more content! */
  g_return_if_fail (store_has_next == FALSE);
  g_return_if_fail (filter_has_next == FALSE);

  gtk_tree_path_free (store_parent_path);
  gtk_tree_path_free (filter_parent_path);
}

static void
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));
}

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 ());
}

/* Helpers */

static void
check_level_length (GtkTreeModelFilter *filter,
                    const gchar        *level,
                    const int           length)
{
  if (!level)
    {
      int l = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (filter), NULL);
      g_return_if_fail (l == length);
    }
  else
    {
      int l;
      gboolean retrieved_iter = FALSE;
      GtkTreeIter iter;

      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);
    }
}

static void
set_path_visibility (FilterTest  *fixture,
                     const gchar *path,
                     gboolean     visible)
{
  GtkTreeIter store_iter;

  gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store),
                                       &store_iter, path);
  gtk_tree_store_set (fixture->store, &store_iter,
                      1, visible,
                      -1);
}

#if 0
static void
insert_path_with_visibility (FilterTest  *fixture,
                             const gchar *path_string,
                             gboolean     visible)
{
  int position;
  GtkTreePath *path;
  GtkTreeIter parent, iter;

  path = gtk_tree_path_new_from_string (path_string);
  position = gtk_tree_path_get_indices (path)[gtk_tree_path_get_depth (path)];
  gtk_tree_path_up (path);

  if (gtk_tree_model_get_iter (GTK_TREE_MODEL (fixture->store), &parent, path))
    {
      gtk_tree_store_insert (fixture->store, &iter, &parent, position);
      create_tree_store_set_values (fixture->store, &iter, visible);
    }
  gtk_tree_path_free (path);
}
#endif

/*
 * The actual tests.
 */

static void
verify_test_suite (FilterTest    *fixture,
                   gconstpointer  user_data)
{
  check_filter_model (fixture);
}

static void
verify_test_suite_vroot (FilterTest    *fixture,
                         gconstpointer  user_data)
{
  check_filter_model_with_root (fixture, (GtkTreePath *)user_data);
}


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);

  set_path_visibility (fixture, "3", FALSE);
  check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 5);

  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);

  set_path_visibility (fixture, "3", TRUE);
  check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 3);

  check_filter_model (fixture);
}

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);
  check_level_length (fixture->filter, "0", LEVEL_LENGTH - 2);

  set_path_visibility (fixture, "0:4:3", FALSE);
  check_filter_model (fixture);
  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
  check_level_length (fixture->filter, "0", LEVEL_LENGTH - 2);

  set_path_visibility (fixture, "0:4:0", FALSE);
  set_path_visibility (fixture, "0:4:1", FALSE);
  set_path_visibility (fixture, "0:4:2", FALSE);
  set_path_visibility (fixture, "0:4:4", FALSE);
  check_filter_model (fixture);
  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
  check_level_length (fixture->filter, "0", LEVEL_LENGTH - 2);

  /* 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);
}


static void
filled_vroot_hide_root_level (FilterTest    *fixture,
                              gconstpointer  user_data)
{
  GtkTreePath *path = (GtkTreePath *)user_data;

  /* These changes do not affect the filter's root level */
  set_path_visibility (fixture, "0", FALSE);
  check_filter_model_with_root (fixture, path);
  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
  check_level_length (fixture->filter, "0", LEVEL_LENGTH);

  set_path_visibility (fixture, "4", FALSE);
  check_filter_model_with_root (fixture, path);
  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
  check_level_length (fixture->filter, "0", LEVEL_LENGTH);

  /* Even though we set the virtual root parent node to FALSE,
   * the virtual root contents remain.
   */
  set_path_visibility (fixture, "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);

  /* No change */
  set_path_visibility (fixture, "1", FALSE);
  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
  check_level_length (fixture->filter, "0", LEVEL_LENGTH);

  set_path_visibility (fixture, "3", FALSE);
  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
  check_level_length (fixture->filter, "0", LEVEL_LENGTH);

  check_filter_model_with_root (fixture, path);

  /* Show some */
  set_path_visibility (fixture, "2", TRUE);
  check_filter_model_with_root (fixture, path);
  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
  check_level_length (fixture->filter, "0", LEVEL_LENGTH);

  set_path_visibility (fixture, "1", TRUE);
  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
  check_level_length (fixture->filter, "0", LEVEL_LENGTH);

  set_path_visibility (fixture, "3", TRUE);
  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
  check_level_length (fixture->filter, "0", LEVEL_LENGTH);

  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);

  set_path_visibility (fixture, "1:4", FALSE);
  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);

  set_path_visibility (fixture, "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, "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);
  set_path_visibility (fixture, "2:3", FALSE);
  set_path_visibility (fixture, "2:4", FALSE);
  check_filter_model_with_root (fixture, path);
  check_level_length (fixture->filter, NULL, 0);

  set_path_visibility (fixture, "2", TRUE);
  check_filter_model_with_root (fixture, path);
  check_level_length (fixture->filter, NULL, 0);

  set_path_visibility (fixture, "1:4", 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: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);

  set_path_visibility (fixture, "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");
  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);
  check_filter_model_with_root (fixture, path);
  check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 2);

  set_path_visibility (fixture, "2", TRUE);
  check_filter_model_with_root (fixture, path);
  check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 2);
}

static void
filled_vroot_hide_child_levels (FilterTest    *fixture,
                                gconstpointer  user_data)
{
  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);
  check_level_length (fixture->filter, "0", LEVEL_LENGTH - 2);

  set_path_visibility (fixture, "2:0:4:3", FALSE);
  check_filter_model_with_root (fixture, path);
  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
  check_level_length (fixture->filter, "0", LEVEL_LENGTH - 2);

  set_path_visibility (fixture, "2:0:4:0", FALSE);
  set_path_visibility (fixture, "2:0:4:1", FALSE);
  set_path_visibility (fixture, "2:0:4:2", FALSE);
  set_path_visibility (fixture, "2:0:4:4", FALSE);
  check_filter_model_with_root (fixture, path);
  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
  check_level_length (fixture->filter, "0", LEVEL_LENGTH - 2);

  /* 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);
}


static void
empty_show_nodes (FilterTest    *fixture,
                  gconstpointer  user_data)
{
  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);
  check_level_length (fixture->filter, "0", 0);

  set_path_visibility (fixture, "3:2:2", TRUE);
  check_filter_model (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);
  check_level_length (fixture->filter, "0", 1);
  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);
  check_level_length (fixture->filter, NULL, 1);
  check_level_length (fixture->filter, "0", 1);
  check_level_length (fixture->filter, "0:0", 2);
  check_level_length (fixture->filter, "0:0:0", 0);
}

static void
empty_show_multiple_nodes (FilterTest    *fixture,
                           gconstpointer  user_data)
{
  GtkTreeIter iter;
  GtkTreePath *changed_path;

  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");
  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_CHANGED, "1");
  signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "1");

  /* We simulate a change in visible func condition with this.  The
   * visibility state of multiple nodes changes at once, we emit row-changed
   * for these nodes (and others) after that.
   */
  filter_test_block_signals (fixture);
  set_path_visibility (fixture, "3", TRUE);
  set_path_visibility (fixture, "4", TRUE);
  filter_test_unblock_signals (fixture);

  changed_path = gtk_tree_path_new ();
  gtk_tree_path_append_index (changed_path, 2);
  gtk_tree_model_get_iter (GTK_TREE_MODEL (fixture->store),
                           &iter, changed_path);
  gtk_tree_model_row_changed (GTK_TREE_MODEL (fixture->store),
                              changed_path, &iter);

  gtk_tree_path_next (changed_path);
  gtk_tree_model_iter_next (GTK_TREE_MODEL (fixture->store), &iter);
  gtk_tree_model_row_changed (GTK_TREE_MODEL (fixture->store),
                              changed_path, &iter);

  gtk_tree_path_next (changed_path);
  gtk_tree_model_iter_next (GTK_TREE_MODEL (fixture->store), &iter);
  gtk_tree_model_row_changed (GTK_TREE_MODEL (fixture->store),
                              changed_path, &iter);

  gtk_tree_path_free (changed_path);

  check_filter_model (fixture);
  check_level_length (fixture->filter, NULL, 2);
  check_level_length (fixture->filter, "0", 0);

  set_path_visibility (fixture, "3:2:2", TRUE);
  check_filter_model (fixture);
  check_level_length (fixture->filter, NULL, 2);
  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, 2);
  check_level_length (fixture->filter, "0", 1);
  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, 1);

  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);
  check_level_length (fixture->filter, NULL, 2);
  check_level_length (fixture->filter, "0", 1);
  check_level_length (fixture->filter, "0:0", 2);
  check_level_length (fixture->filter, "0:0:0", 0);
}

static void
empty_vroot_show_nodes (FilterTest    *fixture,
                        gconstpointer  user_data)
{
  GtkTreePath *path = (GtkTreePath *)user_data;

  check_filter_model_with_root (fixture, path);
  check_level_length (fixture->filter, NULL, 0);

  set_path_visibility (fixture, "2", TRUE);
  check_filter_model_with_root (fixture, path);
  check_level_length (fixture->filter, NULL, 0);

  set_path_visibility (fixture, "2:2:2", TRUE);
  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", 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", 0);

  set_path_visibility (fixture, "3", TRUE);
  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);
  check_level_length (fixture->filter, NULL, 1);
  check_level_length (fixture->filter, "0", 2);
  check_level_length (fixture->filter, "0:1", 0);
}

static void
empty_vroot_show_multiple_nodes (FilterTest    *fixture,
                                 gconstpointer  user_data)
{
  GtkTreeIter iter;
  GtkTreePath *changed_path;
  GtkTreePath *path = (GtkTreePath *)user_data;

  check_filter_model_with_root (fixture, path);
  check_level_length (fixture->filter, NULL, 0);

  /* We simulate a change in visible func condition with this.  The
   * visibility state of multiple nodes changes at once, we emit row-changed
   * for these nodes (and others) after that.
   */
  filter_test_block_signals (fixture);
  set_path_visibility (fixture, "2", TRUE);
  set_path_visibility (fixture, "3", TRUE);
  filter_test_unblock_signals (fixture);

  changed_path = gtk_tree_path_new ();
  gtk_tree_path_append_index (changed_path, 1);
  gtk_tree_model_get_iter (GTK_TREE_MODEL (fixture->store),
                           &iter, changed_path);
  gtk_tree_model_row_changed (GTK_TREE_MODEL (fixture->store),
                              changed_path, &iter);

  gtk_tree_path_next (changed_path);
  gtk_tree_model_iter_next (GTK_TREE_MODEL (fixture->store), &iter);
  gtk_tree_model_row_changed (GTK_TREE_MODEL (fixture->store),
                              changed_path, &iter);

  gtk_tree_path_next (changed_path);
  gtk_tree_model_iter_next (GTK_TREE_MODEL (fixture->store), &iter);
  gtk_tree_model_row_changed (GTK_TREE_MODEL (fixture->store),
                              changed_path, &iter);

  gtk_tree_path_next (changed_path);
  gtk_tree_model_iter_next (GTK_TREE_MODEL (fixture->store), &iter);
  gtk_tree_model_row_changed (GTK_TREE_MODEL (fixture->store),
                              changed_path, &iter);

  gtk_tree_path_free (changed_path);

  check_filter_model_with_root (fixture, path);
  check_level_length (fixture->filter, NULL, 0);

  set_path_visibility (fixture, "2:2:2", TRUE);
  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");

  /* Again, we simulate a call to refilter */
  filter_test_block_signals (fixture);
  set_path_visibility (fixture, "2:2", TRUE);
  set_path_visibility (fixture, "2:3", TRUE);
  filter_test_unblock_signals (fixture);

  changed_path = gtk_tree_path_new ();
  gtk_tree_path_append_index (changed_path, 2);
  gtk_tree_path_append_index (changed_path, 1);
  gtk_tree_model_get_iter (GTK_TREE_MODEL (fixture->store),
                           &iter, changed_path);
  gtk_tree_model_row_changed (GTK_TREE_MODEL (fixture->store),
                              changed_path, &iter);

  gtk_tree_path_next (changed_path);
  gtk_tree_model_iter_next (GTK_TREE_MODEL (fixture->store), &iter);
  gtk_tree_model_row_changed (GTK_TREE_MODEL (fixture->store),
                              changed_path, &iter);

  gtk_tree_path_next (changed_path);
  gtk_tree_model_iter_next (GTK_TREE_MODEL (fixture->store), &iter);
  gtk_tree_model_row_changed (GTK_TREE_MODEL (fixture->store),
                              changed_path, &iter);

  gtk_tree_path_next (changed_path);
  gtk_tree_model_iter_next (GTK_TREE_MODEL (fixture->store), &iter);
  gtk_tree_model_row_changed (GTK_TREE_MODEL (fixture->store),
                              changed_path, &iter);

  gtk_tree_path_free (changed_path);

  check_filter_model_with_root (fixture, path);
  check_level_length (fixture->filter, NULL, 2);
  check_level_length (fixture->filter, "0", 1);
  check_level_length (fixture->filter, "0:0", 0);

  set_path_visibility (fixture, "3", TRUE);
  check_filter_model_with_root (fixture, path);
  check_level_length (fixture->filter, NULL, 2);

  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, 1);

  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);
  check_level_length (fixture->filter, NULL, 2);
  check_level_length (fixture->filter, "0", 2);
  check_level_length (fixture->filter, "0:1", 0);
}


static void
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);
  check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 1);
}

static void
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);
  check_level_length (fixture->filter, NULL, LEVEL_LENGTH);
  check_level_length (fixture->filter, "2", LEVEL_LENGTH - 1);
}

static void
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);
  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_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);
  check_level_length (fixture->filter, NULL, 1);
}

static void
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, "0", 1);
}

static void
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, "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);
}


static gboolean
specific_path_dependent_filter_func (GtkTreeModel *model,
                                     GtkTreeIter  *iter,
                                     gpointer      data)
{
  GtkTreePath *path;

  path = gtk_tree_model_get_path (model, iter);
  if (gtk_tree_path_get_indices (path)[0] < 4)
    return FALSE;

  return TRUE;
}

static void
specific_path_dependent_filter (void)
{
  int i;
  GtkTreeIter iter;
  GtkListStore *list;
  GtkTreeModel *sort;
  GtkTreeModel *filter;

  list = gtk_list_store_new (1, G_TYPE_INT);
  gtk_list_store_insert_with_values (list, &iter, 0, 0, 1, -1);
  gtk_list_store_insert_with_values (list, &iter, 1, 0, 2, -1);
  gtk_list_store_insert_with_values (list, &iter, 2, 0, 3, -1);
  gtk_list_store_insert_with_values (list, &iter, 3, 0, 4, -1);
  gtk_list_store_insert_with_values (list, &iter, 4, 0, 5, -1);
  gtk_list_store_insert_with_values (list, &iter, 5, 0, 6, -1);
  gtk_list_store_insert_with_values (list, &iter, 6, 0, 7, -1);
  gtk_list_store_insert_with_values (list, &iter, 7, 0, 8, -1);

  sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (list));
  filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (sort), NULL);
  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
                                          specific_path_dependent_filter_func,
                                          NULL, NULL);

  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sort), 0,
                                        GTK_SORT_DESCENDING);

  for (i = 0; i < 4; i++)
    {
      if (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (list), &iter,
                                         NULL, 1))
        gtk_list_store_remove (list, &iter);

      if (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (list), &iter,
                                         NULL, 2))
        gtk_list_store_remove (list, &iter);
    }
}


static gboolean
specific_append_after_collapse_visible_func (GtkTreeModel *model,
                                             GtkTreeIter  *iter,
                                             gpointer      data)
{
  gint number;
  gboolean hide_negative_numbers;

  gtk_tree_model_get (model, iter, 1, &number, -1);
  hide_negative_numbers = GPOINTER_TO_INT (g_object_get_data (data, "private-hide-negative-numbers"));

  return (number >= 0 || !hide_negative_numbers);
}

static void
specific_append_after_collapse (void)
{
  /* This test is based on one of the test cases I found in my
   * old test cases directory.  I unfortunately do not have a record
   * from who this test case originated.  -Kris.
   *
   * General idea:
   * - Construct tree.
   * - Show tree, expand, collapse.
   * - Add a row.
   */

  GtkTreeIter iter;
  GtkTreeIter child_iter;
  GtkTreeIter child_iter2;
  GtkTreePath *append_path;
  GtkTreeStore *store;
  GtkTreeModel *filter;
  GtkTreeModel *sort;

  GtkWidget *window;
  GtkWidget *tree_view;

  store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_INT);

  filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL);
  g_object_set_data (G_OBJECT (filter), "private-hide-negative-numbers",
                     GINT_TO_POINTER (FALSE));
  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
                                          specific_append_after_collapse_visible_func,
                                          filter, NULL);

  sort = gtk_tree_model_sort_new_with_model (filter);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  tree_view = gtk_tree_view_new_with_model (sort);
  gtk_container_add (GTK_CONTAINER (window), tree_view);
  gtk_widget_realize (tree_view);

  while (gtk_events_pending ())
    gtk_main_iteration ();

  gtk_tree_store_prepend (store, &iter, NULL);
  gtk_tree_store_set (store, &iter,
                      0, "hallo", 1, 1, -1);

  gtk_tree_store_append (store, &child_iter, &iter);
  gtk_tree_store_set (store, &child_iter,
                      0, "toemaar", 1, 1, -1);

  gtk_tree_store_append (store, &child_iter2, &child_iter);
  gtk_tree_store_set (store, &child_iter2,
                      0, "very deep", 1, 1, -1);

  append_path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &child_iter2);

  gtk_tree_store_append (store, &child_iter, &iter);
  gtk_tree_store_set (store, &child_iter,
                      0, "sja", 1, 1, -1);

  gtk_tree_store_append (store, &child_iter, &iter);
  gtk_tree_store_set (store, &child_iter,
                      0, "some word", 1, -1, -1);

  /* Expand and collapse the tree */
  gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view));
  while (gtk_events_pending ())
    gtk_main_iteration ();

  gtk_tree_view_collapse_all (GTK_TREE_VIEW (tree_view));
  while (gtk_events_pending ())
    gtk_main_iteration ();

  /* Add another it */
  g_object_set_data (G_OBJECT (filter), "private-hide-negative-numbers",
                     GINT_TO_POINTER (TRUE));

  if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, append_path))
    {
      gtk_tree_store_append (store, &child_iter, &iter);
      gtk_tree_store_set (store, &child_iter,
                          0, "new new new !!", 1, 1, -1);
    }
  gtk_tree_path_free (append_path);

  /* Expand */
  gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view));
  while (gtk_events_pending ())
    gtk_main_iteration ();
}


static gint
specific_sort_filter_remove_node_compare_func (GtkTreeModel  *model,
                                               GtkTreeIter   *iter1,
                                               GtkTreeIter   *iter2,
                                               gpointer       data)
{
  return -1;
}

static gboolean
specific_sort_filter_remove_node_visible_func (GtkTreeModel  *model,
                                               GtkTreeIter   *iter,
                                               gpointer       data)
{
  char *item = NULL;

  /* Do reference the model */
  gtk_tree_model_get (model, iter, 0, &item, -1);
  g_free (item);

  return FALSE;
}

static void
specific_sort_filter_remove_node (void)
{
  /* This test is based on one of the test cases I found in my
   * old test cases directory.  I unfortunately do not have a record
   * from who this test case originated.  -Kris.
   *
   * General idea:
   *  - Create tree store, sort, filter models.  The sort model has
   *    a default sort func that is enabled, filter model a visible func
   *    that defaults to returning FALSE.
   *  - Remove a node from the tree store.
   */

  GtkTreeIter iter;
  GtkTreeStore *store;
  GtkTreeModel *filter;
  GtkTreeModel *sort;

  GtkWidget *window;
  GtkWidget *tree_view;

  store = gtk_tree_store_new (1, G_TYPE_STRING);
  gtk_tree_store_append (store, &iter, NULL);
  gtk_tree_store_set (store, &iter, 0, "Hello1", -1);

  gtk_tree_store_append (store, &iter, NULL);
  gtk_tree_store_set (store, &iter, 0, "Hello2", -1);

  sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (store));
  gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (sort),
                                           specific_sort_filter_remove_node_compare_func, NULL, NULL);

  filter = gtk_tree_model_filter_new (sort, NULL);
  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
                                          specific_sort_filter_remove_node_visible_func,
                                          filter, NULL);


  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  tree_view = gtk_tree_view_new_with_model (filter);
  gtk_container_add (GTK_CONTAINER (window), tree_view);
  gtk_widget_realize (tree_view);

  while (gtk_events_pending ())
    gtk_main_iteration ();

  /* Remove a node */
  gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter);
  gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter);
  gtk_tree_store_remove (store, &iter);

  while (gtk_events_pending ())
    gtk_main_iteration ();
}


static void
specific_sort_filter_remove_root (void)
{
  /* This test is based on one of the test cases I found in my
   * old test cases directory.  I unfortunately do not have a record
   * from who this test case originated.  -Kris.
   */

  GtkTreeModel *model, *sort, *filter;
  GtkTreeIter root, mid, leaf;
  GtkTreePath *path;

  model = GTK_TREE_MODEL (gtk_tree_store_new (1, G_TYPE_INT));
  gtk_tree_store_append (GTK_TREE_STORE (model), &root, NULL);
  gtk_tree_store_append (GTK_TREE_STORE (model), &mid, &root);
  gtk_tree_store_append (GTK_TREE_STORE (model), &leaf, &mid);

  path = gtk_tree_model_get_path (model, &mid);

  sort = gtk_tree_model_sort_new_with_model (model);
  filter = gtk_tree_model_filter_new (sort, path);

  gtk_tree_store_remove (GTK_TREE_STORE (model), &root);

  g_object_unref (filter);
  g_object_unref (sort);
  g_object_unref (model);
}


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)
{
  /* This test is based on one of the test cases I found in my
   * old test cases directory.  I unfortunately do not have a record
   * from who this test case originated.  -Kris.
   */

  GtkTreeIter iter;
  GtkTreeIter iter_first;
  GtkTreeIter child;
  GtkTreeStore *store;
  GtkTreeModel *filter G_GNUC_UNUSED;

  store = gtk_tree_store_new (1, G_TYPE_STRING);

  gtk_tree_store_append (store, &iter_first, NULL);
  gtk_tree_store_set (store, &iter_first, 0, "Hello", -1);

  gtk_tree_store_append (store, &iter, NULL);
  gtk_tree_store_set (store, &iter, 0, "Hello", -1);

  gtk_tree_store_append (store, &iter, NULL);
  gtk_tree_store_set (store, &iter, 0, "Hello", -1);

  gtk_tree_store_append (store, &iter, NULL);
  gtk_tree_store_set (store, &iter, 0, "Hello", -1);

  filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL);

  gtk_tree_store_set (store, &iter, 0, "Hello", -1);
  gtk_tree_store_append (store, &child, &iter_first);
  gtk_tree_store_set (store, &child, 0, "Hello", -1);
}

static void
specific_list_store_clear (void)
{
  GtkTreeIter iter;
  GtkListStore *list;
  GtkTreeModel *filter;
  GtkWidget *view G_GNUC_UNUSED;

  list = gtk_list_store_new (1, G_TYPE_INT);
  gtk_list_store_insert_with_values (list, &iter, 0, 0, 1, -1);
  gtk_list_store_insert_with_values (list, &iter, 1, 0, 2, -1);
  gtk_list_store_insert_with_values (list, &iter, 2, 0, 3, -1);
  gtk_list_store_insert_with_values (list, &iter, 3, 0, 4, -1);
  gtk_list_store_insert_with_values (list, &iter, 4, 0, 5, -1);
  gtk_list_store_insert_with_values (list, &iter, 5, 0, 6, -1);
  gtk_list_store_insert_with_values (list, &iter, 6, 0, 7, -1);
  gtk_list_store_insert_with_values (list, &iter, 7, 0, 8, -1);

  filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (list), NULL);
  view = gtk_tree_view_new_with_model (filter);

  gtk_list_store_clear (list);
}

static void
specific_bug_300089 (void)
{
  /* Test case for GNOME Bugzilla bug 300089.  Written by
   * Matthias Clasen.
   */
  GtkTreeModel *sort_model, *child_model;
  GtkTreePath *path;
  GtkTreeIter iter, iter2, sort_iter;

  child_model = GTK_TREE_MODEL (gtk_tree_store_new (1, G_TYPE_STRING));

  gtk_tree_store_append (GTK_TREE_STORE (child_model), &iter, NULL);
  gtk_tree_store_set (GTK_TREE_STORE (child_model), &iter, 0, "A", -1);
  gtk_tree_store_append (GTK_TREE_STORE (child_model), &iter, NULL);
  gtk_tree_store_set (GTK_TREE_STORE (child_model), &iter, 0, "B", -1);

  gtk_tree_store_append (GTK_TREE_STORE (child_model), &iter2, &iter);
  gtk_tree_store_set (GTK_TREE_STORE (child_model), &iter2, 0, "D", -1);
  gtk_tree_store_append (GTK_TREE_STORE (child_model), &iter2, &iter);
  gtk_tree_store_set (GTK_TREE_STORE (child_model), &iter2, 0, "E", -1);

  gtk_tree_store_append (GTK_TREE_STORE (child_model), &iter, NULL);
  gtk_tree_store_set (GTK_TREE_STORE (child_model), &iter, 0, "C", -1);


  sort_model = GTK_TREE_MODEL (gtk_tree_model_sort_new_with_model (child_model));
  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sort_model),
                                        0, GTK_SORT_ASCENDING);

  path = gtk_tree_path_new_from_indices (1, 1, -1);

  /* make sure a level is constructed */ 
  gtk_tree_model_get_iter (sort_model, &sort_iter, path);

  /* change the "E" row in a way that causes it to change position */ 
  gtk_tree_model_get_iter (child_model, &iter, path);
  gtk_tree_store_set (GTK_TREE_STORE (child_model), &iter, 0, "A", -1);
}


static int
specific_bug_301558_sort_func (GtkTreeModel *model,
                               GtkTreeIter  *a,
                               GtkTreeIter  *b,
                               gpointer      data)
{
  int i, j;

  gtk_tree_model_get (model, a, 0, &i, -1);
  gtk_tree_model_get (model, b, 0, &j, -1);

  return j - i;
}

static void
specific_bug_301558 (void)
{
  /* Test case for GNOME Bugzilla bug 301558 provided by
   * Markku Vire.
   */
  GtkTreeStore *tree;
  GtkTreeModel *filter;
  GtkTreeModel *sort;
  GtkTreeIter root, iter, iter2;
  GtkWidget *view G_GNUC_UNUSED;
  int i;
  gboolean add;

  tree = gtk_tree_store_new (2, G_TYPE_INT, G_TYPE_BOOLEAN);
  gtk_tree_store_append (tree, &iter, NULL);
  gtk_tree_store_set (tree, &iter, 0, 123, 1, TRUE, -1);
  gtk_tree_store_append (tree, &iter2, &iter);
  gtk_tree_store_set (tree, &iter2, 0, 73, 1, TRUE, -1);

  sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (tree));
  gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (sort),
                                           specific_bug_301558_sort_func,
                                           NULL, NULL);

  filter = gtk_tree_model_filter_new (sort, NULL);
  gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter), 1);

  view = gtk_tree_view_new_with_model (filter);

  while (gtk_events_pending ())
    gtk_main_iteration ();

  add = TRUE;

  for (i = 0; i < 10; i++)
    {
      if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (tree), &root))
        g_assert_not_reached ();

      if (add)
        {
          gtk_tree_store_append (tree, &iter, &root);
          gtk_tree_store_set (tree, &iter, 0, 456, 1, TRUE, -1);
        }
      else
        {
          int n;
          n = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (tree), &root);
          gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (tree), &iter,
                                         &root, n - 1);
          gtk_tree_store_remove (tree, &iter);
        }

      add = !add;
    }
}


static gboolean
specific_bug_311955_filter_func (GtkTreeModel *model,
                                 GtkTreeIter  *iter,
                                 gpointer      data)
{
  int value;

  gtk_tree_model_get (model, iter, 0, &value, -1);

  return (value != 0);
}

static void
specific_bug_311955 (void)
{
  /* This is a test case for GNOME Bugzilla bug 311955.  It was written
   * by Markku Vire.
   */
  GtkTreeIter iter, child, root;
  GtkTreeStore *store;
  GtkTreeModel *sort;
  GtkTreeModel *filter;

  GtkWidget *window G_GNUC_UNUSED;
  GtkWidget *tree_view;
  int i;
  int n;

  store = gtk_tree_store_new (1, G_TYPE_INT);

  gtk_tree_store_append (store, &root, NULL);
  gtk_tree_store_set (store, &root, 0, 33, -1);

  gtk_tree_store_append (store, &iter, &root);
  gtk_tree_store_set (store, &iter, 0, 50, -1);

  gtk_tree_store_append (store, &iter, NULL);
  gtk_tree_store_set (store, &iter, 0, 22, -1);

  sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (store));
  filter = gtk_tree_model_filter_new (sort, NULL);

  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
                                          specific_bug_311955_filter_func,
                                          NULL, NULL);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  tree_view = gtk_tree_view_new_with_model (filter);
  g_object_unref (store);

  gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view));

  while (gtk_events_pending ())
    gtk_main_iteration ();

  /* Fill model */
  for (i = 0; i < 4; i++)
    {
      gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &root);

      gtk_tree_store_append (store, &iter, &root);

      if (i < 3)
        gtk_tree_store_set (store, &iter, 0, i, -1);

      if (i % 2 == 0)
        {
          gtk_tree_store_append (store, &child, &iter);
          gtk_tree_store_set (store, &child, 0, 10, -1);
        }
    }

  while (gtk_events_pending ())
    gtk_main_iteration ();

  /* Remove bottommost child from the tree. */
  gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &root);
  n = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), &root);

  if (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (store), &iter, &root, n - 2))
    {
      if (gtk_tree_model_iter_children (GTK_TREE_MODEL (store), &child, &iter))
        gtk_tree_store_remove (store, &child);
    }
  else
    g_assert_not_reached ();
}

static void
specific_bug_346800 (void)
{
  /* This is a test case for GNOME Bugzilla bug 346800.  It was written
   * by Jonathan Matthew.
   */

  GtkTreeIter node_iters[50];
  GtkTreeIter child_iters[50];
  GtkTreeModel *model;
  GtkTreeModelFilter *filter;
  GtkTreeStore *store;
  GType *columns;
  int i;
  int items = 50;
  columns = g_new (GType, 2);
  columns[0] = G_TYPE_STRING;
  columns[1] = G_TYPE_BOOLEAN;
  store = gtk_tree_store_newv (2, columns);
  model = GTK_TREE_MODEL (store);

  filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (model, NULL));
  gtk_tree_model_filter_set_visible_column (filter, 1);

  for (i=0; i<items; i++)
    {
      /* allocate random amounts of junk, otherwise the filter model's arrays can expand without moving */

      g_malloc (138);
      gtk_tree_store_append (store, &node_iters[i], NULL);
      gtk_tree_store_set (store, &node_iters[i],
                          0, "something",
                          1, ((i%6) == 0) ? FALSE : TRUE,
                          -1);

      g_malloc (47);
      gtk_tree_store_append (store, &child_iters[i], &node_iters[i]);
      gtk_tree_store_set (store, &child_iters[i],
                          0, "something else",
                          1, FALSE,
                          -1);
      gtk_tree_model_filter_refilter (filter);

      if (i > 6)
        {
          gtk_tree_store_set (GTK_TREE_STORE (model), &child_iters[i-1], 1,
                              (i & 1) ? TRUE : FALSE, -1);
          gtk_tree_model_filter_refilter (filter);

          gtk_tree_store_set (GTK_TREE_STORE (model), &child_iters[i-2], 1,
                              (i & 1) ? FALSE: TRUE, -1);
          gtk_tree_model_filter_refilter (filter);
        }
    }
}


static void
specific_bug_364946 (void)
{
  /* This is a test case for GNOME Bugzilla bug 364946.  It was written
   * by Andreas Koehler.
   */
  GtkTreeStore *store;
  GtkTreeIter a, aa, aaa, aab, iter;
  GtkTreeModel *s_model;

  store = gtk_tree_store_new (1, G_TYPE_STRING);

  gtk_tree_store_append (store, &a, NULL);
  gtk_tree_store_set (store, &a, 0, "0", -1);

  gtk_tree_store_append (store, &aa, &a);
  gtk_tree_store_set (store, &aa, 0, "0:0", -1);

  gtk_tree_store_append (store, &aaa, &aa);
  gtk_tree_store_set (store, &aaa, 0, "0:0:0", -1);

  gtk_tree_store_append (store, &aab, &aa);
  gtk_tree_store_set (store, &aab, 0, "0:0:1", -1);

  s_model = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (store));
  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (s_model), 0,
                                        GTK_SORT_ASCENDING);

  gtk_tree_model_get_iter_from_string (s_model, &iter, "0:0:0");

  gtk_tree_store_set (store, &aaa, 0, "0:0:0", -1);
  gtk_tree_store_remove (store, &aaa);
  gtk_tree_store_remove (store, &aab);

  gtk_tree_model_sort_clear_cache (GTK_TREE_MODEL_SORT (s_model));
}


static gboolean
specific_bug_464173_visible_func (GtkTreeModel *model,
                                  GtkTreeIter  *iter,
                                  gpointer      data)
{
  gboolean *visible = (gboolean *)data;

  return *visible;
}

static void
specific_bug_464173 (void)
{
  /* Test case for GNOME Bugzilla bug 464173, test case written
   * by Andreas Koehler.
   */
  GtkTreeStore *model;
  GtkTreeModelFilter *f_model;
  GtkTreeIter iter1, iter2;
  GtkWidget *view G_GNUC_UNUSED;
  gboolean visible = TRUE;

  model = gtk_tree_store_new (1, G_TYPE_STRING);
  gtk_tree_store_append (model, &iter1, NULL);
  gtk_tree_store_set (model, &iter1, 0, "Foo", -1);
  gtk_tree_store_append (model, &iter2, &iter1);
  gtk_tree_store_set (model, &iter2, 0, "Bar", -1);

  f_model = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL(model), NULL));
  gtk_tree_model_filter_set_visible_func (f_model,
                                          specific_bug_464173_visible_func,
                                          &visible, NULL);

  view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (f_model));

  visible = FALSE;
  gtk_tree_model_filter_refilter (f_model);
}


static gboolean
specific_bug_540201_filter_func (GtkTreeModel *model,
                                 GtkTreeIter  *iter,
                                 gpointer      data)
{
  gboolean has_children;

  has_children = gtk_tree_model_iter_has_child (model, iter);

  return has_children;
}

static void
specific_bug_540201 (void)
{
  /* Test case for GNOME Bugzilla bug 540201, steps provided by
   * Charles Day.
   */
  GtkTreeIter iter, root;
  GtkTreeStore *store;
  GtkTreeModel *filter;

  GtkWidget *tree_view G_GNUC_UNUSED;

  store = gtk_tree_store_new (1, G_TYPE_INT);

  gtk_tree_store_append (store, &root, NULL);
  gtk_tree_store_set (store, &root, 0, 33, -1);

  filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL);
  tree_view = gtk_tree_view_new_with_model (filter);

  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
                                          specific_bug_540201_filter_func,
                                          NULL, NULL);

  gtk_tree_store_append (store, &iter, &root);
  gtk_tree_store_set (store, &iter, 0, 50, -1);

  gtk_tree_store_append (store, &iter, &root);
  gtk_tree_store_set (store, &iter, 0, 22, -1);


  gtk_tree_store_append (store, &root, NULL);
  gtk_tree_store_set (store, &root, 0, 33, -1);

  gtk_tree_store_append (store, &iter, &root);
  gtk_tree_store_set (store, &iter, 0, 22, -1);
}


static gboolean
specific_bug_549287_visible_func (GtkTreeModel *model,
                                  GtkTreeIter  *iter,
                                  gpointer      data)
{
  gboolean result = FALSE;

  result = gtk_tree_model_iter_has_child (model, iter);

  return result;
}

static void
specific_bug_549287 (void)
{
  /* Test case for GNOME Bugzilla bug 529287, provided by Julient Puydt */

  int i;
  GtkTreeStore *store;
  GtkTreeModel *filtered;
  GtkWidget *view G_GNUC_UNUSED;
  GtkTreeIter iter;
  GtkTreeIter *swap, *parent, *child;

  store = gtk_tree_store_new (1, G_TYPE_STRING);
  filtered = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL);
  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filtered),
                                          specific_bug_549287_visible_func,
                                          NULL, NULL);

  view = gtk_tree_view_new_with_model (filtered);

  for (i = 0; i < 4; i++)
    {
      if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
        {
          parent = gtk_tree_iter_copy (&iter);
          child = gtk_tree_iter_copy (&iter);

          while (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (store),
                                                child, parent, 0))
            {

              swap = parent;
              parent = child;
              child = swap;
            }

          gtk_tree_store_append (store, child, parent);
          gtk_tree_store_set (store, child,
                              0, "Something",
                              -1);

          gtk_tree_iter_free (parent);
          gtk_tree_iter_free (child);
        }
      else
        {
          gtk_tree_store_append (store, &iter, NULL);
          gtk_tree_store_set (store, &iter,
                              0, "Something",
                              -1);
        }

      /* since we inserted something, we changed the visibility conditions: */
      gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filtered));
    }
}

/* main */

int
main (int    argc,
      char **argv)
{
  gtk_test_init (&argc, &argv, NULL);

  g_test_add ("/FilterModel/self/verify-test-suite",
              FilterTest, NULL,
              filter_test_setup,
              verify_test_suite,
              filter_test_teardown);

  g_test_add ("/FilterModel/self/verify-test-suite/vroot/depth-1",
              FilterTest, gtk_tree_path_new_from_indices (2, -1),
              filter_test_setup,
              verify_test_suite_vroot,
              filter_test_teardown);
  g_test_add ("/FilterModel/self/verify-test-suite/vroot/depth-2",
              FilterTest, gtk_tree_path_new_from_indices (2, 3, -1),
              filter_test_setup,
              verify_test_suite_vroot,
              filter_test_teardown);


  g_test_add ("/FilterModel/filled/hide-root-level",
              FilterTest, NULL,
              filter_test_setup,
              filled_hide_root_level,
              filter_test_teardown);
  g_test_add ("/FilterModel/filled/hide-child-levels",
              FilterTest, NULL,
              filter_test_setup,
              filled_hide_child_levels,
              filter_test_teardown);

  g_test_add ("/FilterModel/filled/hide-root-level/vroot",
              FilterTest, gtk_tree_path_new_from_indices (2, -1),
              filter_test_setup,
              filled_vroot_hide_root_level,
              filter_test_teardown);
  g_test_add ("/FilterModel/filled/hide-child-levels/vroot",
              FilterTest, gtk_tree_path_new_from_indices (2, -1),
              filter_test_setup,
              filled_vroot_hide_child_levels,
              filter_test_teardown);


  g_test_add ("/FilterModel/empty/show-nodes",
              FilterTest, NULL,
              filter_test_setup_empty,
              empty_show_nodes,
              filter_test_teardown);
  g_test_add ("/FilterModel/empty/show-multiple-nodes",
              FilterTest, NULL,
              filter_test_setup_empty,
              empty_show_multiple_nodes,
              filter_test_teardown);

  g_test_add ("/FilterModel/empty/show-nodes/vroot",
              FilterTest, gtk_tree_path_new_from_indices (2, -1),
              filter_test_setup_empty,
              empty_vroot_show_nodes,
              filter_test_teardown);
  g_test_add ("/FilterModel/empty/show-multiple-nodes/vroot",
              FilterTest, gtk_tree_path_new_from_indices (2, -1),
              filter_test_setup_empty,
              empty_vroot_show_multiple_nodes,
              filter_test_teardown);


  g_test_add ("/FilterModel/unfiltered/hide-single",
              FilterTest, NULL,
              filter_test_setup_unfiltered,
              unfiltered_hide_single,
              filter_test_teardown);
  g_test_add ("/FilterModel/unfiltered/hide-single-child",
              FilterTest, NULL,
              filter_test_setup_unfiltered,
              unfiltered_hide_single_child,
              filter_test_teardown);
  g_test_add ("/FilterModel/unfiltered/hide-single-multi-level",
              FilterTest, NULL,
              filter_test_setup_unfiltered,
              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,
              unfiltered_show_single,
              filter_test_teardown);
  g_test_add ("/FilterModel/unfiltered/show-single-child",
              FilterTest, NULL,
              filter_test_setup_empty_unfiltered,
              unfiltered_show_single_child,
              filter_test_teardown);
  g_test_add ("/FilterModel/unfiltered/show-single-multi-level",
              FilterTest, NULL,
              filter_test_setup_empty_unfiltered,
              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);
  g_test_add_func ("/FilterModel/specific/append-after-collapse",
                   specific_append_after_collapse);
  g_test_add_func ("/FilterModel/specific/sort-filter-remove-node",
                   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);
  g_test_add_func ("/FilterModel/specific/list-store-clear",
                   specific_list_store_clear);

  g_test_add_func ("/FilterModel/specific/bug-300089",
                   specific_bug_300089);
  g_test_add_func ("/FilterModel/specific/bug-301558",
                   specific_bug_301558);
  g_test_add_func ("/FilterModel/specific/bug-311955",
                   specific_bug_311955);
  g_test_add_func ("/FilterModel/specific/bug-346800",
                   specific_bug_346800);
  g_test_add_func ("/FilterModel/specific/bug-364946",
                   specific_bug_364946);
  g_test_add_func ("/FilterModel/specific/bug-464173",
                   specific_bug_464173);
  g_test_add_func ("/FilterModel/specific/bug-540201",
                   specific_bug_540201);
  g_test_add_func ("/FilterModel/specific/bug-549287",
                   specific_bug_549287);

  return g_test_run ();
}
