Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(#249): Adds default geopoint question type #300

Merged
merged 54 commits into from
Mar 3, 2025
Merged
Changes from 32 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
5b292dc
Adds demo xml form for geopoint
latin-panda Feb 4, 2025
321d486
Adds watch for geolocation
latin-panda Feb 5, 2025
f919181
Adds buttons logic
latin-panda Feb 5, 2025
c94d612
Styles UI and DOM structure
latin-panda Feb 6, 2025
1e08674
Node Definition for Geo types
latin-panda Feb 6, 2025
a10f171
Revert "Node Definition for Geo types"
latin-panda Feb 6, 2025
889fe90
Fixes type check in webapp
latin-panda Feb 6, 2025
da8d36c
Fixes lint and type check in webapp
latin-panda Feb 7, 2025
44655b6
Adds nodeOptions to access attributes
latin-panda Feb 7, 2025
b330e74
Adds parser functions for nodeOptions
latin-panda Feb 7, 2025
03fba0d
Extract node option parsers to a file and test coverage
latin-panda Feb 8, 2025
f1446c1
Input node options is optional
latin-panda Feb 8, 2025
362612f
Implement accuracy threshold logic in client vue component
latin-panda Feb 8, 2025
cd351bb
Fixes GitHub lint
latin-panda Feb 8, 2025
1ea7869
Implements geolocation error case and improves quality assessment logic
latin-panda Feb 10, 2025
dca688a
Implements readonly
latin-panda Feb 10, 2025
1b57c73
Implements saving when control leaves the viewport as the user scrolls
latin-panda Feb 10, 2025
36ea1d1
Fixes vue component type check warning
latin-panda Feb 10, 2025
05e12f4
Implements autosave when form submits for geopoint
latin-panda Feb 10, 2025
001fdf8
Improving code style
latin-panda Feb 10, 2025
cf21460
Adding scenario tests for input type geopoint
latin-panda Feb 11, 2025
059d4d4
Fixing title and icons
latin-panda Feb 11, 2025
79a5432
codec
latin-panda Feb 12, 2025
c2dd023
Fixes codec to work with Note and Integrate codec to Geopoint Vue Com…
latin-panda Feb 14, 2025
8fa68af
Refactored UI to match redesigned UI for geopoint
latin-panda Feb 15, 2025
b6e965c
Refactored Geopoint codec to support string in encode
latin-panda Feb 15, 2025
d54bb0a
Simplified decode and properly registered geopoint note
latin-panda Feb 15, 2025
c3d09fc
format
latin-panda Feb 15, 2025
36c483d
Format constant for accuracy quality
latin-panda Feb 17, 2025
e97cb69
Fixes scenario's geopoint tests
latin-panda Feb 18, 2025
443eb5c
Fixes type check in web forms package
latin-panda Feb 18, 2025
f907cc8
Fixes scenario smoke test
latin-panda Feb 18, 2025
9bdff51
Tiny improvements in code
latin-panda Feb 18, 2025
eefb1e9
UIUX Feedback: fix margins and text indentation. Try again is hidden …
latin-panda Feb 19, 2025
c9ed78b
Codec feedback - minimum valuable solution
latin-panda Feb 19, 2025
a50983a
Codec feedback - extended solution with tuple type check
latin-panda Feb 19, 2025
aa8c1ab
Codec feedback - extended solution codec fix
latin-panda Feb 19, 2025
cecd173
Codec feedback - extended solution - geopoint class generates runtime…
latin-panda Feb 19, 2025
f18e875
Adding JSDoc and removing unnecessary symbol
latin-panda Feb 19, 2025
1d9514c
Adding more scenario tests for geopoint codec
latin-panda Feb 19, 2025
c652c70
Feedback:
latin-panda Feb 21, 2025
21f8964
Adds scenario tests for input as GeopointValue object
latin-panda Feb 21, 2025
79b501f
Putting back the XML prolog
latin-panda Feb 21, 2025
d4e2066
Fixed bind type tests
latin-panda Feb 21, 2025
790c325
Implements state machine in Web Form client
latin-panda Feb 22, 2025
2714b21
Simplifying Geopoint tuple code and renaming NodeOptionsParser to Num…
latin-panda Feb 23, 2025
a0699d1
Moving code from Geopoint codec to Geopoint class
latin-panda Feb 23, 2025
241db93
Validates null island case
latin-panda Feb 23, 2025
1eca15a
Adds nodeOptions to base nodes
latin-panda Feb 24, 2025
89255da
Adding changeset
latin-panda Feb 25, 2025
37efc2f
Adding readonly nodeOptions null to node definitions
latin-panda Feb 25, 2025
80e5251
Refactores geopoint state machine
latin-panda Feb 27, 2025
62b4c6c
From Feedback: Truncating to max 3 decimals, space before meter unit
latin-panda Feb 27, 2025
bf60966
Resolves feedback: Refactors truncate function, rename number parser …
latin-panda Mar 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions packages/common/src/fixtures/geopoint/1-geopoint.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0"?>
<h:html xmlns="http://www.w3.org/2002/xforms" xmlns:h="http://www.w3.org/1999/xhtml"
xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:jr="http://openrosa.org/javarosa" xmlns:orx="http://openrosa.org/xforms"
xmlns:odk="http://www.opendatakit.org/xforms">
<h:head>
<h:title>Geopoint</h:title>
<model odk:xforms-version="1.0.0">
<itext>
<translation lang="English (en)">
<text id="/data/facility_gps:label">
<value>Collect the GPS coordinates of this facility.</value>
</text>
</translation>
<translation lang="French (fr)">
<text id="/data/facility_gps:label">
<value>Collectez les coordonnées GPS de cet établissement.</value>
</text>
</translation>
</itext>
<instance>
<data id="1_geopoint" version="2025020401">
<facility_gps/>
<meta>
<instanceID/>
</meta>
</data>
</instance>
<bind nodeset="/data/facility_gps" type="geopoint"/>
<bind nodeset="/data/meta/instanceID" type="string" readonly="true()" jr:preload="uid"/>
</model>
</h:head>
<h:body>
<input accuracyThreshold="5" unacceptableAccuracyThreshold="50" ref="/data/facility_gps">
<label ref="jr:itext('/data/facility_gps:label')"/>
</input>
</h:body>
</h:html>
7 changes: 6 additions & 1 deletion packages/common/src/fixtures/notes/2-all-possible-notes.xml
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@
<read_only_int />
<read_only_int_value>3</read_only_int_value>
<note_calc_decimal_from_int />
<geopoint-note>38.253094215699576 21.756382658677467 0 150</geopoint-note>
</group>
<meta>
<instanceID />
@@ -38,6 +39,7 @@
<bind nodeset="/data/group/note_calc_decimal_from_int" type="decimal"
calculate="/data/group/read_only_int_value + 1.5" readonly="true()" />
<bind nodeset="/data/meta/instanceID" type="string" readonly="true()" jr:preload="uid" />
<bind nodeset="/data/group/geopoint-note" type="geopoint" readonly="true()" />
</model>
</h:head>
<h:body>
@@ -76,6 +78,9 @@
<input ref="/data/group/note_calc_decimal_from_int">
<label>A note with decimal type calculated from int</label>
</input>
<input ref="/data/group/geopoint-note">
<label>A note with geopoint type</label>
</input>
</group>
</h:body>
</h:html>
</h:html>
76 changes: 73 additions & 3 deletions packages/scenario/test/bind-types.test.ts
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ import {
t,
title,
} from '@getodk/common/test/fixtures/xform-dsl/index.ts';
import type { ValueType } from '@getodk/xforms-engine';
import type { InputValue, ValueType } from '@getodk/xforms-engine';
import { assert, beforeEach, describe, expect, expectTypeOf, it } from 'vitest';
import { intAnswer } from '../src/answer/ExpectedIntAnswer.ts';
import { InputNodeAnswer } from '../src/answer/InputNodeAnswer.ts';
@@ -37,12 +37,14 @@ describe('Data (<bind type>) type support', () => {
t('implicit-string-value', 'implicit string'),
t('int-value', '123'),
t('decimal-value', '45.67'),
t('geopoint-value', '38.25146813817506 21.758421137528785 0 0'),
)
),
bind('/root/string-value').type('string').relevant(modelNodeRelevanceExpression),
bind('/root/implicit-string-value').relevant(modelNodeRelevanceExpression),
bind('/root/int-value').type('int').relevant(modelNodeRelevanceExpression),
bind('/root/decimal-value').type('decimal').relevant(modelNodeRelevanceExpression)
bind('/root/decimal-value').type('decimal').relevant(modelNodeRelevanceExpression),
bind('/root/geopoint-value').type('geopoint').relevant(modelNodeRelevanceExpression)
)
),
body(
@@ -182,6 +184,33 @@ describe('Data (<bind type>) type support', () => {
expect(answer.value).toBe(null);
});
});

