gtk: Create a MessageView
The message view will be used to display either stdout or stderr messages to the user. It has built-in 'diff' detection, and adds nice colors to the diff output if we are asked to display one. Signed-off-by: Anna Schumaker <anna@nowheycreamery.com>
This commit is contained in:
parent
54be8f5ce3
commit
5fb9bd6221
|
@ -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"),
|
||||
"<span color='#26a269'>+++ /some/file</span>")
|
||||
self.assertEqual(self.view.markup_diff("--- /some/other/file"),
|
||||
"<span color='#c01c28'>--- /some/other/file</span>")
|
||||
self.assertEqual(self.view.markup_diff("@@ 12,34,5 @@"),
|
||||
"<span color='#1c71d8'>@@ 12,34,5 @@</span>")
|
||||
self.assertEqual(self.view.markup_diff(" a context line"),
|
||||
"<span color='#77767b'> a context line</span>")
|
||||
self.assertEqual(self.view.markup_diff("+an added line"),
|
||||
"<span color='#26a269'>+an added line</span>")
|
||||
self.assertEqual(self.view.markup_diff("-a removed line"),
|
||||
"<span color='#c01c28'>-a removed line</span>")
|
||||
|
||||
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."""
|
||||
|
||||
|
|
|
@ -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"<span color='#26a269'>{text}</span>"
|
||||
elif re.match(r"^-+(.*?)", text):
|
||||
return f"<span color='#c01c28'>{text}</span>"
|
||||
elif re.match(r"^@@(.*?)@@", text):
|
||||
return f"<span color='#1c71d8'>{text}</span>"
|
||||
elif re.match(r"^ (.*?)", text):
|
||||
return f"<span color='#77767b'>{text}</span>"
|
||||
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."""
|
||||
|
||||
|
|
Loading…
Reference in New Issue