diff --git a/tests/gtk/test_view.py b/tests/gtk/test_view.py
index fda4917..cd76f0d 100644
--- a/tests/gtk/test_view.py
+++ b/tests/gtk/test_view.py
@@ -257,6 +257,101 @@ class TestTestCaseView(unittest.TestCase):
"stdout", "stderr")
+class TestMessageView(unittest.TestCase):
+ """Test the MessageView."""
+
+ def setUp(self):
+ """Set up common variables."""
+ self.view = xfstestsdb.gtk.view.MessageView("title")
+
+ def test_init(self):
+ """Check that the MessageView was set up correctly."""
+ self.assertIsInstance(self.view, Gtk.Box)
+ self.assertEqual(self.view.props.orientation, Gtk.Orientation.VERTICAL)
+ self.assertTrue(self.view.has_css_class("view"))
+
+ def test_detect_diff(self):
+ """Check detecting if input test looks like a diff."""
+ self.assertFalse(self.view.detect_diff("not a diff"))
+ lines = ["+++ /some/file"]
+ self.assertFalse(self.view.detect_diff("\n".join(lines)))
+ lines.append("--- /some/other/file")
+ self.assertFalse(self.view.detect_diff("\n".join(lines)))
+ lines.append("@@ 12,34,5 @@")
+ self.assertFalse(self.view.detect_diff("\n".join(lines)))
+ lines.append(" some context line")
+ self.assertTrue(self.view.detect_diff("\n".join(lines)))
+ lines[-1] = "+an added line"
+ self.assertTrue(self.view.detect_diff("\n".join(lines)))
+ lines[-1] = "-a removed line"
+ self.assertTrue(self.view.detect_diff("\n".join(lines)))
+
+ def test_markup_diff(self):
+ """Check colorizing lines with diff colors."""
+ self.assertEqual(self.view.markup_diff("abcde"), "abcde")
+ self.assertEqual(self.view.markup_diff("+++ /some/file"),
+ "+++ /some/file")
+ self.assertEqual(self.view.markup_diff("--- /some/other/file"),
+ "--- /some/other/file")
+ self.assertEqual(self.view.markup_diff("@@ 12,34,5 @@"),
+ "@@ 12,34,5 @@")
+ self.assertEqual(self.view.markup_diff(" a context line"),
+ " a context line")
+ self.assertEqual(self.view.markup_diff("+an added line"),
+ "+an added line")
+ self.assertEqual(self.view.markup_diff("-a removed line"),
+ "-a removed line")
+
+ def test_title(self):
+ """Test the title widgets."""
+ self.assertIsInstance(self.view._label, Gtk.Label)
+ self.assertIsInstance(self.view._label.get_next_sibling(),
+ Gtk.Separator)
+ self.assertEqual(self.view.get_first_child(), self.view._label)
+
+ self.assertEqual(self.view.title, "title")
+ self.assertEqual(self.view._label.props.label, "title")
+ self.assertEqual(self.view._label.props.margin_top, 6)
+ self.assertTrue(self.view._label.has_css_class("large-title"))
+
+ def test_text(self):
+ """Test the text property."""
+ win = self.view.get_last_child()
+ self.assertIsInstance(win, Gtk.ScrolledWindow)
+ self.assertIsInstance(self.view._textview, Gtk.TextView)
+ self.assertEqual(win.props.child, self.view._textview)
+ self.assertTrue(win.props.vexpand)
+
+ self.assertFalse(self.view._textview.props.editable)
+ self.assertTrue(self.view._textview.props.monospace)
+
+ buffer = self.view._textview.get_buffer()
+ self.assertEqual(self.view.text, "")
+ with unittest.mock.patch.object(buffer, "set_text",
+ wraps=buffer.set_text) as mock_set:
+ self.view.text = "text"
+ self.assertEqual(buffer.get_text(buffer.get_start_iter(),
+ buffer.get_end_iter(), True),
+ "text")
+ mock_set.assert_called_with("text")
+ self.assertEqual(self.view.text, "text")
+
+ def test_text_diff(self):
+ """Test setting the text property to a diff string."""
+ buffer = self.view._textview.get_buffer()
+ diff = ["+++ /some/file", "--- /some/other/file", "@@ 12,34,5 @@",
+ " context line", "-removed line", "+added line"]
+
+ with unittest.mock.patch.object(buffer, "set_text",
+ wraps=buffer.set_text) as mock_set:
+ self.view.text = "\n".join(diff)
+ mock_set.assert_not_called()
+ self.assertEqual(self.view.text, "\n".join(diff))
+
+ self.view.text = "\n".join(diff)
+ self.assertEqual(self.view.text, "\n".join(diff))
+
+
class TestSummaryView(unittest.TestCase):
"""Tests the SummaryView."""
diff --git a/xfstestsdb/gtk/view.py b/xfstestsdb/gtk/view.py
index 3ff7438..bc4d587 100644
--- a/xfstestsdb/gtk/view.py
+++ b/xfstestsdb/gtk/view.py
@@ -1,5 +1,6 @@
# Copyright 2023 (c) Anna Schumaker.
"""A view widget used to display our TestCaseModel."""
+import re
from gi.repository import GObject
from gi.repository import Gio
from gi.repository import Gtk
@@ -164,6 +165,72 @@ class TestCaseView(XunitView):
"""Signal that the user wants to inspect stdout and stderr messages."""
+class MessageView(Gtk.Box):
+ """A view for displaying a multiline test result message."""
+
+ title = GObject.Property(type=str)
+
+ def __init__(self, title: str):
+ """Initialize a MessageView."""
+ super().__init__(title=title, orientation=Gtk.Orientation.VERTICAL)
+ self._label = Gtk.Label(label=self.title, margin_top=6)
+ self._textview = Gtk.TextView(monospace=True, editable=False)
+
+ self.append(self._label)
+ self.append(Gtk.Separator())
+ self.append(Gtk.ScrolledWindow(child=self._textview, vexpand=True))
+
+ self._label.add_css_class("large-title")
+ self.add_css_class("view")
+
+ def detect_diff(self, text: str) -> bool:
+ """Detect if the given text looks like a diff."""
+ in_file = out_file = counts = changed = False
+
+ for line in text.split("\n"):
+ if re.match(r"^\+\+\+", line):
+ in_file = True
+ elif re.match(r"^---", line):
+ out_file = True
+ elif re.match(r"^@@(.*?)@@", line):
+ counts = True
+ elif re.match(r"^[\+| |-](.*?)", line):
+ changed = True
+
+ return in_file and out_file and counts and changed
+
+ def markup_diff(self, text: str) -> str:
+ """Add Pango markup to the input string."""
+ if re.match(r"^\++(.*?)", text):
+ return f"{text}"
+ elif re.match(r"^-+(.*?)", text):
+ return f"{text}"
+ elif re.match(r"^@@(.*?)@@", text):
+ return f"{text}"
+ elif re.match(r"^ (.*?)", text):
+ return f"{text}"
+ return text
+
+ @GObject.Property(type=str)
+ def text(self) -> str:
+ """Get the text displayed in the view."""
+ buffer = self._textview.props.buffer
+ return buffer.get_text(buffer.get_start_iter(),
+ buffer.get_end_iter(), True)
+
+ @text.setter
+ def text(self, new_text: str) -> None:
+ buffer = self._textview.props.buffer
+ if self.detect_diff(new_text):
+ buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
+ for i, line in enumerate(new_text.split("\n")):
+ text = self.markup_diff(line)
+ text = f"\n{text}" if i > 0 else text
+ buffer.insert_markup(buffer.get_end_iter(), text, len(text))
+ else:
+ buffer.set_text(new_text)
+
+
class SummaryView(XunitView):
"""Displays our SummaryList model to the user."""