Skip to content

Commit

Permalink
FormDefTest.java - port first test focused on interaction of repeat/o…
Browse files Browse the repository at this point in the history
…utput

… and an alternate approach, with proposed extension to `Scenario` API as opposed to further use of APIs corresponding to JavaRosa internals
  • Loading branch information
eyelidlessness committed May 16, 2024
1 parent 28934c0 commit b3279b0
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 10 deletions.
44 changes: 34 additions & 10 deletions packages/scenario/src/jr/Scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { initializeTestForm } from '../client/init.ts';
import { getClosestRepeatRange, getNodeForReference } from '../client/traversal.ts';
import { ImplementationPendingError } from '../error/ImplementationPendingError.ts';
import { UnclearApplicabilityError } from '../error/UnclearApplicabilityError.ts';
import type { JRFormEntryCaption } from './caption/JRFormEntryCaption.ts';
import type { BeginningOfFormEvent } from './event/BeginningOfFormEvent.ts';
import type { EndOfFormEvent } from './event/EndOfFormEvent.ts';
import { PositionalEvent } from './event/PositionalEvent.ts';
Expand Down Expand Up @@ -51,6 +52,10 @@ type ScenarioStaticInitParameters =
| readonly [formName: string, form: XFormsElement]
| readonly [resource: FormDefinitionResource];

interface AssertCurrentReferenceOptions {
readonly assertCurrentReference: string;
}

/**
* @see {@link Scenario.createNewRepeat} for details
*/
Expand Down Expand Up @@ -175,15 +180,6 @@ export class Scenario {
expect(event.eventType).not.toBe('END_OF_FORM');
}

private assertNodeset(
event: AnyPositionalEvent,
nodeset: string
): asserts event is NonTerminalPositionalEvent {
this.assertNonTerminalEventSelected(event);

expect(event.node.definition.nodeset).toBe(nodeset);
}

