@openmrs/esm-form-engine-lib 2.1.0-pre.1564 → 2.1.0-pre.1575
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { codedTypes } from '../../../constants';
|
2
2
|
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';
|
4
4
|
import { isTrue } from '../../../utils/boolean-utils';
|
5
5
|
import { hasRendering } from '../../../utils/common-utils';
|
6
6
|
import { evaluateAsyncExpression, evaluateExpression } from '../../../utils/expression-runner';
|
@@ -65,6 +65,21 @@ function evaluateFieldDependents(field: FormField, values: any, context: FormCon
|
|
65
65
|
},
|
66
66
|
).then((result) => {
|
67
67
|
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);
|
68
83
|
});
|
69
84
|
}
|
70
85
|
// evaluate hide
|
@@ -212,3 +227,48 @@ function evaluateFieldDependents(field: FormField, values: any, context: FormCon
|
|
212
227
|
setForm(formJson);
|
213
228
|
}
|
214
229
|
}
|
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
|
+
}
|
@@ -21,7 +21,7 @@ import { getFieldControlWithFallback, getRegisteredControl } from '../../../regi
|
|
21
21
|
import styles from './form-field-renderer.scss';
|
22
22
|
import { isTrue } from '../../../utils/boolean-utils';
|
23
23
|
import UnspecifiedField from '../../inputs/unspecified/unspecified.component';
|
24
|
-
import { handleFieldLogic } from './fieldLogic';
|
24
|
+
import { handleFieldLogic, validateFieldValue } from './fieldLogic';
|
25
25
|
|
26
26
|
export interface FormFieldRendererProps {
|
27
27
|
fieldId: string;
|
@@ -221,51 +221,6 @@ function ErrorFallback({ error }) {
|
|
221
221
|
);
|
222
222
|
}
|
223
223
|
|
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
|
-
|
269
224
|
/**
|
270
225
|
* Determines whether a field can be unspecified
|
271
226
|
*/
|
@@ -32,6 +32,7 @@ const Repeat: React.FC<FormFieldInputProps> = ({ field }) => {
|
|
32
32
|
formFields,
|
33
33
|
methods: { getValues, setValue },
|
34
34
|
addFormField,
|
35
|
+
removeFormField,
|
35
36
|
} = context;
|
36
37
|
|
37
38
|
useEffect(() => {
|
@@ -108,6 +109,7 @@ const Repeat: React.FC<FormFieldInputProps> = ({ field }) => {
|
|
108
109
|
clearSubmission(field);
|
109
110
|
}
|
110
111
|
setRows(rows.filter((q) => q.id !== field.id));
|
112
|
+
removeFormField(field.id);
|
111
113
|
};
|
112
114
|
|
113
115
|
const onClickDeleteQuestion = (field: Readonly<FormField>) => {
|
package/src/form-engine.test.tsx
CHANGED
@@ -681,6 +681,8 @@ describe('Form engine component', () => {
|
|
681
681
|
|
682
682
|
describe('Calculated values', () => {
|
683
683
|
it('should evaluate BMI', async () => {
|
684
|
+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
685
|
+
|
684
686
|
await act(async () => renderForm(null, bmiForm));
|
685
687
|
|
686
688
|
const bmiField = screen.getByRole('textbox', { name: /bmi/i });
|
@@ -694,9 +696,17 @@ describe('Form engine component', () => {
|
|
694
696
|
expect(heightField).toHaveValue(150);
|
695
697
|
expect(weightField).toHaveValue(50);
|
696
698
|
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);
|
697
705
|
});
|
698
706
|
|
699
707
|
it('should evaluate BSA', async () => {
|
708
|
+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
709
|
+
|
700
710
|
await act(async () => renderForm(null, bsaForm));
|
701
711
|
|
702
712
|
const bsaField = screen.getByRole('textbox', { name: /bsa/i });
|
@@ -710,6 +720,12 @@ describe('Form engine component', () => {
|
|
710
720
|
expect(heightField).toHaveValue(190.5);
|
711
721
|
expect(weightField).toHaveValue(95);
|
712
722
|
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);
|
713
729
|
});
|
714
730
|
|
715
731
|
it('should evaluate EDD', async () => {
|