summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorMatthew Fennell <matthew@fennell.dev>2025-12-27 12:40:20 +0000
committerMatthew Fennell <matthew@fennell.dev>2025-12-27 12:40:20 +0000
commit5d8e439bc597159e3c9f0a8b65c0ae869dead3a8 (patch)
treeed28aefed8add0da1c55c08fdf80b23c4346e0dc /tests
Import Upstream version 43.0upstream/latest
Diffstat (limited to 'tests')
-rw-r--r--tests/dummy-provider.c525
-rw-r--r--tests/dummy-provider.h41
-rw-r--r--tests/interactive/test-animation.c288
-rw-r--r--tests/interactive/test-colorbutton.c85
-rw-r--r--tests/interactive/test-filter-sort.c290
-rw-r--r--tests/interactive/test-star-widget.c53
-rw-r--r--tests/interactive/test-task-model.c171
-rw-r--r--tests/interactive/test-widget.c130
-rw-r--r--tests/meson.build101
-rw-r--r--tests/test-model-filter.c208
-rw-r--r--tests/test-model-sort.c143
-rw-r--r--tests/test-task-list.c99
-rw-r--r--tests/test-task-model.c67
13 files changed, 2201 insertions, 0 deletions
diff --git a/tests/dummy-provider.c b/tests/dummy-provider.c
new file mode 100644
index 0000000..6d6041f
--- /dev/null
+++ b/tests/dummy-provider.c
@@ -0,0 +1,525 @@
+/* dummy-provider.c
+ *
+ * Copyright 2018-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "DummyProvider"
+
+#include "endeavour.h"
+
+#include "gtd-debug.h"
+#include "dummy-provider.h"
+
+struct _DummyProvider
+{
+ GtdObject parent;
+
+ GSequence *lists;
+
+ guint32 number_of_tasks;
+ guint remove_task_source_id;
+};
+
+static void gtd_provider_iface_init (GtdProviderInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (DummyProvider, dummy_provider, GTD_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GTD_TYPE_PROVIDER, gtd_provider_iface_init))
+
+enum
+{
+ PROP_0,
+ PROP_DESCRIPTION,
+ PROP_ENABLED,
+ PROP_ICON,
+ PROP_ID,
+ PROP_NAME,
+ PROP_PROVIDER_TYPE,
+ N_PROPS
+};
+
+
+/*
+ * Callbacks
+ */
+
+static gboolean
+remove_task_cb (gpointer user_data)
+{
+ DummyProvider *self = (DummyProvider*) user_data;
+
+ dummy_provider_randomly_remove_task (self);
+
+ return G_SOURCE_CONTINUE;
+}
+
+
+/*
+ * Auxiliary methods
+ */
+
+static GList*
+sequence_to_list (GSequence *sequence)
+{
+ GSequenceIter *iter = NULL;
+ GList *list = NULL;
+
+ for (iter = g_sequence_get_begin_iter (sequence);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ list = g_list_prepend (list, g_sequence_get (iter));
+ }
+
+ return g_list_reverse (list);
+}
+
+
+/*
+ * GtdProvider iface
+ */
+
+static const gchar*
+dummy_provider_get_id (GtdProvider *provider)
+{
+ return "dummy-provider";
+}
+
+static const gchar*
+dummy_provider_get_name (GtdProvider *provider)
+{
+ return "Dummy Provider";
+}
+
+static const gchar*
+dummy_provider_get_provider_type (GtdProvider *provider)
+{
+ return "dummy-provider";
+}
+
+static const gchar*
+dummy_provider_get_description (GtdProvider *provider)
+{
+ return "Dummyest provider of the known human history";
+}
+
+static gboolean
+dummy_provider_get_enabled (GtdProvider *provider)
+{
+ return TRUE;
+}
+
+static GIcon*
+dummy_provider_get_icon (GtdProvider *provider)
+{
+ return g_themed_icon_new ("face-monkey-symbolic");
+}
+static void
+dummy_provider_create_task (GtdProvider *provider,
+ GtdTaskList *list,
+ const gchar *title,
+ GDateTime *due_date,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+}
+
+static void
+dummy_provider_update_task (GtdProvider *provider,
+ GtdTask *task,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_debug ("Updating task '%s'", gtd_task_get_title (task));
+}
+
+static void
+dummy_provider_remove_task (GtdProvider *provider,
+ GtdTask *task,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+}
+
+static void
+dummy_provider_create_task_list (GtdProvider *provider,
+ const gchar *name,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSequenceIter *iter;
+ DummyProvider *self;
+ GtdTaskList* list;
+
+ self = DUMMY_PROVIDER (provider);
+
+ list = gtd_task_list_new (provider);
+ gtd_task_list_set_name (list, name);
+
+ iter = g_sequence_append (self->lists, list);
+ g_object_set_data (G_OBJECT (list), "DummyProvider::iter", iter);
+
+ g_signal_emit_by_name (self, "list-added", list);
+}
+
+static void
+dummy_provider_update_task_list (GtdProvider *provider,
+ GtdTaskList *list,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_signal_emit_by_name (provider, "list-changed", list);
+}
+
+static void
+dummy_provider_remove_task_list (GtdProvider *provider,
+ GtdTaskList *list,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+
+ GSequenceIter *iter;
+
+ iter = g_object_get_data (G_OBJECT (list), "DummyProvider::iter");
+ g_sequence_remove (iter);
+
+ g_signal_emit_by_name (provider, "list-removed", list);
+}
+
+static GList*
+dummy_provider_get_task_lists (GtdProvider *provider)
+{
+ DummyProvider *self = DUMMY_PROVIDER (provider);
+ return sequence_to_list (self->lists);
+}
+
+static GtdTaskList*
+dummy_provider_get_inbox (GtdProvider *provider)
+{
+ return NULL;
+}
+
+static void
+gtd_provider_iface_init (GtdProviderInterface *iface)
+{
+ iface->get_id = dummy_provider_get_id;
+ iface->get_name = dummy_provider_get_name;
+ iface->get_provider_type = dummy_provider_get_provider_type;
+ iface->get_description = dummy_provider_get_description;
+ iface->get_enabled = dummy_provider_get_enabled;
+ iface->get_icon = dummy_provider_get_icon;
+ iface->create_task = dummy_provider_create_task;
+ iface->update_task = dummy_provider_update_task;
+ iface->remove_task = dummy_provider_remove_task;
+ iface->create_task_list = dummy_provider_create_task_list;
+ iface->update_task_list = dummy_provider_update_task_list;
+ iface->remove_task_list = dummy_provider_remove_task_list;
+ iface->get_task_lists = dummy_provider_get_task_lists;
+ iface->get_inbox = dummy_provider_get_inbox;
+}
+
+
+/*
+ * GObject overrides
+ */
+
+static void
+dummy_provider_finalize (GObject *object)
+{
+ DummyProvider *self = (DummyProvider *)object;
+
+ g_clear_pointer (&self->lists, g_sequence_free);
+
+ G_OBJECT_CLASS (dummy_provider_parent_class)->finalize (object);
+}
+
+static void
+dummy_provider_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtdProvider *provider = GTD_PROVIDER (object);
+
+ switch (prop_id)
+ {
+ case PROP_DESCRIPTION:
+ g_value_set_string (value, dummy_provider_get_description (provider));
+ break;
+
+ case PROP_ENABLED:
+ g_value_set_boolean (value, dummy_provider_get_enabled (provider));
+ break;
+
+ case PROP_ICON:
+ g_value_set_object (value, dummy_provider_get_icon (provider));
+ break;
+
+ case PROP_ID:
+ g_value_set_string (value, dummy_provider_get_id (provider));
+ break;
+
+ case PROP_NAME:
+ g_value_set_string (value, dummy_provider_get_name (provider));
+ break;
+
+ case PROP_PROVIDER_TYPE:
+ g_value_set_string (value, dummy_provider_get_provider_type (provider));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+dummy_provider_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static void
+dummy_provider_class_init (DummyProviderClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = dummy_provider_finalize;
+ object_class->get_property = dummy_provider_get_property;
+ object_class->set_property = dummy_provider_set_property;
+
+ g_object_class_override_property (object_class, PROP_DESCRIPTION, "description");
+ g_object_class_override_property (object_class, PROP_ENABLED, "enabled");
+ g_object_class_override_property (object_class, PROP_ICON, "icon");
+ g_object_class_override_property (object_class, PROP_ID, "id");
+ g_object_class_override_property (object_class, PROP_NAME, "name");
+ g_object_class_override_property (object_class, PROP_PROVIDER_TYPE, "provider-type");
+}
+
+static void
+dummy_provider_init (DummyProvider *self)
+{
+ self->lists = g_sequence_new (g_object_unref);
+}
+
+DummyProvider*
+dummy_provider_new (void)
+{
+ return g_object_new (DUMMY_TYPE_PROVIDER, NULL);
+}
+
+guint
+dummy_provider_generate_task_list (DummyProvider *self)
+{
+ GSequenceIter *iter;
+ GtdTaskList *list;
+ guint n_generated_tasks;
+ gint i;
+
+ /*
+ * This generates a task list with the following layout:
+ *
+ * - Task
+ * - Task
+ * - Task
+ * - Task
+ * - Task
+ * - Task
+ * - Task
+ * - Task
+ * - Task
+ * - Task
+ */
+
+ gtd_provider_create_task_list (GTD_PROVIDER (self), "List", NULL, NULL, NULL);
+ iter = g_sequence_iter_prev (g_sequence_get_end_iter (self->lists));
+ list = g_sequence_get (iter);
+
+ n_generated_tasks = 0;
+
+ for (i = 0; i < 10; i++)
+ {
+ g_autoptr (GtdTask) task = NULL;
+ g_autofree gchar *title = NULL;
+ g_autofree gchar *uuid = NULL;
+
+ uuid = g_uuid_string_random ();
+ title = g_strdup_printf ("%d", i);
+
+ task = gtd_task_new ();
+ gtd_task_set_list (task, list);
+ gtd_object_set_uid (GTD_OBJECT (task), uuid);
+ gtd_task_set_title (task, title);
+ gtd_task_set_position (task, n_generated_tasks++);
+ gtd_task_list_add_task (list, task);
+ }
+
+ return n_generated_tasks;
+}
+
+guint
+dummy_provider_generate_task_lists (DummyProvider *self)
+{
+ static guint32 task_id = 0;
+ guint32 n_lists;
+ guint32 i;
+ guint32 j;
+
+ g_return_val_if_fail (DUMMY_IS_PROVIDER (self), 0);
+
+ n_lists = g_random_int_range (2, 5);
+
+ g_debug ("Creating %u task lists", n_lists);
+
+ for (i = 0; i < n_lists; i++)
+ {
+ g_autofree gchar *list_name = NULL;
+ GSequenceIter *iter;
+ GtdTaskList *new_list;
+ guint32 n_tasks;
+
+ list_name = g_strdup_printf ("List %u", task_id++ + 1);
+ gtd_provider_create_task_list (GTD_PROVIDER (self), list_name, NULL, NULL, NULL);
+
+ /* The new list is the last one */
+ iter = g_sequence_iter_prev (g_sequence_get_end_iter (self->lists));
+ new_list = g_sequence_get (iter);
+
+ /* Create a random number of stub tasks */
+ n_tasks = g_random_int_range (10, 20);
+
+ g_debug (" Creating %u tasks at list %u (%s)", n_tasks, i, list_name);
+
+ self->number_of_tasks += n_tasks;
+
+ for (j = 0; j < n_tasks; j++)
+ {
+ g_autofree gchar *title = NULL;
+ g_autofree gchar *uuid = NULL;
+ GtdTask *task;
+
+ task = gtd_task_new ();
+ gtd_task_set_list (task, new_list);
+ gtd_task_set_due_date (task, NULL);
+
+ title = g_strdup_printf ("Task %u", j + 1);
+ gtd_task_set_title (task, title);
+
+ uuid = g_uuid_string_random ();
+ gtd_object_set_uid (GTD_OBJECT (task), uuid);
+
+ gtd_task_list_add_task (new_list, task);
+ }
+ }
+
+ return self->number_of_tasks;
+}
+
+void
+dummy_provider_schedule_remove_task (DummyProvider *self)
+{
+ g_return_if_fail (DUMMY_IS_PROVIDER (self));
+
+ if (self->remove_task_source_id > 0)
+ return;
+
+ self->remove_task_source_id = g_timeout_add_seconds (2, remove_task_cb, self);
+}
+
+guint
+dummy_provider_randomly_remove_task (DummyProvider *self)
+{
+ guint32 n_lists;
+ guint32 i;
+ guint32 j;
+
+ g_return_val_if_fail (DUMMY_IS_PROVIDER (self), 0);
+
+ if (self->number_of_tasks == 0)
+ return 0;
+
+ n_lists = g_random_int_range (1, 3);
+
+ g_debug ("Removing tasks from %u task lists", n_lists);
+
+ for (i = 0; i < n_lists; i++)
+ {
+ GSequenceIter *iter;
+ GtdTaskList *list;
+ guint32 list_position;
+ guint32 n_tasks;
+ guint n_list_tasks;
+
+ n_tasks = 0;
+
+ do
+ {
+ list_position = g_random_int_range (0, g_sequence_get_length (self->lists));
+
+ /* The new list is the last one */
+ iter = g_sequence_get_iter_at_pos (self->lists, list_position);
+ list = g_sequence_get (iter);
+
+ n_list_tasks = g_list_model_get_n_items (G_LIST_MODEL (list));
+
+ if (n_list_tasks == 0)
+ continue;
+
+ /* Create a random number of stub tasks */
+ if (n_list_tasks > 1)
+ n_tasks = g_random_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (list)));
+ else
+ n_tasks = 1;
+ }
+ while (n_tasks == 0);
+
+ g_debug (" Selected list was %s (%u)", gtd_task_list_get_name (list), list_position);
+ g_debug (" Removing %u tasks from the list", n_tasks);
+
+ self->number_of_tasks -= n_tasks;
+
+ for (j = 0; j < n_tasks; j++)
+ {
+ g_autoptr (GtdTask) task = NULL;
+ guint task_position;
+
+ if (n_list_tasks > 1)
+ task_position = g_random_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (list)) - 1);
+ else
+ task_position = 0;
+
+ task = g_list_model_get_item (G_LIST_MODEL (list), task_position);
+
+ g_debug (" Removing task %u", task_position);
+
+ gtd_task_list_remove_task (list, task);
+ }
+
+ if (self->number_of_tasks == 0)
+ break;
+ }
+
+ return self->number_of_tasks;
+}
diff --git a/tests/dummy-provider.h b/tests/dummy-provider.h
new file mode 100644
index 0000000..51ba8fb
--- /dev/null
+++ b/tests/dummy-provider.h
@@ -0,0 +1,41 @@
+/* dummy-provider.h
+ *
+ * Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "endeavour.h"
+
+G_BEGIN_DECLS
+
+#define DUMMY_TYPE_PROVIDER (dummy_provider_get_type())
+
+G_DECLARE_FINAL_TYPE (DummyProvider, dummy_provider, DUMMY, PROVIDER, GtdObject)
+
+DummyProvider* dummy_provider_new (void);
+
+guint dummy_provider_generate_task_list (DummyProvider *self);
+
+guint dummy_provider_generate_task_lists (DummyProvider *self);
+
+void dummy_provider_schedule_remove_task (DummyProvider *self);
+
+guint dummy_provider_randomly_remove_task (DummyProvider *self);
+
+G_END_DECLS
diff --git a/tests/interactive/test-animation.c b/tests/interactive/test-animation.c
new file mode 100644
index 0000000..6541551
--- /dev/null
+++ b/tests/interactive/test-animation.c
@@ -0,0 +1,288 @@
+/* test-widget.c
+ *
+ * Copyright 2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "gtd-keyframe-transition.h"
+#include "gtd-widget.h"
+
+static const char *css =
+"translated {"
+" background-image: none;"
+" background-color: red;"
+"}\n"
+"wiggler {"
+" background-image: none;"
+" background-color: green;"
+"}\n"
+"rotated {"
+" background-image: none;"
+" background-color: blue;"
+"}\n"
+"mover {"
+" background-image: none;"
+" background-color: pink;"
+"}\n"
+;
+
+static const char *ui =
+"<interface>"
+" <object class='GtkWindow' id='window'>"
+" <property name='default-width'>600</property>"
+" <property name='default-height'>400</property>"
+" <child>"
+" <object class='GtkBox'>"
+" <child>"
+" <object class='GtdWidget'>"
+" <property name='hexpand'>true</property>"
+" <child>"
+" <object class='GtdWidget'>"
+" <property name='halign'>center</property>"
+" <property name='valign'>center</property>"
+" <child>"
+" <object class='GtdWidget' id='translated'>"
+" <property name='css-name'>translated</property>"
+" <property name='width-request'>30</property>"
+" <property name='height-request'>30</property>"
+" </object>"
+" </child>"
+" </object>"
+" </child>"
+" <child>"
+" <object class='GtdWidget'>"
+" <property name='halign'>center</property>"
+" <property name='valign'>start</property>"
+" <child>"
+" <object class='GtdWidget' id='wiggler'>"
+" <property name='css-name'>wiggler</property>"
+" <property name='translation-y'>40</property>"
+" <property name='width-request'>300</property>"
+" <property name='height-request'>40</property>"
+" </object>"
+" </child>"
+" </object>"
+" </child>"
+" <child>"
+" <object class='GtdWidget'>"
+" <property name='halign'>center</property>"
+" <property name='valign'>end</property>"
+" <child>"
+" <object class='GtdWidget' id='rotated'>"
+" <property name='css-name'>rotated</property>"
+" <property name='translation-y'>-80</property>"
+" <property name='width-request'>40</property>"
+" <property name='height-request'>40</property>"
+" </object>"
+" </child>"
+" </object>"
+" </child>"
+" <child>"
+" <object class='GtdWidget'>"
+" <property name='halign'>center</property>"
+" <property name='valign'>end</property>"
+" <child>"
+" <object class='GtdWidget' id='mover'>"
+" <property name='css-name'>mover</property>"
+" <property name='translation-x'>-200</property>"
+" <property name='translation-y'>-40</property>"
+" <property name='width-request'>50</property>"
+" <property name='height-request'>50</property>"
+" </object>"
+" </child>"
+" </object>"
+" </child>"
+" </object>"
+" </child>"
+" <child>"
+" <object class='GtkButton' id='button'>"
+" <property name='label'>Move</property>"
+" <property name='valign'>start</property>"
+" <property name='margin-top'>12</property>"
+" <property name='margin-start'>12</property>"
+" <property name='margin-end'>12</property>"
+" <property name='margin-bottom'>12</property>"
+" </object>"
+" </child>"
+" </object>"
+" </child>"
+" </object>"
+"</interface>";
+
+static gboolean pink_moved = FALSE;
+
+static void
+animate_rotation (GtdWidget *widget)
+{
+ GtdTransition *rotation_z;
+ GtdTransition *scale_x;
+ GtdTransition *scale_y;
+
+ rotation_z = gtd_property_transition_new ("rotation-z");
+ gtd_transition_set_from (rotation_z, G_TYPE_FLOAT, 0.f);
+ gtd_transition_set_to (rotation_z, G_TYPE_FLOAT, 360.f);
+ gtd_timeline_set_duration (GTD_TIMELINE (rotation_z), 750);
+ gtd_timeline_set_repeat_count (GTD_TIMELINE (rotation_z), -1);
+ gtd_timeline_set_auto_reverse (GTD_TIMELINE (rotation_z), TRUE);
+
+ scale_x = gtd_property_transition_new ("scale-x");
+ gtd_transition_set_from (scale_x, G_TYPE_FLOAT, 1.f);
+ gtd_transition_set_to (scale_x, G_TYPE_FLOAT, 2.f);
+ gtd_timeline_set_duration (GTD_TIMELINE (scale_x), 750);
+ gtd_timeline_set_repeat_count (GTD_TIMELINE (scale_x), -1);
+ gtd_timeline_set_auto_reverse (GTD_TIMELINE (scale_x), TRUE);
+
+ scale_y = gtd_property_transition_new ("scale-y");
+ gtd_transition_set_from (scale_y, G_TYPE_FLOAT, 1.f);
+ gtd_transition_set_to (scale_y, G_TYPE_FLOAT, 2.f);
+ gtd_timeline_set_duration (GTD_TIMELINE (scale_y), 750);
+ gtd_timeline_set_repeat_count (GTD_TIMELINE (scale_y), -1);
+ gtd_timeline_set_auto_reverse (GTD_TIMELINE (scale_y), TRUE);
+
+ gtd_widget_add_transition (widget, "loop-rotation-z", rotation_z);
+ gtd_widget_add_transition (widget, "loop-scale-x", scale_x);
+ gtd_widget_add_transition (widget, "loop-scale-y", scale_y);
+}
+
+static void
+animate_translation (GtdWidget *widget)
+{
+ GtdTransition *transition_x;
+
+ transition_x = gtd_property_transition_new ("translation-x");
+ gtd_transition_set_from (transition_x, G_TYPE_FLOAT, -200.f);
+ gtd_transition_set_to (transition_x, G_TYPE_FLOAT, 200.f);
+ gtd_timeline_set_duration (GTD_TIMELINE (transition_x), 2000);
+ gtd_timeline_set_repeat_count (GTD_TIMELINE (transition_x), -1);
+ gtd_timeline_set_auto_reverse (GTD_TIMELINE (transition_x), TRUE);
+
+ gtd_widget_add_transition (widget, "loop-translation-x", transition_x);
+}
+
+static void
+animate_wiggle (GtdWidget *widget)
+{
+ GtdTransition *transition_x;
+
+ g_message ("Adding wiggle");
+
+ gtd_widget_remove_all_transitions (widget);
+
+ transition_x = gtd_keyframe_transition_new ("translation-x");
+ gtd_transition_set_from (transition_x, G_TYPE_FLOAT, 0.f);
+ gtd_transition_set_to (transition_x, G_TYPE_FLOAT, 0.f);
+ gtd_timeline_set_duration (GTD_TIMELINE (transition_x), 350);
+ gtd_timeline_set_delay (GTD_TIMELINE (transition_x), 1000);
+ gtd_keyframe_transition_set (GTD_KEYFRAME_TRANSITION (transition_x),
+ G_TYPE_FLOAT,
+ 5,
+ 0.20, -15.f, GTD_EASE_OUT_QUAD,
+ 0.40, 15.f, GTD_EASE_LINEAR,
+ 0.60, -15.f, GTD_EASE_LINEAR,
+ 0.80, 15.f, GTD_EASE_LINEAR,
+ 1.00, 0.f, GTD_EASE_IN_QUAD);
+
+ gtd_widget_add_transition (widget, "wiggle", transition_x);
+
+ g_signal_connect_swapped (transition_x,
+ "completed",
+ G_CALLBACK (animate_wiggle),
+ widget);
+}
+
+static void
+move_pink_cb (GtkButton *button,
+ GtdWidget *widget)
+{
+ GtdTransition *rotation_y;
+
+ gtd_widget_remove_all_transitions (widget);
+
+ rotation_y = gtd_property_transition_new ("rotation-y");
+ gtd_transition_set_from (rotation_y, G_TYPE_FLOAT, 0.f);
+ gtd_transition_set_to (rotation_y, G_TYPE_FLOAT, 360.f);
+ gtd_timeline_set_duration (GTD_TIMELINE (rotation_y), 500);
+ gtd_timeline_set_repeat_count (GTD_TIMELINE (rotation_y), 3);
+
+ gtd_widget_save_easing_state (widget);
+ gtd_widget_set_easing_duration (widget, 2000);
+ gtd_widget_set_easing_mode (widget, GTD_EASE_LINEAR);
+ gtd_widget_set_translation (widget, pink_moved ? -200.f : 200.f, -40.f, 0.f);
+ gtd_widget_restore_easing_state (widget);
+
+ gtd_widget_add_transition (widget, "loop-rotation-y", rotation_y);
+
+ pink_moved = !pink_moved;
+}
+
+static GtkWidget *
+create_ui (void)
+{
+ g_autoptr (GtkBuilder) builder = NULL;
+ g_autoptr (GError) error = NULL;
+ GtkWidget *win;
+
+ g_type_ensure (GTD_TYPE_WIDGET);
+
+ builder = gtk_builder_new ();
+ if (!gtk_builder_add_from_string (builder, ui, -1, &error))
+ {
+ g_warning ("%s", error->message);
+ return NULL;
+ }
+
+ win = (GtkWidget *)gtk_builder_get_object (builder, "window");
+ g_object_ref (win);
+
+ animate_rotation ((GtdWidget *)gtk_builder_get_object (builder, "rotated"));
+ animate_translation ((GtdWidget *)gtk_builder_get_object (builder, "translated"));
+ animate_wiggle ((GtdWidget *)gtk_builder_get_object (builder, "wiggler"));
+
+ g_signal_connect (gtk_builder_get_object (builder, "button"),
+ "clicked",
+ G_CALLBACK (move_pink_cb),
+ gtk_builder_get_object (builder, "mover"));
+
+ return win;
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ g_autoptr (GtkCssProvider) css_provider = NULL;
+ GtkWindow *window;
+
+ g_set_prgname ("test-colorbutton");
+ g_set_application_name ("Endeavour | Widget Test");
+
+ gtk_init ();
+
+ css_provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_data (css_provider, css, -1);
+ gtk_style_context_add_provider_for_display (gdk_display_get_default (),
+ GTK_STYLE_PROVIDER (css_provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+ window = GTK_WINDOW (create_ui ());
+ gtk_window_present (window);
+
+ while (TRUE)
+ g_main_context_iteration (NULL, TRUE);
+
+ return 0;
+}
diff --git a/tests/interactive/test-colorbutton.c b/tests/interactive/test-colorbutton.c
new file mode 100644
index 0000000..065c978
--- /dev/null
+++ b/tests/interactive/test-colorbutton.c
@@ -0,0 +1,85 @@
+/* test-colorbutton.c
+ *
+ * Copyright 2018-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "gtd-color-button.h"
+
+static const gchar * const colors[] =
+{
+ "#ffffff",
+ "#dddddd",
+ "#ababab",
+ "#fafa00",
+ "#888888",
+ "#333333",
+ "#000000",
+ "#96ff11",
+ "#03fa95",
+};
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ GtkWindow *window = NULL;
+ GtkWidget *grid = NULL;
+ guint columns;
+ guint i;
+
+ g_set_prgname ("test-colorbutton");
+ g_set_application_name ("Endeavour | Color Button Test");
+
+ gtk_init ();
+
+ grid = g_object_new (GTK_TYPE_GRID,
+ "row-homogeneous", TRUE,
+ "column-homogeneous", TRUE,
+ NULL);
+
+ columns = ceil (sqrt (G_N_ELEMENTS (colors)));
+
+ for (i = 0; i < G_N_ELEMENTS (colors); i++)
+ {
+ GtkWidget *color_button = NULL;
+ GdkRGBA color;
+
+ gdk_rgba_parse (&color, colors[i]);
+
+ color_button = g_object_new (GTD_TYPE_COLOR_BUTTON,
+ "color", &color,
+ NULL);
+
+ gtk_grid_attach (GTK_GRID (grid),
+ color_button,
+ i % columns,
+ i / columns,
+ 1,
+ 1);
+ }
+
+ window = GTK_WINDOW (gtk_window_new ());
+ gtk_window_set_child (window, grid);
+ gtk_window_present (window);
+
+ while (TRUE)
+ g_main_context_iteration (NULL, TRUE);
+
+ return 0;
+}
+
diff --git a/tests/interactive/test-filter-sort.c b/tests/interactive/test-filter-sort.c
new file mode 100644
index 0000000..a356e3c
--- /dev/null
+++ b/tests/interactive/test-filter-sort.c
@@ -0,0 +1,290 @@
+/* test-filter-sort.c
+ *
+ * Copyright 2018-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+
+#include <gtk/gtk.h>
+
+#include "core/gtd-log.h"
+#include "models/gtd-list-model-filter.h"
+#include "models/gtd-list-model-sort.h"
+#include "models/gtd-task-model.h"
+#include "models/gtd-task-model-private.h"
+#include "dummy-provider.h"
+#include "gtd-manager.h"
+#include "gtd-manager-protected.h"
+#include "gtd-provider.h"
+#include "gtd-task.h"
+#include "gtd-task-list.h"
+#include "gtd-utils.h"
+
+
+/*
+ * Auxiliary methods
+ */
+
+static GtkWidget*
+create_bold_label_for_task_row (GtkListBoxRow *row)
+{
+ g_autofree gchar *markup = NULL;
+ GtdTask *task;
+
+ task = g_object_get_data (G_OBJECT (row), "task");
+ markup = g_strdup_printf ("<big><b>%s</b></big>", gtd_task_list_get_name (gtd_task_get_list (task)));
+
+ return g_object_new (GTK_TYPE_LABEL,
+ "margin", 6,
+ "margin-top", 18,
+ "use-markup", TRUE,
+ "label", markup,
+ "xalign", 0.0,
+ NULL);
+}
+
+static GtkWidget*
+create_label_for_string (const gchar *string)
+{
+ return g_object_new (GTK_TYPE_LABEL,
+ "label", string,
+ "hexpand", TRUE,
+ "xalign", 0.0,
+ "margin", 6,
+ "margin-start", 18,
+ NULL);
+}
+
+
+/*
+ * Callbacks
+ */
+
+static void
+header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ GtkWidget *header = NULL;
+
+ if (!before)
+ {
+ header = create_bold_label_for_task_row (row);
+ }
+ else
+ {
+ GtdTask *before_task;
+ GtdTask *task;
+
+ before_task = g_object_get_data (G_OBJECT (before), "task");
+ task = g_object_get_data (G_OBJECT (row), "task");
+
+ if (gtd_task_get_list (task) != gtd_task_get_list (before_task))
+ header = create_bold_label_for_task_row (row);
+ }
+
+ gtk_list_box_row_set_header (row, header);
+}
+
+static gboolean
+filter_func (GObject *item,
+ gpointer user_data)
+{
+ g_autofree gchar *normalized_entry_text = NULL;
+ g_autofree gchar *normalized_task_name = NULL;
+ GtkEntry *entry;
+ GtdTask *task;
+
+ task = (GtdTask*) item;
+ entry = (GtkEntry*) user_data;
+
+ normalized_entry_text = gtd_normalize_casefold_and_unaccent (gtk_editable_get_text (GTK_EDITABLE (entry)));
+ normalized_task_name = gtd_normalize_casefold_and_unaccent (gtd_task_get_title (task));
+
+ return strstr (normalized_task_name, normalized_entry_text) != NULL;
+}
+
+static GtkWidget*
+create_task_cb (gpointer item,
+ gpointer user_data)
+{
+ GtkWidget *row;
+ GtkWidget *box;
+ GtdTask *task;
+
+ task = item;
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 16);
+
+ gtk_box_append (GTK_BOX (box), create_label_for_string (gtd_task_get_title (task)));
+
+ row = gtk_list_box_row_new ();
+ gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), box);
+
+ g_object_set_data (G_OBJECT (row), "task", task);
+
+ return row;
+}
+
+static gint
+sort_func (GObject *a,
+ GObject *b,
+ gpointer user_data)
+{
+ GtkCheckButton *check;
+ GtdTask *task_a;
+ GtdTask *task_b;
+
+ check = (GtkCheckButton*) user_data;
+
+ if (gtk_check_button_get_active (check))
+ {
+ task_a = GTD_TASK (a);
+ task_b = GTD_TASK (b);
+ }
+ else
+ {
+ task_a = GTD_TASK (b);
+ task_b = GTD_TASK (a);
+ }
+
+ if (gtd_task_get_list (task_a) == gtd_task_get_list (task_b))
+ {
+ return gtd_task_compare (task_b, task_a);
+ }
+ else
+ {
+ GtdTaskList *list_a = gtd_task_get_list (task_a);
+ GtdTaskList *list_b = gtd_task_get_list (task_b);
+
+ return g_strcmp0 (gtd_task_list_get_name (list_b), gtd_task_list_get_name (list_a));
+ }
+}
+
+static void
+on_check_active_changed_cb (GtkCheckButton *check,
+ GParamSpec *pspec,
+ GtdListModelSort *sort)
+{
+ gtd_list_model_sort_invalidate (sort);
+}
+
+static void
+on_remove_button_clicked_cb (GtkButton *button,
+ DummyProvider *provider)
+{
+ dummy_provider_randomly_remove_task (provider);
+}
+
+static void
+on_search_text_changed_cb (GtkEntry *entry,
+ GParamSpec *pspec,
+ GtdListModelFilter *filter)
+{
+ gtd_list_model_filter_invalidate (filter);
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ g_autoptr (GtdListModelFilter) filter = NULL;
+ g_autoptr (GtdListModelSort) sort = NULL;
+ g_autoptr (DummyProvider) dummy_provider = NULL;
+ GtdTaskModel *model = NULL;
+ GtkWidget *scrolledwindow = NULL;
+ GtkWidget *search_entry = NULL;
+ GtkWindow *window = NULL;
+ GtkWidget *listbox = NULL;
+ GtkWidget *button = NULL;
+ GtkWidget *check = NULL;
+ GtkWidget *hbox = NULL;
+ GtkWidget *vbox = NULL;
+
+ g_set_prgname ("test-filter-sort");
+ g_set_application_name ("Endeavour | Filter & Sort Test");
+
+ gtk_init ();
+ gtd_log_init ();
+
+ /* Create a DumbProvider and pre-populate it */
+ dummy_provider = dummy_provider_new ();
+ dummy_provider_generate_task_lists (dummy_provider);
+ dummy_provider_generate_task_lists (dummy_provider);
+ gtd_manager_add_provider (gtd_manager_get_default (), GTD_PROVIDER (dummy_provider));
+
+ /* Models */
+ model = _gtd_task_model_new (gtd_manager_get_default ());
+ filter = gtd_list_model_filter_new (G_LIST_MODEL (model));
+ sort = gtd_list_model_sort_new (G_LIST_MODEL (filter));
+
+ /* Listbox */
+ listbox = gtk_list_box_new ();
+ gtk_list_box_bind_model (GTK_LIST_BOX (listbox),
+ G_LIST_MODEL (sort),
+ create_task_cb,
+ NULL,
+ NULL);
+
+ gtk_list_box_set_header_func (GTK_LIST_BOX (listbox), header_func, NULL, NULL);
+
+ /* Scrolled window */
+ scrolledwindow = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
+ "propagate-natural-height", TRUE,
+ "max-content-height", 600,
+ "hscrollbar-policy", GTK_POLICY_NEVER,
+ NULL);
+ gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolledwindow), listbox);
+
+ /* Search entry */
+ search_entry = gtk_search_entry_new ();
+ gtd_list_model_filter_set_filter_func (filter, filter_func, search_entry, NULL);
+
+ g_signal_connect_object (search_entry, "notify::text", G_CALLBACK (on_search_text_changed_cb), filter, 0);
+
+ /* Reverse order checkbox */
+ check = gtk_check_button_new_with_label ("Reverse Order");
+ gtd_list_model_sort_set_sort_func (sort, sort_func, check, NULL);
+
+ g_signal_connect_object (check, "notify::active", G_CALLBACK (on_check_active_changed_cb), sort, 0);
+
+ /* Remove Tasks button */
+ button = gtk_button_new_with_label ("Randomly Remove Tasks");
+ g_signal_connect_object (button, "clicked", G_CALLBACK (on_remove_button_clicked_cb), dummy_provider, 0);
+
+ /* Horizontal box */
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+ gtk_box_append (GTK_BOX (hbox), search_entry);
+ gtk_box_append (GTK_BOX (hbox), check);
+ gtk_box_append (GTK_BOX (hbox), button);
+
+ /* Box */
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_box_append (GTK_BOX (vbox), hbox);
+ gtk_box_append (GTK_BOX (vbox), scrolledwindow);
+
+ /* Window */
+ window = GTK_WINDOW (gtk_window_new ());
+ gtk_window_set_default_size (window, 800, 600);
+ gtk_window_set_child (GTK_WINDOW (window), vbox);
+ gtk_window_present (window);
+
+ while (TRUE)
+ g_main_context_iteration (NULL, TRUE);
+
+ return 0;
+}
diff --git a/tests/interactive/test-star-widget.c b/tests/interactive/test-star-widget.c
new file mode 100644
index 0000000..cfb102c
--- /dev/null
+++ b/tests/interactive/test-star-widget.c
@@ -0,0 +1,53 @@
+/* test-star-widget.c
+ *
+ * Copyright 2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+
+#include <gtk/gtk.h>
+
+#include "gtd-log.h"
+#include "gtd-star-widget.h"
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ GtkWidget *start_widget = NULL;
+ GtkWindow *window = NULL;
+
+ g_set_prgname ("test-star-widget");
+ g_set_application_name ("Endeavour | Star Widget");
+
+ gtk_init ();
+ gtd_log_init ();
+
+ /* Box */
+ start_widget = gtd_star_widget_new ();
+
+ /* Window */
+ window = GTK_WINDOW (gtk_window_new ());
+ gtk_window_set_default_size (window, 200, 150);
+ gtk_window_set_child (window, start_widget);
+ gtk_window_present (window);
+
+ while (TRUE)
+ g_main_context_iteration (NULL, TRUE);
+
+ return 0;
+}
diff --git a/tests/interactive/test-task-model.c b/tests/interactive/test-task-model.c
new file mode 100644
index 0000000..aa61ba9
--- /dev/null
+++ b/tests/interactive/test-task-model.c
@@ -0,0 +1,171 @@
+/* test-task-model.c
+ *
+ * Copyright 2018-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <gtk/gtk.h>
+
+#include "models/gtd-task-model.h"
+#include "models/gtd-task-model-private.h"
+#include "dummy-provider.h"
+#include "gtd-log.h"
+#include "gtd-manager.h"
+#include "gtd-manager-protected.h"
+#include "gtd-provider.h"
+#include "gtd-task.h"
+#include "gtd-task-list.h"
+
+static GtkWidget*
+create_bold_label_for_task_row (GtkListBoxRow *row)
+{
+ g_autofree gchar *markup = NULL;
+ GtdTask *task;
+
+ task = g_object_get_data (G_OBJECT (row), "task");
+ markup = g_strdup_printf ("<big><b>%s</b></big>", gtd_task_list_get_name (gtd_task_get_list (task)));
+
+ return g_object_new (GTK_TYPE_LABEL,
+ "margin", 6,
+ "margin-top", 18,
+ "use-markup", TRUE,
+ "label", markup,
+ "xalign", 0.0,
+ NULL);
+}
+
+static void
+header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ GtkWidget *header = NULL;
+
+ if (!before)
+ {
+ header = create_bold_label_for_task_row (row);
+ }
+ else
+ {
+ GtdTask *before_task;
+ GtdTask *task;
+
+ before_task = g_object_get_data (G_OBJECT (before), "task");
+ task = g_object_get_data (G_OBJECT (row), "task");
+
+ if (gtd_task_get_list (task) != gtd_task_get_list (before_task))
+ header = create_bold_label_for_task_row (row);
+ }
+
+ gtk_list_box_row_set_header (row, header);
+}
+
+static GtkWidget*
+create_label_for_string (const gchar *string)
+{
+ return g_object_new (GTK_TYPE_LABEL,
+ "label", string,
+ "hexpand", TRUE,
+ "xalign", 0.0,
+ "margin", 6,
+ "margin-start", 18,
+ NULL);
+}
+
+static GtkWidget*
+create_task_cb (gpointer item,
+ gpointer user_data)
+{
+ GtkWidget *row;
+ GtkWidget *box;
+ GtdTask *task;
+
+ task = item;
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 16);
+
+ gtk_box_append (GTK_BOX (box), create_label_for_string (gtd_task_get_title (task)));
+
+ row = gtk_list_box_row_new ();
+ gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), box);
+
+ g_object_set_data (G_OBJECT (row), "task", task);
+
+ return row;
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ g_autoptr (DummyProvider) dummy_provider = NULL;
+ GtdTaskModel *model = NULL;
+ GtkWidget *scrolledwindow = NULL;
+ GtkWindow *window = NULL;
+ GtkWidget *listbox = NULL;
+
+ g_set_prgname ("test-task-model");
+ g_set_application_name ("Endeavour | Task Model Test");
+
+ gtk_init ();
+ gtd_log_init ();
+
+ /* Create a DumbProvider and pre-populate it */
+ dummy_provider = dummy_provider_new ();
+ dummy_provider_generate_task_lists (dummy_provider);
+
+ /* Inject a dumb fake provider */
+ gtd_manager_add_provider (gtd_manager_get_default (), GTD_PROVIDER (dummy_provider));
+
+ /* Now create the model - the initial providers must be there already */
+ model = _gtd_task_model_new (gtd_manager_get_default ());
+
+ /* Listbox */
+ listbox = gtk_list_box_new ();
+ gtk_list_box_bind_model (GTK_LIST_BOX (listbox),
+ G_LIST_MODEL (model),
+ create_task_cb,
+ NULL,
+ NULL);
+
+ gtk_list_box_set_header_func (GTK_LIST_BOX (listbox), header_func, NULL, NULL);
+
+ /* Scrolled window */
+ scrolledwindow = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
+ "propagate-natural-height", TRUE,
+ "max-content-height", 600,
+ "hscrollbar-policy", GTK_POLICY_NEVER,
+ NULL);
+ gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolledwindow), listbox);
+
+ /* Window */
+ window = GTK_WINDOW (gtk_window_new ());
+ gtk_window_set_default_size (window, 800, 600);
+ gtk_window_set_child (GTK_WINDOW (window), scrolledwindow);
+ gtk_window_present (window);
+
+ /* Now, generate more tasks and lists after injecting to the manager */
+ dummy_provider_generate_task_lists (dummy_provider);
+
+ /* Schedule a live removal of tasks */
+ dummy_provider_schedule_remove_task (dummy_provider);
+
+ while (TRUE)
+ g_main_context_iteration (NULL, TRUE);
+
+ return 0;
+}
diff --git a/tests/interactive/test-widget.c b/tests/interactive/test-widget.c
new file mode 100644
index 0000000..32c1d2d
--- /dev/null
+++ b/tests/interactive/test-widget.c
@@ -0,0 +1,130 @@
+/* test-widget.c
+ *
+ * Copyright 2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "gtd-widget.h"
+
+static const char *css =
+"translated {"
+" background-image: none;"
+" background-color: red;"
+"}\n"
+"scaled {"
+" background-image: none;"
+" background-color: green;"
+"}\n"
+"rotated {"
+" background-image: none;"
+" background-color: blue;"
+"}\n"
+;
+
+static const char *ui =
+"<interface>"
+" <object class='GtkWindow' id='window'>"
+" <property name='default-width'>400</property>"
+" <property name='default-height'>300</property>"
+" <child>"
+" <object class='GtdWidget'>"
+" <child>"
+" <object class='GtdWidget' id='translated'>"
+" <property name='css-name'>translated</property>"
+" <property name='translation-x'>30.0</property>"
+" <property name='translation-y'>15.0</property>"
+" <property name='translation-z'>0.0</property>"
+" <property name='valign'>start</property>"
+" <property name='width-request'>30</property>"
+" <property name='height-request'>30</property>"
+" </object>"
+" </child>"
+" <child>"
+" <object class='GtdWidget' id='scaled'>"
+" <property name='css-name'>scaled</property>"
+" <property name='scale-x'>0.75</property>"
+" <property name='scale-y'>1.25</property>"
+" <property name='scale-z'>1.0</property>"
+" <property name='valign'>center</property>"
+" <property name='width-request'>30</property>"
+" <property name='height-request'>30</property>"
+" </object>"
+" </child>"
+" <child>"
+" <object class='GtdWidget' id='rotated'>"
+" <property name='css-name'>rotated</property>"
+" <property name='rotation-x'>30.0</property>"
+" <property name='rotation-y'>-15.0</property>"
+" <property name='rotation-z'>0</property>"
+" <property name='valign'>end</property>"
+" <property name='width-request'>30</property>"
+" <property name='height-request'>30</property>"
+" </object>"
+" </child>"
+" </object>"
+" </child>"
+" </object>"
+"</interface>";
+
+static GtkWidget *
+create_ui (void)
+{
+ g_autoptr (GtkBuilder) builder = NULL;
+ g_autoptr (GError) error = NULL;
+ GtkWidget *win;
+
+ g_type_ensure (GTD_TYPE_WIDGET);
+
+ builder = gtk_builder_new ();
+ if (!gtk_builder_add_from_string (builder, ui, -1, &error))
+ {
+ g_warning ("%s", error->message);
+ return NULL;
+ }
+
+ win = (GtkWidget *)gtk_builder_get_object (builder, "window");
+ g_object_ref (win);
+
+ return win;
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ g_autoptr (GtkCssProvider) css_provider = NULL;
+ GtkWindow *window;
+
+ g_set_prgname ("test-colorbutton");
+ g_set_application_name ("Endeavour | Widget Test");
+
+ gtk_init ();
+
+ css_provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_data (css_provider, css, -1);
+ gtk_style_context_add_provider_for_display (gdk_display_get_default (),
+ GTK_STYLE_PROVIDER (css_provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+ window = GTK_WINDOW (create_ui ());
+ gtk_window_present (window);
+
+ while (TRUE)
+ g_main_context_iteration (NULL, TRUE);
+
+ return 0;
+}
diff --git a/tests/meson.build b/tests/meson.build
new file mode 100644
index 0000000..6ebebe3
--- /dev/null
+++ b/tests/meson.build
@@ -0,0 +1,101 @@
+#################
+# Tests Library #
+#################
+
+sources = files(
+ 'dummy-provider.c',
+)
+
+tests_incs = [
+ include_directories('../src'),
+ include_directories('.'),
+ incs,
+]
+
+libgtd_tests = static_library(
+ 'gtd_tests',
+ sources : sources,
+ include_directories : tests_incs,
+ dependencies : endeavour_deps,
+ c_args : cflags,
+)
+
+tests_libs = [
+ libgtd,
+ libgtd_tests,
+]
+
+
+################
+# Static tests #
+################
+
+static_test_env = [
+ 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
+ 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()),
+ 'G_DEBUG=gc-friendly',
+ 'GSETTINGS_BACKEND=memory',
+ 'GSETTINGS_SCHEMA_DIR=@0@'.format(join_paths(meson.project_build_root(),'data')),
+ 'PYTHONDONTWRITEBYTECODE=yes',
+ 'MALLOC_CHECK_=2',
+ 'MALLOC_PERTURB_=$((${RANDOM:-256} % 256))',
+]
+
+static_test_cflags = [
+ '-DTEST_DATA_DIR="@0@/data"'.format(meson.current_source_dir()),
+]
+
+static_tests = [
+ 'test-model-filter',
+ 'test-model-sort',
+ 'test-task-list',
+ 'test-task-model',
+]
+
+foreach static_test : static_tests
+
+ source = ['@0@.c'.format(static_test)]
+
+ static_test_program = executable(
+ static_test,
+ source,
+ c_args : static_test_cflags,
+ dependencies : endeavour_deps,
+ pie : true,
+ link_with : tests_libs,
+ include_directories : tests_incs,
+ )
+
+ test(static_test, static_test_program, env: static_test_env)
+endforeach
+
+
+
+#####################
+# Interactive tests #
+#####################
+
+interactive_tests = [
+ 'test-animation',
+ 'test-colorbutton',
+ 'test-filter-sort',
+ 'test-star-widget',
+ 'test-task-model',
+ 'test-widget',
+]
+
+foreach interactive_test : interactive_tests
+
+ interactive_test_name = 'interactive-@0@'.format(interactive_test)
+
+ source = ['interactive/@0@.c'.format(interactive_test)]
+
+ interactive_test_program = executable(
+ interactive_test_name,
+ source,
+ include_directories: tests_incs,
+ dependencies: endeavour_deps,
+ c_args: cflags,
+ link_with: tests_libs,
+ )
+endforeach
diff --git a/tests/test-model-filter.c b/tests/test-model-filter.c
new file mode 100644
index 0000000..025b9e3
--- /dev/null
+++ b/tests/test-model-filter.c
@@ -0,0 +1,208 @@
+#include "models/gtd-list-model-filter.h"
+#include <math.h>
+#include <string.h>
+
+#define TEST_TYPE_ITEM (test_item_get_type())
+
+struct _TestItem
+{
+ GObject p;
+ guint n;
+};
+
+G_DECLARE_FINAL_TYPE (TestItem, test_item, TEST, ITEM, GObject)
+G_DEFINE_TYPE (TestItem, test_item, G_TYPE_OBJECT)
+
+static void
+test_item_class_init (TestItemClass *klass)
+{
+}
+
+static void
+test_item_init (TestItem *self)
+{
+}
+
+static TestItem *
+test_item_new (guint n)
+{
+ TestItem *item;
+
+ item = g_object_new (TEST_TYPE_ITEM, NULL);
+ item->n = n;
+
+ return item;
+}
+
+static gboolean
+filter_func1 (GObject *object,
+ gpointer user_data)
+{
+ return (TEST_ITEM (object)->n & 1) == 0;
+}
+
+static gboolean
+filter_func2 (GObject *object,
+ gpointer user_data)
+{
+ return (TEST_ITEM (object)->n & 1) == 1;
+}
+
+static void
+test_basic (void)
+{
+ GListStore *model;
+ GtdListModelFilter *filter;
+ TestItem *item;
+ guint i;
+
+ model = g_list_store_new (TEST_TYPE_ITEM);
+ g_assert (model);
+
+ filter = gtd_list_model_filter_new (G_LIST_MODEL (model));
+ g_assert (filter);
+
+ /* Test requesting past boundary */
+ g_assert_null (g_list_model_get_item (G_LIST_MODEL (filter), 0));
+ g_assert_null (g_list_model_get_item (G_LIST_MODEL (filter), 1));
+
+ for (i = 0; i < 1000; i++)
+ {
+ g_autoptr (TestItem) val = test_item_new (i);
+
+ g_list_store_append (model, val);
+ }
+
+ /* Test requesting past boundary */
+ g_assert_null (g_list_model_get_item (G_LIST_MODEL (filter), 1000));
+
+ g_assert_cmpint (1000, ==, g_list_model_get_n_items (G_LIST_MODEL (model)));
+ g_assert_cmpint (1000, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+
+ g_assert_cmpint (1000, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+ gtd_list_model_filter_set_filter_func (filter, filter_func1, NULL, NULL);
+ g_assert_cmpint (500, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+
+ for (i = 0; i < 500; i++)
+ {
+ g_autoptr (TestItem) ele = g_list_model_get_item (G_LIST_MODEL (filter), i);
+
+ g_assert_nonnull (ele);
+ g_assert (TEST_IS_ITEM (ele));
+ g_assert (filter_func1 (G_OBJECT (ele), NULL));
+
+ /* filter_func1 filters odd numbers out. The even numbers that
+ * weren't filtered should keep their order.
+ */
+ g_assert (ele->n == i * 2);
+ }
+
+ for (i = 0; i < 1000; i += 2)
+ g_list_store_remove (model, 998 - i);
+
+ g_assert_cmpint (500, ==, g_list_model_get_n_items (G_LIST_MODEL (model)));
+ g_assert_cmpint (0, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+
+ gtd_list_model_filter_set_filter_func (filter, NULL, NULL, NULL);
+ g_assert_cmpint (500, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+
+ gtd_list_model_filter_set_filter_func (filter, filter_func2, NULL, NULL);
+ g_assert_cmpint (500, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+
+ {
+ g_autoptr (TestItem) freeme = test_item_new (1001);
+ g_list_store_append (model, freeme);
+ }
+
+ for (i = 0; i < 500; i++)
+ g_list_store_remove (model, 0);
+
+ g_assert_cmpint (1, ==, g_list_model_get_n_items (G_LIST_MODEL (model)));
+ g_assert_cmpint (1, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+
+ gtd_list_model_filter_set_filter_func (filter, NULL, NULL, NULL);
+ g_assert_cmpint (1, ==, g_list_model_get_n_items (G_LIST_MODEL (model)));
+ g_assert_cmpint (1, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+
+ item = g_list_model_get_item (G_LIST_MODEL (filter), 0);
+ g_assert (item);
+ g_assert (TEST_IS_ITEM (item));
+ g_assert_cmpint (item->n, ==, 1001);
+ g_clear_object (&item);
+
+ g_clear_object (&model);
+ g_clear_object (&filter);
+}
+
+static guint last_n_added = 0;
+static guint last_n_removed = 0;
+static guint last_changed_position = 0;
+
+static void
+model_items_changed_cb (GtdListModelFilter *filter,
+ guint position,
+ guint n_removed,
+ guint n_added,
+ GListModel *model)
+{
+ last_n_added = n_added;
+ last_n_removed = n_removed;
+ last_changed_position = position;
+}
+
+
+static void
+filter_items_changed_cb (GtdListModelFilter *filter,
+ guint position,
+ guint n_removed,
+ guint n_added,
+ GListModel *model)
+{
+ g_assert_cmpint (n_added, ==, last_n_added);
+ g_assert_cmpint (n_removed, ==, last_n_removed);
+ g_assert_cmpint (position, ==, last_changed_position);
+
+}
+
+static void
+test_items_changed (void)
+{
+ GtdListModelFilter *filter;
+ GListStore *model;
+ guint i;
+
+ model = g_list_store_new (TEST_TYPE_ITEM);
+ g_assert (model);
+
+ g_signal_connect (model, "items-changed", G_CALLBACK (model_items_changed_cb), NULL);
+
+ filter = gtd_list_model_filter_new (G_LIST_MODEL (model));
+ g_assert (filter);
+
+ g_signal_connect_after (filter, "items-changed", G_CALLBACK (filter_items_changed_cb), model);
+
+ for (i = 0; i < 100; i++)
+ {
+ g_autoptr (TestItem) val = test_item_new (i);
+ g_list_store_append (model, val);
+ }
+
+ g_assert_cmpint (100, ==, g_list_model_get_n_items (G_LIST_MODEL (model)));
+ g_assert_cmpint (100, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+
+ for (i = 0; i < 100; i++)
+ g_list_store_remove (model, 0);
+
+ g_clear_object (&model);
+ g_clear_object (&filter);
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+ g_test_add_func ("/models/model-filter/basic", test_basic);
+ g_test_add_func ("/models/model-filter/items-changed", test_items_changed);
+ return g_test_run ();
+}
diff --git a/tests/test-model-sort.c b/tests/test-model-sort.c
new file mode 100644
index 0000000..b54b29f
--- /dev/null
+++ b/tests/test-model-sort.c
@@ -0,0 +1,143 @@
+/* test-model-sort.c
+ *
+ * Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "models/gtd-list-model-sort.h"
+
+#include <math.h>
+#include <string.h>
+
+#define TEST_TYPE_ITEM (test_item_get_type())
+
+struct _TestItem
+{
+ GObject p;
+ guint n;
+};
+
+G_DECLARE_FINAL_TYPE (TestItem, test_item, TEST, ITEM, GObject)
+G_DEFINE_TYPE (TestItem, test_item, G_TYPE_OBJECT)
+
+static void
+test_item_class_init (TestItemClass *klass)
+{
+}
+
+static void
+test_item_init (TestItem *self)
+{
+}
+
+static TestItem *
+test_item_new (guint n)
+{
+ TestItem *item;
+
+ item = g_object_new (TEST_TYPE_ITEM, NULL);
+ item->n = n;
+
+ return item;
+}
+
+static gint
+sort_func1 (GObject *a,
+ GObject *b,
+ gpointer user_data)
+{
+ return TEST_ITEM (a)->n - TEST_ITEM (b)->n;
+}
+
+static gint
+sort_func2 (GObject *a,
+ GObject *b,
+ gpointer user_data)
+{
+ return (TEST_ITEM (a)->n & 1) - (TEST_ITEM (b)->n & 1);
+}
+
+static void
+test_basic (void)
+{
+ GtdListModelSort *sort;
+ GListStore *model;
+ guint i;
+
+ model = g_list_store_new (TEST_TYPE_ITEM);
+ g_assert (model);
+
+ sort = gtd_list_model_sort_new (G_LIST_MODEL (model));
+ g_assert (sort);
+
+ /* Test requesting past boundary */
+ g_assert_null (g_list_model_get_item (G_LIST_MODEL (sort), 0));
+ g_assert_null (g_list_model_get_item (G_LIST_MODEL (sort), 1));
+
+ for (i = 0; i < 1000; i++)
+ {
+ g_autoptr (TestItem) val = test_item_new (1000 - i);
+ g_list_store_append (model, val);
+ }
+
+ /* Test requesting past boundary */
+ g_assert_null (g_list_model_get_item (G_LIST_MODEL (sort), 1000));
+
+ /* Ascendent sorting */
+ gtd_list_model_sort_set_sort_func (sort, sort_func1, NULL, NULL);
+
+ g_assert_cmpint (1000, ==, g_list_model_get_n_items (G_LIST_MODEL (model)));
+ g_assert_cmpint (1000, ==, g_list_model_get_n_items (G_LIST_MODEL (sort)));
+
+ for (i = 1; i < 1000; i++)
+ {
+ g_autoptr (TestItem) current = g_list_model_get_item (G_LIST_MODEL (sort), i);
+ g_autoptr (TestItem) previous = g_list_model_get_item (G_LIST_MODEL (sort), i - 1);
+
+ g_assert_cmpint (previous->n, <, current->n);
+ }
+
+ /* Odd/Even sorting */
+ gtd_list_model_sort_set_sort_func (sort, sort_func2, NULL, NULL);
+
+ g_assert_cmpint (1000, ==, g_list_model_get_n_items (G_LIST_MODEL (model)));
+ g_assert_cmpint (1000, ==, g_list_model_get_n_items (G_LIST_MODEL (sort)));
+
+ for (i = 0; i < 1000; i++)
+ {
+ g_autoptr (TestItem) current = g_list_model_get_item (G_LIST_MODEL (sort), i);
+
+ if (i < 500)
+ g_assert_cmpint (current->n % 2, ==, 0);
+ else
+ g_assert_cmpint (current->n % 2, ==, 1);
+ }
+
+ g_clear_object (&model);
+ g_clear_object (&sort);
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/models/model-sort/basic", test_basic);
+
+ return g_test_run ();
+}
diff --git a/tests/test-task-list.c b/tests/test-task-list.c
new file mode 100644
index 0000000..cee6adb
--- /dev/null
+++ b/tests/test-task-list.c
@@ -0,0 +1,99 @@
+/* test-task-model.c
+ *
+ * Copyright 2018-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "endeavour.h"
+
+#include "core/gtd-log.h"
+#include "gtd-task-list.h"
+#include "gtd-manager-protected.h"
+#include "dummy-provider.h"
+
+static void
+test_move (void)
+{
+ g_autoptr (DummyProvider) dummy_provider = NULL;
+ g_autoptr (GtdTask) first_task = NULL;
+ g_autoptr (GtdTask) last_task = NULL;
+ g_autoptr (GList) lists = NULL;
+ GtdTaskList *list = NULL;
+ GListModel *model;
+ guint n_tasks;
+
+ dummy_provider = dummy_provider_new ();
+ n_tasks = dummy_provider_generate_task_list (dummy_provider);
+ g_assert_cmpuint (n_tasks, ==, 10);
+
+ gtd_manager_add_provider (gtd_manager_get_default (), GTD_PROVIDER (dummy_provider));
+
+ lists = gtd_provider_get_task_lists (GTD_PROVIDER (dummy_provider));
+ g_assert_nonnull (lists);
+ g_assert_cmpint (g_list_length (lists), ==, 1);
+
+ list = lists->data;
+ model = G_LIST_MODEL (list);
+ g_assert_nonnull (list);
+ g_assert_cmpstr (gtd_task_list_get_name (list), ==, "List");
+
+ first_task = g_list_model_get_item (model, 0);
+ g_assert_nonnull (first_task);
+ g_assert_cmpint (gtd_task_get_position (first_task), ==, 0);
+ g_assert_true (g_list_model_get_item (model, 0) == first_task);
+
+ last_task = g_list_model_get_item (model, 9);
+ g_assert_nonnull (last_task);
+ g_assert_cmpint (gtd_task_get_position (last_task), ==, 9);
+ g_assert_true (g_list_model_get_item (model, 9) == last_task);
+
+ /* Move the task to 0 */
+ gtd_task_list_move_task_to_position (list, last_task, 0);
+
+ g_assert_cmpint (gtd_task_get_position (last_task), ==, 0);
+ g_assert_cmpint (gtd_task_get_position (first_task), ==, 1);
+ g_assert_true (g_list_model_get_item (model, 0) == last_task);
+ g_assert_true (g_list_model_get_item (model, 1) == first_task);
+
+ /* Move the task to 1 */
+ gtd_task_list_move_task_to_position (list, last_task, 1);
+
+ g_assert_cmpint (gtd_task_get_position (last_task), ==, 1);
+ g_assert_cmpint (gtd_task_get_position (first_task), ==, 0);
+ g_assert_true (g_list_model_get_item (model, 0) == first_task);
+ g_assert_true (g_list_model_get_item (model, 1) == last_task);
+
+ /* Move the task to 9 */
+ gtd_task_list_move_task_to_position (list, last_task, 9);
+
+ g_assert_cmpint (gtd_task_get_position (last_task), ==, 9);
+ g_assert_true (g_list_model_get_item (model, 9) == last_task);
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+
+ if (g_getenv ("G_MESSAGES_DEBUG"))
+ gtd_log_init ();
+
+ g_test_add_func ("/task-list/move", test_move);
+
+ return g_test_run ();
+}
diff --git a/tests/test-task-model.c b/tests/test-task-model.c
new file mode 100644
index 0000000..04da5a6
--- /dev/null
+++ b/tests/test-task-model.c
@@ -0,0 +1,67 @@
+/* test-task-model.c
+ *
+ * Copyright 2018-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "endeavour.h"
+
+#include "core/gtd-log.h"
+#include "models/gtd-task-model.h"
+#include "models/gtd-task-model-private.h"
+#include "gtd-manager-protected.h"
+#include "dummy-provider.h"
+
+static void
+test_basic (void)
+{
+ g_autoptr (DummyProvider) dummy_provider = NULL;
+ GtdTaskModel *model = NULL;
+ guint n_tasks;
+
+ /* Create a DumbProvider and pre-populate it */
+ dummy_provider = dummy_provider_new ();
+ n_tasks = dummy_provider_generate_task_lists (dummy_provider);
+ gtd_manager_add_provider (gtd_manager_get_default (), GTD_PROVIDER (dummy_provider));
+
+ model = _gtd_task_model_new (gtd_manager_get_default ());
+ g_assert_cmpint (g_list_model_get_n_items (G_LIST_MODEL (model)), ==, n_tasks);
+
+ /* Generate more */
+ n_tasks = dummy_provider_generate_task_lists (dummy_provider);
+ g_assert_cmpint (g_list_model_get_n_items (G_LIST_MODEL (model)), ==, n_tasks);
+
+ while (n_tasks > 0)
+ {
+ n_tasks = dummy_provider_randomly_remove_task (dummy_provider);
+ g_assert_cmpint (g_list_model_get_n_items (G_LIST_MODEL (model)), ==, n_tasks);
+ }
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+
+ if (g_getenv ("G_MESSAGES_DEBUG"))
+ gtd_log_init ();
+
+ g_test_add_func ("/models/task-model/basic", test_basic);
+
+ return g_test_run ();
+}