Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions pyxform/question.py
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,27 @@ def build_xml(self, survey: "Survey"):


class GeoQuestion(_SupportsItemset):
def _build_itemset_reference(
self, question: "Question", survey: "Survey"
) -> ItemsetNode | None:
"""Build a geo-specific itemset node using a reference to a repeat."""
if self.itemset_has_ref:
path = survey.insert_xpaths(
text=self.itemset, context=question, reference_parent=True
).strip()
# A default choice_filter is added here as a workaround for Collect appending
# an index predicate (e.g. "/data/r1[1]") to absolute references in a repeat
# context (the whole nodeset is desired for the user to choose from). Assumes
# that Collect will not alter or replace an existing predicate, and also that
# pyxform emits an absolute reference for this topology. See pyxform/#848.
choice_filter = "./geometry != ''"
return ItemsetNode(
value_ref="geometry",
label_ref="geometry",
nodeset=path,
choice_filter=choice_filter,
)

def build_xml(self, survey: "Survey"):
result = self._build_xml(survey=survey)

Expand Down
100 changes: 81 additions & 19 deletions tests/test_geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,15 +522,15 @@ def test_entity_list__choice_filter__ok(self):
)

def test_pyxform_reference__ok(self):
"""Should find that a child itemset is emitted, with reference-specific value/label."""
"""Should find that a child itemset is emitted, with geometry value/label."""
# RG005 RG007 RG010 RG016
md = """
| survey |
| | type | name | label | parameters |
| | begin_repeat | r1 | R1 | |
| | text | q1 | Q1 | |
| | end_repeat | r1 | | |
| | {type} | q2 | Q2 | reference-geometry=${{r1}} |
| | type | name | label | parameters |
| | begin_repeat | r1 | R1 | |
| | text | geometry | Q1 | |
| | end_repeat | r1 | | |
| | {type} | q2 | Q2 | reference-geometry=${{r1}} |
"""
for t in GEO_TYPES:
with self.subTest(t):
Expand All @@ -541,9 +541,9 @@ def test_pyxform_reference__ok(self):
xpq.model_instance_bind("q2", t),
xpq.body_itemset(
q_name="q2",
nodeset="/test_name[./r1 != '']",
value_ref="r1",
label_ref="r1",
nodeset="/test_name/r1[./geometry != '']",
value_ref="geometry",
label_ref="geometry",
extra_q_assertions="and not(@reference-geometry)",
),
],
Expand All @@ -554,25 +554,54 @@ def test_pyxform_reference__choice_filter__ok(self):
# RG005 RG007 RG010 RG014 RG016
md = """
| survey |
| | type | name | label | parameters | choice_filter |
| | begin_repeat | r1 | R1 | | |
| | text | q1 | Q1 | | |
| | end_repeat | r1 | | | |
| | {type} | q2 | Q2 | reference-geometry=${{r1}} | ${{q1}} = 1 |
| | type | name | label | parameters | choice_filter |
| | begin_repeat | r1 | R1 | | |
| | geopoint | geometry | Q1 | | |
| | text | q2 | Q2 |
| | end_repeat | r1 | | | |
| | {type} | q3 | Q3 | reference-geometry=${{r1}} | ${{q2}} = 1 |
"""
for t in GEO_TYPES:
with self.subTest(t):
self.assertPyxformXform(
md=md.format(type=t),
xml__xpath_match=[
xpq.model_instance_item("r1[not(@jr:template)]"),
xpq.model_instance_bind("q2", t),
xpq.model_instance_bind("q3", t),
xpq.body_itemset(
q_name="q2",
nodeset="/test_name[ ./r1/q1 = 1]",
value_ref="r1",
label_ref="r1",
q_name="q3",
nodeset="/test_name/r1[ ./q2 = 1]",
value_ref="geometry",
label_ref="geometry",
extra_q_assertions="and not(@reference-geometry)",
),
],
)

def test_pyxform_reference__inside_repeat__ok(self):
"""Should find that a child itemset is emitted, with geometry value/label."""
# RG005 RG007 RG010 RG016
md = """
| survey |
| | type | name | label | parameters |
| | begin_repeat | r1 | R1 | |
| | {type} | geometry | Q1 | reference-geometry=${{r1}} |
| | end_repeat | r1 | | |
"""
for t in GEO_TYPES:
with self.subTest(t):
self.assertPyxformXform(
md=md.format(type=t),
xml__xpath_match=[
xpq.model_instance_item("r1[not(@jr:template)]"),
xpq.model_instance_bind("r1/geometry", t),
xpq.body_itemset(
q_name="r1/geometry",
nodeset="/test_name/r1[./geometry != '']",
value_ref="geometry",
label_ref="geometry",
extra_q_assertions="and not(@reference-geometry)",
body_path="/x:group/x:repeat",
),
],
)
Expand Down Expand Up @@ -677,6 +706,39 @@ def test_select_from_file__choice_filter__ok(self):
],
)

def test_select_from_file__params_value_label__ok(self):
"""Should find that a child itemset is emitted, with default value/label."""
# RG005 RG006 RG012 RG016
md = """
| survey |
| | type | name | label | parameters |
| | select_one_from_file s1{ext} | q1 | Q1 | value=v, label=l |
| | {type} | q2 | Q2 | reference-geometry=s1 |
"""
for t in GEO_TYPES:
for ext in co.EXTERNAL_INSTANCE_EXTENSIONS:
with self.subTest((t, ext)):
self.assertPyxformXform(
md=md.format(type=t, ext=ext),
xml__xpath_match=[
xpq.model_instance_exists("s1"),
# The "select from file" params are separate to reference-geometry.
xpq.body_itemset(
q_name="q1",
q_type="select1",
nodeset="instance('s1')/root/item",
value_ref="v",
label_ref="l",
),
xpq.model_instance_bind("q2", t),
xpq.body_itemset(
q_name="q2",
nodeset="instance('s1')/root/item",
extra_q_assertions="and not(@reference-geometry)",
),
],
)

def test_select_one_external__ok(self):
"""Should find that a child itemset is emitted, with default value/label."""
# RG005 RG006 RG011 RG015 RG016
Expand Down
3 changes: 2 additions & 1 deletion tests/xpath_helpers/questions.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,11 @@ def body_itemset(
value_ref: str = "name",
label_ref: str = "label",
extra_q_assertions: str = "",
body_path: str = "",
) -> str:
"""Body has a direct child control with an itemset, and no inline items."""
return rf"""
/h:html/h:body/x:{q_type}[
/h:html/h:body{body_path}/x:{q_type}[
@ref="/test_name/{q_name}"
and ./x:itemset[@nodeset="{nodeset}"]
and ./x:itemset/x:value[@ref="{value_ref}"]
Expand Down
Loading