Skip to content

Commit a26a78c

Browse files
authored
(fix) Validate and transform calculated value (#425)
1 parent e0b0512 commit a26a78c

File tree

3 files changed

+78
-47
lines changed

3 files changed

+78
-47
lines changed

src/components/renderer/field/fieldLogic.ts

+61-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { codedTypes } from '../../../constants';
22
import { type FormContextProps } from '../../../provider/form-provider';
3-
import { type FormField } from '../../../types';
3+
import { type FormFieldValidator, type SessionMode, type ValidationResult, type FormField } from '../../../types';
44
import { isTrue } from '../../../utils/boolean-utils';
55
import { hasRendering } from '../../../utils/common-utils';
66
import { evaluateAsyncExpression, evaluateExpression } from '../../../utils/expression-runner';
@@ -65,6 +65,21 @@ function evaluateFieldDependents(field: FormField, values: any, context: FormCon
6565
},
6666
).then((result) => {
6767
setValue(dependent.id, result);
68+
// validate calculated value
69+
const { errors, warnings } = validateFieldValue(dependent, result, context.formFieldValidators, {
70+
formFields,
71+
values,
72+
expressionContext: { patient, mode: sessionMode },
73+
});
74+
if (!dependent.meta.submission) {
75+
dependent.meta.submission = {};
76+
}
77+
dependent.meta.submission.errors = errors;
78+
dependent.meta.submission.warnings = warnings;
79+
if (!errors.length) {
80+
context.formFieldAdapters[dependent.type].transformFieldValue(dependent, result, context);
81+
}
82+
updateFormField(dependent);
6883
});
6984
}
7085
// evaluate hide
@@ -212,3 +227,48 @@ function evaluateFieldDependents(field: FormField, values: any, context: FormCon
212227
setForm(formJson);
213228
}
214229
}
230+
231+
export interface ValidatorConfig {
232+
formFields: FormField[];
233+
values: Record<string, any>;
234+
expressionContext: {
235+
patient: fhir.Patient;
236+
mode: SessionMode;
237+
};
238+
}
239+
240+
export function validateFieldValue(
241+
field: FormField,
242+
value: any,
243+
validators: Record<string, FormFieldValidator>,
244+
context: ValidatorConfig,
245+
): { errors: ValidationResult[]; warnings: ValidationResult[] } {
246+
const errors: ValidationResult[] = [];
247+
const warnings: ValidationResult[] = [];
248+
249+
if (field.meta.submission?.unspecified) {
250+
return { errors: [], warnings: [] };
251+
}
252+
253+
try {
254+
field.validators.forEach((validatorConfig) => {
255+
const results = validators[validatorConfig.type]?.validate?.(field, value, {
256+
...validatorConfig,
257+
...context,
258+
});
259+
if (results) {
260+
results.forEach((result) => {
261+
if (result.resultType === 'error') {
262+
errors.push(result);
263+
} else if (result.resultType === 'warning') {
264+
warnings.push(result);
265+
}
266+
});
267+
}
268+
});
269+
} catch (error) {
270+
console.error(error);
271+
}
272+
273+
return { errors, warnings };
274+
}

src/components/renderer/field/form-field-renderer.component.tsx

+1-46
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { getFieldControlWithFallback, getRegisteredControl } from '../../../regi
2121
import styles from './form-field-renderer.scss';
2222
import { isTrue } from '../../../utils/boolean-utils';
2323
import UnspecifiedField from '../../inputs/unspecified/unspecified.component';
24-
import { handleFieldLogic } from './fieldLogic';
24+
import { handleFieldLogic, validateFieldValue } from './fieldLogic';
2525

2626
export interface FormFieldRendererProps {
2727
fieldId: string;
@@ -221,51 +221,6 @@ function ErrorFallback({ error }) {
221221
);
222222
}
223223

224-
export interface ValidatorConfig {
225-
formFields: FormField[];
226-
values: Record<string, any>;
227-
expressionContext: {
228-
patient: fhir.Patient;
229-
mode: SessionMode;
230-
};
231-
}
232-
233-
function validateFieldValue(
234-
field: FormField,
235-
value: any,
236-
validators: Record<string, FormFieldValidator>,
237-
context: ValidatorConfig,
238-
): { errors: ValidationResult[]; warnings: ValidationResult[] } {
239-
const errors: ValidationResult[] = [];
240-
const warnings: ValidationResult[] = [];
241-
242-
if (field.meta.submission?.unspecified) {
243-
return { errors: [], warnings: [] };
244-
}
245-
246-
try {
247-
field.validators.forEach((validatorConfig) => {
248-
const results = validators[validatorConfig.type]?.validate?.(field, value, {
249-
...validatorConfig,
250-
...context,
251-
});
252-
if (results) {
253-
results.forEach((result) => {
254-
if (result.resultType === 'error') {
255-
errors.push(result);
256-
} else if (result.resultType === 'warning') {
257-
warnings.push(result);
258-
}
259-
});
260-
}
261-
});
262-
} catch (error) {
263-
console.error(error);
264-
}
265-
266-
return { errors, warnings };
267-
}
268-
269224
/**
270225
* Determines whether a field can be unspecified
271226
*/

src/form-engine.test.tsx

+16
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,8 @@ describe('Form engine component', () => {
681681

682682
describe('Calculated values', () => {
683683
it('should evaluate BMI', async () => {
684+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
685+
684686
await act(async () => renderForm(null, bmiForm));
685687

686688
const bmiField = screen.getByRole('textbox', { name: /bmi/i });
@@ -694,9 +696,17 @@ describe('Form engine component', () => {
694696
expect(heightField).toHaveValue(150);
695697
expect(weightField).toHaveValue(50);
696698
expect(bmiField).toHaveValue('22.2');
699+
700+
await user.click(screen.getByRole('button', { name: /save/i }));
701+
702+
const encounter = saveEncounterMock.mock.calls[0][1];
703+
expect(encounter.obs.length).toEqual(3);
704+
expect(encounter.obs.find((obs) => obs.formFieldPath === 'rfe-forms-bmi').value).toBe(22.2);
697705
});
698706

699707
it('should evaluate BSA', async () => {
708+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
709+
700710
await act(async () => renderForm(null, bsaForm));
701711

702712
const bsaField = screen.getByRole('textbox', { name: /bsa/i });
@@ -710,6 +720,12 @@ describe('Form engine component', () => {
710720
expect(heightField).toHaveValue(190.5);
711721
expect(weightField).toHaveValue(95);
712722
expect(bsaField).toHaveValue('2.24');
723+
724+
await user.click(screen.getByRole('button', { name: /save/i }));
725+
726+
const encounter = saveEncounterMock.mock.calls[0][1];
727+
expect(encounter.obs.length).toEqual(3);
728+
expect(encounter.obs.find((obs) => obs.formFieldPath === 'rfe-forms-bsa').value).toBe(2.24);
713729
});
714730

715731
it('should evaluate EDD', async () => {

0 commit comments

Comments
 (0)