From caab7d20dce351052878c6b0cb2eb34c2c4ba4ae Mon Sep 17 00:00:00 2001 From: Steve Holden Date: Sun, 22 Jun 2025 16:41:44 +0100 Subject: [PATCH 1/2] Honour the `valid_empty` flag even when `Input` has no validators. --- src/textual/widgets/_input.py | 17 ++++++++--------- tests/input/test_input_empty_valid.py | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 tests/input/test_input_empty_valid.py diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index 326e535958..8ad9e1b5c9 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -149,7 +149,7 @@ class Input(ScrollView): | ctrl+k | Delete everything to the right of the cursor. | | ctrl+x | Cut selected text. | | ctrl+c | Copy selected text. | - | ctrl+v | Paste text from the clipboard. | + | ctrl+v | Paste text from the clipboard. | """ COMPONENT_CLASSES: ClassVar[set[str]] = { @@ -187,9 +187,9 @@ class Input(ScrollView): } &:focus { - border: tall $border; + border: tall $border; background-tint: $foreground 5%; - + } &>.input--cursor { background: $input-cursor-background; @@ -207,12 +207,12 @@ class Input(ScrollView): } &.-invalid:focus { border: tall $error; - } + } &:ansi { background: ansi_default; color: ansi_default; - &>.input--cursor { + &>.input--cursor { text-style: reverse; } &>.input--placeholder, &>.input--suggestion { @@ -224,8 +224,8 @@ class Input(ScrollView): } &.-invalid:focus { border: tall ansi_red; - } - + } + } } @@ -417,8 +417,7 @@ def __init__( ``` """ self._reactive_valid_empty = valid_empty - self._valid = True - + self._valid = not (input and not valid_empty) self.restrict = restrict if type not in _RESTRICT_TYPES: raise ValueError( diff --git a/tests/input/test_input_empty_valid.py b/tests/input/test_input_empty_valid.py new file mode 100644 index 0000000000..6e9b80fac4 --- /dev/null +++ b/tests/input/test_input_empty_valid.py @@ -0,0 +1,17 @@ +from textual.app import App, ComposeResult +from textual.widgets import Input + + +class InputApp(App): + + def compose(self) -> ComposeResult: + yield Input(valid_empty=False) + + +async def test_cut(): + """Check that cut removes text and places it in the clipboard.""" + app = InputApp() + async with app.run_test() as pilot: + input = app.query_one(Input) + assert input.value == "" + assert not input.is_valid From b2ed9cf1ae46272298db13fde8ce708c6cef1345 Mon Sep 17 00:00:00 2001 From: Steve Holden Date: Tue, 24 Jun 2025 16:22:38 +0100 Subject: [PATCH 2/2] Modifies Input validation to correctly honour `valid_empty`. Formerly an Input with `valid_empty=True` and no validators would validate successfully even with an empty value. --- tests/input/test_input_empty_valid.py | 17 ----------------- tests/input/test_input_validation.py | 20 +++++++++++++++++++- 2 files changed, 19 insertions(+), 18 deletions(-) delete mode 100644 tests/input/test_input_empty_valid.py diff --git a/tests/input/test_input_empty_valid.py b/tests/input/test_input_empty_valid.py deleted file mode 100644 index 6e9b80fac4..0000000000 --- a/tests/input/test_input_empty_valid.py +++ /dev/null @@ -1,17 +0,0 @@ -from textual.app import App, ComposeResult -from textual.widgets import Input - - -class InputApp(App): - - def compose(self) -> ComposeResult: - yield Input(valid_empty=False) - - -async def test_cut(): - """Check that cut removes text and places it in the clipboard.""" - app = InputApp() - async with app.run_test() as pilot: - input = app.query_one(Input) - assert input.value == "" - assert not input.is_valid diff --git a/tests/input/test_input_validation.py b/tests/input/test_input_validation.py index 3ea17191b5..d9878b1533 100644 --- a/tests/input/test_input_validation.py +++ b/tests/input/test_input_validation.py @@ -207,7 +207,6 @@ async def test_none_validate_on_means_all_validations_happen(): await pilot.pause() assert input.has_class("-valid") - async def test_valid_empty(): app = InputApp(None) async with app.run_test() as pilot: @@ -222,3 +221,22 @@ async def test_valid_empty(): assert input.has_class("-valid") assert not input.has_class("-invalid") + + +class InvalidEmptyApp(App): + + def compose(self) -> ComposeResult: + yield Input(valid_empty=False, value="x") + +async def test_invalid_empty(): + app = InvalidEmptyApp() + with app.run_test() as pilot: + input = app.query_one(Input) + + assert input.has_class('-valid') + assert not input.has_class('-invalid') + + await pilot.press('backspace') + assert input.has_class('-invalid') + assert not input.has_class('-valid') +