Skip to content

Commit 61fe59f

Browse files
authored
Merge pull request #848 from lognaturel/repeat-reference-geometry
Fix reference-geometry from repeat
2 parents 25cb119 + 5c4900a commit 61fe59f

3 files changed

Lines changed: 104 additions & 20 deletions

File tree

pyxform/question.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,27 @@ def build_xml(self, survey: "Survey"):
668668

669669

670670
class GeoQuestion(_SupportsItemset):
671+
def _build_itemset_reference(
672+
self, question: "Question", survey: "Survey"
673+
) -> ItemsetNode | None:
674+
"""Build a geo-specific itemset node using a reference to a repeat."""
675+
if self.itemset_has_ref:
676+
path = survey.insert_xpaths(
677+
text=self.itemset, context=question, reference_parent=True
678+
).strip()
679+
# A default choice_filter is added here as a workaround for Collect appending
680+
# an index predicate (e.g. "/data/r1[1]") to absolute references in a repeat
681+
# context (the whole nodeset is desired for the user to choose from). Assumes
682+
# that Collect will not alter or replace an existing predicate, and also that
683+
# pyxform emits an absolute reference for this topology. See pyxform/#848.
684+
choice_filter = "./geometry != ''"
685+
return ItemsetNode(
686+
value_ref="geometry",
687+
label_ref="geometry",
688+
nodeset=path,
689+
choice_filter=choice_filter,
690+
)
691+
671692
def build_xml(self, survey: "Survey"):
672693
result = self._build_xml(survey=survey)
673694

tests/test_geo.py

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -522,15 +522,15 @@ def test_entity_list__choice_filter__ok(self):
522522
)
523523

524524
def test_pyxform_reference__ok(self):
525-
"""Should find that a child itemset is emitted, with reference-specific value/label."""
525+
"""Should find that a child itemset is emitted, with geometry value/label."""
526526
# RG005 RG007 RG010 RG016
527527
md = """
528528
| survey |
529-
| | type | name | label | parameters |
530-
| | begin_repeat | r1 | R1 | |
531-
| | text | q1 | Q1 | |
532-
| | end_repeat | r1 | | |
533-
| | {type} | q2 | Q2 | reference-geometry=${{r1}} |
529+
| | type | name | label | parameters |
530+
| | begin_repeat | r1 | R1 | |
531+
| | text | geometry | Q1 | |
532+
| | end_repeat | r1 | | |
533+
| | {type} | q2 | Q2 | reference-geometry=${{r1}} |
534534
"""
535535
for t in GEO_TYPES:
536536
with self.subTest(t):
@@ -541,9 +541,9 @@ def test_pyxform_reference__ok(self):
541541
xpq.model_instance_bind("q2", t),
542542
xpq.body_itemset(
543543
q_name="q2",
544-
nodeset="/test_name[./r1 != '']",
545-
value_ref="r1",
546-
label_ref="r1",
544+
nodeset="/test_name/r1[./geometry != '']",
545+
value_ref="geometry",
546+
label_ref="geometry",
547547
extra_q_assertions="and not(@reference-geometry)",
548548
),
549549
],
@@ -554,25 +554,54 @@ def test_pyxform_reference__choice_filter__ok(self):
554554
# RG005 RG007 RG010 RG014 RG016
555555
md = """
556556
| survey |
557-
| | type | name | label | parameters | choice_filter |
558-
| | begin_repeat | r1 | R1 | | |
559-
| | text | q1 | Q1 | | |
560-
| | end_repeat | r1 | | | |
561-
| | {type} | q2 | Q2 | reference-geometry=${{r1}} | ${{q1}} = 1 |
557+
| | type | name | label | parameters | choice_filter |
558+
| | begin_repeat | r1 | R1 | | |
559+
| | geopoint | geometry | Q1 | | |
560+
| | text | q2 | Q2 |
561+
| | end_repeat | r1 | | | |
562+
| | {type} | q3 | Q3 | reference-geometry=${{r1}} | ${{q2}} = 1 |
562563
"""
563564
for t in GEO_TYPES:
564565
with self.subTest(t):
565566
self.assertPyxformXform(
566567
md=md.format(type=t),
567568
xml__xpath_match=[
568569
xpq.model_instance_item("r1[not(@jr:template)]"),
569-
xpq.model_instance_bind("q2", t),
570+
xpq.model_instance_bind("q3", t),
570571
xpq.body_itemset(
571-
q_name="q2",
572-
nodeset="/test_name[ ./r1/q1 = 1]",
573-
value_ref="r1",
574-
label_ref="r1",
572+
q_name="q3",
573+
nodeset="/test_name/r1[ ./q2 = 1]",
574+
value_ref="geometry",
575+
label_ref="geometry",
576+
extra_q_assertions="and not(@reference-geometry)",
577+
),
578+
],
579+
)
580+
581+
def test_pyxform_reference__inside_repeat__ok(self):
582+
"""Should find that a child itemset is emitted, with geometry value/label."""
583+
# RG005 RG007 RG010 RG016
584+
md = """
585+
| survey |
586+
| | type | name | label | parameters |
587+
| | begin_repeat | r1 | R1 | |
588+
| | {type} | geometry | Q1 | reference-geometry=${{r1}} |
589+
| | end_repeat | r1 | | |
590+
"""
591+
for t in GEO_TYPES:
592+
with self.subTest(t):
593+
self.assertPyxformXform(
594+
md=md.format(type=t),
595+
xml__xpath_match=[
596+
xpq.model_instance_item("r1[not(@jr:template)]"),
597+
xpq.model_instance_bind("r1/geometry", t),
598+
xpq.body_itemset(
599+
q_name="r1/geometry",
600+
nodeset="/test_name/r1[./geometry != '']",
601+
value_ref="geometry",
602+
label_ref="geometry",
575603
extra_q_assertions="and not(@reference-geometry)",
604+
body_path="/x:group/x:repeat",
576605
),
577606
],
578607
)
@@ -677,6 +706,39 @@ def test_select_from_file__choice_filter__ok(self):
677706
],
678707
)
679708

