aboutsummaryrefslogtreecommitdiff
path: root/tests/test_hub.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_hub.py')
-rw-r--r--tests/test_hub.py91
1 files changed, 91 insertions, 0 deletions
diff --git a/tests/test_hub.py b/tests/test_hub.py
new file mode 100644
index 0000000..5f6d992
--- /dev/null
+++ b/tests/test_hub.py
@@ -0,0 +1,91 @@
+"""Tests for the TUI hub (hub.py) menu and helpers.
+
+The hub drives the same curses widgets as tui.py, so these tests reuse the
+fake curses/screen from test_tui to run the menu without a terminal.
+"""
+
+import unittest
+from pathlib import Path
+from unittest.mock import patch
+
+import hub
+import tui
+from tests.test_tui import FakeCurses, FakeScreen
+
+
+class HubHelperTests(unittest.TestCase):
+ """Pure helpers in hub.py (no curses)."""
+
+ def test_is_float(self):
+ self.assertTrue(hub._is_float("1.0"))
+ self.assertTrue(hub._is_float("2"))
+ self.assertFalse(hub._is_float("abc"))
+ self.assertFalse(hub._is_float(""))
+
+ def test_list_voices_from_dir(self):
+ with __import__("tempfile").TemporaryDirectory() as td:
+ d = Path(td)
+ (d / "Narrator.wav").write_bytes(b"x")
+ (d / "Alpha.WAV").write_bytes(b"x")
+ (d / "notes.txt").write_bytes(b"x")
+ voices = hub._list_voices(str(d))
+ # Stems preserve case; sorting is case-insensitive.
+ self.assertEqual(voices, ["Alpha", "Narrator"])
+
+ def test_list_voices_missing_dir(self):
+ self.assertEqual(hub._list_voices("/no/such/dir"), [])
+
+ def test_status_mark(self):
+ from backends import BackendStatus
+ ready = BackendStatus("k", "l", installed=True, configured=True)
+ half = BackendStatus("k", "l", installed=True, configured=False)
+ none = BackendStatus("k", "l", installed=False, configured=False)
+ self.assertEqual(hub._status_mark("k", [ready]), "ready")
+ self.assertEqual(hub._status_mark("k", [half]), "installed")
+ self.assertEqual(hub._status_mark("k", [none]), "not set up")
+ self.assertEqual(hub._status_mark("missing", []), "not set up")
+
+
+class HubMenuTests(unittest.TestCase):
+ """Drive _hub_menu with a fake screen (no terminal)."""
+
+ def setUp(self):
+ tui._THEME.clear()
+ self.curses = FakeCurses()
+ from unittest.mock import patch as _patch
+ self._patcher = _patch.dict("sys.modules", {"curses": self.curses})
+ self._patcher.start()
+ self.addCleanup(self._patcher.stop)
+ self.addCleanup(tui._THEME.clear)
+
+ def test_quit_returns_none(self):
+ # Main menu: move to "Quit" (4th option, index 3) and press Enter.
+ screen = FakeScreen(keys=[FakeCurses.KEY_DOWN, FakeCurses.KEY_DOWN,
+ FakeCurses.KEY_DOWN, 10])
+ with patch.object(hub, "detect_all", return_value=[]):
+ result = hub._hub_menu(screen)
+ self.assertIsNone(result)
+
+ def test_convert_with_no_ready_backend_offers_setup(self):
+ # Convert -> "Set up a backend..." is the only entry -> Enter selects
+ # it -> setup menu lists 3 backends; press Esc to go back -> convert
+ # returns None -> main menu loops. Then quit (Down x3 + Enter).
+ from backends import BackendInfo, BackendStatus
+ none = BackendStatus("k", "l", installed=False, configured=False)
+ infos = [BackendInfo("audiocpp", "audio.cpp", lambda: none,
+ lambda: 0),
+ BackendInfo("qwen", "Qwen", lambda: none, lambda: 0),
+ BackendInfo("faster", "faster", lambda: none, lambda: 0)]
+ with patch.object(hub, "detect_all", return_value=[none, none, none]), \
+ patch.object(hub, "REGISTRY", infos):
+ # Convert(Enter), setup-entry(Enter), Esc on setup menu,
+ # back at main menu -> Down x3 -> Enter (Quit).
+ screen = FakeScreen(keys=[10, 10, 27,
+ FakeCurses.KEY_DOWN, FakeCurses.KEY_DOWN,
+ FakeCurses.KEY_DOWN, 10])
+ result = hub._hub_menu(screen)
+ self.assertIsNone(result)
+
+
+if __name__ == "__main__":
+ unittest.main()