gtk: Put the TestResultList behind a TestCaseFilter
And control the filter using the newly-created FilterButtons class. This lets us hide completely skipped tests by default, since those are mostly noise. I also add some custom icons used by the buttons to indicate passed, failed, or skipped tests. Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
This commit is contained in:
parent
37079ca7f5
commit
8a54cb5d98
|
@ -1 +1,2 @@
|
|||
*__pycache__*
|
||||
xfstestsdb/gtk/icons/xfstestsdb.gresource
|
||||
|
|
11
Makefile
11
Makefile
|
@ -3,6 +3,9 @@
|
|||
export PREFIX = /usr/local
|
||||
export XFSTESTSDB_BIN = ${PREFIX}/bin
|
||||
export XFSTESTSDB_LIB = ${PREFIX}/lib/xfstestsdb
|
||||
export GTK_DIR = xfstestsdb/gtk/icons
|
||||
|
||||
all: xfstestsdb.gresource flake8
|
||||
|
||||
clean:
|
||||
find . -type d -name __pycache__ -exec rm -r {} \+
|
||||
|
@ -11,8 +14,12 @@ clean:
|
|||
flake8:
|
||||
flake8
|
||||
|
||||
.PHONY: xfstestsdb.gresource
|
||||
xfstestsdb.gresource:
|
||||
glib-compile-resources --sourcedir=$(GTK_DIR) $(GTK_DIR)/xfstestsdb.gresource.xml
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
install: xfstestsdb.gresource
|
||||
find ./xfstestsdb -type f -not -path "*/__pycache__/*" \
|
||||
-exec install -v -C -D -m 755 "{}" "$(XFSTESTSDB_LIB)/{}" \;
|
||||
install -C -v -m 644 xfstestsdb.py $(XFSTESTSDB_LIB)/xfstestsdb.py
|
||||
|
@ -24,7 +31,7 @@ pytest:
|
|||
pytest
|
||||
|
||||
.PHONY: tests
|
||||
tests: pytest flake8
|
||||
tests: xfstestsdb.gresource pytest flake8
|
||||
|
||||
.PHONY: uninstall
|
||||
uninstall:
|
||||
|
|
|
@ -40,3 +40,15 @@ class TestGSetup(unittest.TestCase):
|
|||
mock_add.assert_called_with(mock_get_default.return_value,
|
||||
xfstestsdb.gtk.gsetup.CSS_PROVIDER,
|
||||
xfstestsdb.gtk.gsetup.CSS_PRIORITY)
|
||||
|
||||
@unittest.mock.patch("gi.repository.Gio.resources_register")
|
||||
def test_resources(self, mock_register: unittest.mock.Mock):
|
||||
"""Test that icon resources have been added to the app."""
|
||||
gtk_init_py = pathlib.Path(xfstestsdb.gtk.__file__)
|
||||
resources = gtk_init_py.parent / "icons" / "xfstestsdb.gresource"
|
||||
|
||||
self.assertEqual(xfstestsdb.gtk.gsetup.RESOURCE_FILE, resources)
|
||||
self.assertEqual(xfstestsdb.gtk.gsetup.RESOURCE_PATH,
|
||||
"/com/nowheycreamery/xfstestsdb")
|
||||
self.assertIsInstance(xfstestsdb.gtk.gsetup.RESOURCE,
|
||||
gi.repository.Gio.Resource)
|
||||
|
|
|
@ -6,6 +6,63 @@ import xfstestsdb.gtk.view
|
|||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class TestFilterButtons(unittest.TestCase):
|
||||
"""Test case for our TestCaseView FilterButtons."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.buttons = xfstestsdb.gtk.view.FilterButtons()
|
||||
|
||||
def test_init(self):
|
||||
"""Check that the buttons were created correctly."""
|
||||
self.assertIsInstance(self.buttons, Gtk.Box)
|
||||
self.assertTrue(self.buttons.has_css_class("linked"))
|
||||
|
||||
def test_passed_button(self):
|
||||
"""Test the 'passed' button."""
|
||||
self.assertIsInstance(self.buttons._passed,
|
||||
xfstestsdb.gtk.button.StatusToggle)
|
||||
self.assertEqual(self.buttons.get_first_child(), self.buttons._passed)
|
||||
|
||||
self.assertEqual(self.buttons._passed.props.icon_name, "test-pass")
|
||||
self.assertTrue(self.buttons._passed.has_css_class("passed"))
|
||||
self.assertFalse(self.buttons._passed.props.active)
|
||||
|
||||
self.assertFalse(self.buttons.passed)
|
||||
self.buttons._passed.props.active = True
|
||||
self.assertTrue(self.buttons.passed)
|
||||
|
||||
def test_skipped_button(self):
|
||||
"""Test the 'skipped' button."""
|
||||
self.assertIsInstance(self.buttons._skipped,
|
||||
xfstestsdb.gtk.button.StatusToggle)
|
||||
self.assertEqual(self.buttons._passed.get_next_sibling(),
|
||||
self.buttons._skipped)
|
||||
|
||||
self.assertEqual(self.buttons._skipped.props.icon_name, "test-skip")
|
||||
self.assertTrue(self.buttons._skipped.has_css_class("skipped"))
|
||||
self.assertFalse(self.buttons._skipped.props.active)
|
||||
|
||||
self.assertFalse(self.buttons.skipped)
|
||||
self.buttons._skipped.props.active = True
|
||||
self.assertTrue(self.buttons.skipped)
|
||||
|
||||
def test_failure_button(self):
|
||||
"""Test the 'failure' button."""
|
||||
self.assertIsInstance(self.buttons._failure,
|
||||
xfstestsdb.gtk.button.StatusToggle)
|
||||
self.assertEqual(self.buttons._skipped.get_next_sibling(),
|
||||
self.buttons._failure)
|
||||
|
||||
self.assertEqual(self.buttons._failure.props.icon_name, "test-fail")
|
||||
self.assertTrue(self.buttons._failure.has_css_class("failure"))
|
||||
self.assertTrue(self.buttons._failure.props.active)
|
||||
|
||||
self.assertTrue(self.buttons.failure)
|
||||
self.buttons._failure.props.active = False
|
||||
self.assertFalse(self.buttons.failure)
|
||||
|
||||
|
||||
class TestTestCaseView(unittest.TestCase):
|
||||
"""Tests the TestCaseView."""
|
||||
|
||||
|
@ -39,6 +96,31 @@ class TestTestCaseView(unittest.TestCase):
|
|||
self.assertTrue(self.view.props.child.get_hexpand())
|
||||
self.assertTrue(self.view.props.child.get_vexpand())
|
||||
|
||||
def test_filter(self):
|
||||
"""Test that we set up the Gtk.FilterModel and Buttons correctly."""
|
||||
self.assertIsInstance(self.view._filtermodel, Gtk.FilterListModel)
|
||||
self.assertIsInstance(self.view._testfilter,
|
||||
xfstestsdb.gtk.model.TestCaseFilter)
|
||||
self.assertIsInstance(self.view.filterbuttons,
|
||||
xfstestsdb.gtk.view.FilterButtons)
|
||||
|
||||
self.assertEqual(self.view.props.child.get_model().get_model(),
|
||||
self.view._filtermodel)
|
||||
self.assertEqual(self.view._filtermodel.props.filter,
|
||||
self.view._testfilter)
|
||||
|
||||
self.assertFalse(self.view._testfilter.passed)
|
||||
self.view.filterbuttons.passed = True
|
||||
self.assertTrue(self.view._testfilter.passed)
|
||||
|
||||
self.assertFalse(self.view._testfilter.skipped)
|
||||
self.view.filterbuttons.skipped = True
|
||||
self.assertTrue(self.view._testfilter.skipped)
|
||||
|
||||
self.assertTrue(self.view._testfilter.failure)
|
||||
self.view.filterbuttons.failure = False
|
||||
self.assertFalse(self.view._testfilter.failure)
|
||||
|
||||
def test_testcase_column(self):
|
||||
"""Test that we set up the 'testcase' column correctly."""
|
||||
self.assertIsInstance(self.view._testcase, Gtk.ColumnViewColumn)
|
||||
|
@ -51,8 +133,7 @@ class TestTestCaseView(unittest.TestCase):
|
|||
def test_model(self):
|
||||
"""Test setting the model property."""
|
||||
self.view.model = self.model
|
||||
self.assertEqual(self.view.props.child.get_model().get_model(),
|
||||
self.model)
|
||||
self.assertEqual(self.view._filtermodel.props.model, self.model)
|
||||
|
||||
columns = self.view.props.child.get_columns()
|
||||
self.assertEqual(len(columns), 3)
|
||||
|
@ -106,3 +187,8 @@ class TestXfstestsView(unittest.TestCase):
|
|||
self.assertIsNone(self.view.model)
|
||||
self.view.model = self.model
|
||||
self.assertEqual(self.view._testcaseview.model, self.model)
|
||||
|
||||
def test_filterbuttons(self):
|
||||
"""Test the XfstestsView 'filterbuttons' property."""
|
||||
self.assertEqual(self.view.filterbuttons,
|
||||
self.view._testcaseview.filterbuttons)
|
||||
|
|
|
@ -24,6 +24,8 @@ class TestApplication(unittest.TestCase):
|
|||
xfstestsdb.gtk.gsetup.APPLICATION_ID)
|
||||
self.assertEqual(self.application.get_flags(),
|
||||
Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
|
||||
self.assertEqual(self.application.get_resource_base_path(),
|
||||
xfstestsdb.gtk.gsetup.RESOURCE_PATH)
|
||||
self.assertEqual(self.application.runid, 0)
|
||||
self.assertIsNone(self.application.model)
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ class Application(Adw.Application):
|
|||
def __init__(self, sql: sqlite.Connection):
|
||||
"""Initialize the application."""
|
||||
super().__init__(application_id=gsetup.APPLICATION_ID,
|
||||
resource_base_path=gsetup.RESOURCE_PATH,
|
||||
flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE,
|
||||
sql=sql)
|
||||
|
||||
|
@ -50,6 +51,8 @@ class Application(Adw.Application):
|
|||
|
||||
self.view = view.XfstestsView()
|
||||
self.win = window.Window(child=self.view)
|
||||
self.win.headerbar.pack_end(self.view.filterbuttons)
|
||||
|
||||
self.bind_property("runid", self.win, "runid")
|
||||
self.bind_property("model", self.view, "model")
|
||||
self.add_window(self.win)
|
||||
|
|
|
@ -9,11 +9,17 @@ gi.importlib.import_module("gi.repository.Adw")
|
|||
DEBUG_STR = "-debug" if __debug__ else ""
|
||||
APPLICATION_ID = f"com.nowheycreamery.xfstestsdb.gtk{DEBUG_STR}"
|
||||
|
||||
CSS_FILE = pathlib.Path(__file__).parent / "xfstestsdb.css"
|
||||
__here = pathlib.Path(__file__).parent
|
||||
CSS_FILE = __here / "xfstestsdb.css"
|
||||
CSS_PRIORITY = gi.repository.Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
CSS_PROVIDER = gi.repository.Gtk.CssProvider()
|
||||
CSS_PROVIDER.load_from_path(str(CSS_FILE))
|
||||
|
||||
RESOURCE_FILE = __here / "icons" / "xfstestsdb.gresource"
|
||||
RESOURCE_PATH = "/com/nowheycreamery/xfstestsdb"
|
||||
RESOURCE = gi.repository.Gio.Resource.load(str(RESOURCE_FILE))
|
||||
gi.repository.Gio.resources_register(RESOURCE)
|
||||
|
||||
|
||||
def add_style():
|
||||
"""Add our custom stylesheet to the Gdk.Display."""
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 7.96875 1 c -3.851562 0 -6.96875 3.117188 -6.96875 6.96875 s 3.117188 6.96875 6.96875 6.96875 s 6.96875 -3.117188 6.96875 -6.96875 s -3.117188 -6.96875 -6.96875 -6.96875 z m -3 3.96875 h 1 h 0.03125 c 0.253906 0.011719 0.511719 0.128906 0.6875 0.3125 l 1.28125 1.28125 l 1.3125 -1.28125 c 0.265625 -0.230469 0.445312 -0.304688 0.6875 -0.3125 h 1 v 1 c 0 0.285156 -0.035156 0.550781 -0.25 0.75 l -1.28125 1.28125 l 1.25 1.25 c 0.1875 0.1875 0.28125 0.453125 0.28125 0.71875 v 1 h -1 c -0.265625 0 -0.53125 -0.09375 -0.71875 -0.28125 l -1.28125 -1.28125 l -1.28125 1.28125 c -0.1875 0.1875 -0.453125 0.28125 -0.71875 0.28125 h -1 v -1 c 0 -0.265625 0.09375 -0.53125 0.28125 -0.71875 l 1.28125 -1.25 l -1.28125 -1.28125 c -0.210938 -0.195312 -0.304688 -0.46875 -0.28125 -0.75 z m 0 0"/></svg>
|
After Width: | Height: | Size: 927 B |
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 8.234375 1.003906 c -2.15625 -0.070312 -4.277344 0.859375 -5.683594 2.601563 c -1.871093 2.316406 -2.066406 5.582031 -0.484375 8.105469 c 1.582032 2.527343 4.601563 3.777343 7.503906 3.109374 c 2.90625 -0.667968 5.074219 -3.117187 5.390626 -6.082031 c 0.046874 -0.359375 -0.101563 -0.71875 -0.394532 -0.933593 c -0.292968 -0.21875 -0.679687 -0.257813 -1.011718 -0.109376 c -0.332032 0.152344 -0.554688 0.472657 -0.582032 0.835938 c -0.226562 2.121094 -1.769531 3.863281 -3.851562 4.34375 c -2.078125 0.476562 -4.226563 -0.414062 -5.359375 -2.222656 c -1.132813 -1.808594 -0.992188 -4.128906 0.347656 -5.792969 s 3.578125 -2.289063 5.585937 -1.5625 c 0.339844 0.128906 0.71875 0.066406 0.996094 -0.167969 c 0.28125 -0.230468 0.410156 -0.59375 0.34375 -0.949218 c -0.0625 -0.355469 -0.316406 -0.648438 -0.660156 -0.761719 c -0.699219 -0.253907 -1.421875 -0.390625 -2.140625 -0.414063 z m 0 0"/><path d="m 13.167969 2.542969 l -5.292969 5.292969 l -2.417969 -2.417969 l -1.414062 1.414062 l 3.832031 3.832031 l 6.707031 -6.707031 z m 0 0"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 8 1 c -3.855469 0 -7 3.144531 -7 7 s 3.144531 7 7 7 s 7 -3.144531 7 -7 s -3.144531 -7 -7 -7 z m 0 2 c 2.753906 0 5 2.246094 5 5 s -2.246094 5 -5 5 s -5 -2.246094 -5 -5 s 2.246094 -5 5 -5 z m 0 0"/></svg>
|
After Width: | Height: | Size: 341 B |
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/com/nowheycreamery/xfstestsdb/icons/scalable/actions">
|
||||
<file preprocess="xml-stripblanks">test-pass-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">test-skip-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">test-fail-symbolic.svg</file>
|
||||
</gresource>
|
||||
</gresources>
|
|
@ -3,37 +3,80 @@
|
|||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from .model import TestCaseList
|
||||
from .model import TestCaseFilter
|
||||
from . import button
|
||||
from . import row
|
||||
|
||||
|
||||
class FilterButtons(Gtk.Box):
|
||||
"""Buttons for controlling the TestCaseFilter."""
|
||||
|
||||
passed = GObject.Property(type=bool, default=False)
|
||||
skipped = GObject.Property(type=bool, default=False)
|
||||
failure = GObject.Property(type=bool, default=True)
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the FilterButtons."""
|
||||
super().__init__()
|
||||
self.add_css_class("linked")
|
||||
|
||||
self._passed = button.StatusToggle("test-pass", "passed")
|
||||
self._skipped = button.StatusToggle("test-skip", "skipped")
|
||||
self._failure = button.StatusToggle("test-fail", "failure",
|
||||
active=True)
|
||||
|
||||
self._passed.bind_property("active", self, "passed")
|
||||
self._skipped.bind_property("active", self, "skipped")
|
||||
self._failure.bind_property("active", self, "failure")
|
||||
|
||||
self.append(self._passed)
|
||||
self.append(self._skipped)
|
||||
self.append(self._failure)
|
||||
|
||||
|
||||
class TestCaseView(Gtk.ScrolledWindow):
|
||||
"""Displays our TestCaseList model to the user."""
|
||||
|
||||
filterbuttons = GObject.Property(type=FilterButtons)
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize a TestCaseView."""
|
||||
super().__init__(child=Gtk.ColumnView(model=Gtk.NoSelection(),
|
||||
show_row_separators=True,
|
||||
show_column_separators=True,
|
||||
hexpand=True, vexpand=True))
|
||||
hexpand=True, vexpand=True),
|
||||
filterbuttons=FilterButtons())
|
||||
self._testcase = Gtk.ColumnViewColumn(title="testcase",
|
||||
factory=row.LabelFactory("name"))
|
||||
|
||||
self._testfilter = TestCaseFilter()
|
||||
self._filtermodel = Gtk.FilterListModel(filter=self._testfilter)
|
||||
|
||||
for prop in ["passed", "skipped", "failure"]:
|
||||
self.filterbuttons.bind_property(prop, self._testfilter, prop)
|
||||
|
||||
self.props.child.get_model().set_model(self._filtermodel)
|
||||
self.add_css_class("card")
|
||||
|
||||
def __xunit_column(self, xunit: str) -> None:
|
||||
return Gtk.ColumnViewColumn(title=xunit, expand=True,
|
||||
factory=row.ResultFactory(xunit))
|
||||
|
||||
def make_buttons(self) -> FilterButtons:
|
||||
"""Make a new FilterButtons instance connected to this View."""
|
||||
return FilterButtons()
|
||||
|
||||
@GObject.Property(type=TestCaseList)
|
||||
def model(self) -> TestCaseList:
|
||||
"""Get the TestCaseList shown by the View."""
|
||||
return self.props.child.get_model().get_model()
|
||||
return self._filtermodel.props.model
|
||||
|
||||
@model.setter
|
||||
def model(self, new: TestCaseList) -> None:
|
||||
for col in [col for col in self.props.child.get_columns()]:
|
||||
self.props.child.remove_column(col)
|
||||
|
||||
self.props.child.get_model().set_model(new)
|
||||
self._filtermodel.props.model = new
|
||||
|
||||
if new is not None:
|
||||
self.props.child.append_column(self._testcase)
|
||||
|
@ -54,3 +97,8 @@ class XfstestsView(Gtk.Box):
|
|||
self.bind_property("model", self._testcaseview, "model")
|
||||
|
||||
self.append(self._testcaseview)
|
||||
|
||||
@GObject.Property(type=FilterButtons)
|
||||
def filterbuttons(self) -> FilterButtons:
|
||||
"""Get the FilterButtons attached to the child TestCaseView."""
|
||||
return self._testcaseview.filterbuttons
|
||||
|
|
|
@ -1,15 +1,27 @@
|
|||
/* Copyright 2023 (c) Anna Schumaker. */
|
||||
|
||||
button.passed > image {
|
||||
color: @success_color;
|
||||
}
|
||||
|
||||
cell.passed {
|
||||
color: @success_fg_color;
|
||||
background-color: @success_bg_color;
|
||||
}
|
||||
|
||||
button.skipped > image {
|
||||
color: @warning_color;
|
||||
}
|
||||
|
||||
cell.skipped {
|
||||
color: @warning_fg_color;
|
||||
background-color: @warning_bg_color;
|
||||
}
|
||||
|
||||
button.failure > image {
|
||||
color: @error_color;
|
||||
}
|
||||
|
||||
cell.failure {
|
||||
color: @error_fg_color;
|
||||
background-color: @error_bg_color;
|
||||
|
|
Loading…
Reference in New Issue