aboutsummaryrefslogtreecommitdiff
path: root/app/tests
diff options
context:
space:
mode:
Diffstat (limited to 'app/tests')
-rw-r--r--app/tests/test_hub.py50
-rw-r--r--app/tests/test_taskview.py60
-rw-r--r--app/tests/test_tui.py28
3 files changed, 122 insertions, 16 deletions
diff --git a/app/tests/test_hub.py b/app/tests/test_hub.py
index 68d0f34..9842148 100644
--- a/app/tests/test_hub.py
+++ b/app/tests/test_hub.py
@@ -323,7 +323,8 @@ class SubmenuStatusTableTests(unittest.TestCase):
"""First picker screen of every flow repeats the backend status table.
Entries themselves stay clean: the configure-backends menu lists flat
- actions, and the Start/Stop menu offers only installed backends.
+ actions, and the Start/Stop menu offers only installed backends'
+ servers, showing running/stopped inline instead of the table.
"""
def _capture_menu(self, captured):
@@ -580,25 +581,30 @@ class SubmenuStatusTableTests(unittest.TestCase):
def test_server_menu_lists_only_installed_backends(self):
captured = {}
installed = BackendStatus("audiocpp", "audio.cpp", installed=True,
- configured=True)
+ configured=True,
+ servers=[ServerSpec("audiocpp", "http://127.0.0.1:8080", [])])
remote = BackendStatus("qwen", "qwen-tts", installed=False,
configured=False, running=True)
gone = BackendStatus("faster", "faster-qwen3-tts", installed=False,
configured=False)
with patch.object(hub.tui, "menu",
self._capture_menu(captured)), \
+ patch.object(hub.common, "server_running",
+ return_value=False), \
patch.object(hub, "detect_all",
return_value=[installed, remote, gone]), \
patch.object(hub.shutil, "which", return_value="/x"):
result = hub._Hub(None).screen_server()
self.assertIs(result, tui.Wizard.BACK)
- # Only the installed backend is offered; a running external server
- # (remote) can't be stopped from here and must not appear.
- self.assertEqual([label for label, _ in captured["options"]],
+ # Only the installed backend's server is offered; a running external
+ # server (remote) can't be stopped from here and must not appear.
+ self.assertEqual([opt[0] for opt in captured["options"]],
["audio.cpp"])
- # The status table still shows all three, states included.
- self.assertEqual([row[0] for row in captured["table_rows"]],
- ["audio.cpp", "qwen-tts", "faster-qwen3-tts"])
+ # The running/stopped state lives in the status table above the
+ # menu (not on the entries, whose colors the selection bar covers).
+ self.assertEqual(captured["table_title"], "Server status")
+ self.assertEqual(captured["table_rows"],
+ [("audio.cpp", "stopped", "err", "body")])
def test_server_menu_flashes_when_nothing_installed(self):
flashed = []
@@ -1961,17 +1967,29 @@ class HubNavigationTests(unittest.TestCase):
status = BackendStatus("qwen", "qwen-tts", installed=True,
configured=True, servers=specs)
registry = [self._info("qwen", "qwen-tts")]
- with patch.object(hub.common, "server_running", return_value=False):
- titles = self._drive(
- ["server", "qwen", "qwen-clone", tui.Wizard.BACK,
- tui.Wizard.BACK, tui.Wizard.BACK, tui.Wizard.BACK],
- [status], registry)
+ titles = []
+ script = ["server", specs[0], tui.Wizard.BACK, tui.Wizard.BACK]
+
+ def menu(stdscr, title, options, **kwargs):
+ titles.append(title)
+ return script.pop(0)
+
+ with patch.object(hub, "REGISTRY", registry), \
+ patch.object(hub, "detect_all", return_value=[status]), \
+ patch.object(hub.tui, "menu", menu), \
+ patch.object(hub.common, "server_running",
+ return_value=False), \
+ patch.object(hub.taskview, "run_steps", return_value=0), \
+ patch.object(hub.tui, "flash", lambda *a, **k: None), \
+ patch.object(hub.audiocpp_backend, "find_local_checkout",
+ return_value=None):
+ hub._Hub(None).run()
+ # Selecting a server toggles it directly (no action sub-menu), then
+ # Esc steps back one screen at a time to the server list and main.
self.assertEqual(
titles,
["tts-audiobook-generator", "Start / Stop a server",
- "qwen-tts server", "qwen-clone (stopped)",
- "qwen-tts server", "Start / Stop a server",
- "tts-audiobook-generator"])
+ "Start / Stop a server", "tts-audiobook-generator"])
def test_esc_on_main_menu_quits(self):
titles = self._drive([tui.Wizard.BACK], [], [])
diff --git a/app/tests/test_taskview.py b/app/tests/test_taskview.py
index d1bc658..5d97902 100644
--- a/app/tests/test_taskview.py
+++ b/app/tests/test_taskview.py
@@ -212,6 +212,66 @@ class RenderTests(_FakeTui, unittest.TestCase):
self.assertIn("Progress", text)
+class NoWaitTests(_FakeTui, unittest.TestCase):
+ """wait_on_finish=False: the view returns to the caller on finish."""
+
+ def make_view(self, steps=(), width=80, height=24, wait_on_finish=False):
+ screen = FakeScreen(width=width, height=height)
+ with patch.object(taskview.TaskView, "_worker_main", lambda self: None):
+ view = taskview.TaskView(screen, "Setup", list(steps),
+ clock=lambda: 1000.0,
+ wait_on_finish=wait_on_finish)
+ return view, screen
+
+ def _strings(self, screen):
+ return " ".join(text for _, _, text, _ in screen.strings)
+
+ def _run_view(self, wait_on_finish):
+ """Run() with a synchronous worker that posts a successful finish."""
+ screen = FakeScreen(width=80, height=24)
+ step = _step("one")
+
+ def fake_worker_main(self):
+ self._queue.put({"kind": "step_start", "index": 0,
+ "title": "one"})
+ self._queue.put({"kind": "step_done", "index": 0, "rc": 0})
+ self._queue.put({"kind": "finish", "phase": "done", "rc": 0})
+
+ with patch.object(taskview.TaskView, "_worker_main",
+ fake_worker_main):
+ view = taskview.TaskView(screen, "Setup", [step],
+ clock=lambda: 1000.0,
+ wait_on_finish=wait_on_finish)
+ # Replace the daemon thread with a synchronous runner so the
+ # events are queued before run() enters its loop (deterministic).
+ view._worker = _SyncWorker(view._worker_main)
+ return view.run()
+
+ def test_returns_immediately_when_not_waiting(self):
+ # No scripted keys: run() must return on finish without asking for one.
+ self.assertEqual(self._run_view(wait_on_finish=False), 0)
+
+ def test_done_footer_omits_key_hint_when_not_waiting(self):
+ view, screen = self.make_view(steps=[_step("one")])
+ view.handle_event({"kind": "step_start", "index": 0, "title": "one"})
+ view.handle_event({"kind": "step_done", "index": 0, "rc": 0})
+ view.handle_event({"kind": "finish", "phase": "done", "rc": 0})
+ view.render()
+ text = self._strings(screen)
+ self.assertIn("completed", text)
+ self.assertNotIn("press any key", text)
+
+
+class _SyncWorker:
+ """A stand-in for threading.Thread that runs the target synchronously."""
+
+ def __init__(self, fn):
+ self._fn = fn
+
+ def start(self):
+ self._fn()
+
+
class LabelTests(unittest.TestCase):
def test_fmt_bytes(self):
self.assertEqual(taskview._fmt_bytes(512), "512B")
diff --git a/app/tests/test_tui.py b/app/tests/test_tui.py
index 5b5cb3c..030b668 100644
--- a/app/tests/test_tui.py
+++ b/app/tests/test_tui.py
@@ -50,6 +50,7 @@ class FakeCurses:
ACS_LRCORNER = "lr"
ACS_VLINE = "v"
ACS_HLINE = "h"
+ ACS_RARROW = "ra"
class error(Exception):
pass
@@ -254,6 +255,33 @@ class MenuTests(TuiTestCase):
if drawn == "Build audio.cpp server")
self.assertEqual(label_attr, tui._THEME["body"])
+ def test_selected_row_has_arrow_in_the_margin(self):
+ # The highlighted option gets an ACS_RARROW in the blank margin
+ # left of its text; unselected rows draw none.
+ screen = FakeScreen(keys=[10])
+ tui.menu(screen, "Pick", self.OPTIONS)
+ x0, _ = self.dialog_box(screen)
+ arrow_x = x0 + 1 + tui.Frame.LIST_MARGIN - 1
+ arrows = [(y, x) for y, x, ch, _ in screen.chars
+ if ch == FakeCurses.ACS_RARROW]
+ y_first = next(y for y, _, text, _ in screen.strings
+ if text == "first option")
+ self.assertEqual(arrows, [(y_first, arrow_x)])
+
+ def test_arrow_follows_the_cursor(self):
+ # Down moves the arrow onto the second option on the redraw.
+ screen = FakeScreen(keys=[FakeCurses.KEY_DOWN, 10])
+ tui.menu(screen, "Pick", self.OPTIONS)
+ x0, _ = self.dialog_box(screen)
+ arrow_x = x0 + 1 + tui.Frame.LIST_MARGIN - 1
+ arrows = [y for y, x, ch, _ in screen.chars
+ if ch == FakeCurses.ACS_RARROW and x == arrow_x]
+ y_first = next(y for y, _, text, _ in screen.strings
+ if text == "first option")
+ y_second = next(y for y, _, text, _ in screen.strings
+ if text == "second option")
+ self.assertEqual(arrows, [y_first, y_second])
+
class MenuTableTests(TuiTestCase):
"""The optional status table: aligned columns and colored statuses."""