From 5af37bbd575eec89c6ac2fecf2d8c2eda4c1728d Mon Sep 17 00:00:00 2001 From: historia Date: Wed, 26 Aug 2026 19:12:11 -0400 Subject: fix: crash when menu of clone voices is empty --- app/tests/test_tui.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'app/tests/test_tui.py') diff --git a/app/tests/test_tui.py b/app/tests/test_tui.py index 7821ebb..4ff70fb 100644 --- a/app/tests/test_tui.py +++ b/app/tests/test_tui.py @@ -781,6 +781,41 @@ class FormTests(TuiTestCase): result = tui.form(screen, "Settings", fields) self.assertEqual(result, {"fmt": "b"}) + def test_empty_static_choices_flash_hint_and_keep_form_open(self): + # A choice field with no options at all cannot be opened (menu() + # would raise): the on_empty_choices hint flashes instead and the + # form stays usable. + fields = [{"key": "fmt", "label": "Format", "kind": "choice", + "value": "", "choices": [], + "on_empty_choices": "nothing to pick — add some"}, + {"key": "chunk", "label": "Chunk", "kind": "text", + "value": "1"}] + # Enter flashes (the next key dismisses it), Tab -> Save, Enter. + screen = FakeScreen(keys=[10, ord("x"), 9, 10]) + result = tui.form(screen, "Settings", fields) + self.assertEqual(result, {"fmt": "", "chunk": "1"}) + texts = [text for _, _, text, _ in screen.strings] + self.assertTrue(any("nothing to pick" in text for text in texts)) + + def test_empty_callable_choices_flash_without_opening_menu(self): + # The regression this guards: a dynamic list that resolved empty + # used to crash form() with ValueError from menu(). A message may + # itself be a callable of the field list. + fields = [{"key": "voice", "label": "Voice", "kind": "choice", + "value": None, + "choices": lambda fs: [], + "on_empty_choices": + lambda fs: f"{len(fs)} fields but no voices"}] + # Enter opens nothing (the flash consumes the next key), then + # Tab -> Save, Enter. + screen = FakeScreen(keys=[10, ord("x"), 9, 10]) + with patch.object(tui, "menu", + side_effect=AssertionError("menu() was opened")): + result = tui.form(screen, "Convert", fields) + self.assertEqual(result, {"voice": None}) + texts = [text for _, _, text, _ in screen.strings] + self.assertTrue(any("no voices" in text for text in texts)) + def test_on_change_fires_after_value_change(self): calls = [] fields = [{"key": "fmt", "label": "Format", "kind": "choice", -- cgit v1.2.3