diff options
| author | historia <historiavg@proton.me> | 2026-08-24 02:59:26 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-08-24 02:59:26 -0400 |
| commit | f00249db9d1ea051d29aa1bcca869fc4b88e83eb (patch) | |
| tree | a75f076fac1b63e0b4bf2eb8f54affbcc681a891 /tests/test_tui.py | |
| parent | 9dd4f9595be3b1d76a3a07dc3eca90cfaf8a3f97 (diff) | |
| download | tts-audiobook-generator-f00249db9d1ea051d29aa1bcca869fc4b88e83eb.tar.gz | |
refactor: add app directory, dir structure change
Diffstat (limited to 'tests/test_tui.py')
| -rw-r--r-- | tests/test_tui.py | 661 |
1 files changed, 0 insertions, 661 deletions
diff --git a/tests/test_tui.py b/tests/test_tui.py deleted file mode 100644 index c121d55..0000000 --- a/tests/test_tui.py +++ /dev/null @@ -1,661 +0,0 @@ -"""Tests for the DOS-style curses TUI widgets in tui.py. - -The widget module imports curses lazily, so these tests swap the -curses module for a small fake (patched into sys.modules) and drive -the widgets with scripted keys against a recording screen. That works -without a terminal and lets the tests assert exact drawing -coordinates: theme colors, the left margin of list rows, and that no -row ever paints over the dialog border. -""" - -import sys -import tempfile -import unittest -from pathlib import Path -from unittest.mock import patch - -from ui import tui - - -class FakeCurses: - """Minimal curses stand-in: attributes, key codes, color pairs.""" - - A_BOLD = 1 - A_DIM = 2 - A_REVERSE = 4 - - COLOR_BLACK = 0 - COLOR_RED = 1 - COLOR_GREEN = 2 - COLOR_YELLOW = 3 - COLOR_BLUE = 4 - COLOR_MAGENTA = 5 - COLOR_CYAN = 6 - COLOR_WHITE = 7 - - KEY_DOWN = 0x101 - KEY_UP = 0x102 - KEY_LEFT = 0x103 - KEY_RIGHT = 0x104 - KEY_HOME = 0x105 - KEY_END = 0x106 - KEY_PPAGE = 0x107 - KEY_NPAGE = 0x108 - KEY_BACKSPACE = 0x109 - KEY_BTAB = 0x10A - - ACS_ULCORNER = "ul" - ACS_URCORNER = "ur" - ACS_LLCORNER = "ll" - ACS_LRCORNER = "lr" - ACS_VLINE = "v" - ACS_HLINE = "h" - - class error(Exception): - pass - - def __init__(self): - self.pairs = {} # pair number -> (fg, bg) - self.colors = True - - def has_colors(self): - return self.colors - - def start_color(self): - pass - - def init_pair(self, number, fg, bg): - self.pairs[number] = (fg, bg) - - def color_pair(self, number): - return number << 8 - - def curs_set(self, visibility): - pass - - def endwin(self): - pass - - -class FakeScreen: - """Recording curses window; getch() replays scripted keys.""" - - def __init__(self, keys=(), width=80, height=24): - self.keys = list(keys) - self.width = width - self.height = height - self.strings = [] # (y, x, text, attr) from addstr - self.chars = [] # (y, x, ch, attr) from addch - - def getmaxyx(self): - return self.height, self.width - - def erase(self): - pass - - def refresh(self): - pass - - def bkgd(self, ch, attr): - pass - - def addstr(self, y, x, text, attr=0): - self.strings.append((y, x, text, attr)) - - def addch(self, y, x, ch, attr=0): - self.chars.append((y, x, ch, attr)) - - def hline(self, y, x, ch, n, attr=0): - pass - - def redrawwin(self): - pass - - def getch(self): - if not self.keys: - raise AssertionError("the script ran out of keys") - return self.keys.pop(0) - - -class TuiTestCase(unittest.TestCase): - """Base class: fresh theme + fake curses module for every test.""" - - def setUp(self): - tui._THEME.clear() - self.curses = FakeCurses() - self.screen = FakeScreen() - patcher = patch.dict(sys.modules, {"curses": self.curses}) - patcher.start() - self.addCleanup(patcher.stop) - self.addCleanup(tui._THEME.clear) - - def dialog_box(self, screen=None): - """(x0, x_right) border columns of the drawn dialog.""" - screen = screen or self.screen - corners = screen.chars[:2] - self.assertEqual(corners[0][2], FakeCurses.ACS_ULCORNER) - self.assertEqual(corners[1][2], FakeCurses.ACS_URCORNER) - return corners[0][1], corners[1][1] - - def assert_inside_border(self, screen=None): - """No drawn string may reach the right border column.""" - screen = screen or self.screen - _, x_right = self.dialog_box(screen) - for _, x, text, _ in screen.strings: - self.assertLessEqual( - x + len(text), x_right, - f"{text!r} painted over the border at x={x}") - - -class ThemeTests(TuiTestCase): - def test_desktop_and_message_backgrounds_are_black(self): - frame = tui.Frame(self.screen, "Title", "footer") - theme, pairs = frame.theme, self.curses.pairs - for name in ("desktop", "border", "title", "ok", "warn", "err", - "info", "input", "check", "accent"): - fg, bg = pairs[theme[name] >> 8] - self.assertEqual(bg, FakeCurses.COLOR_BLACK, name) - - def test_cursor_bar_and_active_button_stand_out(self): - frame = tui.Frame(self.screen, "Title", "footer") - theme, pairs = frame.theme, self.curses.pairs - fg, bg = pairs[theme["bar"] >> 8] - self.assertEqual((fg, bg), - (FakeCurses.COLOR_BLACK, FakeCurses.COLOR_CYAN)) - fg, bg = pairs[theme["btn_on"] >> 8] - self.assertEqual((fg, bg), - (FakeCurses.COLOR_BLACK, FakeCurses.COLOR_GREEN)) - - def test_without_colors_theme_uses_plain_attributes(self): - self.curses.colors = False - frame = tui.Frame(self.screen, "Title", "footer") - self.assertEqual(self.curses.pairs, {}) - self.assertEqual(frame.theme["desktop"], 0) - self.assertEqual(frame.theme["title"], FakeCurses.A_BOLD) - - -class MenuTests(TuiTestCase): - OPTIONS = [("first option", "one"), ("second option", "two")] - - def test_option_rows_left_justified_help_centered(self): - screen = FakeScreen(keys=[10]) - value = tui.menu(screen, "Pick one", self.OPTIONS, - help_lines=["Help text"]) - self.assertEqual(value, "one") - x0, x_right = self.dialog_box(screen) - margin = x0 + 1 + tui.Frame.LIST_MARGIN - for label in ("first option", "second option"): - x = next(x for _, x, text, _ in screen.strings if text == label) - self.assertEqual(x, margin, label) - inner_w = x_right - x0 - 1 - help_x = next(x for _, x, text, _ in screen.strings - if text == "Help text") - self.assertEqual(help_x, x0 + 1 + (inner_w - len("Help text")) // 2) - self.assertGreater(help_x, margin) - self.assert_inside_border(screen) - - def test_up_wraps_around_to_last_option(self): - screen = FakeScreen(keys=[FakeCurses.KEY_UP, 10]) - value = tui.menu(screen, "Pick one", self.OPTIONS) - self.assertEqual(value, "two") - - def test_no_option_painted_over_the_border(self): - screen = FakeScreen(keys=[FakeCurses.KEY_END, 10]) - tui.menu(screen, "Pick", [("a" * 60, "a"), ("b", "b")]) - self.assert_inside_border(screen) - - def test_empty_options_rejected(self): - with self.assertRaises(ValueError): - tui.menu(self.screen, "Pick", []) - - def test_esc_returns_back_value(self): - marker = object() - screen = FakeScreen(keys=[27]) - self.assertIs( - tui.menu(screen, "Pick", self.OPTIONS, back_value=marker), - marker) - - def test_q_still_aborts_with_back_value(self): - marker = object() - screen = FakeScreen(keys=[ord("q")]) - with self.assertRaises(tui.WizardCancelled): - tui.menu(screen, "Pick", self.OPTIONS, back_value=marker) - - -class MenuTableTests(TuiTestCase): - """The optional status table: aligned columns and colored statuses.""" - - ROWS = [("audio.cpp", "not installed", "err"), - ("qwen-tts", "installed", "warn"), - ("faster-qwen3-tts", "running", "ok")] - - def test_name_column_left_aligned_at_margin(self): - screen = FakeScreen(keys=[10]) - tui.menu(screen, "Hub", [("Quit", "quit")], - table_title="Backend status", table_rows=self.ROWS) - x0, _ = self.dialog_box(screen) - margin = x0 + 1 + tui.Frame.LIST_MARGIN - for name, _, _ in self.ROWS: - x = next(x for _, x, text, _ in screen.strings - if text.rstrip() == name) - self.assertEqual(x, margin, name) - - def test_status_column_aligned_at_one_fixed_offset(self): - screen = FakeScreen(keys=[10]) - tui.menu(screen, "Hub", [("Quit", "quit")], - table_title="Backend status", table_rows=self.ROWS) - x0, _ = self.dialog_box(screen) - margin = x0 + 1 + tui.Frame.LIST_MARGIN - name_w = max(len(name) for name, _, _ in self.ROWS) - expected_x = margin + name_w # the " status" segment starts here - for _, status, _ in self.ROWS: - x = next(x for _, x, text, _ in screen.strings - if text.strip() == status) - self.assertEqual(x, expected_x, status) - - def test_status_text_uses_the_theme_kind_color(self): - screen = FakeScreen(keys=[10]) - tui.menu(screen, "Hub", [("Quit", "quit")], - table_title="Backend status", table_rows=self.ROWS) - want = {"err": tui._THEME["err"], "warn": tui._THEME["warn"], - "ok": tui._THEME["ok"]} - for _, status, kind in self.ROWS: - attr = next(a for _, _, text, a in screen.strings - if text.strip() == status) - self.assertEqual(attr, want[kind], status) - - def test_optional_name_kind_colors_the_name_column(self): - # 4-element rows: the 4th value is a theme kind for the name. - rows = [("gone", "unavailable", "err", "dim"), - ("here", "running", "ok", "body")] - screen = FakeScreen(keys=[10]) - tui.menu(screen, "Hub", [("Quit", "quit")], table_rows=rows) - drawn = {text.rstrip(): attr for _, _, text, attr in screen.strings} - self.assertEqual(drawn["gone"], tui._THEME["dim"]) - self.assertEqual(drawn["here"], tui._THEME["body"]) - - def test_three_element_rows_default_to_body_names(self): - screen = FakeScreen(keys=[10]) - tui.menu(screen, "Hub", [("Quit", "quit")], - table_title="Backend status", table_rows=self.ROWS) - for name, _, _ in self.ROWS: - attr = next(a for _, _, text, a in screen.strings - if text.rstrip() == name) - self.assertEqual(attr, tui._THEME["body"], name) - - def test_table_title_is_dim_and_left_aligned(self): - screen = FakeScreen(keys=[10]) - tui.menu(screen, "Hub", [("Quit", "quit")], - table_title="Backend status", table_rows=self.ROWS) - x0, _ = self.dialog_box(screen) - margin = x0 + 1 + tui.Frame.LIST_MARGIN - x, attr = next((x, a) for _, x, text, a in screen.strings - if text == "Backend status") - self.assertEqual(x, margin) - self.assertEqual(attr, tui._THEME["dim"]) - - def test_table_does_not_paint_over_the_border(self): - screen = FakeScreen(keys=[10]) - tui.menu(screen, "Hub", [("Quit", "quit")], - table_title="Backend status", table_rows=self.ROWS) - self.assert_inside_border(screen) - - -class ConfirmTests(TuiTestCase): - def test_tab_switches_and_enter_activates(self): - screen = FakeScreen(keys=[9, 10]) - self.assertFalse(tui.confirm(screen, "Overwrite?", default=True)) - - def test_y_answers_directly(self): - screen = FakeScreen(keys=[ord("y")]) - self.assertTrue(tui.confirm(screen, "Overwrite?", default=False)) - - def test_enter_takes_the_default(self): - screen = FakeScreen(keys=[10]) - self.assertTrue(tui.confirm(screen, "Overwrite?", default=True)) - - def test_esc_aborts_without_cancel_value(self): - screen = FakeScreen(keys=[27]) - with self.assertRaises(tui.WizardCancelled): - tui.confirm(screen, "Overwrite?", default=True) - - def test_esc_returns_cancel_value(self): - marker = object() - screen = FakeScreen(keys=[27]) - self.assertIs( - tui.confirm(screen, "Overwrite?", default=True, - cancel_value=marker), - marker) - - def test_q_returns_cancel_value(self): - marker = object() - screen = FakeScreen(keys=[ord("q")]) - self.assertIs( - tui.confirm(screen, "Overwrite?", default=True, - cancel_value=marker), - marker) - - -class LineEditTests(TuiTestCase): - def test_typing_backspace_and_enter(self): - keys = [ord("c"), ord("d"), FakeCurses.KEY_BACKSPACE, 10] - screen = FakeScreen(keys=keys) - value = tui.line_edit(screen, "Edit", "ab") - self.assertEqual(value, "abc") - - def test_validation_error_keeps_editing(self): - keys = [ord("x"), 10, FakeCurses.KEY_BACKSPACE, 10] - screen = FakeScreen(keys=keys) - value = tui.line_edit( - screen, "Edit", "5", - validate=lambda s: None if s.isdigit() else "digits only") - self.assertEqual(value, "5") - - def test_esc_returns_back_value(self): - marker = object() - screen = FakeScreen(keys=[27]) - self.assertIs( - tui.line_edit(screen, "Edit", "text", back_value=marker), - marker) - - def test_q_stays_typeable_with_back_value(self): - marker = object() - screen = FakeScreen(keys=[ord("q"), 10]) - value = tui.line_edit(screen, "Edit", "te", back_value=marker) - self.assertEqual(value, "teq") - - -class FormTests(TuiTestCase): - def _fields(self): - # Fresh dicts each call: form() edits field values in place, and a - # shared class attribute would leak edits between tests. - return [ - {"key": "fmt", "label": "Format", "kind": "choice", - "value": "m4b", "choices": ["mp3", "m4b", "ogg"]}, - {"key": "chunk", "label": "Chunk", "kind": "text", - "value": "250"}, - ] - - def test_save_returns_current_values(self): - screen = FakeScreen(keys=[9, 10]) # Tab -> Save, Enter - result = tui.form(screen, "Settings", self._fields()) - self.assertEqual(result, {"fmt": "m4b", "chunk": "250"}) - - def test_cancel_returns_back_value(self): - marker = object() - # Tab -> buttons, Left -> Cancel, Enter. - screen = FakeScreen(keys=[9, FakeCurses.KEY_LEFT, 10]) - result = tui.form(screen, "Settings", self._fields(), - back_value=marker) - self.assertIs(result, marker) - - def test_esc_returns_back_value(self): - marker = object() - screen = FakeScreen(keys=[27]) - self.assertIs( - tui.form(screen, "Settings", self._fields(), back_value=marker), - marker) - - def test_q_still_aborts_with_back_value(self): - marker = object() - screen = FakeScreen(keys=[ord("q")]) - with self.assertRaises(tui.WizardCancelled): - tui.form(screen, "Settings", self._fields(), back_value=marker) - - def test_choice_field_picks_another_value(self): - # Enter opens the choice menu on 'm4b' (index 1); Down moves to - # 'ogg', Enter accepts; Tab -> Save, Enter. - screen = FakeScreen(keys=[10, FakeCurses.KEY_DOWN, 10, 9, 10]) - result = tui.form(screen, "Settings", self._fields()) - self.assertEqual(result, {"fmt": "ogg", "chunk": "250"}) - - def test_text_field_edits_then_saves(self): - # Down to the text row, Enter opens the editor, type 'x', Enter, - # then Tab -> Save, Enter. - screen = FakeScreen( - keys=[FakeCurses.KEY_DOWN, 10, ord("x"), 10, 9, 10]) - result = tui.form(screen, "Settings", self._fields()) - self.assertEqual(result, {"fmt": "m4b", "chunk": "250x"}) - - def test_validation_error_refuses_save_then_recovers(self): - fields = [ - {"key": "chunk", "label": "Chunk", "kind": "text", "value": "bad", - "validate": lambda s: None if s.isdigit() else "digits only"}, - {"key": "fmt", "label": "Format", "kind": "text", "value": "x"}, - ] - # Tab->Save(Enter) fails, the flash consumes the next key; Enter - # reopens the editor, Ctrl-U clears 'bad', type '120', Enter; then - # Tab->Save(Enter). - keys = [9, 10, 10, 10, 21, ord("1"), ord("2"), ord("0"), 10, 9, 10] - screen = FakeScreen(keys=keys) - result = tui.form(screen, "Settings", fields) - self.assertEqual(result, {"chunk": "120", "fmt": "x"}) - - def test_rows_left_justified_inside_the_border(self): - screen = FakeScreen(keys=[9, 10]) - tui.form(screen, "Settings", self._fields()) - self.assert_inside_border(screen) - - def test_empty_fields_rejected(self): - with self.assertRaises(ValueError): - tui.form(self.screen, "Settings", []) - - -def _accept_audio_cpp(entry: Path): - """auto_select callback that accepts an 'audio.cpp' checkout root.""" - if entry.name == "audio.cpp" and (entry / "model_specs").is_dir(): - return entry - return None - - -class BrowseDirectoryTests(TuiTestCase): - def setUp(self): - super().setUp() - tmp = tempfile.TemporaryDirectory() - self.addCleanup(tmp.cleanup) - self.root = Path(tmp.name) - for name in ("alpha", "beta", "zulu"): - (self.root / name).mkdir() - (self.root / "noise.txt").write_text("x", encoding="utf-8") - - def _checkout_tree(self): - """A temp dir containing an 'audio.cpp' checkout + a sibling dir.""" - tmp = tempfile.TemporaryDirectory() - self.addCleanup(tmp.cleanup) - root = Path(tmp.name) - (root / "audio.cpp").mkdir() - (root / "audio.cpp" / "model_specs").mkdir() - (root / "other").mkdir() - return root - - def test_listing_rows_left_justified(self): - screen = FakeScreen(keys=[10]) - chosen = tui.browse_directory(screen, "Pick", start=self.root) - self.assertEqual(chosen, self.root.resolve()) - x0, x_right = self.dialog_box(screen) - margin = x0 + 1 + tui.Frame.LIST_MARGIN - for label in ("[ Use this directory ]", "..", - "alpha/", "beta/", "zulu/"): - x = next(x for _, x, text, _ in screen.strings if text == label) - self.assertEqual(x, margin, label) - drawn = " ".join(text for _, _, text, _ in screen.strings) - self.assertNotIn("noise.txt", drawn) - self.assert_inside_border(screen) - - def test_enter_opens_highlighted_subdirectory(self): - keys = [FakeCurses.KEY_DOWN, FakeCurses.KEY_DOWN, 10, 10] - screen = FakeScreen(keys=keys) - chosen = tui.browse_directory(screen, "Pick", start=self.root) - self.assertEqual(chosen, (self.root / "alpha").resolve()) - self.assert_inside_border(screen) - - def test_enter_auto_accepts_matching_subdir(self): - root = self._checkout_tree() - # sel 0 = [ Use this directory ], 1 = .., 2 = audio.cpp/ - keys = [FakeCurses.KEY_DOWN, FakeCurses.KEY_DOWN, 10] - screen = FakeScreen(keys=keys) - chosen = tui.browse_directory(screen, "Pick", start=root, - auto_select=_accept_audio_cpp) - self.assertEqual(chosen, (root / "audio.cpp").resolve()) - - def test_right_auto_accepts_matching_subdir(self): - root = self._checkout_tree() - keys = [FakeCurses.KEY_DOWN, FakeCurses.KEY_DOWN, - FakeCurses.KEY_RIGHT] - screen = FakeScreen(keys=keys) - chosen = tui.browse_directory(screen, "Pick", start=root, - auto_select=_accept_audio_cpp) - self.assertEqual(chosen, (root / "audio.cpp").resolve()) - - def test_use_this_directory_ignores_auto_select(self): - # Enter on '[ Use this directory ]' must accept the listed dir - # without ever consulting auto_select. - root = self._checkout_tree() - calls = [] - - def callback(entry): - calls.append(entry) - return entry # would auto-accept any subdir if consulted - - screen = FakeScreen(keys=[10]) - chosen = tui.browse_directory(screen, "Pick", start=root, - auto_select=callback) - self.assertEqual(chosen, root.resolve()) - self.assertEqual(calls, []) - - def test_auto_select_returning_none_descends_normally(self): - # A non-matching subdir (or a None reply) keeps browsing: Enter - # descends into it, then '[ Use this directory ]' accepts it. - root = self._checkout_tree() - calls = [] - - def callback(entry): - calls.append(entry) - return None - - # sel 0 = use, 1 = .., 2 = audio.cpp/, 3 = other/ - keys = [FakeCurses.KEY_DOWN, FakeCurses.KEY_DOWN, - FakeCurses.KEY_DOWN, 10, 10] - screen = FakeScreen(keys=keys) - chosen = tui.browse_directory(screen, "Pick", start=root, - auto_select=callback) - self.assertEqual(chosen, (root / "other").resolve()) - # auto_select was consulted only for the highlighted 'other/' row. - self.assertEqual([p.name for p in calls], ["other"]) - - def test_esc_returns_back_value(self): - marker = object() - screen = FakeScreen(keys=[27]) - self.assertIs( - tui.browse_directory(screen, "Pick", start=self.root, - back_value=marker), - marker) - - def test_q_still_aborts_with_back_value(self): - marker = object() - screen = FakeScreen(keys=[ord("q")]) - with self.assertRaises(tui.WizardCancelled): - tui.browse_directory(screen, "Pick", start=self.root, - back_value=marker) - - -class CheckboxTreeTests(TuiTestCase): - FAMILIES = [ - {"label": "Family one", "detail": "tts", - "options": [{"key": "pkg-a", "label": "pkg-a", "recommended": True}, - {"key": "pkg-b", "label": "pkg-b", "recommended": False}]}, - {"label": "Family two", "detail": "tts, cloning", - "options": [{"key": "pkg-c", "label": "pkg-c", "recommended": True}]}, - ] - - def test_nothing_selected_by_default(self): - # Nothing is pre-checked: Enter alone flashes and waits, and a - # selection only happens after Space checks an option. The first - # Enter and the flash each consume a key. - screen = FakeScreen(keys=[10, 10, ord(" "), 10]) - picked = tui.checkbox_tree(screen, "Pick models", self.FAMILIES) - self.assertEqual(picked, [(0, "pkg-a")]) - - def test_rows_left_justified_inside_the_border(self): - screen = FakeScreen(keys=[ord(" "), 10]) - tui.checkbox_tree(screen, "Pick models", self.FAMILIES) - x0, x_right = self.dialog_box(screen) - margin = x0 + 1 + tui.Frame.LIST_MARGIN - strings = sorted((y, x, text) for y, x, text, _ in screen.strings) - # Family row: its checkbox starts at the margin. - y_family = next(y for y, _, text in strings if text == "- Family one") - family_box_x = next(x for y, x, text in strings - if y == y_family and text == "[x] ") - self.assertEqual(family_box_x, margin) - # Option row: its checkbox sits one indent (2 columns) deeper. - option_box_x = next(x for y, x, text in strings - if text == "[x] " and y != y_family) - self.assertEqual(option_box_x, margin + 4) - self.assert_inside_border(screen) - - def test_long_indented_options_do_not_paint_over_the_border(self): - # A wide screen keeps the dialog width driven by the option row - # itself (not the footer), the geometry where the old centered - # segments drawing could paint over the right border. - families = [{"label": "F", "detail": "tts", - "options": [{"key": "long", "label": "x" * 60, - "recommended": True}]}] - screen = FakeScreen(keys=[ord(" "), 10], width=120) - picked = tui.checkbox_tree(screen, "Pick", families) - self.assertEqual(picked, [(0, "long")]) - self.assert_inside_border(screen) - - def test_space_checks_then_enter_accepts(self): - screen = FakeScreen(keys=[ord(" "), 10]) - picked = tui.checkbox_tree(screen, "Pick models", self.FAMILIES) - self.assertEqual(picked, [(0, "pkg-a")]) - - def test_empty_families_rejected(self): - with self.assertRaises(ValueError): - tui.checkbox_tree(self.screen, "Pick", []) - - def test_esc_returns_back_value(self): - marker = object() - screen = FakeScreen(keys=[27]) - self.assertIs( - tui.checkbox_tree(screen, "Pick models", self.FAMILIES, - back_value=marker), - marker) - - def test_q_still_aborts_with_back_value(self): - marker = object() - screen = FakeScreen(keys=[ord("q")]) - with self.assertRaises(tui.WizardCancelled): - tui.checkbox_tree(screen, "Pick models", self.FAMILIES, - back_value=marker) - - -class SuspendTests(TuiTestCase): - """tui.suspend leaves curses, runs code, then repaints.""" - - def test_suspend_runs_block_and_restores(self): - ran = [] - with tui.suspend(self.screen): - ran.append("inside") - self.assertEqual(ran, ["inside"]) - - def test_suspend_always_restores_on_exception(self): - class Boom(Exception): - pass - with self.assertRaises(Boom): - with tui.suspend(self.screen): - raise Boom() - - -class FlashTests(TuiTestCase): - """tui.flash shows a notice until any key is pressed.""" - - def test_notice_dismissed_by_any_key(self): - screen = FakeScreen(keys=[10]) - # Should return (None) after consuming one key; not raise. - tui.flash(screen, "a notice", kind="warn") - self.assertEqual(screen.keys, []) - - -if __name__ == "__main__": - unittest.main() |
