[gnome-builder] plugins/testui: port to GTK 4



commit a503edbf4fede3524385de40233c1d05995e124f
Author: Christian Hergert <chergert redhat com>
Date:   Mon Jul 11 23:23:55 2022 -0700

    plugins/testui: port to GTK 4
    
    This also breaks things out into a new panel. The position may move still
    but this at least gets it into shape to be used.
    
    There are still issues to be ironed out, like why our PTY breaks after
    running the first test. We also want to add a context menu so that we
    can allow running under the debugger.

 src/plugins/testui/gbp-test-path.c                 | 181 ---------
 src/plugins/testui/gbp-test-tree-addin.c           | 452 ---------------------
 src/plugins/testui/gbp-testui-item.c               | 274 +++++++++++++
 src/plugins/testui/gbp-testui-item.h               |  38 ++
 ...st-output-panel.c => gbp-testui-output-panel.c} | 104 +++--
 ...st-output-panel.h => gbp-testui-output-panel.h} |  13 +-
 ...-output-panel.ui => gbp-testui-output-panel.ui} |  57 +--
 src/plugins/testui/gbp-testui-panel.c              | 156 +++++++
 .../testui/{gbp-test-path.h => gbp-testui-panel.h} |  15 +-
 src/plugins/testui/gbp-testui-panel.ui             |  61 +++
 src/plugins/testui/gbp-testui-preferences-addin.c  | 106 +++++
 src/plugins/testui/gbp-testui-preferences-addin.h  |  31 ++
 src/plugins/testui/gbp-testui-workspace-addin.c    | 144 +++++++
 ...t-tree-addin.h => gbp-testui-workspace-addin.h} |   8 +-
 src/plugins/testui/gtk/keybindings.json            |   1 +
 src/plugins/testui/gtk/menus.ui                    |  14 +
 src/plugins/testui/meson.build                     |   8 +-
 src/plugins/testui/testui-plugin.c                 |  13 +-
 src/plugins/testui/testui.gresource.xml            |   5 +-
 src/plugins/testui/testui.plugin                   |   8 +-
 20 files changed, 951 insertions(+), 738 deletions(-)