describe('type="geopoint"', () => {
let answer: ModelValueNodeAnswer<'geopoint'>;

beforeEach(() => {
answer = getTypedModelValueNodeAnswer('/root/geopoint-value', 'geopoint');
});

it('has a GeopointValue | null static type', () => {
expectTypeOf(answer.value).toEqualTypeOf<InputValue<'geopoint'>>();
});

it('has a GeopointValue populated value', () => {
expect(answer.value).toEqual({
accuracy: 0,
altitude: 0,
latitude: 38.25146813817506,
longitude: 21.758421137528785,
});
});

it('has an null as blank value', () => {
scenario.answer(modelNodeRelevancePath, 'no');
answer = getTypedModelValueNodeAnswer('/root/geopoint-value', 'geopoint');
expect(answer.value).toBeNull();
});
});
});

describe('inputs', () => {
@@ -202,12 +231,14 @@ describe('Data (<bind type>) type support', () => {
t('implicit-string-value', 'implicit string'),
t('int-value', '123'),
t('decimal-value', '45.67'),
t('geopoint-value', '38.25146813817506 21.758421137528785 1000 25'),
)
),
bind('/root/string-value').type('string').relevant(inputRelevanceExpression),
bind('/root/implicit-string-value').relevant(inputRelevanceExpression),
bind('/root/int-value').type('int').relevant(inputRelevanceExpression),
bind('/root/decimal-value').type('decimal').relevant(inputRelevanceExpression)
bind('/root/decimal-value').type('decimal').relevant(inputRelevanceExpression),
bind('/root/geopoint-value').type('geopoint').relevant(inputRelevanceExpression)
)
),
body(
@@ -216,6 +247,7 @@ describe('Data (<bind type>) type support', () => {
input('/root/implicit-string-value'),
input('/root/int-value'),
input('/root/decimal-value'),
input('/root/geopoint-value'),
)
);

@@ -480,6 +512,44 @@ describe('Data (<bind type>) type support', () => {
});
});
});

