Skip to content

Commit

Permalink
Update Prepop branch with changes from main (#1119)
Browse files Browse the repository at this point in the history
  • Loading branch information
berroar authored Jun 1, 2023
1 parent 98962e7 commit 3e45486
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 58 deletions.
7 changes: 2 additions & 5 deletions app/forms/fields/decimal_field_with_separator.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from decimal import Decimal, InvalidOperation

from babel import numbers
from wtforms import DecimalField

from app.settings import DEFAULT_LOCALE
from app.helpers.form_helpers import sanitise_number


class DecimalFieldWithSeparator(DecimalField):
Expand All @@ -23,8 +22,6 @@ def __init__(self, **kwargs):
def process_formdata(self, valuelist):
if valuelist:
try:
self.data = Decimal(
valuelist[0].replace(numbers.get_group_symbol(DEFAULT_LOCALE), "")
)
self.data = Decimal(sanitise_number(valuelist[0]))
except (ValueError, TypeError, InvalidOperation):
pass
7 changes: 2 additions & 5 deletions app/forms/fields/integer_field_with_separator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from babel import numbers
from wtforms import IntegerField

from app.settings import DEFAULT_LOCALE
from app.helpers.form_helpers import sanitise_number


class IntegerFieldWithSeparator(IntegerField):
Expand All @@ -21,8 +20,6 @@ def __init__(self, **kwargs):
def process_formdata(self, valuelist):
if valuelist:
try:
self.data = int(
valuelist[0].replace(numbers.get_group_symbol(DEFAULT_LOCALE), "")
)
self.data = int(sanitise_number(valuelist[0]))
except ValueError:
pass
44 changes: 12 additions & 32 deletions app/forms/validators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import math
import re
from datetime import datetime, timezone
from decimal import Decimal, InvalidOperation
Expand All @@ -19,10 +20,14 @@
DecimalFieldWithSeparator,
IntegerFieldWithSeparator,
)
from app.jinja_filters import format_number, get_formatted_currency
from app.helpers.form_helpers import (
format_message_with_title,
format_playback_value,
sanitise_mobile_number,
sanitise_number,
)
from app.questionnaire.questionnaire_store_updater import QuestionnaireStoreUpdater
from app.questionnaire.rules.utils import parse_datetime
from app.utilities import safe_content

if TYPE_CHECKING:
from app.forms.questionnaire_form import QuestionnaireForm # pragma: no cover
Expand All @@ -49,15 +54,12 @@ def __call__(
field: Union[DecimalFieldWithSeparator, IntegerFieldWithSeparator],
) -> None:
try:
Decimal(
field.raw_data[0].replace(
numbers.get_group_symbol(flask_babel.get_locale()), ""
)
)
# number is sanitised to guard against inputs like `,NaN_` etc
number = Decimal(sanitise_number(number=field.raw_data[0]))
except (ValueError, TypeError, InvalidOperation, AttributeError) as exc:
raise validators.StopValidation(self.message) from exc

if "e" in field.raw_data[0].lower():
if "e" in field.raw_data[0].lower() or math.isnan(number):
raise validators.StopValidation(self.message)


Expand Down Expand Up @@ -126,6 +128,7 @@ def __call__(
field: Union[DecimalFieldWithSeparator, IntegerFieldWithSeparator],
) -> None:
value: Union[int, Decimal] = field.data

