Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/wise-bears-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@getodk/xforms-engine': patch
'@getodk/web-forms': patch
'@getodk/scenario': patch
---

Decimals and geopoint values are now serialized with a minimum of one digit after the decimal point
32 changes: 16 additions & 16 deletions packages/scenario/test/actions-events/set-geopoint-action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import {
t,
title,
} from '@getodk/common/test/fixtures/xform-dsl/index.ts';
import { describe, expect, it } from 'vitest';
import { flushPromises } from '@vue/test-utils';
import { describe, expect, it } from 'vitest';
import { stringAnswer } from '../../src/answer/ExpectedStringAnswer.ts';
import { createGeolocationProvider } from '../../src/client/createGeolocationProvider.ts';
import { Scenario } from '../../src/jr/Scenario.ts';
Expand All @@ -34,7 +34,7 @@ describe('odk:setgeopoint action', () => {
),
body(input('/data/destination'))
),
{ geolocationProvider: createGeolocationProvider('300 21.7567 110 5') }
{ geolocationProvider: createGeolocationProvider('300 21.7567 110.0 5.0') }
);

expect(scenario.answerOf('/data/destination')).toEqualAnswer(stringAnswer(''));
Expand All @@ -54,7 +54,7 @@ describe('odk:setgeopoint action', () => {
),
body(input('/data/destination'))
),
{ geolocationProvider: createGeolocationProvider('38.295 21.7567 110 5') }
{ geolocationProvider: createGeolocationProvider('38.295 21.7567 110.0 5.0') }
);

expect(scenario.answerOf('/data/destination')).toEqualAnswer(stringAnswer(''));
Expand Down Expand Up @@ -95,7 +95,7 @@ describe('odk:setgeopoint action', () => {
),
body(input('/data/destination'))
),
{ geolocationProvider: createGeolocationProvider('38.295 21.7567 110 5') }
{ geolocationProvider: createGeolocationProvider('38.295 21.7567 110.0 5.0') }
);
}).rejects.toThrowError(
'An action was registered for unsupported events: odk-some-random-event'
Expand All @@ -116,11 +116,11 @@ describe('odk:setgeopoint action', () => {
),
body(input('/data/destination'))
),
{ geolocationProvider: createGeolocationProvider('38.295 21.7567 110 5') }
{ geolocationProvider: createGeolocationProvider('38.295 21.7567 110.0 5.0') }
);

expect(scenario.answerOf('/data/destination')).toEqualAnswer(
stringAnswer('38.295 21.7567 110 5')
stringAnswer('38.295 21.7567 110.0 5.0')
);
});

Expand All @@ -138,11 +138,11 @@ describe('odk:setgeopoint action', () => {
),
body(input('/data/house_location'))
),
{ geolocationProvider: createGeolocationProvider('38.295 21.7567 110 5') }
{ geolocationProvider: createGeolocationProvider('38.295 21.7567 110.0 5.0') }
);

expect(scenario.answerOf('/data/house_location')).toEqualAnswer(
stringAnswer('38.295 21.7567 110 5')
stringAnswer('38.295 21.7567 110.0 5.0')
);
});

Expand Down Expand Up @@ -170,22 +170,22 @@ describe('odk:setgeopoint action', () => {
),
{
geolocationProvider: createGeolocationProvider(
'38.295 21.7567 110 5',
'38.333 21.766 150 3'
'38.295 21.7567 110.0 5.0',
'38.333 21.766 150.0 3.0'
),
}
);

scenario.createNewRepeat('/data/person');
await flushPromises();
expect(scenario.answerOf('/data/person[1]/location')).toEqualAnswer(
stringAnswer('38.295 21.7567 110 5')
stringAnswer('38.295 21.7567 110.0 5.0')
);

scenario.createNewRepeat('/data/person');
await flushPromises();
expect(scenario.answerOf('/data/person[2]/location')).toEqualAnswer(
stringAnswer('38.333 21.766 150 3')
stringAnswer('38.333 21.766 150.0 3.0')
);
});

Expand All @@ -205,22 +205,22 @@ describe('odk:setgeopoint action', () => {
),
{
geolocationProvider: createGeolocationProvider(
'38.295 21.7567 110 5',
'38.333 21.766 150 3'
'38.295 21.7567 110.0 5.0',
'38.333 21.766 150.0 3.0'
),
}
);

