diff options
| author | historia <historiavg@proton.me> | 2026-09-02 20:33:44 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-09-02 20:33:44 -0400 |
| commit | dfce6c38a9a67ea2760fedae73ee9f5989d52f13 (patch) | |
| tree | 4212258c63908201c9720d016f0ea86d7f209aae /app/tests | |
| parent | 08e702700303f9bb5767f02a337b8b34be410df9 (diff) | |
| download | tts-audiobook-generator-dfce6c38a9a67ea2760fedae73ee9f5989d52f13.tar.gz | |
feat: add model and voice labels to generate audiobooks status tui
Diffstat (limited to 'app/tests')
| -rw-r--r-- | app/tests/test_hub.py | 57 | ||||
| -rw-r--r-- | app/tests/test_runview.py | 37 |
2 files changed, 93 insertions, 1 deletions
diff --git a/app/tests/test_hub.py b/app/tests/test_hub.py index f49b8d1..e38acbe 100644 --- a/app/tests/test_hub.py +++ b/app/tests/test_hub.py @@ -1445,6 +1445,9 @@ class ConvertFlowTests(unittest.TestCase): {"alpha": "narrator", "beta": "narrator"}) self.assertNotIn("model_id", kwargs) self.assertNotIn("voice", kwargs) + # The run view's header rows: an "All" summary plus the picked voice. + self.assertEqual(kwargs["run_model"], "All (2 models)") + self.assertEqual(kwargs["run_voice"], "narrator") self.assertEqual(kwargs["api_url"], "http://audiocpp.local:8080") self.assertEqual(mk_pre.call_count, 2) self.assertEqual(mk_pre.call_args_list[0].kwargs["name_tag"], @@ -1824,6 +1827,9 @@ class ConvertFlowTests(unittest.TestCase): cmd = self._convert( None, [self._remote("audiocpp", "audio.cpp")]) self.assertEqual(cmd[2]["instructions"], "stale description") + # The single pick's model and voice name the run view's rows. + self.assertEqual(cmd[2]["run_model"], "higgs") + self.assertEqual(cmd[2]["run_voice"], "narrator") def test_audiocpp_required_voice_validates(self): # A clone-only family (Chatterbox) needs a --voice; a blank value @@ -2311,6 +2317,9 @@ class ConvertFlowTests(unittest.TestCase): self.assertIsNone(cmd[2].get("instructions")) # The picked speaker travels with the run; nothing is persisted. self.assertEqual(cmd[2]["voice"], "Serena") + # The run view's rows show the mode word and the speaker. + self.assertEqual(cmd[2]["run_model"], "CustomVoice") + self.assertEqual(cmd[2]["run_voice"], "Serena") fields = self.tui.forms_seen[0][1] self.assertEqual([f["key"] for f in fields], ["backend", "qwen.mode", "qwen.speaker", @@ -2355,6 +2364,9 @@ class ConvertFlowTests(unittest.TestCase): [self._ready("qwen", "qwen-tts")]) self.assertEqual(cmd[2]["clone"], None) self.assertEqual(cmd[2]["instructions"], "A warm narrator") + # VoiceDesign designs the voice: a mode word, but no voice row. + self.assertEqual(cmd[2]["run_model"], "VoiceDesign") + self.assertEqual(cmd[2]["run_voice"], "") # Per-run choices are not persisted to the config file. mk_update.assert_not_called() @@ -2366,6 +2378,10 @@ class ConvertFlowTests(unittest.TestCase): cmd = self._convert(None, [self._ready("qwen", "qwen-tts")]) self.assertEqual(cmd[2]["clone"], "/tmp/ref.wav") + # The run view's rows: the mode word and the reference .wav's + # file name (no extension). + self.assertEqual(cmd[2]["run_model"], "Base") + self.assertEqual(cmd[2]["run_voice"], "ref") mk_update.assert_not_called() def test_qwen_custom_mode_passes_the_speaker(self): @@ -2503,6 +2519,9 @@ class ConvertFlowTests(unittest.TestCase): cmd = self._convert(None, [st]) self.assertIsNotNone(cmd) self.assertIs(self.nav, hub.tui.Wizard.BACK) + # faster has no model pick: the run view shows the voice only. + self.assertNotIn("run_model", cmd[2]) + self.assertEqual(cmd[2]["run_voice"], "obama") def test_settings_default_feeds_the_stop_and_exit_toggle(self): # The Settings menu's "Stop server and exit" value decides the @@ -2621,6 +2640,9 @@ class ConvertFlowTests(unittest.TestCase): kwargs = cmd[2] self.assertEqual(kwargs["model_id"], entry.key) self.assertEqual(kwargs["voice"], "Vivian") + # The run view's rows: the catalog label and the preset voice. + self.assertEqual(kwargs["run_model"], entry.label) + self.assertEqual(kwargs["run_voice"], "Vivian") self.assertNotIn("clone", kwargs) self.assertNotIn("api_url", kwargs) @@ -2649,6 +2671,9 @@ class ConvertFlowTests(unittest.TestCase): self.assertEqual(kwargs["clone"], str(ref)) # The .wav pick rides the clone kwarg; no named voice is sent. self.assertNotIn("voice", kwargs) + # The run view's voice row names the reference (no extension). + self.assertEqual(kwargs["run_model"], entry.label) + self.assertEqual(kwargs["run_voice"], "ref") # The combined Voice menu opens on the built-in default voice, # with the built-in entry above the clone directory's .wavs. self.assertEqual(voice_field["value"], "") @@ -2671,6 +2696,8 @@ class ConvertFlowTests(unittest.TestCase): kwargs = cmd[2] self.assertNotIn("clone", kwargs) self.assertNotIn("voice", kwargs) + # A blank pick is the model's own default: no voice row either. + self.assertEqual(kwargs["run_voice"], "") def test_sglomni_managed_design_model_sends_instructions(self): entry = hub.sglomni_backend.entry_by_key("qwen3_tts_1_7b_voicedesign") @@ -2690,6 +2717,9 @@ class ConvertFlowTests(unittest.TestCase): self.assertEqual(kwargs["instructions"], "A warm narrator.") self.assertNotIn("clone", kwargs) self.assertNotIn("voice", kwargs) + # Design models describe the voice: a model row, no voice row. + self.assertEqual(kwargs["run_model"], entry.label) + self.assertEqual(kwargs["run_voice"], "") def test_sglomni_remote_offers_the_hosted_model_and_uploaded_voices(self): served = patch.object(hub.backend_probe, "sglomni_served_model", @@ -2927,11 +2957,16 @@ class PrepareRunConfigTests(unittest.TestCase): def test_remote_targets_the_api_url(self): with patch.object(hub, "detect_all", return_value=[]) as mk_detect: cfg = hub._prepare_run_config( - "audiocpp", {"api_url": "http://10.0.0.5:8080"}) + "audiocpp", {"api_url": "http://10.0.0.5:8080", + "run_model": "alpha", "run_voice": "narrator"}) self.assertEqual(cfg.server_url, "http://10.0.0.5:8080") self.assertIsNone(cfg.autostart_spec) self.assertEqual(cfg.server_identity, "audiocpp") self.assertIn("remote", cfg.backend_label) + # The display picks reach the remote config too (the run view's + # Model/Voice rows draw there as well). + self.assertEqual(cfg.model_label, "alpha") + self.assertEqual(cfg.voice_label, "narrator") # The remote path never re-detects or touches managed-instance state. mk_detect.assert_not_called() @@ -3015,6 +3050,26 @@ class PrepareRunConfigTests(unittest.TestCase): self.assertIn("skipped non-TTS model(s): plex", cfg.notice) self.assertIn("re-hosted clone-only", cfg.notice) + def test_run_model_and_voice_picks_move_onto_the_config(self): + # The form's display picks (the run view's Model/Voice rows) ride + # along like run_notice: popped from the converter kwargs and + # stored on the config, never passed to convert(). + kwargs = {"run_model": "qwen3_tts", "run_voice": "Vivian"} + with patch.object(hub, "detect_all", return_value=[]): + cfg = hub._prepare_run_config("audiocpp", kwargs) + self.assertEqual(cfg.model_label, "qwen3_tts") + self.assertEqual(cfg.voice_label, "Vivian") + self.assertNotIn("run_model", kwargs) + self.assertNotIn("run_voice", kwargs) + + def test_missing_run_picks_default_to_blank_labels(self): + # No picks recorded (faster, or a backend the form could not + # name): blank labels keep both header rows hidden. + with patch.object(hub, "detect_all", return_value=[]): + cfg = hub._prepare_run_config("faster", {}) + self.assertEqual(cfg.model_label, "") + self.assertEqual(cfg.voice_label, "") + def test_stop_and_exit_travels_on_the_config_not_the_kwargs(self): # The run-view toggle is not a converter kwarg: it moves onto the # config (and defaults to off when the form did not send it). diff --git a/app/tests/test_runview.py b/app/tests/test_runview.py index 7f79053..501f1f7 100644 --- a/app/tests/test_runview.py +++ b/app/tests/test_runview.py @@ -419,6 +419,43 @@ class RenderTests(_FakeTui, unittest.TestCase): self.assertIn("starting", text) self.assertIn("Esc or q: cancel", text) + def test_title_says_generating_audiobooks(self): + view, screen = self.make_view() + view.render() + self.assertIn("Generating audiobooks", self._strings(screen)) + + def test_model_and_voice_rows_draw_between_server_and_status(self): + # The form's picks ride the config as display-only labels, drawn + # between the Server and Status header rows. + view, screen = self.make_view(model_label="qwen3_tts", + voice_label="Vivian") + view.render() + labels = [text.strip() for _y, _x, text, _ in screen.strings + if text.strip() in ("Server", "Model", "Voice", "Status")] + self.assertEqual(labels, ["Server", "Model", "Voice", "Status"]) + text = self._strings(screen) + self.assertIn("qwen3_tts", text) + self.assertIn("Vivian", text) + + def test_voice_only_row_draws_without_a_model_row(self): + # faster has no model pick: only the Voice row appears. + view, screen = self.make_view(model_label="", + voice_label="narrator") + view.render() + labels = [text.strip() for _y, _x, text, _ in screen.strings] + self.assertNotIn("Model", labels) + self.assertIn("Voice", labels) + self.assertIn("narrator", self._strings(screen)) + + def test_model_and_voice_rows_hidden_when_not_applicable(self): + # No picks recorded (or none apply): no rows, so the header keeps + # its Server -> Status shape. + view, screen = self.make_view() + view.render() + labels = [text.strip() for _y, _x, text, _ in screen.strings] + self.assertNotIn("Model", labels) + self.assertNotIn("Voice", labels) + def test_ready_without_autostart_shows_the_not_started_tag(self): # A run that talks to a server it did not start (already running # locally, or remote) tags the ready state so the panel does not |