private assertReference(
question: AnyPositionalEvent,
reference: string
Expand Down Expand Up @@ -438,7 +434,7 @@ export class Scenario {

event = this.getSelectedPositionalEvent();

this.assertNodeset(event, assertCurrentReference);
this.assertReference(event, assertCurrentReference);

repeatReference = assertCurrentReference;
} else {
Expand Down Expand Up @@ -631,6 +627,34 @@ export class Scenario {
);
}

/**
* **PORTING NOTES**
*
* This method is proposed as an alternative to
* {@link JRFormEntryCaption.getQuestionText}, intended to:
*
* - intended to be roughly equivalent in semantics without reliance on that
* class, viewed as an aspect of JavaRosa internal APIs
*
* - Provide similar positional semantics to other existing {@link Scenario}
* methods/web forms extensions thereof, where the call site expresses the
* expected XPath reference of the node at the current positional state.
*/
proposed_getQuestionLabelText(options: AssertCurrentReferenceOptions): string {
const event = this.getSelectedPositionalEvent();

this.assertReference(event, options.assertCurrentReference);

const { currentState } = event.node;
const label = currentState.label?.asString;

if (label == null) {
throw new Error(`Question node with reference ${currentState.reference} has no label`);
}

return label;
}

// TODO: consider adapting tests which use the following interfaces to use
// more portable concepts (either by using conceptually similar `Scenario`
// APIs, or by reframing the tests' logic to the same behavioral concerns with
Expand Down
31 changes: 31 additions & 0 deletions packages/scenario/src/jr/caption/JRFormEntryCaption.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { AnyNode } from '@odk-web-forms/xforms-engine';
import { UnclearApplicabilityError } from '../../error/UnclearApplicabilityError.ts';
import type { Scenario } from '../Scenario.ts';
import type { JRFormDef } from '../form/JRFormDef.ts';
import type { JRFormIndex } from '../form/JRFormIndex.ts';

type AnyNodeCurrentState = AnyNode['currentState'];

/**
* **PORTING NOTES**
*
* It appears that this is an API used in JavaRosa to access `<label>`/`<hint>`
* values and/or structures. It seems likely that this will correspond, at least
* conceptually, to our notion of a node's properties with `TextRange` values
* (i.e. currently {@link AnyNodeCurrentState.label} and
* {@link AnyNodeCurrentState.hint}).
*
* This is currently stubbed to fail on construction. To the extent it's
* reasonable, ported tests which exercise it will be accompanied by an
* alternate approach, potentially exercising proposed extensions to
* {@link Scenario}.
*/
export class JRFormEntryCaption {
constructor(_form: JRFormDef, _index: JRFormIndex) {
throw new UnclearApplicabilityError("JavaRosa's `FormEntryCaption` API");
}

getQuestionText(): string {
throw new UnclearApplicabilityError("JavaRosa's `FormEntryCaption` API");
}
}
148 changes: 148 additions & 0 deletions packages/scenario/test/repeat-output.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import {
bind,
body,
head,
html,
input,
label,
mainInstance,
model,
repeat,
t,
title,
} from '@odk-web-forms/common/test/fixtures/xform-dsl/index.ts';
import { describe, expect, it } from 'vitest';
import { Scenario } from '../src/jr/Scenario.ts';
import { JRFormEntryCaption } from '../src/jr/caption/JRFormEntryCaption.ts';

/**
* **PORTING NOTES**
*
* There may be a more general category of multi-feature-interaction to be
* found. This is the most obvious at time of porting.
*/
describe('Interaction between `<repeat>` and `<output>`', () => {
describe('FormDefTest.java', () => {
/**
* **PORTING NOTES**
*
* Rephrase? Unclear what "fill" was meant to reference here.
*/
describe('[output?] fill template string', () => {
/**
* **PORTING NOTES**
*
* {@link JRFormEntryCaption} is stubbed, to fail on invocation. An
* alternate test follows, exercising the apparent intent of this ported
* test with a proposed addition to the {@link Scenario} API.
*/
describe('[direct port/alternate - output resolves relative references]', () => {
it.fails('resolves relative references', async () => {
const scenario = await Scenario.init(
'<output> with relative ref',
html(
head(
title('output with relative ref'),
model(
mainInstance(
t(
'data id="relative-output"',
t('repeat jr:template=""', t('position'), t('position_in_label'))
)
),
bind('/data/repeat/position').type('int').calculate('position(..)'),
bind('/data/repeat/position_in_label').type('int')
)
),
body(
repeat(
'/data/repeat',
input(
'/data/repeat/position_in_label',
label('Position: <output value=" ../position "/>')
)
)
)
)
);

scenario.next('/data/repeat');
scenario.createNewRepeat({
assertCurrentReference: '/data/repeat',
});
scenario.next('/data/repeat[1]/position_in_label');

// FormEntryCaption caption = new FormEntryCaption(scenario.getFormDef(), scenario.getCurrentIndex());
let caption = new JRFormEntryCaption(scenario.getFormDef(), scenario.getCurrentIndex());

expect(caption.getQuestionText()).toBe('Position: 1');

scenario.next('/data/repeat');
scenario.createNewRepeat({
assertCurrentReference: '/data/repeat',
});
scenario.next('/data/repeat[2]/position_in_label');

// caption = new FormEntryCaption(scenario.getFormDef(), scenario.getCurrentIndex());
caption = new JRFormEntryCaption(scenario.getFormDef(), scenario.getCurrentIndex());

expect(caption.getQuestionText()).toBe('Position: 2');
});

it('produces the output of an expression with a relative reference (alternate)', async () => {
const scenario = await Scenario.init(
'<output> with relative ref',
html(
head(
title('output with relative ref'),
model(
mainInstance(
t(
'data id="relative-output"',
t('repeat jr:template=""', t('position'), t('position_in_label'))
)
),
bind('/data/repeat/position').type('int').calculate('position(..)'),
bind('/data/repeat/position_in_label').type('int')
)
),
body(
repeat(
'/data/repeat',
input(
'/data/repeat/position_in_label',
label('Position: <output value=" ../position "/>')
)
)
)
)
);

scenario.next('/data/repeat');
scenario.createNewRepeat({
assertCurrentReference: '/data/repeat',
});
scenario.next('/data/repeat[1]/position_in_label');

expect(
scenario.proposed_getQuestionLabelText({
assertCurrentReference: '/data/repeat[1]/position_in_label',
})
).toBe('Position: 1');

scenario.next('/data/repeat');
scenario.createNewRepeat({
assertCurrentReference: '/data/repeat',
});
scenario.next('/data/repeat[2]/position_in_label');

expect(
scenario.proposed_getQuestionLabelText({
assertCurrentReference: '/data/repeat[2]/position_in_label',
})
).toBe('Position: 2');
});
});
});
});
});

0 comments on commit b3279b0

Please sign in to comment.