scenario.answer('/data/source', 22);
await flushPromises();
expect(scenario.answerOf('/data/destination')).toEqualAnswer(
stringAnswer('38.295 21.7567 110 5')
stringAnswer('38.295 21.7567 110.0 5.0')
);

scenario.answer('/data/source', 33);
await flushPromises();
expect(scenario.answerOf('/data/destination')).toEqualAnswer(
stringAnswer('38.333 21.766 150 3')
stringAnswer('38.333 21.766 150.0 3.0')
);
});
});
27 changes: 14 additions & 13 deletions packages/scenario/test/bind-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ 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'),
t('geopoint-value', '38.25146813817506 21.758421137528785 0.0 0.0'),
t('date-value', '1999-11-23T23:30:05'),
)
),
Expand Down Expand Up @@ -262,7 +262,7 @@ 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'),
t('geopoint-value', '38.25146813817506 21.758421137528785 1000.0 25.0'),
t('date-value', '2025-12-20'),
)
),
Expand Down Expand Up @@ -518,18 +518,19 @@ describe('Data (<bind type>) type support', () => {
readonly inputType: T;
readonly inputValue: SetDecimalInputValueByType[T];
readonly expectedValue: number | null;
readonly expectedStringValue: string;
}

type SetDecimalInputValueCase = {
[T in SetDecimalInputValueType]: BaseSetDecimalInputValueCase<T>;
}[SetDecimalInputValueType];

it.each<SetDecimalInputValueCase>([
{ inputType: 'bigint', inputValue: 89n, expectedValue: 89 },
{ inputType: 'number', inputValue: 10, expectedValue: 10 },
{ inputType: 'string', inputValue: '23', expectedValue: 23 },
{ inputType: 'null', inputValue: null, expectedValue: null },
])('sets value ($inputType)', ({ inputValue, expectedValue }) => {
{ inputType: 'bigint', inputValue: 89n, expectedValue: 89, expectedStringValue: '89.0' },
{ inputType: 'number', inputValue: 10, expectedValue: 10, expectedStringValue: '10.0' },
{ inputType: 'string', inputValue: '23', expectedValue: 23, expectedStringValue: '23.0' },
{ inputType: 'null', inputValue: null, expectedValue: null, expectedStringValue: '' },
])('sets value ($inputType)', ({ inputValue, expectedValue, expectedStringValue }) => {
scenario.answer('/root/decimal-value', inputValue);
answer = getTypedInputNodeAnswer('/root/decimal-value', 'decimal');

Expand All @@ -541,7 +542,7 @@ describe('Data (<bind type>) type support', () => {
} else {
expect(answer.value).toBeTypeOf('number');
expect(answer.value).toBe(expectedValue);
expect(answer.stringValue).toBe(`${expectedValue}`);
expect(answer.stringValue).toBe(`${expectedStringValue}`);
}
});
});
Expand Down Expand Up @@ -571,7 +572,7 @@ describe('Data (<bind type>) type support', () => {
altitude: 1000,
accuracy: 25,
});
expect(answer.stringValue).toEqual('38.25146813817506 21.758421137528785 1000 25');
expect(answer.stringValue).toEqual('38.25146813817506 21.758421137528785 1000.0 25.0');
});

it('has an null as blank value', () => {
Expand All @@ -590,7 +591,7 @@ describe('Data (<bind type>) type support', () => {
altitude: 0,
accuracy: 5,
});
expect(answer.stringValue).toEqual('-5.299 46.663 0 5');
expect(answer.stringValue).toEqual('-5.299 46.663 0.0 5.0');
});

