Skip to content

Commit

Permalink
Merge pull request #1359 from Textualize/validator-first-set
Browse files Browse the repository at this point in the history
Call validator on first set
  • Loading branch information
willmcgugan committed Dec 16, 2022
2 parents 21ab411 + 14a4ad4 commit 62d5810
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Fixed

- Fixed validator not running on first reactive set https://github.com/Textualize/textual/pull/1359
- Ensure only printable characters are used as key_display https://github.com/Textualize/textual/pull/1361

## [0.6.0] - 2022-12-11
Expand Down
4 changes: 2 additions & 2 deletions src/textual/reactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ def __set__(self, obj: Reactable, value: ReactiveType) -> None:
validate_function = getattr(obj, f"validate_{name}", None)
# Check if this is the first time setting the value
first_set = getattr(obj, f"__first_set_{self.internal_name}", True)
# Call validate, but not on first set.
if callable(validate_function) and not first_set:
# Call validate
if callable(validate_function):
value = validate_function(value)
# If the value has changed, or this is the first time setting the value
if current_value != value or first_set or self._always_update:
Expand Down
36 changes: 31 additions & 5 deletions tests/test_reactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

import pytest

from textual.app import App, ComposeResult
from textual.app import App
from textual.reactive import reactive, var
from textual.widget import Widget

OLD_VALUE = 5_000
NEW_VALUE = 1_000_000
Expand Down Expand Up @@ -81,14 +80,16 @@ async def watch_count(self, old_value: int, new_value: int) -> None:
try:
await asyncio.wait_for(app.watcher_called_event.wait(), timeout=0.05)
except TimeoutError:
pytest.fail("Async watcher wasn't called within timeout when reactive init = True")
pytest.fail(
"Async watcher wasn't called within timeout when reactive init = True")

assert app.count == OLD_VALUE
assert app.watcher_old_value == OLD_VALUE
assert app.watcher_new_value == OLD_VALUE # The value wasn't changed


@pytest.mark.xfail(reason="Reactive watcher is incorrectly always called the first time it is set, even if value is same [issue#1230]")
@pytest.mark.xfail(
reason="Reactive watcher is incorrectly always called the first time it is set, even if value is same [issue#1230]")
async def test_watch_init_false_always_update_false():
class WatcherInitFalse(App):
count = reactive(0, init=False)
Expand Down Expand Up @@ -173,20 +174,45 @@ def watch_value(self, new_value):
assert app.watcher_called_with == OLD_VALUE


@pytest.mark.xfail(reason="Validator methods not running when init=True [issue#1220]")
async def test_validate_init_true():
"""When init is True for a reactive attribute, Textual should call the validator
AND the watch method when the app starts."""
validator_call_count = 0

class ValidatorInitTrue(App):
count = var(5, init=True)

def validate_count(self, value: int) -> int:
nonlocal validator_call_count
validator_call_count += 1
return value + 1

app = ValidatorInitTrue()
async with app.run_test():
app.count = 5
assert app.count == 6 # Validator should run, so value should be 5+1=6
assert validator_call_count == 1


async def test_validate_init_true_set_before_dom_ready():
"""When init is True for a reactive attribute, Textual should call the validator
AND the watch method when the app starts."""
validator_call_count = 0

class ValidatorInitTrue(App):
count = var(5, init=True)

def validate_count(self, value: int) -> int:
nonlocal validator_call_count
validator_call_count += 1
return value + 1

app = ValidatorInitTrue()
app.count = 5
async with app.run_test():
assert app.count == 6 # Validator should run, so value should be 5+1=6
assert validator_call_count == 1



@pytest.mark.xfail(reason="Compute methods not called when init=True [issue#1227]")
Expand Down

0 comments on commit 62d5810

Please sign in to comment.