describe('type="geopoint"', () => {
let answer: InputNodeAnswer<'geopoint'>;

beforeEach(() => {
answer = getTypedInputNodeAnswer('/root/geopoint-value', 'geopoint');
});

it('has a GeopointValue | null static type', () => {
expectTypeOf(answer.value).toEqualTypeOf<InputValue<'geopoint'>>();
});

it('has a GeopointValue populated value', () => {
expect(answer.value).toEqual({
accuracy: 25,
altitude: 1000,
latitude: 38.25146813817506,
longitude: 21.758421137528785,
});
});

it('has an null as blank value', () => {
scenario.answer(inputRelevancePath, 'no');
answer = getTypedInputNodeAnswer('/root/geopoint-value', 'geopoint');
expect(answer.value).toBeNull();
});

it('sets a new value', () => {
scenario.answer('/root/geopoint-value', '-1.2936673 36.7260063 1500 10');
answer = getTypedInputNodeAnswer('/root/geopoint-value', 'geopoint');
expect(answer.value).toEqual({
accuracy: 10,
altitude: 1500,
latitude: -1.2936673,
longitude: 36.7260063,
});
});
});
});

describe('casting fractional values to int', () => {
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import { computed, inject, provide, ref } from 'vue';
import ControlText from '../../ControlText.vue';
import ValidationMessage from '../../ValidationMessage.vue';
import InputDecimal from './InputDecimal.vue';
import InputGeopoint from './InputGeopoint.vue';
import InputInt from './InputInt.vue';
import InputNumbersAppearance from './InputNumbersAppearance.vue';
import InputText from './InputText.vue';
@@ -35,13 +36,19 @@ provide('isInvalid', isInvalid);
<template v-else-if="node.valueType === 'string' && node.appearances.numbers">
<InputNumbersAppearance :node="node" />
</template>
<template v-else-if="node.valueType === 'geopoint'">
<InputGeopoint :question="node" />
</template>
<template v-else>
<InputText :node="node" />
</template>

<i v-show="isInvalid && (doneAnswering || submitPressed)" class="icon-error" />
<i v-show="isInvalid && (doneAnswering || submitPressed) && node.valueType !== 'geopoint'" class="icon-error" />
</div>
<ValidationMessage :message="node.validationState.violation?.message.asString" :show-message="doneAnswering || submitPressed" />
<ValidationMessage
:message="node.validationState.violation?.message.asString"
:show-message="doneAnswering || submitPressed"
/>
</template>

<style scoped lang="scss">
Loading

Unchanged files with check annotations Beta

const currentPosition = this.getSelectedPositionalEvent();
if (currentPosition.eventType === 'END_OF_FORM') {
throw 'todo';

Check warning on line 105 in packages/scenario/test/smoketests/child-vaccination.test.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown

Check warning on line 105 in packages/scenario/test/smoketests/child-vaccination.test.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown
}
const events = this.getPositionalEvents();
const next = this.getNextEventPosition();
if (next.eventType === 'END_OF_FORM') {
throw 'todo';

Check warning on line 141 in packages/scenario/test/smoketests/child-vaccination.test.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown

Check warning on line 141 in packages/scenario/test/smoketests/child-vaccination.test.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown
}
return new JRTreeReference(next.node.currentState.reference);
// prettier-ignore
type TextElementChildChunk =
| TextOutputExpression

Check warning on line 24 in packages/xforms-engine/src/parse/text/abstract/TextElementDefinition.ts

GitHub Actions / Lint (global) (22.12.0)

Union type TextElementChildChunk constituents must be sorted

Check warning on line 24 in packages/xforms-engine/src/parse/text/abstract/TextElementDefinition.ts

GitHub Actions / Lint (global) (22.12.0)

Union type TextElementChildChunk constituents must be sorted
| TextLiteralExpression;
// prettier-ignore
const { prefix, localName } = name;
const errorName = prefix == null ? localName : `${prefix}:${localName}`;
throw `todo function not defined: ${errorName}`;

Check warning on line 61 in packages/xpath/src/evaluator/expression/FunctionCallExpressionEvaluator.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown

Check warning on line 61 in packages/xpath/src/evaluator/expression/FunctionCallExpressionEvaluator.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown
}
return functionImplementation.call(context.currentContext(), argumentExpressions);
const lhs = this.lhs.evaluate(context);
if (!(lhs instanceof LocationPathEvaluation)) {
throw 'todo lhs not node-set result';

Check warning on line 28 in packages/xpath/src/evaluator/expression/UnionExpressionEvaluator.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown

Check warning on line 28 in packages/xpath/src/evaluator/expression/UnionExpressionEvaluator.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown
}
const rhs = this.rhs.evaluate(context);
if (!(rhs instanceof LocationPathEvaluation)) {
throw 'todo rhs not node-set result';

Check warning on line 34 in packages/xpath/src/evaluator/expression/UnionExpressionEvaluator.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown

Check warning on line 34 in packages/xpath/src/evaluator/expression/UnionExpressionEvaluator.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown
}
// TODO: sort in result
case ANY_UNORDERED_NODE_TYPE:
case FIRST_ORDERED_NODE_TYPE:
if (nodes == null) {
throw 'todo not a node-set';

Check warning on line 70 in packages/xpath/src/evaluator/result/toXPathEvaluationResult.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown

Check warning on line 70 in packages/xpath/src/evaluator/result/toXPathEvaluationResult.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown
}
return new NodeSetIteratorResult(domProvider, resultType, nodes);
case UNORDERED_NODE_SNAPSHOT_TYPE:
case ORDERED_NODE_SNAPSHOT_TYPE:
if (nodes == null) {
throw 'todo not a node-set';

Check warning on line 78 in packages/xpath/src/evaluator/result/toXPathEvaluationResult.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown

Check warning on line 78 in packages/xpath/src/evaluator/result/toXPathEvaluationResult.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown
}
return new NodeSetSnapshotResult(domProvider, resultType, nodes);
const results = expression!.evaluate(context);
if (results.nodes == null) {
throw 'todo not a node-set';

Check warning on line 17 in packages/xpath/src/functions/fn/node-set.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown

Check warning on line 17 in packages/xpath/src/functions/fn/node-set.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown
}
return toCount(results.nodes);
const evaluated = expression?.evaluate(context) ?? context;
if (!(evaluated instanceof LocationPathEvaluation)) {
throw 'todo not a node-set';

Check warning on line 70 in packages/xpath/src/functions/fn/node-set.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown

Check warning on line 70 in packages/xpath/src/functions/fn/node-set.ts

GitHub Actions / Lint (global) (22.12.0)

Expected an error object to be thrown
}
const node = evaluated.first()?.value;