Skip to content

Commit

Permalink
Minimal port of XFormParserTest.java
Browse files Browse the repository at this point in the history
There may be additional test logic we can repurpose into `Scenario` APIs or testing other layers, but this at least tests:

- Any of the tests from the “vat” exercising `Scenario` APIs
- Initialization of all other forms under test in the “vat”

A few failures, and behavior clarifications, come out of this as well.
  • Loading branch information
eyelidlessness committed May 16, 2024
1 parent 7fb5833 commit 502ca15
Show file tree
Hide file tree
Showing 6 changed files with 1,065 additions and 0 deletions.
52 changes: 52 additions & 0 deletions packages/scenario/resources/countNonEmptyForm-alt.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<h:html xmlns="http://www.w3.org/2002/xforms"
xmlns:h="http://www.w3.org/1999/xhtml"
xmlns:jr="http://openrosa.org/javarosa"
xmlns:odk="http://www.opendatakit.org/xforms"
xmlns:orx="http://openrosa.org/xforms">
<h:head>
<h:title>CountNonEmpty (+ namespaces)</h:title>
<model>
<instance>
<test id="test">
<details>
<group2>
<weight>1</weight>
</group2>
</details>
<details>
<group2>
<weight>2</weight>
</group2>
</details>
<details>
<group2>
<weight/>
</group2>
</details>
<details>
<group2>
<weight/>
</group2>
</details>
<count_value/>
<count_non_empty_value/>
</test>
</instance>
<bind nodeset="/test/details/group2/weight" type="string"/>
<bind calculate="count( /test/details/group2/weight )" nodeset="/test/count_value" type="int"/>
<bind calculate="count-non-empty( /test/details/group2/weight )" nodeset="/test/count_non_empty_value" type="int"/>
</model>
</h:head>
<h:body>
<group ref="/test/details">
<repeat nodeset="/test/details">
<group ref="/test/details/group2">
<input ref="/test/details/group2/weight">
<label>weight (kg)</label>
</input>
</group>
</repeat>
</group>
</h:body>
</h:html>
37 changes: 37 additions & 0 deletions packages/scenario/resources/template-repeat-alt.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<h:html xmlns="http://www.w3.org/2002/xforms"
xmlns:h="http://www.w3.org/1999/xhtml"
xmlns:jr="http://openrosa.org/javarosa"
xmlns:odk="http://www.opendatakit.org/xforms"
xmlns:orx="http://openrosa.org/xforms">
<h:head>
<h:title>Repeat with template</h:title>
<model>
<instance>
<data id="repeat-with-template">
<person jr:template="" >
<name>John Doe</name>
<relationship>spouse</relationship>
</person>
<person>
<name />
<relationship />
</person>
</data>
</instance>
</model>
</h:head>
<h:body>
<group ref="/data/person">
<label>Person</label>
<repeat nodeset="/data/person">
<input ref="/data/person/name">
<label>Enter name</label>
</input>
<input ref="/data/person/relationship">
<label>Enter relationship</label>
</input>
</repeat>
</group>
</h:body>
</h:html>
16 changes: 16 additions & 0 deletions packages/scenario/test/actions-events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2079,4 +2079,20 @@ describe('Actions/Events', () => {
});
});
});

