Skip to content

Commit

Permalink
fix: failure when using or_other + translations + group or repeat
Browse files Browse the repository at this point in the history
- in _create_section_from_dict, the context is the nearest "section"
  which can be a survey, group, or repeat. When it's not the survey,
  the survey-level choices dict isn't directly available. So this is
  now tracked by the builder as a class-level object.
- added tests for groups, repeats, groups-in-groups, repeats-in-groups.
  • Loading branch information
lindsay-stevens committed Aug 25, 2023
1 parent 129abe9 commit fd132be
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 2 deletions.
16 changes: 14 additions & 2 deletions pyxform/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ def __init__(self, **kwargs):

# dictionary of setvalue target and value tuple indexed by triggering element
self.setvalues_by_triggering_ref = {}
# For tracking survey-level choices while recursing through the survey.
self.choices: Dict[str, Any] = {}

def set_sections(self, sections):
"""
Expand All @@ -108,10 +110,14 @@ def create_survey_element_from_dict(
self._add_none_option = d["add_none_option"]

if d["type"] in self.SECTION_CLASSES:
if d["type"] == "survey":
self.choices = copy.deepcopy(d.get(constants.CHOICES, {}))

section = self._create_section_from_dict(d)

if d["type"] == "survey":
section.setvalues_by_triggering_ref = self.setvalues_by_triggering_ref
section.choices = self.choices

return section
elif d["type"] == "loop":
Expand Down Expand Up @@ -254,9 +260,15 @@ def _create_section_from_dict(self, d):
survey_element = self.create_survey_element_from_dict(copy.deepcopy(child))
if child["type"].endswith(" or specify other"):
select_question = survey_element[0]
itemset_choices = result["choices"][select_question["itemset"]]
if OR_OTHER_CHOICE not in itemset_choices:
itemset_choices = self.choices.get(select_question["itemset"], None)
if (
itemset_choices is not None
and isinstance(itemset_choices, list)
and OR_OTHER_CHOICE not in itemset_choices
):
itemset_choices.append(OR_OTHER_CHOICE)
# This is required for builder_tests.BuilderTests.test_loop to pass.
self._add_other_option_to_multiple_choice_question(d=child)
if survey_element:
result.add_children(survey_element)

Expand Down
152 changes: 152 additions & 0 deletions tests/test_translations.py
Original file line number Diff line number Diff line change
Expand Up @@ -1404,6 +1404,158 @@ def test_specify_other__with_translations(self):
],
)

def test_specify_other__with_translations__with_group(self):
"""Should add an "other" choice to the itemset instance and an itext label."""
md = """
| survey | | | | |
| | type | name | label | label::eng |
| | begin group | g1 | Group 1 | Group 1 |
| | select_one c1 or_other | q1 | Question 1 | Question A |
| | end group | g1 | | |
| choices | | | | | |
| | list name | name | label | label::eng | label::fr |
| | c1 | na | la | la-e | la-f |
| | c1 | nb | lb | lb-e | |
"""
self.assertPyxformXform(
md=md,
xml__xpath_match=[
xpc.model_itext_choice_text_label_by_pos(
"eng", "c1", ("la-e", "lb-e", "-")
),
xpc.model_itext_choice_text_label_by_pos("fr", "c1", ("la-f", "-", "-")),
xpc.model_itext_choice_text_label_by_pos(
DEFAULT_LANG, "c1", ("la", "lb", "Other")
),
xpq.body_group_select1_itemset("g1", "q1"),
"""
/h:html/h:body/x:group[@ref='/test_name/g1']
/x:input[@ref='/test_name/g1/q1_other']
/x:label[text() = 'Specify other.']
""",
],
)

def test_specify_other__with_translations__with_repeat(self):
"""Should add an "other" choice to the itemset instance and an itext label."""
md = """
| survey | | | | |
| | type | name | label | label::eng |
| | begin repeat | r1 | Repeat 1 | Repeat 1 |
| | select_one c1 or_other | q1 | Question 1 | Question A |
| | end repeat | r1 | | |
| choices | | | | | |
| | list name | name | label | label::eng | label::fr |
| | c1 | na | la | la-e | la-f |
| | c1 | nb | lb | lb-e | |
"""
self.assertPyxformXform(
md=md,
xml__xpath_match=[
xpc.model_itext_choice_text_label_by_pos(
"eng", "c1", ("la-e", "lb-e", "-")
),
xpc.model_itext_choice_text_label_by_pos("fr", "c1", ("la-f", "-", "-")),
xpc.model_itext_choice_text_label_by_pos(
DEFAULT_LANG, "c1", ("la", "lb", "Other")
),
xpq.body_repeat_select1_itemset("r1", "q1"),
"""
/h:html/h:body/x:group[@ref='/test_name/r1']
/x:repeat[@nodeset='/test_name/r1']
/x:input[@ref='/test_name/r1/q1_other']
/x:label[text() = 'Specify other.']
""",
],
)

def test_specify_other__with_translations__with_nested_group(self):
"""Should add an "other" choice to the itemset instance and an itext label."""
md = """
| survey | | | | |
| | type | name | label | label::eng |
| | begin group | g1 | Group 1 | Group 1 |
| | begin group | g2 | Group 2 | Group 2 |
| | select_one c1 or_other | q1 | Question 1 | Question A |
| | end group | g2 | | |
| | end group | g1 | | |
| choices | | | | | |
| | list name | name | label | label::eng | label::fr |
| | c1 | na | la | la-e | la-f |
| | c1 | nb | lb | lb-e | |
"""
self.assertPyxformXform(
md=md,
xml__xpath_match=[
xpc.model_itext_choice_text_label_by_pos(
"eng", "c1", ("la-e", "lb-e", "-")
),
xpc.model_itext_choice_text_label_by_pos("fr", "c1", ("la-f", "-", "-")),
xpc.model_itext_choice_text_label_by_pos(
DEFAULT_LANG, "c1", ("la", "lb", "Other")
),
"""
/h:html/h:body/x:group[@ref='/test_name/g1']
/x:group[@ref='/test_name/g1/g2']/x:select1[
@ref = '/test_name/g1/g2/q1'
and ./x:itemset
and not(./x:item)
]
""",
"""
/h:html/h:body/x:group[@ref='/test_name/g1']
/x:group[@ref='/test_name/g1/g2']
/x:input[@ref='/test_name/g1/g2/q1_other']
/x:label[text() = 'Specify other.']
""",
],
)

def test_specify_other__with_translations__with_nested_repeat(self):
"""Should add an "other" choice to the itemset instance and an itext label."""
md = """
| survey | | | | |
| | type | name | label | label::eng |
| | begin group | g1 | Group 1 | Group 1 |
| | begin repeat | r1 | Repeat 1 | Repeat 1 |
| | select_one c1 or_other | q1 | Question 1 | Question A |
| | end repeat | r1 | | |
| | end group | g1 | | |
| choices | | | | | |
| | list name | name | label | label::eng | label::fr |
| | c1 | na | la | la-e | la-f |
| | c1 | nb | lb | lb-e | |
"""
self.assertPyxformXform(
md=md,
xml__xpath_match=[
xpc.model_itext_choice_text_label_by_pos(
"eng", "c1", ("la-e", "lb-e", "-")
),
xpc.model_itext_choice_text_label_by_pos("fr", "c1", ("la-f", "-", "-")),
xpc.model_itext_choice_text_label_by_pos(
DEFAULT_LANG, "c1", ("la", "lb", "Other")
),
"""
/h:html/h:body/x:group[@ref='/test_name/g1']
/x:group[@ref='/test_name/g1/r1']
/x:repeat[@nodeset='/test_name/g1/r1']
/x:select1[
@ref = '/test_name/g1/r1/q1'
and ./x:itemset
and not(./x:item)
]
""",
"""
/h:html/h:body/x:group[@ref='/test_name/g1']
/x:group[@ref='/test_name/g1/r1']
/x:repeat[@nodeset='/test_name/g1/r1']
/x:input[@ref='/test_name/g1/r1/q1_other']
/x:label[text() = 'Specify other.']
""",
],
)

def test_specify_other__no_translations(self):
"""Should add an "other" choice to the itemset instance, but not use itext."""
md = """
Expand Down
24 changes: 24 additions & 0 deletions tests/xpath_helpers/questions.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,30 @@ def body_select1_itemset(q_name: str):
]
"""

@staticmethod
def body_group_select1_itemset(g_name: str, q_name: str):
"""Body has a select1 with an itemset, and no inline items."""
return fr"""
/h:html/h:body/x:group[@ref='/test_name/{g_name}']/x:select1[
@ref = '/test_name/{g_name}/{q_name}'
and ./x:itemset
and not(./x:item)
]
"""

@staticmethod
def body_repeat_select1_itemset(r_name: str, q_name: str):
"""Body has a select1 with an itemset, and no inline items."""
return fr"""
/h:html/h:body/x:group[@ref='/test_name/{r_name}']
/x:repeat[@nodeset='/test_name/{r_name}']
/x:select1[
@ref = '/test_name/{r_name}/{q_name}'
and ./x:itemset
and not(./x:item)
]
"""

@staticmethod
def body_odk_rank_itemset(q_name: str):
"""Body has a rank with an itemset, and no inline items."""
Expand Down

0 comments on commit fd132be

Please sign in to comment.