aboutsummaryrefslogtreecommitdiff
path: root/app/tests/test_tui.py
diff options
context:
space:
mode:
Diffstat (limited to 'app/tests/test_tui.py')
-rw-r--r--app/tests/test_tui.py114
1 files changed, 89 insertions, 25 deletions
diff --git a/app/tests/test_tui.py b/app/tests/test_tui.py
index 916f434..e12da76 100644
--- a/app/tests/test_tui.py
+++ b/app/tests/test_tui.py
@@ -1288,6 +1288,16 @@ class TextViewerTests(TuiTestCase):
LINES = ["first line", "second line", "third line"]
+ def _last_frame(self, screen, title=" Help "):
+ """Texts of the final draw after the title (strings accumulate)."""
+ titles = [i for i, entry in enumerate(screen.strings)
+ if entry[2] == title]
+ return [entry[2] for entry in screen.strings[titles[-1] + 1:]]
+
+ def _top_line(self, drawn):
+ """First body-row text of a frame's drawn strings."""
+ return next(text for text in drawn if not text.startswith(" lines"))
+
def test_lines_render_and_enter_closes(self):
marker = object()
screen = FakeScreen(keys=[10])
@@ -1324,45 +1334,99 @@ class TextViewerTests(TuiTestCase):
tui.text_viewer(screen, "Help", self.LINES)
def test_no_cursor_bar_is_drawn(self):
- # Read-only: no row is selectable, so even with the cursor on a
- # line the cyan selection bar never paints.
- screen = FakeScreen(keys=[FakeCurses.KEY_END, ord("q")])
+ # Read-only: no row is selectable, so the cyan selection bar
+ # never paints, however far the text is scrolled.
+ screen = FakeScreen(keys=[FakeCurses.KEY_DOWN, FakeCurses.KEY_DOWN,
+ ord("q")])
tui.text_viewer(screen, "Help", self.LINES, back_value=object())
bars = [s for s in screen.strings
if s[3] == tui._THEME["bar"] and not s[2].strip()]
self.assertEqual(bars, [])
- def test_end_scrolls_the_last_line_into_view(self):
- # Content taller than the terminal: End shows the last line and
- # the frame's scroll indicator appears in the border.
+ def test_down_scrolls_one_line_per_keypress(self):
+ # The text itself scrolls immediately: two Downs put the third
+ # physical line at the top of the viewport, and the border
+ # reports which lines are visible.
lines = [f"line {i}" for i in range(40)]
- screen = FakeScreen(keys=[FakeCurses.KEY_END, ord("q")])
+ screen = FakeScreen(keys=[FakeCurses.KEY_DOWN, FakeCurses.KEY_DOWN,
+ ord("q")])
tui.text_viewer(screen, "Help", lines, back_value=object())
- drawn = [text for _, _, text, _ in screen.strings]
- self.assertIn("line 39", drawn)
- self.assertTrue(any("/40" in text for text in drawn))
+ drawn = self._last_frame(screen)
+ self.assertIn(" lines 3-19 of 40 ", drawn)
+ self.assertEqual(self._top_line(drawn), "line 2")
- def test_home_returns_to_the_top(self):
+ def test_up_stops_at_the_top(self):
lines = [f"line {i}" for i in range(40)]
- screen = FakeScreen(keys=[FakeCurses.KEY_END, FakeCurses.KEY_HOME,
+ screen = FakeScreen(keys=[FakeCurses.KEY_UP, FakeCurses.KEY_UP,
ord("q")])
tui.text_viewer(screen, "Help", lines, back_value=object())
- drawn = [text for _, _, text, _ in screen.strings]
- self.assertIn("line 0", drawn)
+ drawn = self._last_frame(screen)
+ self.assertIn(" lines 1-17 of 40 ", drawn)
+ self.assertEqual(self._top_line(drawn), "line 0")
- def test_page_down_moves_a_full_page(self):
- # PageDown from the top lands one page (17 visible rows) down:
- # the first line scrolls off, the next one is at the top.
+ def test_down_stops_at_the_bottom(self):
+ # 40 lines with 17 visible: scroll clamps at 23, so the last
+ # line stays on screen and the range ends at 40.
lines = [f"line {i}" for i in range(40)]
- screen = FakeScreen(keys=[FakeCurses.KEY_NPAGE, ord("q")])
+ keys = [FakeCurses.KEY_DOWN] * 30 + [ord("q")]
+ screen = FakeScreen(keys=keys)
tui.text_viewer(screen, "Help", lines, back_value=object())
- # Only the final frame counts: strings accumulate across redraws.
- titles = [i for i, entry in enumerate(screen.strings)
- if entry[2] == " Help "]
- drawn = [entry[2] for entry in screen.strings[titles[-1]:]]
- self.assertIn("line 1", drawn)
- self.assertNotIn("line 0", drawn)
- self.assertIn(" 2/40 ", drawn)
+ drawn = self._last_frame(screen)
+ self.assertIn(" lines 24-40 of 40 ", drawn)
+ self.assertEqual(self._top_line(drawn), "line 23")
+ self.assertIn("line 39", drawn)
+
+ def test_j_and_k_scroll_too(self):
+ lines = [f"line {i}" for i in range(40)]
+ # j, j (down twice), k (back up once): one line below the top.
+ screen = FakeScreen(keys=[ord("j"), ord("j"), ord("k"), ord("q")])
+ tui.text_viewer(screen, "Help", lines, back_value=object())
+ self.assertEqual(self._top_line(self._last_frame(screen)), "line 1")
+
+ def test_segments_indent_and_color(self):
+ # Numbered rows start at the margin with their number in the
+ # title color; indent=1 rows sit two columns further in.
+ content = [([("1. ", "title"), ("Step one", None)], 0),
+ ([("continuation", None)], 1),
+ ""]
+ screen = FakeScreen(keys=[10])
+ tui.text_viewer(screen, "Help", content, back_value=object())
+ x0, _ = self.dialog_box(screen)
+ margin = x0 + 1 + tui.Frame.LIST_MARGIN
+ number = next(entry for entry in screen.strings
+ if entry[2] == "1.")
+ self.assertEqual(number[1], margin)
+ self.assertEqual(number[3], tui._THEME["title"])
+ step = next(entry for entry in screen.strings
+ if entry[2] == "Step")
+ self.assertEqual(step[1], margin + 3)
+ cont = next(entry for entry in screen.strings
+ if entry[2] == "continuation")
+ self.assertEqual(cont[1], margin + 2)
+
+ def test_segments_wrap_when_wider_than_the_dialog(self):
+ # wrap=True segments rows word-wrap like text rows (colors
+ # kept), never truncating mid-word at the border.
+ content = [([("word " * 25, None)], 1)] # 125 columns of text
+ screen = FakeScreen(keys=[10])
+ tui.text_viewer(screen, "Help", content, back_value=object())
+ self.assert_inside_border(screen)
+ ys = {y for y, _, text, _ in screen.strings if "word" in text}
+ self.assertGreaterEqual(len(ys), 2)
+
+ def test_segments_truncate_by_default(self):
+ # Without wrap=True a segments row keeps its old behavior —
+ # one physical line truncated with '~' — so menus, forms and
+ # trees (wrap-less callers) never change layout.
+ frame = tui.Frame(self.screen, "T", "footer")
+ frame.mark_segments([("w" * 100, frame.theme["body"])],
+ selectable=True, align="left")
+ frame.cursor = 0
+ frame.draw()
+ drawn = [text for _, _, text, _ in self.screen.strings
+ if text.startswith("w")]
+ self.assertEqual(len(drawn), 1)
+ self.assertTrue(drawn[0].endswith("~"))
class WizardTests(unittest.TestCase):