diff --git a/packages/scenario/test/repeat.test.ts b/packages/scenario/test/repeat.test.ts index b512eea5..e5adecb0 100644 --- a/packages/scenario/test/repeat.test.ts +++ b/packages/scenario/test/repeat.test.ts @@ -2262,15 +2262,18 @@ describe('Tests ported from JavaRosa - repeats', () => { /** * **PORTING NOTES** * - * - Fails pending implementation of `indexed-repeat` XPath function. - * * - Parameters adapted to match values in JavaRosa. Note that the * parameters are passed as {@link options} rather than destructured. Java * lets you reference `group` (the class property) and `group` (the * imported static method) in the same scope. TypeScript/JavaScript don't * let you do that... which is fine, because doing that is really weird! + * + * - `answer` calls updated to omit superfluous position predicate on + * the non-repeat `some-group` step (we do this lookup by `reference`, + * not evaluating arbitrary XPath expressions to identify the question + * being answered). */ - it.fails.each(parameters)('$testName', async (options) => { + it.each(parameters)('$testName', async (options) => { const scenario = await Scenario.init( 'Some form', html( @@ -2311,9 +2314,14 @@ describe('Tests ported from JavaRosa - repeats', () => { ) ); - scenario.answer('/data/some-group[1]/item[1]/value', 11); - scenario.answer('/data/some-group[1]/item[2]/value', 22); - scenario.answer('/data/some-group[1]/item[3]/value', 33); + // scenario.answer('/data/some-group[1]/item[1]/value', 11); + scenario.answer('/data/some-group/item[1]/value', 11); + + // scenario.answer('/data/some-group[1]/item[2]/value', 22); + scenario.answer('/data/some-group/item[2]/value', 22); + + // scenario.answer('/data/some-group[1]/item[3]/value', 33); + scenario.answer('/data/some-group/item[3]/value', 33); expect(scenario.answerOf('/data/total-items')).toEqualAnswer(intAnswer(3)); expect(scenario.answerOf('/data/some-group/last-value')).toEqualAnswer(intAnswer(33)); diff --git a/packages/scenario/test/smoketests/child-vaccination.test.ts b/packages/scenario/test/smoketests/child-vaccination.test.ts index 692a05a2..066a071a 100644 --- a/packages/scenario/test/smoketests/child-vaccination.test.ts +++ b/packages/scenario/test/smoketests/child-vaccination.test.ts @@ -12,6 +12,22 @@ import type { } from '../../src/jr/event/getPositionalEvents.ts'; import { JRTreeReference as BaseJRTreeReference } from '../../src/jr/xpath/JRTreeReference.ts'; +/** + * Most naive approach first: strip numeric positional predicates, ignoring any + * syntax ambiguities. + * + * If we need to do anything more complex than that, we'd likely benefit from + * pulling in the XPath parser to introduce syntax-based analysis and + * serialization. That's not the best investment for this smoke test, but it + * will have broad applicability for a variety of engine work that we've + * deferred, putting another figurative bird in that figurative stone's + * trajectory. (Plus there is considerable WIP that we could draw from if we + * need/wish to do that.) + */ +const naiveStripPositionalPredicates = (expression: string): string => { + return expression.replaceAll(/\[\d+\]/g, ''); +}; + /** * **PORTING NOTES** * @@ -54,7 +70,9 @@ const refSingletons = new UpsertableMap(); class JRTreeReference extends BaseJRTreeReference { genericize(): JRTreeReference { - throw new IncompleteTestPortError('TreeReference.genericize'); + const reference = naiveStripPositionalPredicates(this.xpathReference); + + return new JRTreeReference(reference); } equals(other: JRTreeReference): boolean { @@ -123,6 +141,24 @@ class Scenario extends BaseScenario { /* eslint-enable no-console */ } + private getNextEventPosition(): AnyPositionalEvent { + const currentPosition = this.getSelectedPositionalEvent(); + + if (currentPosition.eventType === 'END_OF_FORM') { + throw 'todo'; + } + + const events = this.getPositionalEvents(); + const position = this.getEventPosition(); + const next = events[position + 1]; + + if (next == null) { + throw new Error(`No question at position: ${position}`); + } + + return next; + } + /** * @deprecated * @@ -142,7 +178,13 @@ class Scenario extends BaseScenario { * increased clarity of intent. */ nextRef(): JRTreeReference { - throw new IncompleteTestPortError('Extended Scenario.nextRef'); + const next = this.getNextEventPosition(); + + if (next.eventType === 'END_OF_FORM') { + throw 'todo'; + } + + return new JRTreeReference(next.node.currentState.reference); } /** @@ -549,42 +591,32 @@ describe('ChildVaccinationTest.java', () => { }); it.fails('[smoke test]', async () => { - let scenario: Scenario | null = null; - - try { - scenario = await Scenario.init('child_vaccination_VOL_tool_v12.xml'); + const scenario = await Scenario.init('child_vaccination_VOL_tool_v12.xml'); - expect.fail('Update `child-vaccination.test.ts`, known failure mode has changed'); - } catch (error) { - expect(error).toBeInstanceOf(Error); + scenario.next('/data/building_type'); + scenario.answer('multi'); + scenario.next('/data/not_single'); + scenario.next('/data/not_single/gps'); + scenario.answer('1.234 5.678'); + scenario.next('/data/building_name'); + scenario.answer('Some building'); + scenario.next('/data/full_address1'); + scenario.answer('Some address, some location'); - // Failure of this assertion likely means that we've implemented the - // `indexed-repeat` XPath function. When that occurs, these error - // condition assertions should be removed, and the `Scenario.init` call - // should be treated normally. - // - // If a new failure occurs after that point, and that failure cannot be - // addressed by updating the test to be more complete (e.g. by specifying - // more of the expected references in `scenario.next` calls, or - // implementing other aspects of the test which are currently deferred), - // consider adding a similar function/assertion/comment to this effect, - // asserting that new known failure condition and prompting the test to be - // updated again once it is resolved. - expect((error as Error).message).toContain('function not defined: indexed-repeat'); - } + // endregion - if (scenario == null) { - return; - } + const currentExpectedPointOfFailure = () => { + expect(scenario.answerOf('/data/household_count').toString()).toBe('2'); - scenario.next('/data/building_type'); + const secondHouseholdControlledByRepeatCount = scenario.getInstanceNode('/data/household[2]'); - const currentExpectedPointOfFailure = () => { - scenario.answer('multi'); + expect(secondHouseholdControlledByRepeatCount).not.toBeNull(); }; try { - expect(currentExpectedPointOfFailure).toThrowError('function not defined: indexed-repeat'); + expect(currentExpectedPointOfFailure).toThrowError( + 'No instance node for reference: /data/household[2]' + ); if (typeof currentExpectedPointOfFailure === 'function') { scenario.trace( @@ -593,20 +625,21 @@ describe('ChildVaccinationTest.java', () => { return; } - } catch { - throw new Error(); - } + } catch (error) { + // Failure of this assertion likely means that we've implemented + // `jr:count`. When that occurs, these error condition assertions should + // be removed, and the test should be updated to add expected node-set + // reference assertions in the `scenario.next` calls, and so on, either + // until the test passes or a new known point of failure is identified. - scenario.answer('multi'); - scenario.next('/data/not_single'); - scenario.next('/data/not_single/gps'); - scenario.answer('1.234 5.678'); - scenario.next('/data/building_name'); - scenario.answer('Some building'); - scenario.next('/data/full_address1'); - scenario.answer('Some address, some location'); + expect(error).toBeInstanceOf(Error); - // endregion + const { message } = error as Error; + + expect.fail( + `Update \`child-vaccination.test.ts\`, known failure mode has changed: ${message}` + ); + } // region Answer all household repeats