it.each([
Expand All @@ -617,17 +618,17 @@ describe('Data (<bind type>) type support', () => {
{
expression: { latitude: 19.899, longitude: 100.55559, accuracy: 15 },
expectedAsObject: { latitude: 19.899, longitude: 100.55559, altitude: 0, accuracy: 15 },
expectedAsText: '19.899 100.55559 0 15',
expectedAsText: '19.899 100.55559 0.0 15.0',
},
{
expression: { latitude: 45.111, longitude: 127.23, altitude: 1350 },
expectedAsObject: { latitude: 45.111, longitude: 127.23, altitude: 1350, accuracy: null },
expectedAsText: '45.111 127.23 1350',
expectedAsText: '45.111 127.23 1350.0',
},
{
expression: { latitude: 14.66599, longitude: 179.9009, altitude: 200, accuracy: 5 },
expectedAsObject: { latitude: 14.66599, longitude: 179.9009, altitude: 200, accuracy: 5 },
expectedAsText: '14.66599 179.9009 200 5',
expectedAsText: '14.66599 179.9009 200.0 5.0',
},
{
expression: { latitude: 0, longitude: 0, altitude: 0, accuracy: 0 },
Expand Down
55 changes: 30 additions & 25 deletions packages/scenario/test/data-types/geo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { ExpectStatic, JestAssertion } from 'vitest';
import { describe, expect, it } from 'vitest';
import { expectedArea, expectedDistance } from '../../src/answer/ExpectedApproximateUOMAnswer.ts';
import { floatAnswer } from '../../src/answer/ExpectedFloatAnswer.ts';
import { stringAnswer } from '../../src/answer/ExpectedStringAnswer.ts';
import { Scenario } from '../../src/jr/Scenario.ts';
import { EARTH_EQUATORIAL_CIRCUMFERENCE_METERS } from '../../src/jr/core/util/GeoUtils.ts';

Expand Down Expand Up @@ -72,11 +73,11 @@ describe('Geopoint', () => {
mainInstance(
t(
'data id="geopoint-area"',
t('location', t('point', '38.253094215699576 21.756382658677467 0 0')),
t('location', t('point', '38.25021274773806 21.756382658677467 0 0')),
t('location', t('point', '38.25007793942195 21.763892843919166 0 0')),
t('location', t('point', '38.25290886154963 21.763935759263404 0 0')),
t('location', t('point', '38.25146813817506 21.758421137528785 0 0')),
t('location', t('point', '38.253094215699576 21.756382658677467 0.0 0.0')),
t('location', t('point', '38.25021274773806 21.756382658677467 0.0 0.0')),
t('location', t('point', '38.25007793942195 21.763892843919166 0.0 0.0')),
t('location', t('point', '38.25290886154963 21.763935759263404 0.0 0.0')),
t('location', t('point', '38.25146813817506 21.758421137528785 0.0 0.0')),
t('area')
)
),
Expand Down Expand Up @@ -106,11 +107,11 @@ describe('Geopoint', () => {
mainInstance(
t(
'data id="string-area"',
t('point1', '38.253094215699576 21.756382658677467 0 0'),
t('point2', '38.25021274773806 21.756382658677467 0 0'),
t('point3', '38.25007793942195 21.763892843919166 0 0'),
t('point4', '38.25290886154963 21.763935759263404 0 0'),
t('point5', '38.25146813817506 21.758421137528785 0 0'),
t('point1', '38.253094215699576 21.756382658677467 0.0 0.0'),
t('point2', '38.25021274773806 21.756382658677467 0.0 0.0'),
t('point3', '38.25007793942195 21.763892843919166 0.0 0.0'),
t('point4', '38.25290886154963 21.763935759263404 0.0 0.0'),
t('point5', '38.25146813817506 21.758421137528785 0.0 0.0'),
t('concat'),
t('area')
)
Expand Down Expand Up @@ -181,8 +182,8 @@ describe('Geopoint', () => {
mainInstance(
t(
'data id="geopoint-distance"',
t('location', t('point', '0 1 0 0')),
t('location', t('point', '0 91 0 0')),
t('location', t('point', '0 1 0.0 0.0')),
t('location', t('point', '0 91 0.0 0.0')),
t('distance')
)
),
Expand Down Expand Up @@ -216,11 +217,11 @@ describe('Geopoint', () => {
mainInstance(
t(
'data id="string-distance"',
t('point1', '38.253094215699576 21.756382658677467 0 0'),
t('point2', '38.25021274773806 21.756382658677467 0 0'),
t('point3', '38.25007793942195 21.763892843919166 0 0'),
t('point4', '38.25290886154963 21.763935759263404 0 0'),
t('point5', '38.25146813817506 21.758421137528785 0 0'),
t('point1', '38.253094215699576 21.756382658677467 0.0 0.0'),
t('point2', '38.25021274773806 21.756382658677467 0.0 0.0'),
t('point3', '38.25007793942195 21.763892843919166 0.0 0.0'),
t('point4', '38.25290886154963 21.763935759263404 0.0 0.0'),
t('point5', '38.25146813817506 21.758421137528785 0.0 0.0'),
t('concat'),
t('distance')
)
Expand Down Expand Up @@ -276,7 +277,7 @@ describe('Geoshape', () => {
'data id="geoshape-area"',
t(
'polygon',
'38.253094215699576 21.756382658677467 0 0; 38.25021274773806 21.756382658677467 0 0; 38.25007793942195 21.763892843919166 0 0; 38.25290886154963 21.763935759263404 0 0; 38.253094215699576 21.756382658677467 0 0;'
'38.253094215699576 21.756382658677467 0.0 0.0; 38.25021274773806 21.756382658677467 0.0 0.0; 38.25007793942195 21.763892843919166 0.0 0.0; 38.25290886154963 21.763935759263404 0.0 0.0; 38.253094215699576 21.756382658677467 0.0 0.0;'
),
t('area')
)
Expand Down Expand Up @@ -313,10 +314,10 @@ describe('Geoshape', () => {
mainInstance(
t(
'data id="geoshape-area"',
t('polygon1', '38.253094215699576 21.756382658677467 0 0;'),
t('polygon1', '38.253094215699576 21.756382658677467 0.0 0.0;'),
t(
'polygon2',
'38.253094215699576 21.756382658677467 0 0; 38.25021274773806 21.756382658677467 0 0;'
'38.253094215699576 21.756382658677467 0.0 0.0; 38.25021274773806 21.756382658677467 0.0 0.0;'
),
t('area1'),
t('area2')
Expand All @@ -333,9 +334,9 @@ describe('Geoshape', () => {
);

// assertThat(Double.parseDouble(scenario.answerOf("/data/area1").getDisplayText()), is(0.0));
expect(scenario.answerOf('/data/area1')).toEqualAnswer(floatAnswer(0.0));
expect(scenario.answerOf('/data/area1')).toEqualAnswer(stringAnswer('0.0'));
// assertThat(Double.parseDouble(scenario.answerOf("/data/area2").getDisplayText()), is(0.0));
expect(scenario.answerOf('/data/area2')).toEqualAnswer(floatAnswer(0.0));
expect(scenario.answerOf('/data/area2')).toEqualAnswer(stringAnswer('0.0'));
});
});
});
Expand Down Expand Up @@ -372,7 +373,7 @@ describe('Geoshape', () => {
mainInstance(
t(
'data id="geoshape-distance"',
t('polygon', '0 1 0 0; 0 91 0 0; 0 1 0 0;'),
t('polygon', '0 1 0.0 0.0; 0 91 0.0 0.0; 0 1 0.0 0.0;'),
t('distance')
)
),
Expand Down Expand Up @@ -429,7 +430,11 @@ describe('Geotrace', () => {
title('Geotrace distance'),
model(
mainInstance(
t('data id="geotrace-distance"', t('line', '0 1 0 0; 0 91 0 0;'), t('distance'))
t(
'data id="geotrace-distance"',
t('line', '0 1 0.0 0.0; 0 91 0.0 0.0;'),
t('distance')
)
),
bind('/data/line').type('geotrace'),
bind('/data/distance').type('decimal').calculate('distance(/data/line)')
Expand Down Expand Up @@ -465,7 +470,7 @@ describe('Geotrace', () => {
title('Geotrace distance'),
model(
mainInstance(
t('data id="geotrace-distance"', t('line', '0 1 0 0;'), t('distance'))
t('data id="geotrace-distance"', t('line', '0 1 0.0 0.0;'), t('distance'))
),
bind('/data/line').type('geotrace'),
bind('/data/distance').type('decimal').calculate('distance(/data/line)')
Expand Down
11 changes: 8 additions & 3 deletions packages/xforms-engine/src/lib/codecs/DecimalValueCodec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { formatDecimal } from '../number-parsers.ts';
import { ValueCodec } from './ValueCodec.ts';

export type DecimalInputValue = bigint | number | string | null;

const encodeDecimal = (value: DecimalInputValue): string => {
if (typeof value === 'string') {
value = decodeDecimal(value);
}

if (value == null) {
return '';
}

if (typeof value === 'string') {
decodeDecimal(value);
if (typeof value === 'bigint') {
return new String(value).toString() + '.0';
}

return String(value);
return formatDecimal(value);
};

export type DecimalRuntimeValue = number | null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { formatDecimal } from '../../number-parsers';

abstract class SemanticValue<Semantic extends string, Value extends number | null> {
abstract readonly semantic: Semantic;

Expand Down Expand Up @@ -141,7 +143,7 @@ export class Geolocation {

return new this(decodedValue)
.getTuple()
.map((item) => item.value ?? 0)
.map((item) => formatDecimal(item.value ?? 0))
.join(' ');
}

Expand Down
Loading