From 4c3e78c39c81d2997e88b84e1b5a25b244e870e4 Mon Sep 17 00:00:00 2001 From: historia Date: Tue, 25 Aug 2026 21:02:21 -0400 Subject: feat: prompt to save settings when going back from settings menu --- app/tests/test_hub.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++++--- app/tests/test_tui.py | 35 ++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 3 deletions(-) (limited to 'app/tests') diff --git a/app/tests/test_hub.py b/app/tests/test_hub.py index 982df1d..f350072 100644 --- a/app/tests/test_hub.py +++ b/app/tests/test_hub.py @@ -723,7 +723,10 @@ class ConvertFlowTests(unittest.TestCase): self._patch_remote( [{"id": "higgs", "family": "higgs_audio_tts", "task": "tts"}], voices=["narrator"]) - with patch.object(hub.config, "AUDIOCPP_INSTRUCTIONS", ""): + # Pin the seeded toggle so this test does not depend on the user's + # saved STOP_SERVER_AND_EXIT value in config.py. + with patch.object(hub.config, "STOP_SERVER_AND_EXIT", True), \ + patch.object(hub.config, "AUDIOCPP_INSTRUCTIONS", ""): self._answer_form(backend="audiocpp-remote", model_id="higgs", audiocpp_voice="narrator", instructions="", speed="1.5") @@ -1711,11 +1714,12 @@ class SettingsTests(unittest.TestCase): "qwen_clone_port": "7861", "faster_port": "8000", "audiocpp_port": "8080"}]) - self.assertEqual(captured["flash"], ("Settings saved.", "ok")) + # Saving is silent: no confirmation flash either way. + self.assertNotIn("flash", captured) def test_settings_menu_cancel_does_not_apply(self): def fake_form(stdscr, title, fields, back_value=None): - return back_value # user pressed Cancel + return back_value # user pressed Cancel / q / Esc applied = [] @@ -1723,10 +1727,70 @@ class SettingsTests(unittest.TestCase): applied.append(values) with patch.object(hub.tui, "form", fake_form), \ + patch.object(hub.tui, "confirm_yn_cancel", + return_value="no") as mk_prompt, \ patch.object(hub, "_apply_settings", fake_apply): hub._Hub(None).screen_settings() + mk_prompt.assert_called_once_with(None, "Save settings?") self.assertEqual(applied, []) + def test_settings_menu_exit_yes_applies_the_edited_fields(self): + # Leaving via Esc and answering Yes applies a values dict built + # from the (edited) field list. + def fake_form(stdscr, title, fields, back_value=None): + next(f for f in fields if f["key"] == "chunk_size")["value"] = \ + "300" + return back_value # leave without the Save button + + applied = [] + + with patch.object(hub.tui, "form", fake_form), \ + patch.object(hub.tui, "confirm_yn_cancel", + return_value="yes"), \ + patch.object(hub, "_apply_settings", + lambda values: applied.append(values)): + hub._Hub(None).screen_settings() + self.assertEqual(len(applied), 1) + self.assertEqual(applied[0]["chunk_size"], "300") + # Every settings field's value travels on the dict. + self.assertIn("stop_and_exit", applied[0]) + self.assertIn("audiocpp_port", applied[0]) + + def test_settings_menu_exit_cancel_reopens_the_form(self): + # "Cancel" on the save prompt returns to the form with edits kept; + # leaving through Save afterwards applies once. + seen = [] + fields_seen = [] + + def fake_form(stdscr, title, fields, back_value=None): + seen.append(title) + fields_seen.append(fields) + if len(seen) == 1: + next(f for f in fields + if f["key"] == "chunk_size")["value"] = "300" + return back_value # first exit: Esc + return {"audio_format": "m4b", "audio_bitrate": "128k", + "language": "English", "chunk_size": "250", + "stop_and_exit": True, "unload_models": True, + "qwen_custom_port": "7860", "qwen_clone_port": "7861", + "faster_port": "8000", "audiocpp_port": "8080"} + + applied = [] + + with patch.object(hub.tui, "form", fake_form), \ + patch.object(hub.tui, "confirm_yn_cancel", + side_effect=["cancel"]), \ + patch.object(hub, "_apply_settings", + lambda values: applied.append(values)): + hub._Hub(None).screen_settings() + # The form reopened with the same field objects (edits intact). + self.assertEqual(len(seen), 2) + self.assertIs(fields_seen[0], fields_seen[1]) + self.assertEqual( + next(f for f in fields_seen[1] + if f["key"] == "chunk_size")["value"], "300") + self.assertEqual(len(applied), 1) + def test_settings_menu_writes_config_end_to_end(self): import tempfile tui._THEME.clear() diff --git a/app/tests/test_tui.py b/app/tests/test_tui.py index 1b7af86..c2cd267 100644 --- a/app/tests/test_tui.py +++ b/app/tests/test_tui.py @@ -440,6 +440,41 @@ class ConfirmTests(TuiTestCase): marker) +class ConfirmYnCancelTests(TuiTestCase): + def test_enter_takes_the_default_yes(self): + screen = FakeScreen(keys=[10]) + self.assertEqual(tui.confirm_yn_cancel(screen, "Save settings?"), + "yes") + + def test_tab_enter_selects_no_then_cancel(self): + screen = FakeScreen(keys=[9, 10]) + self.assertEqual(tui.confirm_yn_cancel(screen, "Save settings?"), + "no") + screen = FakeScreen(keys=[9, 9, 10]) + self.assertEqual(tui.confirm_yn_cancel(screen, "Save settings?"), + "cancel") + + def test_buttons_wrap_around(self): + # Moving back from Yes lands on Cancel. + screen = FakeScreen(keys=[FakeCurses.KEY_LEFT, 10]) + self.assertEqual(tui.confirm_yn_cancel(screen, "Save settings?"), + "cancel") + + def test_y_and_n_answer_directly(self): + screen = FakeScreen(keys=[ord("y")]) + self.assertEqual(tui.confirm_yn_cancel(screen, "Save settings?"), + "yes") + screen = FakeScreen(keys=[ord("n")]) + self.assertEqual(tui.confirm_yn_cancel(screen, "Save settings?"), + "no") + + def test_esc_and_q_count_as_cancel(self): + for key in (27, ord("q")): + screen = FakeScreen(keys=[key]) + self.assertEqual( + tui.confirm_yn_cancel(screen, "Save settings?"), "cancel") + + class LineEditTests(TuiTestCase): def test_typing_backspace_and_enter(self): keys = [ord("c"), ord("d"), FakeCurses.KEY_BACKSPACE, 10] -- cgit v1.2.3