---
diff --git a/src/plugins/testui/gbp-testui-item.c b/src/plugins/testui/gbp-testui-item.c
new file mode 100644
index 000000000..6805e030d
--- /dev/null
+++ b/src/plugins/testui/gbp-testui-item.c
@@ -0,0 +1,274 @@
+/* gbp-testui-item.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat 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 "gbp-testui-item"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include <libide-foundry.h>
+
+#include "gbp-testui-item.h"
+
+struct _GbpTestuiItem
+{
+  GObject  parent_instance;
+  gpointer instance;
+};
+
+enum {
+  PROP_0,
+  PROP_ICON_NAME,
+  PROP_EXPANDED_ICON_NAME,
+  PROP_INSTANCE,
+  PROP_TITLE,
+  N_PROPS
+};
+
+G_DEFINE_FINAL_TYPE (GbpTestuiItem, gbp_testui_item, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static const char *
+gbp_testui_item_get_icon_name (GbpTestuiItem *self)
+{
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_TESTUI_ITEM (self));
+
+  if (IDE_IS_TEST_MANAGER (self->instance))
+    return "folder-symbolic";
+
+  if (IDE_IS_TEST (self->instance))
+    return ide_test_get_icon_name (self->instance);
+
+  return NULL;
+}
+
+static const char *
+gbp_testui_item_get_expanded_icon_name (GbpTestuiItem *self)
+{
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_TESTUI_ITEM (self));
+
+  if (IDE_IS_TEST_MANAGER (self->instance))
+    return "folder-open-symbolic";
+
+  return gbp_testui_item_get_icon_name (self);
+}
+
+static const char *
+gbp_testui_item_get_title (GbpTestuiItem *self)
+{
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_TESTUI_ITEM (self));
+
+  if (IDE_IS_TEST_MANAGER (self->instance))
+    return _("Unit Tests");
+
+  if (IDE_IS_TEST (self->instance))
+    return ide_test_get_title (self->instance);
+
+  return NULL;
+}
+
+static void
+gbp_testui_item_notify_icon_name_cb (GbpTestuiItem *self,
+                                     GParamSpec    *pspec,
+                                     IdeTest       *test)
+{
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_TESTUI_ITEM (self));
+  g_assert (IDE_IS_TEST (test));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ICON_NAME]);
+}
+
+static void
+gbp_testui_item_set_instance (GbpTestuiItem *self,
+                              gpointer       instance)
+{
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_TESTUI_ITEM (self));
+  g_assert (G_IS_OBJECT (instance));
+
+  self->instance = g_object_ref (instance);
+
+  if (IDE_IS_TEST (instance))
+    g_signal_connect_object (instance,
+                             "notify::icon-name",
+                             G_CALLBACK (gbp_testui_item_notify_icon_name_cb),
+                             self,
+                             G_CONNECT_SWAPPED);
+}
+
+static void
+gbp_testui_item_dispose (GObject *object)
+{
+  GbpTestuiItem *self = (GbpTestuiItem *)object;
+
+  g_clear_object (&self->instance);
+
+  G_OBJECT_CLASS (gbp_testui_item_parent_class)->dispose (object);
+}
+
+static void
+gbp_testui_item_get_property (GObject    *object,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  GbpTestuiItem *self = GBP_TESTUI_ITEM (object);
+
+  switch (prop_id)
+    {
+    case PROP_INSTANCE:
+      g_value_set_object (value, self->instance);
+      break;
+
+    case PROP_ICON_NAME:
+      g_value_set_string (value, gbp_testui_item_get_icon_name (self));
+      break;
+
+    case PROP_EXPANDED_ICON_NAME:
+      g_value_set_string (value, gbp_testui_item_get_expanded_icon_name (self));
+      break;
+
+    case PROP_TITLE:
+      g_value_set_string (value, gbp_testui_item_get_title (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_testui_item_set_property (GObject      *object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+  GbpTestuiItem *self = GBP_TESTUI_ITEM (object);
+
+  switch (prop_id)
+    {
+    case PROP_INSTANCE:
+      gbp_testui_item_set_instance (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_testui_item_class_init (GbpTestuiItemClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = gbp_testui_item_dispose;
+  object_class->get_property = gbp_testui_item_get_property;
+  object_class->set_property = gbp_testui_item_set_property;
+
+  properties [PROP_INSTANCE] =
+    g_param_spec_object ("instance", NULL, NULL,
+                         G_TYPE_OBJECT,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_EXPANDED_ICON_NAME] =
+    g_param_spec_string ("expanded-icon-name", NULL, NULL,
+                         NULL,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_ICON_NAME] =
+    g_param_spec_string ("icon-name", NULL, NULL,
+                         NULL,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TITLE] =
+    g_param_spec_string ("title", NULL, NULL,
+                         NULL,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gbp_testui_item_init (GbpTestuiItem *self)
+{
+}
+
+GbpTestuiItem *
+gbp_testui_item_new (gpointer instance)
+{
+  g_return_val_if_fail (instance != NULL, NULL);
+  g_return_val_if_fail (G_IS_OBJECT (instance), NULL);
+
+  g_assert (IDE_IS_TEST (instance) ||
+            IDE_IS_TEST_MANAGER (instance));
+
+  return g_object_new (GBP_TYPE_TESTUI_ITEM,
+                       "instance", instance,
+                       NULL);
+}
+
+gpointer
+gbp_testui_item_map_func (gpointer item,
+                          gpointer user_data)
+{
+  gpointer ret = gbp_testui_item_new (item);
+  g_object_unref (item);
+  return ret;
+}
+
+GListModel *
+gbp_testui_item_create_child_model (gpointer item,
+                                    gpointer user_data)
+{
+  GbpTestuiItem *self = item;
+
+  g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
+  g_return_val_if_fail (GBP_IS_TESTUI_ITEM (self), NULL);
+
+  if (IDE_IS_TEST_MANAGER (self->instance))
+    {
+      GListModel *tests = ide_test_manager_list_tests (self->instance);
+      GtkMapListModel *map = gtk_map_list_model_new (g_object_ref (tests),
+                                                     gbp_testui_item_map_func,
+                                                     NULL, NULL);
+      return G_LIST_MODEL (ide_cached_list_model_new (G_LIST_MODEL (map)));
+    }
+
+  /* TODO: We could insert information about the test run here, like
+   *       a list of passed or failed tests if we have a protocol to
+   *       extract that from the unit test.
+   */
+
+  return NULL;
+}
+
+gpointer
+gbp_testui_item_get_instance (GbpTestuiItem *self)
+{
+  g_return_val_if_fail (GBP_IS_TESTUI_ITEM (self), NULL);
+
+  return self->instance;
+}
diff --git a/src/plugins/testui/gbp-testui-item.h b/src/plugins/testui/gbp-testui-item.h
new file mode 100644
index 000000000..3573b42ac
--- /dev/null
+++ b/src/plugins/testui/gbp-testui-item.h
@@ -0,0 +1,38 @@
+/* gbp-testui-item.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat 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 <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_TESTUI_ITEM (gbp_testui_item_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpTestuiItem, gbp_testui_item, GBP, TESTUI_ITEM, GObject)
+
+GbpTestuiItem *gbp_testui_item_new                (gpointer       instance);
+gpointer       gbp_testui_item_get_instance       (GbpTestuiItem *self);
+GListModel    *gbp_testui_item_create_child_model (gpointer       item,
+                                                   gpointer       user_data);
+gpointer       gbp_testui_item_map_func           (gpointer       item,
+                                                   gpointer       user_data);
+
+G_END_DECLS
diff --git a/src/plugins/testui/gbp-test-output-panel.c b/src/plugins/testui/gbp-testui-output-panel.c
similarity index 65%
rename from src/plugins/testui/gbp-test-output-panel.c
rename to src/plugins/testui/gbp-testui-output-panel.c
index 537bf989a..18b7114b6 100644
--- a/src/plugins/testui/gbp-test-output-panel.c
+++ b/src/plugins/testui/gbp-testui-output-panel.c
@@ -1,4 +1,4 @@
-/* gbp-test-output-panel.c
+/* gbp-testui-output-panel.c
  *
  * Copyright 2019 Christian Hergert <chergert redhat com>
  *
@@ -18,55 +18,41 @@
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
-#define G_LOG_DOMAIN "gbp-test-output-panel"
+#define G_LOG_DOMAIN "gbp-testui-output-panel"
 
 #include "config.h"
 
 #include <glib/gi18n.h>
 #include <libide-terminal.h>
 
-#include "gbp-test-output-panel.h"
+#include "gbp-testui-output-panel.h"
 
-struct _GbpTestOutputPanel
+struct _GbpTestuiOutputPanel
 {
   IdePane       parent_instance;
   IdeTerminal  *terminal;
 };
 
-G_DEFINE_FINAL_TYPE (GbpTestOutputPanel, gbp_test_output_panel, IDE_TYPE_PANE)
+G_DEFINE_FINAL_TYPE (GbpTestuiOutputPanel, gbp_testui_output_panel, IDE_TYPE_PANE)
 
 static void
-gbp_test_output_panel_class_init (GbpTestOutputPanelClass *klass)
+gbp_testui_output_panel_class_init (GbpTestuiOutputPanelClass *klass)
 {
   GtkWidgetClass *widget_class = (GtkWidgetClass*)klass;
 
-  gtk_widget_class_set_template_from_resource (widget_class, "/plugins/testui/gbp-test-output-panel.ui");
-  gtk_widget_class_bind_template_child (widget_class, GbpTestOutputPanel, terminal);
+  gtk_widget_class_set_template_from_resource (widget_class, "/plugins/testui/gbp-testui-output-panel.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpTestuiOutputPanel, terminal);
 }
 
 static void
-gbp_testui_output_panel_save_in_file (GSimpleAction *action,
-                                      GVariant      *param,
-                                      gpointer       user_data)
+gbp_testui_output_panel_save_in_file_cb (GbpTestuiOutputPanel   *self,
+                                       int                   res,
+                                       GtkFileChooserNative *native)
 {
-  GbpTestOutputPanel *self = user_data;
-  g_autoptr(GtkFileChooserNative) native = NULL;
-  GtkWidget *window;
-  gint res;
-
   IDE_ENTRY;
 
-  g_assert (G_IS_SIMPLE_ACTION (action));
-  g_assert (GBP_IS_TEST_OUTPUT_PANEL (self));
-
-  window = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_WINDOW);
-  native = gtk_file_chooser_native_new (_("Save File"),
-                                        GTK_WINDOW (window),
-                                        GTK_FILE_CHOOSER_ACTION_SAVE,
-                                        _("_Save"),
-                                        _("_Cancel"));
-
-  res = gtk_native_dialog_run (GTK_NATIVE_DIALOG (native));
+  g_assert (GBP_IS_TESTUI_OUTPUT_PANEL (self));
+  g_assert (GTK_IS_FILE_CHOOSER_NATIVE (native));
 
   if (res == GTK_RESPONSE_ACCEPT)
     {
@@ -101,6 +87,40 @@ gbp_testui_output_panel_save_in_file (GSimpleAction *action,
         }
     }
 
+  gtk_native_dialog_destroy (GTK_NATIVE_DIALOG (native));
+
+  IDE_EXIT;
+}
+
+static void
+gbp_testui_output_panel_save_in_file (GSimpleAction *action,
+                                      GVariant      *param,
+                                      gpointer       user_data)
+{
+  GbpTestuiOutputPanel *self = user_data;
+  g_autoptr(GtkFileChooserNative) native = NULL;
+  GtkWidget *window;
+
+  IDE_ENTRY;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (GBP_IS_TESTUI_OUTPUT_PANEL (self));
+
+  window = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_WINDOW);
+  native = gtk_file_chooser_native_new (_("Save File"),
+                                        GTK_WINDOW (window),
+                                        GTK_FILE_CHOOSER_ACTION_SAVE,
+                                        _("_Save"),
+                                        _("_Cancel"));
+
+  g_signal_connect_object (native,
+                           "response",
+                           G_CALLBACK (gbp_testui_output_panel_save_in_file_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  gtk_native_dialog_show (GTK_NATIVE_DIALOG (native));
+
   IDE_EXIT;
 
 }
@@ -110,16 +130,16 @@ gbp_testui_output_panel_clear_activate (GSimpleAction *action,
                                         GVariant      *param,
                                         gpointer       user_data)
 {
-  GbpTestOutputPanel *self = user_data;
+  GbpTestuiOutputPanel *self = user_data;
 
-  g_assert (GBP_IS_TEST_OUTPUT_PANEL (self));
+  g_assert (GBP_IS_TESTUI_OUTPUT_PANEL (self));
   g_assert (G_IS_SIMPLE_ACTION (action));
 
   vte_terminal_reset (VTE_TERMINAL (self->terminal), TRUE, TRUE);
 }
 
 static void
-gbp_test_output_panel_init (GbpTestOutputPanel *self)
+gbp_testui_output_panel_init (GbpTestuiOutputPanel *self)
 {
   static const GActionEntry entries[] = {
     { "clear", gbp_testui_output_panel_clear_activate },
@@ -129,23 +149,31 @@ gbp_test_output_panel_init (GbpTestOutputPanel *self)
 
   gtk_widget_init_template (GTK_WIDGET(self));
 
-  dzl_dock_widget_set_title (DZL_DOCK_WIDGET (self), _("Unit Test Output"));
-  dzl_dock_widget_set_icon_name (DZL_DOCK_WIDGET (self), "builder-unit-tests-symbolic");
+  panel_widget_set_title (PANEL_WIDGET (self), _("Unit Test Output"));
+  panel_widget_set_icon_name (PANEL_WIDGET (self), "builder-unit-tests-symbolic");
 
   actions = g_simple_action_group_new ();
   g_action_map_add_action_entries (G_ACTION_MAP (actions), entries, G_N_ELEMENTS (entries), self);
-  gtk_widget_insert_action_group (GTK_WIDGET (self), "test-output", G_ACTION_GROUP (actions));
+  gtk_widget_insert_action_group (GTK_WIDGET (self), "testui-output", G_ACTION_GROUP (actions));
 }
 
-GtkWidget *
-gbp_test_output_panel_new (VtePty *pty)
+GbpTestuiOutputPanel *
+gbp_testui_output_panel_new (VtePty *pty)
 {
-  GbpTestOutputPanel *self;
+  GbpTestuiOutputPanel *self;
 
   g_return_val_if_fail (VTE_IS_PTY (pty), NULL);
 
-  self = g_object_new (GBP_TYPE_TEST_OUTPUT_PANEL, NULL);
+  self = g_object_new (GBP_TYPE_TESTUI_OUTPUT_PANEL, NULL);
   vte_terminal_set_pty (VTE_TERMINAL (self->terminal), pty);
 
-  return GTK_WIDGET (self);
+  return self;
+}
+
+void
+gbp_testui_output_panel_reset (GbpTestuiOutputPanel *self)
+{
+  g_return_if_fail (GBP_IS_TESTUI_OUTPUT_PANEL (self));
+
+  vte_terminal_reset (VTE_TERMINAL (self->terminal), TRUE, TRUE);
 }
diff --git a/src/plugins/testui/gbp-test-output-panel.h b/src/plugins/testui/gbp-testui-output-panel.h
similarity index 69%
rename from src/plugins/testui/gbp-test-output-panel.h
rename to src/plugins/testui/gbp-testui-output-panel.h
index e22147bdb..bf164de3b 100644
--- a/src/plugins/testui/gbp-test-output-panel.h
+++ b/src/plugins/testui/gbp-testui-output-panel.h
@@ -1,4 +1,4 @@
-/* gbp-test-output-panel.h
+/* gbp-testui-output-panel.h
  *
  * Copyright 2019 Christian Hergert <chergert redhat com>
  *
@@ -20,16 +20,17 @@
 
 #pragma once
 
-#include <libide-gui.h>
-#include <dazzle.h>
 #include <vte/vte.h>
 
+#include <libide-gui.h>
+
 G_BEGIN_DECLS
 
-#define GBP_TYPE_TEST_OUTPUT_PANEL (gbp_test_output_panel_get_type())
+#define GBP_TYPE_TESTUI_OUTPUT_PANEL (gbp_testui_output_panel_get_type())
 
-G_DECLARE_FINAL_TYPE (GbpTestOutputPanel, gbp_test_output_panel, GBP, TEST_OUTPUT_PANEL, IdePane)
+G_DECLARE_FINAL_TYPE (GbpTestuiOutputPanel, gbp_testui_output_panel, GBP, TESTUI_OUTPUT_PANEL, IdePane)
 
-GtkWidget *gbp_test_output_panel_new (VtePty *pty);
+GbpTestuiOutputPanel *gbp_testui_output_panel_new   (VtePty               *pty);
+void                  gbp_testui_output_panel_reset (GbpTestuiOutputPanel *self);
 
 G_END_DECLS
diff --git a/src/plugins/testui/gbp-test-output-panel.ui b/src/plugins/testui/gbp-testui-output-panel.ui
similarity index 50%
rename from src/plugins/testui/gbp-test-output-panel.ui
rename to src/plugins/testui/gbp-testui-output-panel.ui
index 5ec96ba4a..026a674ce 100644
--- a/src/plugins/testui/gbp-test-output-panel.ui
+++ b/src/plugins/testui/gbp-testui-output-panel.ui
@@ -1,19 +1,25 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <template class="GbpTestOutputPanel" parent="IdePane">
+  <template class="GbpTestuiOutputPanel" parent="IdePane">
+    <property name="hexpand">true</property>
+    <property name="vexpand">true</property>
     <child>
-      <object class="GtkScrolledWindow">
-        <property name="visible">true</property>
+      <object class="GtkBox">
+        <property name="orientation">horizontal</property>
         <child>
-          <object class="IdeTerminal" id="terminal">
-            <property name="audible-bell">false</property>
-            <property name="expand">true</property>
-            <property name="visible">true</property>
+          <object class="GtkScrolledWindow">
+            <property name="hexpand">true</property>
+            <property name="vexpand">true</property>
+            <child>
+              <object class="IdeTerminal" id="terminal">
+                <property name="audible-bell">false</property>
+              </object>
+            </child>
           </object>
         </child>
         <child>
           <object class="GtkSeparator">
-            <property name="visible">true</property>
+            <property name="orientation">vertical</property>
             <style>
               <class name="sidebar"/>
             </style>
@@ -21,61 +27,40 @@
         </child>
         <child>
           <object class="GtkBox">
-            <property name="border-width">2</property>
-            <property name="hexpand">false</property>
+            <property name="margin-top">2</property>
+            <property name="margin-start">2</property>
+            <property name="margin-end">2</property>
+            <property name="margin-bottom">2</property>
             <property name="orientation">vertical</property>
             <property name="spacing">2</property>
-            <property name="vexpand">true</property>
-            <property name="visible">true</property>
             <child>
               <object class="GtkButton" id="clear_button">
                 <property name="action-name">test-output.clear</property>
-                <property name="expand">false</property>
                 <property name="tooltip-text" translatable="yes">Clear test output</property>
-                <property name="visible">true</property>
+                <property name="icon-name">edit-clear-all-symbolic</property>
                 <style>
                   <class name="flat"/>
                 </style>
-                <child>
-                  <object class="GtkImage">
-                    <property name="icon-name">edit-clear-all-symbolic</property>
-                    <property name="visible">true</property>
-                  </object>
-                </child>
               </object>
             </child>
             <child>
               <object class="GtkButton" id="stop_button">
                 <property name="action-name">test-manager.cancel</property>
-                <property name="expand">false</property>
                 <property name="tooltip-text" translatable="yes">Cancel tests</property>
-                <property name="visible">true</property>
+                <property name="icon-name">builder-build-stop-symbolic</property>
                 <style>
                   <class name="flat"/>
                 </style>
-                <child>
-                  <object class="GtkImage">
-                    <property name="icon-name">builder-build-stop-symbolic</property>
-                    <property name="visible">true</property>
-                  </object>
-                </child>
               </object>
             </child>
             <child>
               <object class="GtkButton" id="save_button">
                 <property name="action-name">test-output.save</property>
-                <property name="expand">false</property>
                 <property name="tooltip-text" translatable="yes">Save test output</property>
-                <property name="visible">true</property>
+                <property name="icon-name">document-save-symbolic</property>
                 <style>
                   <class name="flat"/>
                 </style>
-                <child>
-                  <object class="GtkImage">
-                    <property name="icon-name">document-save-symbolic</property>
-                    <property name="visible">true</property>
-                  </object>
-                </child>
               </object>
             </child>
           </object>
diff --git a/src/plugins/testui/gbp-testui-panel.c b/src/plugins/testui/gbp-testui-panel.c
new file mode 100644
index 000000000..f2787ba3c
--- /dev/null
+++ b/src/plugins/testui/gbp-testui-panel.c
@@ -0,0 +1,156 @@
+/* gbp-testui-panel.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat 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 "gbp-testui-panel"
+
+#include "config.h"
+
+#include <libide-foundry.h>
+
+#include "gbp-testui-item.h"
+#include "gbp-testui-panel.h"
+
+struct _GbpTestuiPanel
+{
+  IdePane           parent_instance;
+  GtkNoSelection   *selection;
+  GtkTreeListModel *tree_model;
+};
+
+G_DEFINE_FINAL_TYPE (GbpTestuiPanel, gbp_testui_panel, IDE_TYPE_PANE)
+
+enum {
+  TEST_ACTIVATED,
+  N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+static void
+gbp_testui_panel_set_test_manager (GbpTestuiPanel *self,
+                                   IdeTestManager *test_manager)
+{
+  g_autoptr(GbpTestuiItem) item = NULL;
+  GListStore *store;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_TESTUI_PANEL (self));
+  g_assert (IDE_IS_TEST_MANAGER (test_manager));
+  g_assert (self->tree_model == NULL);
+
+  store = g_list_store_new (GBP_TYPE_TESTUI_ITEM);
+  item = gbp_testui_item_new (test_manager);
+  g_list_store_append (store, item);
+
+  self->tree_model = gtk_tree_list_model_new (G_LIST_MODEL (store),
+                                              FALSE, TRUE,
+                                              gbp_testui_item_create_child_model,
+                                              NULL, NULL);
+  gtk_no_selection_set_model (self->selection, G_LIST_MODEL (self->tree_model));
+
+
+  IDE_EXIT;
+}
+
+static void
+gbp_testui_panel_activate_cb (GbpTestuiPanel *self,
+                              guint           position,
+                              GtkListView    *list_view)
+{
+  g_autoptr(GtkTreeListRow) row = NULL;
+  GtkSelectionModel *model;
+  GbpTestuiItem *item;
+  gpointer instance;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_TESTUI_PANEL (self));
+  g_assert (GTK_IS_LIST_VIEW (list_view));
+
+  model = gtk_list_view_get_model (list_view);
+
+  row = g_list_model_get_item (G_LIST_MODEL (model), position);
+  g_assert (GTK_IS_TREE_LIST_ROW (row));
+
+  item = gtk_tree_list_row_get_item (row);
+  g_assert (GBP_IS_TESTUI_ITEM (item));
+
+  instance = gbp_testui_item_get_instance (item);
+  g_assert (G_IS_OBJECT (instance));
+
+  if (IDE_IS_TEST (instance))
+    g_signal_emit (self, signals[TEST_ACTIVATED], 0, instance);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_testui_panel_dispose (GObject *object)
+{
+  GbpTestuiPanel *self = (GbpTestuiPanel *)object;
+
+  g_clear_object (&self->tree_model);
+
+  G_OBJECT_CLASS (gbp_testui_panel_parent_class)->dispose (object);
+}
+
+static void
+gbp_testui_panel_class_init (GbpTestuiPanelClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = gbp_testui_panel_dispose;
+
+  signals[TEST_ACTIVATED] =
+    g_signal_new ("test-activated",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE, 1, IDE_TYPE_TEST);
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/plugins/testui/gbp-testui-panel.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpTestuiPanel, selection);
+  gtk_widget_class_bind_template_callback (widget_class, gbp_testui_panel_activate_cb);
+}
+
+static void
+gbp_testui_panel_init (GbpTestuiPanel *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+GbpTestuiPanel *
+gbp_testui_panel_new (IdeTestManager *test_manager)
+{
+  GbpTestuiPanel *self;
+
+  g_return_val_if_fail (IDE_IS_TEST_MANAGER (test_manager), NULL);
+
+  self = g_object_new (GBP_TYPE_TESTUI_PANEL, NULL);
+  gbp_testui_panel_set_test_manager (self, test_manager);
+
+  return self;
+}
diff --git a/src/plugins/testui/gbp-test-path.h b/src/plugins/testui/gbp-testui-panel.h
similarity index 59%
rename from src/plugins/testui/gbp-test-path.h
rename to src/plugins/testui/gbp-testui-panel.h
index 8e1b24dff..4e8f23797 100644
--- a/src/plugins/testui/gbp-test-path.h
+++ b/src/plugins/testui/gbp-testui-panel.h
@@ -1,6 +1,6 @@
-/* gbp-test-path.h
+/* gbp-testui-panel.h
  *
- * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ * Copyright 2022 Christian Hergert <chergert redhat 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
@@ -21,17 +21,14 @@
 #pragma once
 
 #include <libide-foundry.h>
+#include <libide-gui.h>
 
 G_BEGIN_DECLS
 
-#define GBP_TYPE_TEST_PATH (gbp_test_path_get_type())
+#define GBP_TYPE_TESTUI_PANEL (gbp_testui_panel_get_type())
 
-G_DECLARE_FINAL_TYPE (GbpTestPath, gbp_test_path, GBP, TEST_PATH, GObject)
+G_DECLARE_FINAL_TYPE (GbpTestuiPanel, gbp_testui_panel, GBP, TESTUI_PANEL, IdePane)
 
-GbpTestPath *gbp_test_path_new         (IdeTestManager *test_manager,
-                                        const gchar    *path);
-const gchar *gbp_test_path_get_name    (GbpTestPath    *self);
-GPtrArray   *gbp_test_path_get_folders (GbpTestPath    *self);
-GPtrArray   *gbp_test_path_get_tests   (GbpTestPath    *self);
+GbpTestuiPanel *gbp_testui_panel_new (IdeTestManager *test_manager);
 
 G_END_DECLS
diff --git a/src/plugins/testui/gbp-testui-panel.ui b/src/plugins/testui/gbp-testui-panel.ui
new file mode 100644
index 000000000..bdf6f8827
--- /dev/null
+++ b/src/plugins/testui/gbp-testui-panel.ui
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GbpTestuiPanel" parent="IdePane">
+    <property name="title" translatable="yes">Unit Tests</property>
+    <property name="icon-name">builder-unit-tests-symbolic</property>
+    <child>
+      <object class="GtkScrolledWindow">
+        <property name="vexpand">true</property>
+        <child>
+          <object class="GtkListView" id="list_view">
+            <signal name="activate" handler="gbp_testui_panel_activate_cb" swapped="true" 
object="GbpTestuiPanel"/>
+            <property name="orientation">vertical</property>
+            <property name="single-click-activate">true</property>
+            <property name="model">
+              <object class="GtkNoSelection" id="selection">
+              </object>
+            </property>
+            <property name="factory">
+              <object class="GtkBuilderListItemFactory">
+                <property name="bytes"><![CDATA[
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GtkListItem">
+    <property name="child">
+      <object class="IdeTreeExpander" id="expander">
+        <property name="margin-top">4</property>
+        <property name="margin-bottom">4</property>
+        <property name="margin-start">6</property>
+        <property name="margin-end">6</property>
+        <binding name="list-row">
+          <lookup name="item">GtkListItem</lookup>
+        </binding>
+        <binding name="icon-name">
+          <lookup name="icon-name" type="GbpTestuiItem">
+            <lookup name="item">expander</lookup>
+          </lookup>
+        </binding>
+        <binding name="expanded-icon-name">
+          <lookup name="expanded-icon-name" type="GbpTestuiItem">
+            <lookup name="item">expander</lookup>
+          </lookup>
+        </binding>
+        <binding name="title">
+          <lookup name="title" type="GbpTestuiItem">
+            <lookup name="item">expander</lookup>
+          </lookup>
+        </binding>
+      </object>
+    </property>
+  </template>
+</interface>
+]]>
+                </property>
+              </object>
+            </property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/plugins/testui/gbp-testui-preferences-addin.c 
b/src/plugins/testui/gbp-testui-preferences-addin.c
new file mode 100644
index 000000000..79f9fc038
--- /dev/null
+++ b/src/plugins/testui/gbp-testui-preferences-addin.c
@@ -0,0 +1,106 @@
+/* gbp-testui-preferences-addin.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat 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 "gbp-testui-preferences-addin"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include <libide-gui.h>
+
+#include "gbp-testui-preferences-addin.h"
+
+struct _GbpTestuiPreferencesAddin
+{
+  GObject parent_instance;
+};
+
+static const IdePreferenceGroupEntry groups[] = {
+  { "application", "unit-tests", 100, N_("Unit Tests") },
+};
+
+static void
+gbp_testui_preferences_addin_load (IdePreferencesAddin  *addin,
+                                   IdePreferencesWindow *window,
+                                   IdeContext           *context)
+{
+  g_autofree char *project_id = NULL;
+  g_autofree char *project_settings_path = NULL;
+
+  static IdePreferenceItemEntry items[] = {
+    { "application", "unit-tests", "pipeline", 0, ide_preferences_window_check,
+      N_("Build Pipeline"),
+      N_("Run unit tests from within the build pipeline environment."),
+      "org.gnome.builder.project", NULL, "unit-test-locality", "'pipeline'" },
+
+    { "application", "unit-tests", "runtime", 0, ide_preferences_window_check,
+      N_("As Application"),
+      N_("Run unit tests with access to display and other runtime environment features."),
+      "org.gnome.builder.project", NULL, "unit-test-locality", "'runtime'" },
+  };
+
+  g_assert (GBP_IS_TESTUI_PREFERENCES_ADDIN (addin));
+  g_assert (IDE_IS_PREFERENCES_WINDOW (window));
+  g_assert (!context || IDE_IS_CONTEXT (context));
+
+  if (ide_preferences_window_get_mode (window) != IDE_PREFERENCES_MODE_PROJECT)
+    return;
+
+  project_id = ide_context_dup_project_id (context);
+  project_settings_path = g_strdup_printf ("/org/gnome/builder/projects/%s/", project_id);
+
+  for (guint i = 0; i < G_N_ELEMENTS (items); i++)
+    items[i].path = project_settings_path;
+
+  ide_preferences_window_add_groups (window, groups, G_N_ELEMENTS (groups), NULL);
+  ide_preferences_window_add_items (window, items, G_N_ELEMENTS (items), window, NULL);
+}
+
+static void
+gbp_testui_preferences_addin_unload (IdePreferencesAddin  *addin,
+                                     IdePreferencesWindow *window,
+                                     IdeContext           *context)
+{
+  g_assert (GBP_IS_TESTUI_PREFERENCES_ADDIN (addin));
+  g_assert (IDE_IS_PREFERENCES_WINDOW (window));
+  g_assert (!context || IDE_IS_CONTEXT (context));
+
+}
+
+static void
+preferences_addin_iface_init (IdePreferencesAddinInterface *iface)
+{
+  iface->load = gbp_testui_preferences_addin_load;
+  iface->unload = gbp_testui_preferences_addin_unload;
+}
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (GbpTestuiPreferencesAddin, gbp_testui_preferences_addin, G_TYPE_OBJECT,
+                               G_IMPLEMENT_INTERFACE (IDE_TYPE_PREFERENCES_ADDIN, 
preferences_addin_iface_init))
+
+static void
+gbp_testui_preferences_addin_class_init (GbpTestuiPreferencesAddinClass *klass)
+{
+}
+
+static void
+gbp_testui_preferences_addin_init (GbpTestuiPreferencesAddin *self)
+{
+}
diff --git a/src/plugins/testui/gbp-testui-preferences-addin.h 
b/src/plugins/testui/gbp-testui-preferences-addin.h
new file mode 100644
index 000000000..44b57a873
--- /dev/null
+++ b/src/plugins/testui/gbp-testui-preferences-addin.h
@@ -0,0 +1,31 @@
+/* gbp-testui-preferences-addin.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat 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 <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_TESTUI_PREFERENCES_ADDIN (gbp_testui_preferences_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpTestuiPreferencesAddin, gbp_testui_preferences_addin, GBP, 
TESTUI_PREFERENCES_ADDIN, GObject)
+
+G_END_DECLS
diff --git a/src/plugins/testui/gbp-testui-workspace-addin.c b/src/plugins/testui/gbp-testui-workspace-addin.c
new file mode 100644
index 000000000..c8531e193
--- /dev/null
+++ b/src/plugins/testui/gbp-testui-workspace-addin.c
@@ -0,0 +1,144 @@
+/* gbp-testui-workspace-addin.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat 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 "gbp-testui-workspace-addin"
+
+#include "config.h"
+
+#include <libide-foundry.h>
+#include <libide-gui.h>
+
+#include "gbp-testui-output-panel.h"
+#include "gbp-testui-panel.h"
+#include "gbp-testui-workspace-addin.h"
+
+struct _GbpTestuiWorkspaceAddin
+{
+  GObject               parent_instance;
+  IdeWorkspace         *workspace;
+  GbpTestuiPanel       *panel;
+  GbpTestuiOutputPanel *output_panel;
+};
+
+static void
+on_test_activated_cb (GbpTestuiWorkspaceAddin *self,
+                      IdeTest                 *test,
+                      GbpTestuiPanel          *panel)
+{
+  IdeTestManager *test_manager;
+  IdeContext *context;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_TESTUI_WORKSPACE_ADDIN (self));
+  g_assert (IDE_IS_TEST (test));
+  g_assert (GBP_IS_TESTUI_PANEL (panel));
+
+  gbp_testui_output_panel_reset (self->output_panel);
+  panel_widget_raise (PANEL_WIDGET (self->output_panel));
+
+  context = ide_workspace_get_context (self->workspace);
+  test_manager = ide_test_manager_from_context (context);
+  ide_test_manager_run_async (test_manager, test, NULL, NULL, NULL);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_testui_workspace_addin_load (IdeWorkspaceAddin *addin,
+                                 IdeWorkspace      *workspace)
+{
+  GbpTestuiWorkspaceAddin *self = (GbpTestuiWorkspaceAddin *)addin;
+  g_autoptr(IdePanelPosition) position = NULL;
+  g_autoptr(IdePanelPosition) output_position = NULL;
+  IdeTestManager *test_manager;
+  IdeContext *context;
+  VtePty *pty;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_TESTUI_WORKSPACE_ADDIN (self));
+  g_assert (IDE_IS_PRIMARY_WORKSPACE (workspace));
+
+  self->workspace = workspace;
+
+  context = ide_workspace_get_context (workspace);
+  test_manager = ide_test_manager_from_context (context);
+  pty = ide_test_manager_get_pty (test_manager);
+
+  self->panel = gbp_testui_panel_new (test_manager);
+  g_signal_connect_object (self->panel,
+                           "test-activated",
+                           G_CALLBACK (on_test_activated_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  position = ide_panel_position_new ();
+  ide_panel_position_set_edge (position, PANEL_DOCK_POSITION_END);
+  ide_workspace_add_pane (workspace, IDE_PANE (self->panel), position);
+
+  self->output_panel = gbp_testui_output_panel_new (pty);
+  output_position = ide_panel_position_new ();
+  ide_panel_position_set_edge (output_position, PANEL_DOCK_POSITION_BOTTOM);
+  ide_workspace_add_pane (workspace, IDE_PANE (self->output_panel), output_position);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_testui_workspace_addin_unload (IdeWorkspaceAddin *addin,
+                                   IdeWorkspace      *workspace)
+{
+  GbpTestuiWorkspaceAddin *self = (GbpTestuiWorkspaceAddin *)addin;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_TESTUI_WORKSPACE_ADDIN (self));
+  g_assert (IDE_IS_PRIMARY_WORKSPACE (workspace));
+
+  g_clear_pointer ((IdePane **)&self->panel, ide_pane_destroy);
+  g_clear_pointer ((IdePane **)&self->output_panel, ide_pane_destroy);
+
+  self->workspace = NULL;
+
+  IDE_EXIT;
+}
+
+static void
+workspace_addin_iface_init (IdeWorkspaceAddinInterface *iface)
+{
+  iface->load = gbp_testui_workspace_addin_load;
+  iface->unload = gbp_testui_workspace_addin_unload;
+}
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (GbpTestuiWorkspaceAddin, gbp_testui_workspace_addin, G_TYPE_OBJECT,
+                               G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKSPACE_ADDIN, workspace_addin_iface_init))
+
+static void
+gbp_testui_workspace_addin_class_init (GbpTestuiWorkspaceAddinClass *klass)
+{
+}
+
+static void
+gbp_testui_workspace_addin_init (GbpTestuiWorkspaceAddin *self)
+{
+}
diff --git a/src/plugins/testui/gbp-test-tree-addin.h b/src/plugins/testui/gbp-testui-workspace-addin.h
similarity index 73%
rename from src/plugins/testui/gbp-test-tree-addin.h
rename to src/plugins/testui/gbp-testui-workspace-addin.h
index af28845ce..8786331dc 100644
--- a/src/plugins/testui/gbp-test-tree-addin.h
+++ b/src/plugins/testui/gbp-testui-workspace-addin.h
@@ -1,6 +1,6 @@
-/* gbp-test-tree-addin.h
+/* gbp-testui-workspace-addin.h
  *
- * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ * Copyright 2022 Christian Hergert <chergert redhat 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
@@ -24,8 +24,8 @@
 
 G_BEGIN_DECLS
 
-#define GBP_TYPE_TEST_TREE_ADDIN (gbp_test_tree_addin_get_type())
+#define GBP_TYPE_TESTUI_WORKSPACE_ADDIN (gbp_testui_workspace_addin_get_type())
 
-G_DECLARE_FINAL_TYPE (GbpTestTreeAddin, gbp_test_tree_addin, GBP, TEST_TREE_ADDIN, GObject)
+G_DECLARE_FINAL_TYPE (GbpTestuiWorkspaceAddin, gbp_testui_workspace_addin, GBP, TESTUI_WORKSPACE_ADDIN, 
GObject)
 
 G_END_DECLS
diff --git a/src/plugins/testui/gtk/keybindings.json b/src/plugins/testui/gtk/keybindings.json
new file mode 100644
index 000000000..ad9a6a565
--- /dev/null
+++ b/src/plugins/testui/gtk/keybindings.json
@@ -0,0 +1 @@
+{ "trigger" : "<Control><Alt><Shift>u", "action" : "test-manager.test-all", "when" : "hasProject()", "phase" 
: "capture" },
diff --git a/src/plugins/testui/gtk/menus.ui b/src/plugins/testui/gtk/menus.ui
new file mode 100644
index 000000000..7bb90d9e6
--- /dev/null
+++ b/src/plugins/testui/gtk/menus.ui
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="run-menu">
+    <section id="run-menu-tests-section">
+      <item>
+        <attribute name="id">run-unit-tests</attribute>
+        <attribute name="action">test-manager.test-all</attribute>
+        <attribute name="label" translatable="yes">Run all Unit Tests</attribute>
+        <attribute name="verb-icon-name">builder-unit-tests-symbolic</attribute>
+        <attribute name="accel">&lt;control&gt;&lt;shift&gt;&lt;alt&gt;u</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/src/plugins/testui/meson.build b/src/plugins/testui/meson.build
index 3778eb403..4d5470c6a 100644
--- a/src/plugins/testui/meson.build
+++ b/src/plugins/testui/meson.build
@@ -1,8 +1,10 @@
 plugins_sources += files([
   'testui-plugin.c',
-  'gbp-test-path.c',
-  'gbp-test-output-panel.c',
-  'gbp-test-tree-addin.c',
+  'gbp-testui-item.c',
+  'gbp-testui-output-panel.c',
+  'gbp-testui-panel.c',
+  'gbp-testui-preferences-addin.c',
+  'gbp-testui-workspace-addin.c',
 ])
 
 plugin_testui_resources = gnome.compile_resources(
diff --git a/src/plugins/testui/testui-plugin.c b/src/plugins/testui/testui-plugin.c
index 4b8199fab..79c10b43e 100644
--- a/src/plugins/testui/testui-plugin.c
+++ b/src/plugins/testui/testui-plugin.c
@@ -22,15 +22,20 @@
 
 #include "config.h"
 
-#include <libide-tree.h>
 #include <libpeas/peas.h>
 
-#include "gbp-test-tree-addin.h"
+#include <libide-gui.h>
+
+#include "gbp-testui-preferences-addin.h"
+#include "gbp-testui-workspace-addin.h"
 
 _IDE_EXTERN void
 _gbp_testui_register_types (PeasObjectModule *module)
 {
   peas_object_module_register_extension_type (module,
-                                              IDE_TYPE_TREE_ADDIN,
-                                              GBP_TYPE_TEST_TREE_ADDIN);
+                                              IDE_TYPE_PREFERENCES_ADDIN,
+                                              GBP_TYPE_TESTUI_PREFERENCES_ADDIN);
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_WORKSPACE_ADDIN,
+                                              GBP_TYPE_TESTUI_WORKSPACE_ADDIN);
 }
diff --git a/src/plugins/testui/testui.gresource.xml b/src/plugins/testui/testui.gresource.xml
index e26f525cc..ac26e9a85 100644
--- a/src/plugins/testui/testui.gresource.xml
+++ b/src/plugins/testui/testui.gresource.xml
@@ -2,6 +2,9 @@
 <gresources>
   <gresource prefix="/plugins/testui">
     <file>testui.plugin</file>
-    <file preprocess="xml-stripblanks">gbp-test-output-panel.ui</file>
+    <file>gtk/keybindings.json</file>
+    <file preprocess="xml-stripblanks">gtk/menus.ui</file>
+    <file preprocess="xml-stripblanks">gbp-testui-output-panel.ui</file>
+    <file preprocess="xml-stripblanks">gbp-testui-panel.ui</file>
   </gresource>
 </gresources>
diff --git a/src/plugins/testui/testui.plugin b/src/plugins/testui/testui.plugin
index 967ac6dde..5c0309b7e 100644
--- a/src/plugins/testui/testui.plugin
+++ b/src/plugins/testui/testui.plugin
@@ -1,11 +1,11 @@
 [Plugin]
 Authors=Christian Hergert <christian hergert me>
 Builtin=true
-Copyright=Copyright © 2017-2018 Christian Hergert
-Depends=editor;
-Description=Unit testing for Builder
+Copyright=Copyright © 2017-2022 Christian Hergert
+Depends=buildui;
+Description=Unit Testing
 Embedded=_gbp_testui_register_types
 Hidden=true
 Module=testui
 Name=Unit Testing
-X-Tree-Kind=project-tree;
+X-Workspace-Kind=primary;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]