diff options
Diffstat (limited to 'app/tests/test_tui.py')
| -rw-r--r-- | app/tests/test_tui.py | 114 |
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): |