if value is not None:
error_message = self.validate_minimum(value) or self.validate_maximum(value)
if error_message:
Expand Down Expand Up @@ -179,11 +182,7 @@ def __init__(self, max_decimals: int = 0, messages: OptionalMessage = None):
def __call__(
self, form: "QuestionnaireForm", field: DecimalFieldWithSeparator
) -> None:
data = (
field.raw_data[0]
.replace(numbers.get_group_symbol(flask_babel.get_locale()), "")
.replace(" ", "")
)
data = sanitise_number(field.raw_data[0])
decimal_symbol = numbers.get_decimal_symbol(flask_babel.get_locale())
if data and decimal_symbol in data:
if self.max_decimals == 0:
Expand Down Expand Up @@ -450,20 +449,6 @@ def _is_valid(
raise NotImplementedError(f"Condition '{condition}' is not implemented")


def format_playback_value(
value: Union[float, Decimal], currency: Optional[str] = None
) -> str:
if currency:
return get_formatted_currency(value, currency)

formatted_number: str = format_number(value)
return formatted_number


def format_message_with_title(error_message: str, question_title: str) -> str:
return error_message % {"question_title": safe_content(question_title)}


class MutuallyExclusiveCheck:
def __init__(self, question_title: str, messages: OptionalMessage = None):
self.messages = {**error_messages, **(messages or {})}
Expand Down Expand Up @@ -492,11 +477,6 @@ def __call__(
raise validators.ValidationError(message)


def sanitise_mobile_number(data: str) -> str:
data = re.sub(r"[\s.,\t\-{}\[\]()/]", "", data)
return re.sub(r"^(0{1,2}44|\+44|0)", "", data)


class MobileNumberCheck:
def __init__(self, message: OptionalMessage = None):
self.message = message or error_messages["INVALID_MOBILE_NUMBER"]
Expand Down
33 changes: 33 additions & 0 deletions app/helpers/form_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import re
from decimal import Decimal

import flask_babel
from babel import numbers

from app.jinja_filters import format_number, get_formatted_currency
from app.utilities import safe_content


def sanitise_number(number: str) -> str:
return (
number.replace(numbers.get_group_symbol(flask_babel.get_locale()), "")
.replace("_", "")
.replace(" ", "")
)


def sanitise_mobile_number(data: str) -> str:
data = re.sub(r"[\s.,\t\-{}\[\]()/]", "", data)
return re.sub(r"^(0{1,2}44|\+44|0)", "", data)


def format_playback_value(value: float | Decimal, currency: str | None = None) -> str:
if currency:
return get_formatted_currency(value, currency)

formatted_number: str = format_number(value)
return formatted_number


def format_message_with_title(error_message: str, question_title: str) -> str:
return error_message % {"question_title": safe_content(question_title)}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"@wdio/mocha-framework": "^8.3.0",
"@wdio/spec-reporter": "^8.3.0",
"chai": "^4.3.6",
"chromedriver": "^112.0.0",
"chromedriver": "^113.0.0",
"eslint": "^8.10.0",
"eslint-cli": "^1.1.1",
"eslint-config-standard": "^14.1.1",
Expand Down
41 changes: 41 additions & 0 deletions tests/app/forms/test_custom_fields.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from decimal import Decimal

import pytest
from wtforms.fields import Field

Expand All @@ -21,6 +23,7 @@ def test_text_area_supports_maxlength_property(mock_form):
assert text_area.maxlength == 20


@pytest.mark.usefixtures("gb_locale")
def test_integer_field(mock_form):
integer_field = IntegerFieldWithSeparator(_form=mock_form, name="aName")
assert isinstance(integer_field, Field)
Expand All @@ -31,6 +34,44 @@ def test_integer_field(mock_form):
pytest.fail("Exceptions should not thrown by CustomIntegerField")


@pytest.mark.usefixtures("gb_locale")
@pytest.mark.parametrize(
"number_input, result",
[
("_110", 110),
("1_10", 110),
("1__10", 110),
("1_1,0", 110),
("_1_1,0,0", 1100),
("1.10", None),
],
)
def test_integer_field_inputs(mock_form, number_input, result):
integer_field = IntegerFieldWithSeparator(_form=mock_form, name="aName")
integer_field.process_formdata([number_input])

assert integer_field.data == result


@pytest.mark.usefixtures("gb_locale")
@pytest.mark.parametrize(
"number_input, result",
[
("1_1,0", Decimal("110")),
("1.10", Decimal("1.1")),
("_1.1_0", Decimal("1.1")),
("_1.1_0,0", Decimal("1.1")),
("_1._1,0,0", Decimal("1.1")),
],
)
def test_decimal_field_inputs(mock_form, number_input, result):
decimal_field = DecimalFieldWithSeparator(_form=mock_form, name="aName")
decimal_field.process_formdata([number_input])

assert decimal_field.data == result


@pytest.mark.usefixtures("gb_locale")
def test_decimal_field(mock_form):
decimal_field = DecimalFieldWithSeparator(_form=mock_form, name="aName")
assert isinstance(decimal_field, Field)
Expand Down
18 changes: 8 additions & 10 deletions tests/app/forms/validation/test_number_validator.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from decimal import Decimal

import pytest
from wtforms.validators import StopValidation, ValidationError

Expand All @@ -7,12 +9,7 @@

@pytest.mark.parametrize(
"value",
(
[None],
[""],
["a"],
["2E2"],
),
([None], [""], ["a"], ["2E2"], ["NaN"], [",NaN_"]),
)
@pytest.mark.usefixtures("gb_locale")
def test_number_validator_raises_StopValidation(
Expand Down Expand Up @@ -49,12 +46,13 @@ def test_decimal_validator_raises_StopValidation(
@pytest.mark.parametrize(
"value",
(
["0"],
["10"],
["-10"],
"0",
"10",
"-10",
),
)
@pytest.mark.usefixtures("gb_locale")
def test_number_validator(number_check, value, mock_form, mock_field):
mock_field.raw_data = value
mock_field.raw_data = [value]
mock_field.data = Decimal(value)
number_check(mock_form, mock_field)
18 changes: 18 additions & 0 deletions tests/functional/spec/error_messages.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import AboutYou from "../generated_pages/multiple_answers/about-you-block.page";
import BlockPage from "../generated_pages/percentage/block.page";

async function answerAllButOne() {
await $(AboutYou.textfield()).setValue("John Doe");
Expand Down Expand Up @@ -135,3 +136,20 @@ describe("Error Messages", () => {
await expect(await $(AboutYou.textarea()).isFocused()).to.be.true;
});
});
describe("Error Message NaN value", () => {
beforeEach(async () => {
await browser.openQuestionnaire("test_percentage.json");
});
it("Given a NaN value was entered on percentage question, When the error is displayed, Then the error message is correct", async () => {
await $(BlockPage.answer()).setValue("NaN");
await $(BlockPage.submit()).click();
await expect(await $(BlockPage.errorHeader()).getText()).to.equal("There is a problem with your answer");
await expect(await $(BlockPage.answerErrorItem()).getText()).to.equal("Enter a number");
});
it("Given a NaN value with separators was entered on percentage question, When the error is displayed, Then the error message is correct", async () => {
await $(BlockPage.answer()).setValue(",NaN_");
await $(BlockPage.submit()).click();
await expect(await $(BlockPage.errorHeader()).getText()).to.equal("There is a problem with your answer");
await expect(await $(BlockPage.answerErrorItem()).getText()).to.equal("Enter a number");
});
});
3 changes: 2 additions & 1 deletion tests/functional/spec/textfield_suggestions.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ describe("Suggestions", () => {
await browser.keys(" United");
await suggestionsList.waitForExist();
await expect(await $$(".ons-js-autosuggest-listbox li").length).to.not.equal(0);
await suggestionsOption.click();
// TODO there is an issue with the load-time of the auto-suggest dropdown causing this test to fail. Uncomment when this has been resolved.
// await suggestionsOption.click();
await $(MultipleSuggestionsPage.submit()).click();
await expect(await browser.getUrl()).to.contain(SubmitPage.url());
});
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1786,10 +1786,10 @@ chrome-launcher@^0.15.0:
is-wsl "^2.2.0"
lighthouse-logger "^1.0.0"

chromedriver@^112.0.0:
version "112.0.1"
resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-112.0.1.tgz#b1d23720613d7afa17cde6d5057e765109e07e69"
integrity sha512-ieQzvellbtPY4MUrFzzayC1bZa/HoBsGXejUQJhAPWcYALxtkjUZNUYWbojMjIzf8iIhVda9VvdXiRKqdlN7ow==
chromedriver@^113.0.0:
version "113.0.0"
resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-113.0.0.tgz#d4855f156ee51cea4282e04aadd29fa154e44dbb"
integrity sha512-UnQlt2kPicYXVNHPzy9HfcWvEbKJjjKAEaatdcnP/lCIRwuSoZFVLH0HVDAGdbraXp3dNVhfE2Qx7gw8TnHnPw==
dependencies:
"@testim/chrome-version" "^1.1.3"
axios "^1.2.1"
Expand Down

0 comments on commit 3e45486

Please sign in to comment.