diff --git a/pyxform/survey_element.py b/pyxform/survey_element.py index 633bd9d14..0b2ab1af5 100644 --- a/pyxform/survey_element.py +++ b/pyxform/survey_element.py @@ -17,6 +17,7 @@ default_is_dynamic, ) from pyxform.xls2json import print_pyobj_to_json +from typing import TYPE_CHECKING try: from functools import lru_cache @@ -24,6 +25,11 @@ from functools32 import lru_cache +if TYPE_CHECKING: + from pyxform.utils import DetachableElement + from typing import List + + def _overlay(over, under): if type(under) == dict: result = under.copy() @@ -414,19 +420,27 @@ def xml_hint(self): ) return node("hint", hint, toParseString=output_inserted) - def xml_label_and_hint(self): + def xml_label_and_hint(self) -> "List[DetachableElement]": """ Return a list containing one node for the label and if there is a hint one node for the hint. """ result = [] + label_appended = False if self.label or self.media: result.append(self.xml_label()) + label_appended = True + if self.hint or self.guidance_hint: + if not label_appended: + result.append(self.xml_label()) result.append(self.xml_hint()) - if len(result) == 0 or self.guidance_hint and len(result) == 1: - msg = "The survey element named '%s' " "has no label or hint." % self.name + msg = "The survey element named '%s' " "has no label or hint." % self.name + if len(result) == 0: + raise PyXFormError(msg) + # Guidance hint alone is not OK since they may be hidden by default. + if not any((self.label, self.media, self.hint)) and self.guidance_hint: raise PyXFormError(msg) return result diff --git a/pyxform/tests/example_xls/warnings.xls b/pyxform/tests/example_xls/warnings.xls deleted file mode 100644 index 1ad213da9..000000000 Binary files a/pyxform/tests/example_xls/warnings.xls and /dev/null differ diff --git a/pyxform/tests/utils.py b/pyxform/tests/utils.py index 32eed2caf..cca4574c9 100644 --- a/pyxform/tests/utils.py +++ b/pyxform/tests/utils.py @@ -7,6 +7,8 @@ from formencode.doctest_xml_compare import xml_compare from unittest2 import TestCase +import textwrap +from typing import TYPE_CHECKING from pyxform import file_utils from pyxform.builder import create_survey, create_survey_from_path @@ -18,6 +20,11 @@ except ImportError: import configparser + +if TYPE_CHECKING: + from typing import Tuple + + DIR = os.path.dirname(__file__) @@ -127,3 +134,9 @@ def prep_class_config(cls, test_dir="tests"): strings = os.path.join(root, test_dir, "fixtures", "strings.ini") cls.config.read(strings) cls.cls_name = cls.__name__ + + +def prep_for_xml_contains(text: str) -> "Tuple[str]": + """Prep string for finding an exact match to formatted XML text.""" + # noinspection PyRedundantParentheses + return (textwrap.indent(textwrap.dedent(text), " ",),) diff --git a/pyxform/tests/xlsform_spec_test.py b/pyxform/tests/xlsform_spec_test.py index 1833a10b0..f59ad9374 100644 --- a/pyxform/tests/xlsform_spec_test.py +++ b/pyxform/tests/xlsform_spec_test.py @@ -86,24 +86,6 @@ def runTest(self): self.assertXFormEqual(expected_file.read(), actual_file.read()) -class WarningsTest(unittest.TestCase): - """ - Just checks that the number of warnings thrown when reading warnings.xls - doesn't change - """ - - def runTest(self): - filename = "warnings.xls" - path_to_excel_file = os.path.join(DIR, "example_xls", filename) - warnings = [] - pyxform.xls2json.parse_file_to_json( - path_to_excel_file, default_name="warnings", warnings=warnings - ) - self.assertEquals( - len(warnings), 22, "Found " + str(len(warnings)) + " warnings" - ) - - class CalculateWithoutCalculationTest(unittest.TestCase): """ Just checks that calculate field without calculation raises a PyXFormError. diff --git a/pyxform/tests_v1/pyxform_test_case.py b/pyxform/tests_v1/pyxform_test_case.py index ce821ab97..7cea1490a 100644 --- a/pyxform/tests_v1/pyxform_test_case.py +++ b/pyxform/tests_v1/pyxform_test_case.py @@ -153,10 +153,12 @@ def assertPyxformXform(self, **kwargs): * title: (str) * run_odk_validate: (bool) when True, runs ODK Validate process Default value = False because it slows down tests + * warnings: (list) a list to use for storing warnings for inspection. """ debug = kwargs.get("debug", False) expecting_invalid_survey = kwargs.get("errored", False) errors = [] + warnings = kwargs.get("warnings", []) xml_nodes = {} run_odk_validate = kwargs.get("run_odk_validate", False) @@ -165,7 +167,9 @@ def assertPyxformXform(self, **kwargs): try: if "md" in kwargs.keys(): kwargs = self._autoname_inputs(kwargs) - survey = self.md_to_pyxform_survey(kwargs.get("md"), kwargs) + survey = self.md_to_pyxform_survey( + kwargs.get("md"), kwargs, warnings=warnings, + ) elif "ss_structure" in kwargs.keys(): kwargs = self._autoname_inputs(kwargs) survey = self._ss_structure_to_pyxform_survey( diff --git a/pyxform/tests_v1/test_guidance_hint.py b/pyxform/tests_v1/test_guidance_hint.py index 0812e6515..763a8e6e6 100644 --- a/pyxform/tests_v1/test_guidance_hint.py +++ b/pyxform/tests_v1/test_guidance_hint.py @@ -75,12 +75,12 @@ def test_guidance_hint_only(self): """Test guidance_hint only.""" self.assertPyxformXform( name="data", - errored=True, md=""" - | survey | | | | - | | type | name | guidance_hint | - | | string | name | as shown on birth certificate| + | survey | | | | + | | type | name | guidance_hint | + | | string | name | as shown on birth certificate | """, + errored=True, error__contains=["The survey element named 'name' has no label or hint."], ) @@ -88,12 +88,12 @@ def test_multi_language_guidance_only(self): # pylint:disable=C0103 """Test guidance_hint only in multiple languages.""" self.assertPyxformXform( name="data", - errored=True, md=""" - | survey | | | | | - | | type | name | guidance_hint | guidance_hint::French (fr) | - | | string | name | as shown on birth certificate| comme sur le certificat de naissance| + | survey | | | | | + | | type | name | guidance_hint | guidance_hint::French (fr) | + | | string | name | as shown on birth certificate| comme sur le certificat de naissance | """, # noqa + errored=True, error__contains=["The survey element named 'name' has no label or hint."], ) diff --git a/pyxform/tests_v1/test_sheet_columns.py b/pyxform/tests_v1/test_sheet_columns.py index f3c67a830..cb801f362 100644 --- a/pyxform/tests_v1/test_sheet_columns.py +++ b/pyxform/tests_v1/test_sheet_columns.py @@ -3,6 +3,7 @@ Test XLSForm sheet names. """ from pyxform.tests_v1.pyxform_test_case import PyxformTestCase +from pyxform.tests.utils import prep_for_xml_contains class InvalidSurveyColumnsTests(PyxformTestCase): @@ -38,6 +39,32 @@ def test_missing_label(self): error__contains=["no label or hint"], ) + def test_label_node_added_when_hint_given_but_no_label_value(self): + """Should output a label node even if no label is specified.""" + expected = """ + +