709+
def test_select_from_file__params_value_label__ok(self):
710+
"""Should find that a child itemset is emitted, with default value/label."""
711+
# RG005 RG006 RG012 RG016
712+
md = """
713+
| survey |
714+
| | type | name | label | parameters |
715+
| | select_one_from_file s1{ext} | q1 | Q1 | value=v, label=l |
716+
| | {type} | q2 | Q2 | reference-geometry=s1 |
717+
"""
718+
for t in GEO_TYPES:
719+
for ext in co.EXTERNAL_INSTANCE_EXTENSIONS:
720+
with self.subTest((t, ext)):
721+
self.assertPyxformXform(
722+
md=md.format(type=t, ext=ext),
723+
xml__xpath_match=[
724+
xpq.model_instance_exists("s1"),
725+
# The "select from file" params are separate to reference-geometry.
726+
xpq.body_itemset(
727+
q_name="q1",
728+
q_type="select1",
729+
nodeset="instance('s1')/root/item",
730+
value_ref="v",
731+
label_ref="l",
732+
),
733+
xpq.model_instance_bind("q2", t),
734+
xpq.body_itemset(
735+
q_name="q2",
736+
nodeset="instance('s1')/root/item",
737+
extra_q_assertions="and not(@reference-geometry)",
738+
),
739+
],
740+
)
741+
680742
def test_select_one_external__ok(self):
681743
"""Should find that a child itemset is emitted, with default value/label."""
682744
# RG005 RG006 RG011 RG015 RG016

tests/xpath_helpers/questions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,11 @@ def body_itemset(
111111
value_ref: str = "name",
112112
label_ref: str = "label",
113113
extra_q_assertions: str = "",
114+
body_path: str = "",
114115
) -> str:
115116
"""Body has a direct child control with an itemset, and no inline items."""
116117
return rf"""
117-
/h:html/h:body/x:{q_type}[
118+
/h:html/h:body{body_path}/x:{q_type}[
118119
@ref="/test_name/{q_name}"
119120
and ./x:itemset[@nodeset="{nodeset}"]
120121
and ./x:itemset/x:value[@ref="{value_ref}"]

0 commit comments

Comments
 (0)