Skip to content

Commit 89b15a8

Browse files
committed
Fix issue with non-enumerable props in calculated expressions
1 parent f219510 commit 89b15a8

File tree

3 files changed

+67
-1
lines changed

3 files changed

+67
-1
lines changed

src/components.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
evaluateQuestionItemExpression,
1313
getBranchItems,
1414
getEnabledQuestions,
15+
stripNonEnumerable,
1516
wrapAnswerValue,
1617
} from './utils.js';
1718

@@ -73,12 +74,16 @@ export function QuestionItem(props: QuestionItemProps) {
7374
useEffect(() => {
7475
// TODO: think about use cases for group context
7576
if (itemContext && calculatedExpression) {
77+
// Fhirpath returns result with non-enumerable properties such as __path__
78+
// It's cleaned up here because it's not part of actual value
79+
// And it might invoke endless recursion because
80+
// we rely on fact of equality of prev and current values
7681
const newValues = evaluateQuestionItemExpression(
7782
linkId,
7883
'calculatedExpression',
7984
itemContext,
8085
calculatedExpression,
81-
);
86+
).map(stripNonEnumerable);
8287

8388
const newAnswers: FormAnswerItems[] | undefined = newValues.length
8489
? repeats

src/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,18 @@ export function evaluateQuestionItemExpression(
837837
}
838838
}
839839

840+
export function stripNonEnumerable<T>(obj: T): T {
841+
if (obj === null || typeof obj !== 'object') {
842+
return obj;
843+
}
844+
845+
if (Array.isArray(obj)) {
846+
return [...obj] as unknown as T;
847+
}
848+
849+
return { ...obj };
850+
}
851+
840852
export function getChoiceTypeValue(obj: Record<any, any>, prefix: string): any | undefined {
841853
const prefixKey = Object.keys(obj).filter((key: string) => key.startsWith(prefix))[0];
842854

tests/utils.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
evaluateQuestionItemExpression,
2222
findAnswersForQuestion,
2323
ITEM_KEY,
24+
stripNonEnumerable,
2425
} from '../src/utils';
2526
import {
2627
ParametersParameter,
@@ -2833,3 +2834,51 @@ describe('findAnswersForQuestion', () => {
28332834
);
28342835
});
28352836
});
2837+
2838+
describe('stripNonEnumerable', () => {
2839+
test('works correctly for array without non-enums', () => {
2840+
expect(stripNonEnumerable([1, null, undefined])).toStrictEqual([1, null, undefined]);
2841+
});
2842+
2843+
test('works correctly for array with non-enums', () => {
2844+
const arrWithNonEnum = [1, 2, 3];
2845+
Object.defineProperty(arrWithNonEnum, '__path__', {
2846+
value: 'non-enum-prop',
2847+
enumerable: false,
2848+
});
2849+
expect(stripNonEnumerable(arrWithNonEnum)).toStrictEqual([1, 2, 3]);
2850+
});
2851+
2852+
test('works correctly for object without non-enums', () => {
2853+
expect(stripNonEnumerable({ a: 1, b: null, c: undefined })).toStrictEqual({ a: 1, b: null, c: undefined });
2854+
});
2855+
2856+
test('works correctly for object with non-enums', () => {
2857+
const objWithNonEnum = { a: 1, b: 2, c: 3 };
2858+
Object.defineProperty(objWithNonEnum, '__path__', {
2859+
value: 'non-enum-prop',
2860+
enumerable: false,
2861+
});
2862+
expect(stripNonEnumerable(objWithNonEnum)).toStrictEqual({ a: 1, b: 2, c: 3 });
2863+
});
2864+
2865+
test('works correctly for null', () => {
2866+
expect(stripNonEnumerable(null)).toStrictEqual(null);
2867+
});
2868+
2869+
test('works correctly for undefined', () => {
2870+
expect(stripNonEnumerable(undefined)).toStrictEqual(undefined);
2871+
});
2872+
2873+
test('works correctly for string', () => {
2874+
expect(stripNonEnumerable('string')).toStrictEqual('string');
2875+
});
2876+
2877+
test('works correctly for number', () => {
2878+
expect(stripNonEnumerable(1)).toStrictEqual(1);
2879+
});
2880+
2881+
test('works correctly for boolean', () => {
2882+
expect(stripNonEnumerable(true)).toStrictEqual(true);
2883+
});
2884+
});

0 commit comments

Comments
 (0)