Skip to content

Fix valid_empty #5909

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,7 @@ sandbox/

# Used by mkdocs-material social plugin
.cache

# Wing Pro configuration files
*.wp[ru]

2 changes: 1 addition & 1 deletion docs/widgets/input.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ as seen for `Palindrome` in the example above.

#### Validate Empty

If you set `valid_empty=True` then empty values will bypass any validators, and empty values will be considered valid.
Empty values _always_ bypass validation, and are considered valid only when `valid_empty=True`.

## Reactive Attributes

Expand Down
35 changes: 18 additions & 17 deletions src/textual/widgets/_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]] = {
Expand Down Expand Up @@ -187,9 +187,9 @@ class Input(ScrollView):
}

&:focus {
border: tall $border;
border: tall $border;
background-tint: $foreground 5%;

}
&>.input--cursor {
background: $input-cursor-background;
Expand All @@ -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 {
Expand All @@ -224,8 +224,8 @@ class Input(ScrollView):
}
&.-invalid:focus {
border: tall ansi_red;
}
}

}
}

Expand Down Expand Up @@ -553,13 +553,13 @@ def _watch_valid_empty(self) -> None:
def validate(self, value: str) -> ValidationResult | None:
"""Run all the validators associated with this Input on the supplied value.

Runs all validators, combines the result into one. If any of the validators
failed, the combined result will be a failure. If no validators are present,
None will be returned. This also sets the `-invalid` CSS class on the Input
if the validation fails, and sets the `-valid` CSS class on the Input if
the validation succeeds.
Runs all validators, combines the result into one. If any of the
validators failed, the combined result will be a failure. If no
validators are present, or the value is empty, None will be returned.
After validation the result is reflected in the DOM, and the
Input will either have the `-valid` or `-invalid` CSS class.

Returns:
Returns:
A ValidationResult indicating whether *all* validators succeeded or not.
That is, if *any* validator fails, the result will be an unsuccessful
validation.
Expand All @@ -571,13 +571,14 @@ def set_classes() -> None:
self.set_class(not valid, "-invalid")
self.set_class(valid, "-valid")

# If no validators are supplied, and therefore no validation occurs, we return None.
if not self.validators:
self._valid = True
# Empty values never run the validators.
if not value:
self._valid = self.valid_empty
set_classes()
return None

if self.valid_empty and not value:
# If no validators are supplied, and therefore no validation occurs, we return None.
if not self.validators:
self._valid = True
set_classes()
return None
Expand Down
21 changes: 21 additions & 0 deletions tests/input/test_input_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,24 @@ async def test_valid_empty():

assert input.has_class("-valid")
assert not input.has_class("-invalid")


async def test_invalid_empty():

class InvalidEmptyApp(App):

def compose(self) -> ComposeResult:
yield Input(valid_empty=False, id="test-in", value="x")

app = InvalidEmptyApp()
async 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')

2 changes: 1 addition & 1 deletion tests/snapshot_tests/test_snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -3532,7 +3532,7 @@ class FocusWithinTransparentApp(App[None]):
"""

def compose(self) -> ComposeResult:
yield Input(placeholder="This is here to escape to")
yield Input(placeholder="This is here to escape to", valid_empty=True)
with Panel():
yield OptionList(*["This is an option" for _ in range(30)])
yield Input(placeholder="Escape out via here for the bug")
Expand Down
Loading