Skip to content

Commit

Permalink
Merge pull request #2596 from PistachioCannoli/fix-2325-textinput-val…
Browse files Browse the repository at this point in the history
…idation
  • Loading branch information
freakboy3742 authored May 23, 2024
2 parents 2bf76fe + b2a7942 commit 2e7c6c8
Show file tree
Hide file tree
Showing 10 changed files with 45 additions and 14 deletions.
3 changes: 1 addition & 2 deletions android/src/toga_android/widgets/textinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,7 @@ def is_valid(self):
return self.native.getError() is None

def _on_change(self):
self.interface.on_change()
self.interface._validate()
self.interface._value_changed()

def _on_confirm(self):
self.interface.on_confirm()
Expand Down
1 change: 1 addition & 0 deletions changes/2325.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Invocation order of TextInput on_change and validation is now correct.
3 changes: 1 addition & 2 deletions cocoa/src/toga_cocoa/widgets/textinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,7 @@ def get_value(self):

def set_value(self, value):
self.native.stringValue = value
self.interface.on_change()
self.interface._validate()
self.interface._value_changed()

def rehint(self):
# Height of a text input is known and fixed.
Expand Down
5 changes: 5 additions & 0 deletions core/src/toga/widgets/textinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ def _validate(self):
else:
self._impl.clear_error()

def _value_changed(self):
"""Validate the current value of the widget and invoke the on_change handler."""
self._validate()
self.on_change()

@property
def on_confirm(self) -> callable:
"""The handler to invoke when the user accepts the value of the widget,
Expand Down
30 changes: 30 additions & 0 deletions core/tests/widgets/test_textinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pytest

import toga
from toga.validators import Number
from toga_dummy.utils import (
EventLog,
assert_action_not_performed,
Expand Down Expand Up @@ -113,6 +114,35 @@ def test_value(widget, value, expected, validator):
on_change_handler.assert_called_once_with(widget)


def test_validation_order():
"""Widget value validation is performed in the correct order."""
results = {}

def on_change(widget):
results["valid"] = widget.is_valid

# Define a validator that only accepts numbers
text_input = toga.TextInput(on_change=on_change, validators=[Number()])

# Widget is initially valid with a number
text_input.value = "1234"

# Change handler was invoked and results are checked
assert results["valid"]

# Widget is invalid with text
text_input.value = "hello"

# Change handler was invoked and results are checked
assert not results["valid"]

# Widget is valid again with a number
text_input.value = "1234"

# Confirm final results are True
assert results["valid"]


@pytest.mark.parametrize(
"value, expected",
[
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/api/widgets/textinput.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ The input can also be provided a list of :ref:`validators <validators>`. A
validator is a function that will be invoked whenever the content of the input
changes. The function should return ``None`` if the current value of the input
is valid; if the current value is invalid, it should return an error message.
When ``on_change`` is invoked, the field will automatically be validated based on
specified validators.

Notes
-----
Expand Down
3 changes: 1 addition & 2 deletions dummy/src/toga_dummy/widgets/textinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ def is_valid(self):
return self._get_value("valid")

def simulate_change(self):
self.interface.on_change()
self.interface._validate()
self.interface._value_changed()

def simulate_confirm(self):
self.interface.on_confirm()
Expand Down
3 changes: 1 addition & 2 deletions gtk/src/toga_gtk/widgets/textinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ def create(self):
self.native.connect("key-press-event", self.gtk_key_press_event)

def gtk_on_change(self, entry):
self.interface.on_change()
self.interface._validate()
self.interface._value_changed()

def gtk_focus_in_event(self, entry, user_data):
self.interface.on_gain_focus()
Expand Down
6 changes: 2 additions & 4 deletions iOS/src/toga_iOS/widgets/textinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ def textFieldDidBeginEditing_(self, textField) -> None:

@objc_method
def textFieldDidChange_(self, textField) -> None:
self.interface.on_change()
self.interface._validate()
self.interface._value_changed()

@objc_method
def textFieldDidEndEditing_(self, textField) -> None:
Expand Down Expand Up @@ -127,8 +126,7 @@ def get_value(self):

def set_value(self, value):
self.native.text = value
self.interface.on_change()
self.interface._validate()
self.interface._value_changed()

def set_alignment(self, value):
self.native.textAlignment = NSTextAlignment(value)
Expand Down
3 changes: 1 addition & 2 deletions winforms/src/toga_winforms/widgets/textinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ def rehint(self):
)

def winforms_text_changed(self, sender, event):
self.interface.on_change()
self.interface._validate()
self.interface._value_changed()

def winforms_key_press(self, sender, event):
if ord(event.KeyChar) == int(WinForms.Keys.Enter):
Expand Down

0 comments on commit 2e7c6c8

Please sign in to comment.