Repository navigation
Accept None in check_value for optional numeric items #116
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
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # | ||
| # Licensed under the terms of the BSD 3-Clause | ||
| # (see guidata/LICENSE for details) | ||
|
|
||
| """ | ||
| Test check_value of optional numeric items | ||
|
|
||
| Form widgets validate user input with ``check_value``: an empty field of an | ||
| optional numeric item (``allow_none=True``) is converted to None and must be | ||
| accepted, while required items keep rejecting None. | ||
| """ | ||
|
|
||
| import pytest | ||
| from qtpy import QtWidgets as QW | ||
|
|
||
| from guidata.dataset import DataSet | ||
| from guidata.dataset.dataitems import FloatItem, IntItem | ||
| from guidata.dataset.datatypes import DataItemVariable | ||
| from guidata.dataset.qtwidgets import DataSetEditLayout | ||
| from guidata.qthelpers import qt_app_context | ||
|
|
||
|
|
||
| class _OptionalNumbersDataSet(DataSet): | ||
| """Dataset with optional and required numeric items""" | ||
|
|
||
| optional_int = IntItem("Optional int", default=None, min=1, allow_none=True) | ||
| optional_even = IntItem("Optional even", default=None, even=True, allow_none=True) | ||
| optional_float = FloatItem("Optional float", default=None, allow_none=True) | ||
| required_int = IntItem("Required int", default=1, min=1) | ||
|
|
||
|
|
||
| def _get_item(dataset: DataSet, name: str): | ||
| """Return the data item named ``name``""" | ||
| return next(item for item in dataset.get_items() if item.get_name() == name) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("name", ["optional_int", "optional_even", "optional_float"]) | ||
| def test_optional_numeric_item_accepts_none(name: str) -> None: | ||
| """Optional numeric items accept None, e.g. from an empty field""" | ||
| dataset = _OptionalNumbersDataSet() | ||
| variable = DataItemVariable(_get_item(dataset, name), dataset) | ||
|
|
||
| assert variable.from_string("") is None | ||
| assert variable.check_value(None) is True | ||
| assert variable.check_value(None, raise_exception=True) is True | ||
|
|
||
|
|
||
| def test_optional_numeric_item_keeps_value_constraints() -> None: | ||
| """Optional numeric items still validate non-None values""" | ||
| dataset = _OptionalNumbersDataSet() | ||
|
|
||
| assert _get_item(dataset, "optional_int").check_value(0) is False | ||
| assert _get_item(dataset, "optional_int").check_value(2) is True | ||
| assert _get_item(dataset, "optional_even").check_value(3) is False | ||
| assert _get_item(dataset, "optional_even").check_value(4) is True | ||
| with pytest.raises(ValueError): | ||
| _get_item(dataset, "optional_int").check_value(0, raise_exception=True) | ||
|
|
||
|
|
||
| def test_required_numeric_item_rejects_none() -> None: | ||
| """Required numeric items keep rejecting None""" | ||
| item = _get_item(_OptionalNumbersDataSet(), "required_int") | ||
|
|
||
| assert item.check_value(None) is False | ||
| with pytest.raises(TypeError): | ||
| item.check_value(None, raise_exception=True) | ||
|
|
||
|
|
||
| def test_empty_optional_numeric_fields_are_valid_in_form() -> None: | ||
| """A form with empty optional numeric fields passes validation""" | ||
| with qt_app_context(): | ||
| dialog = QW.QDialog() | ||
| dataset = _OptionalNumbersDataSet() | ||
| layout = DataSetEditLayout(dialog, dataset, QW.QGridLayout(dialog)) | ||
| try: | ||
| assert layout.check_all_values() is True | ||
| layout.accept_changes() | ||
| assert dataset.optional_int is None | ||
| assert dataset.optional_float is None | ||
| finally: | ||
| dialog.deleteLater() | ||
|
|
||
|
|
||
| def _get_widget(layout: DataSetEditLayout, name: str): | ||
| """Return the terminal widget of the data item named ``name``""" | ||
| return next( | ||
| widget | ||
| for widget in layout.get_terminal_widgets() | ||
| if widget.item.item.get_name() == name | ||
| ) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("name", ["optional_int", "optional_float"]) | ||
| @pytest.mark.parametrize("text", ["abc", "1/0", "1e"]) | ||
| def test_malformed_optional_numeric_input_is_invalid(name: str, text: str) -> None: | ||
| """Malformed nonempty input in an optional numeric field blocks the form""" | ||
| with qt_app_context(): | ||
| dialog = QW.QDialog() | ||
| dataset = _OptionalNumbersDataSet() | ||
| layout = DataSetEditLayout(dialog, dataset, QW.QGridLayout(dialog)) | ||
| try: | ||
| widget = _get_widget(layout, name) | ||
| widget.edit.setText(text) | ||
| assert widget.check() is False | ||
| assert layout.check_all_values() is False | ||
| widget.edit.setText(" ") | ||
| assert widget.check() is True | ||
| assert layout.check_all_values() is True | ||
| finally: | ||
| dialog.deleteLater() | ||
|
|
||
|
|
||
| def test_display_callback_ignores_malformed_optional_numeric_input() -> None: | ||
| """Only empty or valid input triggers the display callback""" | ||
| values = [] | ||
|
|
||
| class _CallbackDataSet(DataSet): | ||
| """Dataset with an optional numeric item having a display callback""" | ||
|
|
||
| value = IntItem("Value", default=None, allow_none=True).set_prop( | ||
| "display", callback=lambda _instance, _item, value: values.append(value) | ||
| ) | ||
|
|
||
| with qt_app_context(): | ||
| dialog = QW.QDialog() | ||
| dataset = _CallbackDataSet() | ||
| layout = DataSetEditLayout(dialog, dataset, QW.QGridLayout(dialog)) | ||
| try: | ||
| widget = _get_widget(layout, "value") | ||
| values.clear() | ||
| widget.edit.setText("abc") | ||
| assert not values | ||
| widget.edit.setText("3") | ||
| widget.edit.setText("") | ||
| assert values == [3, None] | ||
| finally: | ||
| dialog.deleteLater() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.