aboutsummaryrefslogtreecommitdiff
path: root/app/tests
diff options
context:
space:
mode:
Diffstat (limited to 'app/tests')
-rw-r--r--app/tests/test_hub.py28
-rw-r--r--app/tests/test_tui.py114
2 files changed, 111 insertions, 31 deletions
diff --git a/app/tests/test_hub.py b/app/tests/test_hub.py
index 798c27f..fd22a4e 100644
--- a/app/tests/test_hub.py
+++ b/app/tests/test_hub.py
@@ -344,21 +344,37 @@ class HubMenuTests(unittest.TestCase):
result = hub._Hub(screen).run()
self.assertIsNone(result)
self.assertEqual(len(calls), 1)
- title, lines, kwargs = calls[0]
+ title, items, kwargs = calls[0]
self.assertEqual(title, "Help")
self.assertIs(kwargs.get("back_value"), tui.Wizard.BACK)
- text = "\n".join(lines)
+ # Flatten the viewer rows: (segments, indent) pairs join their
+ # texts; plain strings (the blank lines) pass through.
+ text = "\n".join(
+ "".join(part for part, _ in item[0])
+ if isinstance(item, tuple) else item
+ for item in items)
self.assertIn("1. Put your ebooks (epub, txt, or pdf) here:", text)
- self.assertIn(str(hub.BOOKS_FOLDER), text)
self.assertIn("2. Put any .wavs of voices to clone here:", text)
- self.assertIn(str(hub.common.VOICES_DIR), text)
- self.assertIn("Install Backend and install audio.cpp.", text)
+ self.assertIn("3. If no backend is installed, go to Configure "
+ "Backends > Install Backend and install audio.cpp.",
+ text)
+ self.assertIn("4. Select TTS models to install. If you're unsure, "
+ "try these qwen3-tts models:", text)
self.assertIn("qwen3_tts_1_7b_base_q8_0", text)
self.assertIn("qwen3_tts_1_7b_customvoice_q8_0", text)
self.assertIn("5. Go to Generate Audiobooks.", text)
+ # The "no need to manually start/stop" sentence sits on its own
+ # indented line below the step-5 paragraph.
+ self.assertIn("stop it.\nThere is no need to manually "
+ "start/stop servers.", text)
self.assertIn("6. Generated audiobooks (m4b, mp3, etc.) will "
"output here:", text)
- self.assertIn(str(hub.AUDIOBOOKS_FOLDER), text)
+ # The three folder paths are their own indented, white-bold
+ # ("input") rows; the numbered steps start at the margin.
+ self.assertIn(([(str(hub.BOOKS_FOLDER), "input")], 1), items)
+ self.assertIn(([(str(hub.common.VOICES_DIR), "input")], 1), items)
+ self.assertIn(([(str(hub.AUDIOBOOKS_FOLDER), "input")], 1), items)
+ self.assertEqual(items[0][1], 0)
class SubmenuStatusTableTests(unittest.TestCase):
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):