Skip to content

Commit fbce18a

Browse files
committed
Check tick alignment for labelset values
1 parent e01b6df commit fbce18a

3 files changed

Lines changed: 65 additions & 12 deletions

File tree

pyxform/errors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ class ErrorCode(Enum):
465465
msg=(
466466
"[row : {row}] On the 'survey' sheet, the 'parameters' value is invalid. "
467467
"For the 'range' question type, the parameter 'tick_labelset' choices must "
468-
"be a within the range (between the 'start' and 'end' values, inclusive)."
468+
"be between the 'start' and 'end' values, inclusive."
469469
),
470470
)
471471
RANGE_011 = Detail(

pyxform/validators/pyxform/question_types.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def process_parameter(name: str) -> Decimal | None:
184184
parameters["odk:tick-interval"] = parameters.pop("tick_interval")
185185

186186
if placeholder is not None:
187-
if (abs(placeholder - start) % step) != 0:
187+
if (placeholder - start) % step != 0:
188188
raise PyXFormError(
189189
ErrorCode.RANGE_004.value.format(row=row_number, name="placeholder")
190190
)
@@ -200,28 +200,28 @@ def process_parameter(name: str) -> Decimal | None:
200200
raise PyXFormError(ErrorCode.RANGE_006.value.format(row=row_number))
201201

202202
no_ticks_labels = set()
203-
for label in tick_list:
203+
for item in tick_list:
204204
errored = False
205205
try:
206-
label = Decimal(label.get("name"))
206+
value = Decimal(item.get("name"))
207207
except InvalidOperation:
208208
errored = True
209209

210-
if errored or isinf(label):
210+
if errored or isinf(value):
211211
raise PyXFormError(ErrorCode.RANGE_009.value.format(row=row_number))
212212

213-
if label < start or label > end:
213+
if value < min(start, end) or value > max(start, end):
214214
raise PyXFormError(ErrorCode.RANGE_010.value.format(row=row_number))
215-
if tick_interval is not None and (label % tick_interval) != 0:
215+
if tick_interval is not None and (value - start) % tick_interval != 0:
216216
raise PyXFormError(
217217
ErrorCode.RANGE_011.value.format(row=row_number, name="tick_interval")
218218
)
219-
elif (label % step) != 0:
219+
elif (value - start) % step != 0:
220220
raise PyXFormError(
221221
ErrorCode.RANGE_011.value.format(row=row_number, name="step")
222222
)
223223
if no_ticks_appearance:
224-
no_ticks_labels.add(label)
224+
no_ticks_labels.add(value)
225225

226226
if no_ticks_appearance:
227227
if len(no_ticks_labels) > 2:

tests/test_range.py

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,27 @@ def test_tick_labelset_choice_outside_range__error(self):
751751
error__contains=[ErrorCode.RANGE_010.value.format(row=2)],
752752
)
753753

754+
def test_tick_labelset_choice_outside_inverted_range__error(self):
755+
"""Should raise an error if any tick_labelset choices are outside the inverted range."""
756+
# RL004
757+
md = """
758+
| survey |
759+
| | type | name | label | parameters |
760+
| | range | q1 | Q1 | start=7 end=3 step=2 tick_labelset=c1 |
761+
762+
| choices |
763+
| | list_name | name | label |
764+
| | c1 | {value} | N1 |
765+
"""
766+
cases = ("9", "1")
767+
for value in cases:
768+
with self.subTest(value):
769+
self.assertPyxformXform(
770+
md=md.format(value=value),
771+
errored=True,
772+
error__contains=[ErrorCode.RANGE_010.value.format(row=2)],
773+
)
774+
754775
def test_tick_labelset_choice_outside_range__ok(self):
755776
"""Should not raise an error if any tick_labelset choices are inside the range."""
756777
# RL004
@@ -781,6 +802,36 @@ def test_tick_labelset_choice_outside_range__ok(self):
781802
],
782803
)
783804

805+
def test_tick_labelset_choice_outside_inverted_range__ok(self):
806+
"""Should not raise an error if any tick_labelset choices are inside the range."""
807+
# RL004
808+
md = """
809+
| survey |
810+
| | type | name | label | parameters |
811+
| | range | q1 | Q1 | start=7 end=3 step=2 tick_labelset=c1 |
812+
813+
| choices |
814+
| | list_name | name | label |
815+
| | c1 | {value} | N1 |
816+
"""
817+
cases = ("7", "5", "3")
818+
for value in cases:
819+
with self.subTest(value):
820+
self.assertPyxformXform(
821+
md=md.format(value=value),
822+
xml__xpath_match=[
823+
xpq.body_range(
824+
"q1",
825+
{
826+
"start": "7",
827+
"end": "3",
828+
"step": "2",
829+
"odk:tick-labelset": "c1",
830+
},
831+
),
832+
],
833+
)
834+
784835
def test_tick_labelset_choice_not_a_multiple_of_tick__error(self):
785836
"""Should raise an error if the relevant ticks parameter is not a multiple of 'step'."""
786837
# RL003
@@ -836,8 +887,8 @@ def test_tick_labelset_choice_not_a_multiple_of_step__ok(self):
836887
],
837888
)
838889

839-
def test_tick_labelset_choice_not_a_multiple_of_step__both__ok(self):
840-
"""Should not raise an error if the choice is a multiple of 'step'."""
890+
def test_tick_labelset_choice_not_aligned_with_tick_interval__both__ok(self):
891+
"""Should not raise an error if the choice is aligned with ticks."""
841892
# RL003
842893
md = """
843894
| survey |
@@ -846,7 +897,9 @@ def test_tick_labelset_choice_not_a_multiple_of_step__both__ok(self):
846897
847898
| choices |
848899
| | list_name | name | label |
849-
| | c1 | 8 | N1 |
900+
| | c1 | 1 | N1 |
901+
| | c1 | 5 | N2 |
902+
| | c1 | 9 | N3 |
850903
"""
851904
self.assertPyxformXform(
852905
md=md,

0 commit comments

Comments
 (0)