From 757bad9fa1de0b4e05e08966f1e5f54b04d69b17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne=20Martin?= Date: Mon, 8 Jul 2024 13:25:19 -0700 Subject: [PATCH 1/3] Add indexed-repeat tests --- .../javarosa/xpath/expr/XPathFuncExpr.java | 18 +-- .../xpath/expr/IndexedRepeatTest.java | 132 ++++++++++++++++++ 2 files changed, 141 insertions(+), 9 deletions(-) create mode 100644 src/test/java/org/javarosa/xpath/expr/IndexedRepeatTest.java diff --git a/src/main/java/org/javarosa/xpath/expr/XPathFuncExpr.java b/src/main/java/org/javarosa/xpath/expr/XPathFuncExpr.java index d3edd19d2..fdb5e62c0 100644 --- a/src/main/java/org/javarosa/xpath/expr/XPathFuncExpr.java +++ b/src/main/java/org/javarosa/xpath/expr/XPathFuncExpr.java @@ -970,15 +970,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. *

- * 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 @@ -994,7 +994,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 diff --git a/src/test/java/org/javarosa/xpath/expr/IndexedRepeatTest.java b/src/test/java/org/javarosa/xpath/expr/IndexedRepeatTest.java new file mode 100644 index 000000000..f58cef546 --- /dev/null +++ b/src/test/java/org/javarosa/xpath/expr/IndexedRepeatTest.java @@ -0,0 +1,132 @@ +package org.javarosa.xpath.expr; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +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.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; + +import org.javarosa.test.Scenario; +import org.javarosa.xpath.XPathTypeMismatchException; +import org.junit.Test; + +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("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("repeat", + t("inside")), + t("calc") + )), + bind("/data/calc").calculate("indexed-repeat(/data/repeat/inside, /data/repeat, /data/index)") + ) + ), + body( + input("/data/index"), + repeat("/data/repeat", + input("/data/repeat/inside")) + )) + ); + + scenario.createNewRepeat("/data/repeat"); + scenario.answer("/data/repeat[1]/inside", "index1"); + + scenario.createNewRepeat("/data/repeat"); + scenario.answer("/data/repeat[2]/inside", "index2"); + + scenario.createNewRepeat("/data/repeat"); + scenario.answer("/data/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")) + )) + ); + + 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"))); + } +} From 3e8c91ffb7f1af4d9981c2771def519a3ca1e7c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne=20Martin?= Date: Mon, 8 Jul 2024 15:09:32 -0700 Subject: [PATCH 2/3] Use outer group and relative ref to clarify evaluation context --- .../xpath/expr/IndexedRepeatTest.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/test/java/org/javarosa/xpath/expr/IndexedRepeatTest.java b/src/test/java/org/javarosa/xpath/expr/IndexedRepeatTest.java index f58cef546..8169960ab 100644 --- a/src/test/java/org/javarosa/xpath/expr/IndexedRepeatTest.java +++ b/src/test/java/org/javarosa/xpath/expr/IndexedRepeatTest.java @@ -6,6 +6,7 @@ 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; @@ -58,28 +59,30 @@ public void getsIndexedValueInSingleRepeat() throws Exception { model( mainInstance(t("data id=\"indexed-repeat\"", t("index"), - t("repeat", - t("inside")), + 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/repeat/inside, /data/repeat, /data/index)") + bind("/data/calc").calculate("indexed-repeat(/data/outer_group/repeat/inside, /data/outer_group/repeat, ../index)") ) ), body( input("/data/index"), - repeat("/data/repeat", - input("/data/repeat/inside")) + group("/data/outer_group", + repeat("/data/outer_group/repeat", + input("/data/outer_group/repeat/inside"))) )) ); - scenario.createNewRepeat("/data/repeat"); - scenario.answer("/data/repeat[1]/inside", "index1"); + scenario.createNewRepeat("/data/outer_group[1]/repeat"); + scenario.answer("/data/outer_group[1]/repeat[1]/inside", "index1"); - scenario.createNewRepeat("/data/repeat"); - scenario.answer("/data/repeat[2]/inside", "index2"); + scenario.createNewRepeat("/data/outer_group[1]/repeat"); + scenario.answer("/data/outer_group[1]/repeat[2]/inside", "index2"); - scenario.createNewRepeat("/data/repeat"); - scenario.answer("/data/repeat[3]/inside", "index3"); + 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"))); From 9748beadaa2f892226093054f90d09e1b6dca55b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne=20Martin?= Date: Mon, 26 Aug 2024 16:02:06 -0700 Subject: [PATCH 3/3] Use correct absolute paths --- .../org/javarosa/xpath/expr/IndexedRepeatTest.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/javarosa/xpath/expr/IndexedRepeatTest.java b/src/test/java/org/javarosa/xpath/expr/IndexedRepeatTest.java index 8169960ab..fc5fd9f74 100644 --- a/src/test/java/org/javarosa/xpath/expr/IndexedRepeatTest.java +++ b/src/test/java/org/javarosa/xpath/expr/IndexedRepeatTest.java @@ -1,8 +1,12 @@ package org.javarosa.xpath.expr; +import org.javarosa.test.Scenario; +import org.javarosa.xpath.XPathTypeMismatchException; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.CoreMatchers.is; import static org.javarosa.core.test.AnswerDataMatchers.stringAnswer; import static org.javarosa.test.BindBuilderXFormsElement.bind; import static org.javarosa.test.XFormsElement.body; @@ -17,10 +21,6 @@ import static org.javarosa.test.XFormsElement.title; import static org.junit.Assert.fail; -import org.javarosa.test.Scenario; -import org.javarosa.xpath.XPathTypeMismatchException; -import org.junit.Test; - public class IndexedRepeatTest { @Test public void firstArgNotChildOfRepeat_throwsException() throws Exception { @@ -35,7 +35,7 @@ public void firstArgNotChildOfRepeat_throwsException() throws Exception { t("inside")), t("calc") )), - bind("calc").calculate("indexed-repeat(/data/outside, /data/repeat, 1)") + bind("/data/calc").calculate("indexed-repeat(/data/outside, /data/repeat, 1)") ) ), body( @@ -113,7 +113,7 @@ public void getsIndexedValueUsingParallelRepeatPosition() throws Exception { input("/data/repeat1/inside1")), repeat("/data/repeat2", - input("/data/repeat2")) + input("/data/repeat2/inside2")) )) );