From 67ce919e529c1ee31dc0303d3c718fc5466f3c45 Mon Sep 17 00:00:00 2001 From: Viraj Patel <144226112+VirajP1002@users.noreply.github.com> Date: Thu, 18 Jan 2024 10:07:31 +0000 Subject: [PATCH] Update a schema to demonstrate minimum/maximum supports floats (#1277) --- app/jinja_filters.py | 25 ++++++++++++---------- app/questionnaire/questionnaire_schema.py | 6 ++++-- app/translations/messages.pot | 4 ++-- schemas/test/en/test_metadata_routing.json | 6 +++--- schemas/test/en/test_numbers.json | 4 ++-- tests/app/forms/test_questionnaire_form.py | 2 +- tests/functional/spec/numbers.spec.js | 15 +++++++++++++ 7 files changed, 41 insertions(+), 21 deletions(-) diff --git a/app/jinja_filters.py b/app/jinja_filters.py index 8f7920e512..b5cdee9898 100644 --- a/app/jinja_filters.py +++ b/app/jinja_filters.py @@ -250,18 +250,21 @@ def get_min_max_value_width( """ This function gets the minimum and maximum value accepted for a question. Which then allows us to use that value to set the width of the textbox to suit that min and max. - """ - - if ( - answer.get(min_max, {}) - and isinstance(answer[min_max]["value"], Mapping) - and answer[min_max]["value"].get("source") == "answers" - ): - schema: QuestionnaireSchema = g.get("schema") - identifier = answer[min_max]["value"].get("identifier") - return schema.min_and_max_map[identifier][min_max] - return len(str(answer.get(min_max, {}).get("value", default_value))) + If the min or max for the answer is a value source but not an "answers" source, such as a calculated or grand calculated summary, + use the length of the default value for the min and max width, as the actual min and max width cannot currently be determined + """ + min_max_value = answer.get(min_max, {}) + if min_max_value and isinstance(answer[min_max]["value"], Mapping): + if answer[min_max]["value"].get("source") == "answers": + schema: QuestionnaireSchema = g.get("schema") + identifier = answer[min_max]["value"]["identifier"] + return schema.min_and_max_map[identifier][min_max] + return len(str(default_value)) + + # Factor out the decimals as it's accounted for in get_width_for_number + result = int(min_max_value.get("value", default_value)) + return len(str(result)) @blueprint.app_template_filter() diff --git a/app/questionnaire/questionnaire_schema.py b/app/questionnaire/questionnaire_schema.py index a4a0671292..1fda8a2778 100644 --- a/app/questionnaire/questionnaire_schema.py +++ b/app/questionnaire/questionnaire_schema.py @@ -2,6 +2,7 @@ from collections import defaultdict from copy import deepcopy from dataclasses import dataclass +from decimal import Decimal from functools import cached_property from typing import Any, Generator, Iterable, Literal, Mapping, Sequence, TypeAlias @@ -167,8 +168,9 @@ def _create_min_max_map( for answer in answers: value = answer.get(min_max, {}).get("value") - if isinstance(value, float | int): - value_length = len(str(value)) + if isinstance(value, float | int | Decimal): + # Factor out the decimals as it's accounted for in jinja_filters.py + value_length = len(str(int(value))) longest_value_length = max(longest_value_length, value_length) diff --git a/app/translations/messages.pot b/app/translations/messages.pot index 991f75061d..ac138a2682 100644 --- a/app/translations/messages.pot +++ b/app/translations/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-01-02 14:50+0000\n" +"POT-Creation-Date: 2024-01-02 11:59+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -41,7 +41,7 @@ msgstr "" msgid "%(from_date)s to %(to_date)s" msgstr "" -#: app/jinja_filters.py:731 +#: app/jinja_filters.py:734 #: templates/partials/summary/collapsible-summary.html:27 #: templates/partials/summary/summary.html:25 msgid "No answer provided" diff --git a/schemas/test/en/test_metadata_routing.json b/schemas/test/en/test_metadata_routing.json index b97f19e384..1142d57f9e 100644 --- a/schemas/test/en/test_metadata_routing.json +++ b/schemas/test/en/test_metadata_routing.json @@ -17,7 +17,7 @@ "type": "string" }, { - "name": "flag_1", + "name": "boolean_flag", "type": "boolean" }, { @@ -63,10 +63,10 @@ "when": { "==": [ { - "identifier": "flag_1", + "identifier": "boolean_flag", "source": "metadata" }, - "true" + true ] } }, diff --git a/schemas/test/en/test_numbers.json b/schemas/test/en/test_numbers.json index 85353843d1..195f2ea576 100644 --- a/schemas/test/en/test_numbers.json +++ b/schemas/test/en/test_numbers.json @@ -48,7 +48,7 @@ "type": "Number", "decimal_places": 2, "minimum": { - "value": -1000 + "value": -1000.98 }, "maximum": { "value": 1000 @@ -65,7 +65,7 @@ "value": 1001 }, "maximum": { - "value": 10000 + "value": 10000.98 } } ], diff --git a/tests/app/forms/test_questionnaire_form.py b/tests/app/forms/test_questionnaire_form.py index 360af156f4..3885064967 100644 --- a/tests/app/forms/test_questionnaire_form.py +++ b/tests/app/forms/test_questionnaire_form.py @@ -1489,7 +1489,7 @@ def test_answer_errors_are_interpolated(app, data_stores): form.validate() answer_errors = form.answer_errors("set-minimum") assert ( - schema.error_messages["NUMBER_TOO_SMALL"] % {"min": "-1,000"} + schema.error_messages["NUMBER_TOO_SMALL"] % {"min": "-1,000.98"} in answer_errors ) diff --git a/tests/functional/spec/numbers.spec.js b/tests/functional/spec/numbers.spec.js index e1e7f0036f..e6c8bb2a49 100644 --- a/tests/functional/spec/numbers.spec.js +++ b/tests/functional/spec/numbers.spec.js @@ -12,7 +12,22 @@ describe("Number validation", () => { before(async () => { await browser.openQuestionnaire("test_numbers.json"); }); + describe("Given I am completing the test numbers questionnaire,", () => { + it("When a minimum value with decimals is used and I enter a value less than the minimum, Then the error message includes the minimum value with the decimals values", async () => { + await $(SetMinMax.setMinimum()).setValue(-1000.99); + await $(SetMinMax.setMaximum()).setValue(1000); + await click(SetMinMax.submit()); + await expect(await $(SetMinMax.errorNumber(1)).getText()).toBe("Enter an answer more than or equal to -1,000.98"); + }); + + it("When a maximum value with decimals is used and I enter a value greater than the maximum, Then the error message includes the minimum value with the decimal values", async () => { + await $(SetMinMax.setMinimum()).setValue(100); + await $(SetMinMax.setMaximum()).setValue(10000.99); + await click(SetMinMax.submit()); + await expect(await $(SetMinMax.errorNumber(1)).getText()).toBe("Enter an answer less than or equal to 10,000.98"); + }); + it("When I am on the set minimum and maximum page, Then each field has a label", async () => { await expect(await $(SetMinMax.setMinimumLabelDescription()).getText()).toBe("This is a description of the minimum value"); await expect(await $(SetMinMax.setMaximumLabelDescription()).getText()).toBe("This is a description of the maximum value");