Skip to content

Commit

Permalink
Merge pull request #776 from lognaturel/indexed-repeat-tests
Browse files Browse the repository at this point in the history
Add indexed-repeat tests
  • Loading branch information
lognaturel authored Dec 19, 2024
2 parents ab0e8f4 + fa16c43 commit 57b978c
Show file tree
Hide file tree
Showing 2 changed files with 288 additions and 9 deletions.
18 changes: 9 additions & 9 deletions src/main/java/org/javarosa/xpath/expr/XPathFuncExpr.java
Original file line number Diff line number Diff line change
Expand Up @@ -1001,15 +1001,15 @@ public static Object ifThenElse(DataInstance model, EvaluationContext ec, XPathE
}

/**
* This provides a method of indexing fields stored in prior repeat groups.
* Provides a method of indexing fields stored in a repeat without writing an XPath filter expression.
* <p>
* args[0] = generic XPath expression to index
* args[1] = generic XPath expression for group to index
* args[2] = index number for group
* args[3] = generic XPath expression for add'l group to index (if 5 or 7 parameters passed)
* args[4] = index number for group (if 5 or 7 parameters passed)
* args[5] = generic XPath expression for add'l group to index (if 7 parameters passed)
* args[6] = index number for group (if 7 parameters passed)
* args[0] = reference to a field inside one or more repeats
* args[1] = generic XPath expression for repeat to index
* args[2] = index number for repeat instance
* args[3] = generic XPath expression for add'l repeat to index (if 5 or 7 parameters passed)
* args[4] = index number for repeat (if 5 or 7 parameters passed)
* args[5] = generic XPath expression for add'l repeat to index (if 7 parameters passed)
* args[6] = index number for repeat (if 7 parameters passed)
*/
public static Object indexedRepeat(DataInstance model, EvaluationContext ec, XPathExpression[] args, Object[] argVals) throws XPathTypeMismatchException {
// initialize target and context references
Expand All @@ -1025,7 +1025,7 @@ public static Object indexedRepeat(DataInstance model, EvaluationContext ec, XPa
for (int pathargi = 1, idxargi = 2; idxargi < args.length; pathargi += 2, idxargi += 2) {
// confirm that we were passed an XPath
if (!(args[pathargi] instanceof XPathPathExpr)) {
throw new XPathTypeMismatchException("indexed-repeat(): parameter " + (pathargi + 1) + " must be XPath repeat-group reference");
throw new XPathTypeMismatchException("indexed-repeat(): parameter " + (pathargi + 1) + " must be a reference to a repeat");
}
// confirm that the passed XPath is a parent of our overall target path
// Ensure we can deal with relative group refs by contextualizing with the EC's context ref
Expand Down
279 changes: 279 additions & 0 deletions src/test/java/org/javarosa/xpath/expr/IndexedRepeatTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
package org.javarosa.xpath.expr;

import org.javarosa.test.Scenario;
import org.javarosa.xform.parse.XFormParser;
import org.javarosa.xpath.XPathTypeMismatchException;
import org.junit.Test;

import java.io.IOException;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.javarosa.core.test.AnswerDataMatchers.stringAnswer;
import static org.javarosa.test.BindBuilderXFormsElement.bind;
import static org.javarosa.test.XFormsElement.body;
import static org.javarosa.test.XFormsElement.group;
import static org.javarosa.test.XFormsElement.head;
import static org.javarosa.test.XFormsElement.html;
import static org.javarosa.test.XFormsElement.input;
import static org.javarosa.test.XFormsElement.mainInstance;
import static org.javarosa.test.XFormsElement.model;
import static org.javarosa.test.XFormsElement.repeat;
import static org.javarosa.test.XFormsElement.t;
import static org.javarosa.test.XFormsElement.title;
import static org.junit.Assert.fail;

public class IndexedRepeatTest {
@Test
public void firstArgNotChildOfRepeat_throwsException() throws Exception {
try {
Scenario.init("indexed-repeat", html(
head(
title("indexed-repeat"),
model(
mainInstance(t("data id=\"indexed-repeat\"",
t("outside"),
t("repeat",
t("inside")),
t("calc")
)),
bind("/data/calc").calculate("indexed-repeat(/data/outside, /data/repeat, 1)")
)
),
body(
input("/data/outside"),
repeat("/data/repeat",
input("/data/repeat/inside"))
))
);

fail("RuntimeException caused by XPathTypeMismatchException expected");
} catch (RuntimeException e) {
assertThat(e.getCause(), instanceOf(XPathTypeMismatchException.class));
}
}

@Test
public void getsIndexedValueInSingleRepeat() throws Exception {
Scenario scenario = Scenario.init("indexed-repeat", html(
head(
title("indexed-repeat"),
model(
mainInstance(t("data id=\"indexed-repeat\"",
t("index"),
t("outer_group", // included to clarify intended evaluation context for index references
t("repeat",
t("inside"))),
t("calc")
)),
bind("/data/calc").calculate("indexed-repeat(/data/outer_group/repeat/inside, /data/outer_group/repeat, ../index)")
)
),
body(
input("/data/index"),
group("/data/outer_group",
repeat("/data/outer_group/repeat",
input("/data/outer_group/repeat/inside")))
))
);

scenario.createNewRepeat("/data/outer_group[1]/repeat");
scenario.answer("/data/outer_group[1]/repeat[1]/inside", "index1");

scenario.createNewRepeat("/data/outer_group[1]/repeat");
scenario.answer("/data/outer_group[1]/repeat[2]/inside", "index2");

scenario.createNewRepeat("/data/outer_group[1]/repeat");
scenario.answer("/data/outer_group[1]/repeat[3]/inside", "index3");

scenario.answer("/data/index", "2");
assertThat(scenario.answerOf("/data/calc"), is(stringAnswer("index2")));

scenario.answer("/data/index", "1");
assertThat(scenario.answerOf("/data/calc"), is(stringAnswer("index1")));
}

@Test
public void getsIndexedValueUsingParallelRepeatPosition() throws Exception {
Scenario scenario = Scenario.init("indexed-repeat", html(
head(
title("indexed-repeat"),
model(
mainInstance(t("data id=\"indexed-repeat\"",
t("repeat1",
t("inside1")),

t("repeat2",
t("inside2"),
t("from_repeat1"))
)),
bind("/data/repeat2/from_repeat1").calculate("indexed-repeat(/data/repeat1/inside1, /data/repeat1, position(..))")
)
),
body(
repeat("/data/repeat1",
input("/data/repeat1/inside1")),

repeat("/data/repeat2",
input("/data/repeat2/inside2"))
))
);

scenario.createNewRepeat("/data/repeat1");
scenario.createNewRepeat("/data/repeat2");
scenario.answer("/data/repeat1[1]/inside1", "index1");

scenario.createNewRepeat("/data/repeat1");
scenario.createNewRepeat("/data/repeat2");
scenario.answer("/data/repeat1[2]/inside1", "index2");

scenario.createNewRepeat("/data/repeat1");
scenario.createNewRepeat("/data/repeat2");
scenario.answer("/data/repeat1[3]/inside1", "index3");

assertThat(scenario.answerOf("/data/repeat2[1]/from_repeat1"), is(stringAnswer("index1")));
assertThat(scenario.answerOf("/data/repeat2[2]/from_repeat1"), is(stringAnswer("index2")));
}

@Test
public void handlesTopLevelRepeats() throws IOException, XFormParser.ParseException {
Scenario scenario = buildNestedRepeatForm();
assertThat(scenario.answerOf("/data/r2-d1[1]/from-r1-d1"), is(stringAnswer("[1]")));
assertThat(scenario.answerOf("/data/r2-d1[2]/from-r1-d1"), is(stringAnswer("[2]")));
}

@Test
public void handlesRepeatsTwoDeep() throws IOException, XFormParser.ParseException {
Scenario scenario = buildNestedRepeatForm();

assertThat(scenario.answerOf("/data/r2-d1[1]/r2-d2[1]/from-r1-d2-a"), is(stringAnswer("[1][1]")));
assertThat(scenario.answerOf("/data/r2-d1[1]/r2-d2[2]/from-r1-d2-a"), is(stringAnswer("[1][2]")));
assertThat(scenario.answerOf("/data/r2-d1[2]/r2-d2[1]/from-r1-d2-a"), is(stringAnswer("[2][1]")));
assertThat(scenario.answerOf("/data/r2-d1[2]/r2-d2[2]/from-r1-d2-a"), is(stringAnswer("[2][2]")));

assertThat(scenario.answerOf("/data/r2-d1[1]/r2-d2[1]/from-r1-d2-b"), is(stringAnswer("[1][1]")));
assertThat(scenario.answerOf("/data/r2-d1[1]/r2-d2[2]/from-r1-d2-b"), is(stringAnswer("[1][2]")));
assertThat(scenario.answerOf("/data/r2-d1[2]/r2-d2[1]/from-r1-d2-b"), is(stringAnswer("[2][1]")));
assertThat(scenario.answerOf("/data/r2-d1[2]/r2-d2[2]/from-r1-d2-b"), is(stringAnswer("[2][2]")));
}

@Test
public void handlesRepeatsThreeDeep() throws IOException, XFormParser.ParseException {
Scenario scenario = buildNestedRepeatForm();

assertThat(scenario.answerOf("/data/r2-d1[1]/r2-d2[1]/r2-d3[1]/from-r1-d3-a"), is(stringAnswer("[1][1][1]")));
assertThat(scenario.answerOf("/data/r2-d1[1]/r2-d2[1]/r2-d3[2]/from-r1-d3-a"), is(stringAnswer("[1][1][2]")));
assertThat(scenario.answerOf("/data/r2-d1[1]/r2-d2[2]/r2-d3[1]/from-r1-d3-a"), is(stringAnswer("[1][2][1]")));
assertThat(scenario.answerOf("/data/r2-d1[1]/r2-d2[2]/r2-d3[2]/from-r1-d3-a"), is(stringAnswer("[1][2][2]")));
assertThat(scenario.answerOf("/data/r2-d1[2]/r2-d2[1]/r2-d3[1]/from-r1-d3-a"), is(stringAnswer("[2][1][1]")));
assertThat(scenario.answerOf("/data/r2-d1[2]/r2-d2[1]/r2-d3[2]/from-r1-d3-a"), is(stringAnswer("[2][1][2]")));
assertThat(scenario.answerOf("/data/r2-d1[2]/r2-d2[2]/r2-d3[1]/from-r1-d3-a"), is(stringAnswer("[2][2][1]")));
assertThat(scenario.answerOf("/data/r2-d1[2]/r2-d2[2]/r2-d3[2]/from-r1-d3-a"), is(stringAnswer("[2][2][2]")));

assertThat(scenario.answerOf("/data/r2-d1[1]/r2-d2[1]/r2-d3[1]/from-r1-d3-b"), is(stringAnswer("[1][1][1]")));
assertThat(scenario.answerOf("/data/r2-d1[1]/r2-d2[1]/r2-d3[2]/from-r1-d3-b"), is(stringAnswer("[1][1][2]")));
assertThat(scenario.answerOf("/data/r2-d1[1]/r2-d2[2]/r2-d3[1]/from-r1-d3-b"), is(stringAnswer("[1][2][1]")));
assertThat(scenario.answerOf("/data/r2-d1[1]/r2-d2[2]/r2-d3[2]/from-r1-d3-b"), is(stringAnswer("[1][2][2]")));
assertThat(scenario.answerOf("/data/r2-d1[2]/r2-d2[1]/r2-d3[1]/from-r1-d3-b"), is(stringAnswer("[2][1][1]")));
assertThat(scenario.answerOf("/data/r2-d1[2]/r2-d2[1]/r2-d3[2]/from-r1-d3-b"), is(stringAnswer("[2][1][2]")));
assertThat(scenario.answerOf("/data/r2-d1[2]/r2-d2[2]/r2-d3[1]/from-r1-d3-b"), is(stringAnswer("[2][2][1]")));
assertThat(scenario.answerOf("/data/r2-d1[2]/r2-d2[2]/r2-d3[2]/from-r1-d3-b"), is(stringAnswer("[2][2][2]")));
}

public static Scenario buildNestedRepeatForm() throws IOException, XFormParser.ParseException {
Scenario scenario = Scenario.init("indexed-repeat", html(
head(
title("indexed-repeat"),
model(
mainInstance(t("data id=\"indexed-repeat\"",
t("r1-d1 jr:template=\"\"",
t("inside-r1-d1"),
t("r1-d2 jr:template=\"\"",
t("inside-r1-d2"),
t("r1-d3 jr:template=\"\"",
t("inside-r1-d3")))),
t("r2-d1 jr:template=\"\"",
t("inside-r2-d1"),
t("from-r1-d1"),
t("r2-d2 jr:template=\"\"",
t("inside-r2-d2"),
t("from-r1-d2-a"),
t("from-r1-d2-b"),
t("r2-d3 jr:template=\"\"",
t("inside-r2-d3"),
t("from-r1-d3-a"),
t("from-r1-d3-b"))))
)),
bind("/data/r1-d1/inside-r1-d1")
.calculate("concat('[', position(..), ']')"),
bind("/data/r1-d1/r1-d2/inside-r1-d2")
.calculate("concat('[', position(../..), ']', '[', position(..), ']')"),
bind("/data/r1-d1/r1-d2/r1-d3/inside-r1-d3")
.calculate("concat('[', position(../../..), ']', '[', position(../..), ']', '[', position(..), ']')"),
bind("/data/r2-d1/from-r1-d1")
.calculate("indexed-repeat(/data/r1-d1/inside-r1-d1, /data/r1-d1, position(..))"),
bind("/data/r2-d1/r2-d2/from-r1-d2-a")
.calculate("indexed-repeat(/data/r1-d1/r1-d2/inside-r1-d2, /data/r1-d1, position(../..), /data/r1-d1/r1-d2, position(..))"),
bind("/data/r2-d1/r2-d2/from-r1-d2-b")
// Same as from-r1-d2-a with the repeatN/indexN pairs swapped
.calculate("indexed-repeat(/data/r1-d1/r1-d2/inside-r1-d2, /data/r1-d1/r1-d2, position(..), /data/r1-d1, position(../..))"),
bind("/data/r2-d1/r2-d2/r2-d3/from-r1-d3-a")
.calculate("indexed-repeat(/data/r1-d1/r1-d2/r1-d3/inside-r1-d3, /data/r1-d1, position(../../..), /data/r1-d1/r1-d2, position(../..), /data/r1-d1/r1-d2/r1-d3, position(..))"),
bind("/data/r2-d1/r2-d2/r2-d3/from-r1-d3-b")
// Same as from-r1-d3-a with the repeatN/indexN pairs reordered
.calculate("indexed-repeat(/data/r1-d1/r1-d2/r1-d3/inside-r1-d3, /data/r1-d1/r1-d2, position(../..), /data/r1-d1, position(../../..), /data/r1-d1/r1-d2/r1-d3, position(..))")
)
),
body(
repeat("/data/r1-d1",
input("/data/r1-d1/inside-r1-d1"),
repeat("/data/r1-d1/r1-d2",
input("/data/r1-d1/r1-d2/inside-r1-d2"),
repeat("/data/r1-d1/r1-d2/r1-d3",
input("/data/r1-d1/r1-d2/r1-d3/inside-r1-d3")))),
repeat("/data/r2-d1",
input("/data/r2-d1/inside-r2-d1"),
repeat("/data/r2-d1/r2-d2",
input("/data/r2-d1/r2-d2/inside-r2-d2"),
repeat("/data/r2-d1/r2-d2/r2-d3",
input("/data/r2-d1/r2-d2/r2-d3/inside-r2-d3"))))
)));

scenario.createNewRepeat("/data/r1-d1");
scenario.createNewRepeat("/data/r1-d1");

scenario.createNewRepeat("/data/r1-d1[1]/r1-d2");
scenario.createNewRepeat("/data/r1-d1[1]/r1-d2");
scenario.createNewRepeat("/data/r1-d1[2]/r1-d2");
scenario.createNewRepeat("/data/r1-d1[2]/r1-d2");

scenario.createNewRepeat("/data/r1-d1[1]/r1-d2[1]/r1-d3");
scenario.createNewRepeat("/data/r1-d1[1]/r1-d2[1]/r1-d3");
scenario.createNewRepeat("/data/r1-d1[1]/r1-d2[2]/r1-d3");
scenario.createNewRepeat("/data/r1-d1[1]/r1-d2[2]/r1-d3");
scenario.createNewRepeat("/data/r1-d1[2]/r1-d2[1]/r1-d3");
scenario.createNewRepeat("/data/r1-d1[2]/r1-d2[1]/r1-d3");
scenario.createNewRepeat("/data/r1-d1[2]/r1-d2[2]/r1-d3");
scenario.createNewRepeat("/data/r1-d1[2]/r1-d2[2]/r1-d3");

scenario.createNewRepeat("/data/r2-d1");
scenario.createNewRepeat("/data/r2-d1");

scenario.createNewRepeat("/data/r2-d1[1]/r2-d2");
scenario.createNewRepeat("/data/r2-d1[1]/r2-d2");
scenario.createNewRepeat("/data/r2-d1[2]/r2-d2");
scenario.createNewRepeat("/data/r2-d1[2]/r2-d2");

scenario.createNewRepeat("/data/r2-d1[1]/r2-d2[1]/r2-d3");
scenario.createNewRepeat("/data/r2-d1[1]/r2-d2[1]/r2-d3");
scenario.createNewRepeat("/data/r2-d1[1]/r2-d2[2]/r2-d3");
scenario.createNewRepeat("/data/r2-d1[1]/r2-d2[2]/r2-d3");
scenario.createNewRepeat("/data/r2-d1[2]/r2-d2[1]/r2-d3");
scenario.createNewRepeat("/data/r2-d1[2]/r2-d2[1]/r2-d3");
scenario.createNewRepeat("/data/r2-d1[2]/r2-d2[2]/r2-d3");
scenario.createNewRepeat("/data/r2-d1[2]/r2-d2[2]/r2-d3");

return scenario;
}
}

0 comments on commit 57b978c

Please sign in to comment.