describe('XFormParserTest.java', () => {
/**
* **PORTING NOTES**
*
* - `getValue().getValue().toString()` -> `currentState.value`
*
* - Fails pending feature support
*/
it.fails('sets [default] value[s] [~~]with strings[~~]', async () => {
const scenario = await Scenario.init('default_test.xml');

expect(scenario.getAnswerNode('/data/string_val').currentState.value).toBe('string-value');
expect(scenario.getAnswerNode('/data/inline_val').currentState.value).toBe('inline-value');
});
});
});
157 changes: 157 additions & 0 deletions packages/scenario/test/label-hint-text.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import {
bind,
body,
head,
html,
input,
mainInstance,
model,
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';

interface TitleOptions {
readonly temporarilyAddFormTitleElement: boolean;
}

describe('`<label>` and/or `<hint>` text', () => {
describe('XFormParserTest.java', () => {
type AssertOutputNameValues =
// eslint-disable-next-line @typescript-eslint/sort-type-constituents
'JR_PLACEHOLDER_DIRECT_PORT' | 'BLANK' | 'POPULATED';

interface SpacesBetweenOutputTagsOptions extends TitleOptions {
readonly assertOutputNameValues: AssertOutputNameValues;
}

/**
* **PORTING NOTES**
*
* - Fails as directly ported, as we presently expect a form definition to
* explicitly include a `<h:title>`. An alternate parameterization of the
* test includes a title so that the rest of the test may proceed.
*
* - JavaRosa produces "placeholders" where outputs are expected in a
* label/hint's "inner" text; the output values are substituted after the
* fact. The direct port of this test fails, because we include output
* results in our label/text serialization. (While it isn't presently
* addressed besides type stubs, we anticipate retaining a structural
* distinction between outputs and surrounding text when we address
* limited Markdown/HTML feature support.)
*
* The `assertOutputNameValues` parameter demonstrates this difference in
* behavior, where the option:
*
* - `JR_PLACEHOLDER_DIRECT_PORT`:
* - `first_name` and `last_name` fields are blank
* - label text assertion expects JavaRosa's expected placeholders
* - assertion fails (web forms doesn't produce output placeholders)
*
* - `BLANK`:
* - `first_name` and `last_name` fields are blank
* - label text assertion expects only `nbsp` preservation
* - assertion passes
*
* - `POPULATED`:
* - `first_name` and `last_name` fields are non-blank
* - assertion expects `first_name` + `nbsp` + `last_name`
* - assertion passes
*
* - Rephrase test description? "Preserves" seems more clear in intent.
*/
describe.each<SpacesBetweenOutputTagsOptions>([
{
temporarilyAddFormTitleElement: false,
assertOutputNameValues: 'JR_PLACEHOLDER_DIRECT_PORT',
},
{ temporarilyAddFormTitleElement: true, assertOutputNameValues: 'BLANK' },
{ temporarilyAddFormTitleElement: true, assertOutputNameValues: 'POPULATED' },
])(
'temporarily add form title element: $temporarilyAddFormTitleElement; replace JR output placeholders with expected text: $replaceJROutputPlaceholdersWithExpectedText; set name values: $setNameValues',
({ temporarilyAddFormTitleElement, assertOutputNameValues }) => {
let testFn: typeof it | typeof it.fails;

if (
temporarilyAddFormTitleElement &&
assertOutputNameValues !== 'JR_PLACEHOLDER_DIRECT_PORT'
) {
testFn = it;
} else {
testFn = it.fails;
}

testFn(
'[preserves spaces between `<output>` tags?] spaces between `<output>`s are respected',
async () => {
let firstNameValue: string;
let lastNameValue: string;

if (assertOutputNameValues === 'POPULATED') {
firstNameValue = 'Bobby';
lastNameValue = 'Tables';
} else {
firstNameValue = '';
lastNameValue = '';
}

const scenario = await Scenario.init(
'spaces-outputs',
html(
head(
...(temporarilyAddFormTitleElement ? [title('spaces-outputs')] : []),
model(
mainInstance(
t(
'data id="spaces-outputs"',
t('first_name', firstNameValue),
t('last_name', lastNameValue),
t('question')
)
),
bind('/data/question').type('string')
)
),
body(
input(
'/data/question',
t(
'label',
'Full name: <output value=" ../first_name "/>\u00A0<output value=" ../last_name "/>'
)
)
)
)
);

scenario.next('/data/question');

// String innerText = scenario.getQuestionAtIndex().getLabelInnerText();
const text = scenario.proposed_getQuestionLabelText({
assertCurrentReference: '/data/question',
});

// JR:
//
// char nbsp = 0x00A0;
const nbsp = '\u00a0';

// JR:
//
// String expected = "Full name: ${0}" + nbsp + "${1}";
let expected: string;

if (assertOutputNameValues === 'JR_PLACEHOLDER_DIRECT_PORT') {
expected = 'Full name: ${0}' + nbsp + '${1}';
} else {
expected = `Full name: ${firstNameValue}${nbsp}${lastNameValue}`;
}

expect(text).toEqual(expected);
}
);
}
);
});
});
55 changes: 55 additions & 0 deletions packages/scenario/test/repeat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2865,4 +2865,59 @@ describe('Tests ported from JavaRosa - repeats', () => {
);
});
});

describe('XFormParserTest.java', () => {
describe('form with `count-non-empty` func[tion]', () => {
interface NamespaceOptions {
readonly includeCommonNamespaces: boolean;
}

/**
* **PORTING NOTES**
*
* - It's not clear if this belongs here. It's fundamentally a test of the
* XPath `count-non-empty` function, but appears to depend on repeat
* instances as part of exercising the functionality. Otherwise, it
* would probably be best to make the `xpath` package is exercising all
* functionality under test in this form.
*
* - Fails with directly ported form fixture, which is missing (at least)
* the `xf` namespace declaration. Unclear whether we should:
*
* - Produce an initialization error (form is technically invalid)
*
* - Detect the condition and handle silently
*
* - Detect the condition and handle gracefully, perhaps with a
* warning
*
* If we do handle it, we should probably do a sanity check that the
* form doesn't have a namespace collision on the `xf` prefix.
*
* - Parameterized to use an alternate fixture with common namespaces
* present, to demonstrate the test otherwise passe.
*/
describe.each<NamespaceOptions>([
{ includeCommonNamespaces: false },
{ includeCommonNamespaces: true },
])('include common namespaces: $includeCommonNamespaces', ({ includeCommonNamespaces }) => {
let testFn: typeof it | typeof it.fails;

if (includeCommonNamespaces) {
testFn = it;
} else {
testFn = it.fails;
}

testFn('[calculates the count of non-empty nodes]', async () => {
const scenario = await Scenario.init(
includeCommonNamespaces ? 'countNonEmptyForm-alt.xml' : 'countNonEmptyForm.xml'
);

expect(scenario.answerOf('/test/count_value')).toEqualAnswer(intAnswer(4));
expect(scenario.answerOf('/test/count_non_empty_value')).toEqualAnswer(intAnswer(2));
});
});
});
});
});
Loading

0 comments on commit 502ca15

Please sign in to comment.