Skip to content

Commit

Permalink
(fix) Validate and transform calculated value (#425)
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelmale authored Nov 19, 2024
1 parent e0b0512 commit a26a78c
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 47 deletions.
62 changes: 61 additions & 1 deletion src/components/renderer/field/fieldLogic.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { codedTypes } from '../../../constants';
import { type FormContextProps } from '../../../provider/form-provider';
import { type FormField } from '../../../types';
import { type FormFieldValidator, type SessionMode, type ValidationResult, type FormField } from '../../../types';
import { isTrue } from '../../../utils/boolean-utils';
import { hasRendering } from '../../../utils/common-utils';
import { evaluateAsyncExpression, evaluateExpression } from '../../../utils/expression-runner';
Expand Down Expand Up @@ -65,6 +65,21 @@ function evaluateFieldDependents(field: FormField, values: any, context: FormCon
},
).then((result) => {
setValue(dependent.id, result);
// validate calculated value
const { errors, warnings } = validateFieldValue(dependent, result, context.formFieldValidators, {
formFields,
values,
expressionContext: { patient, mode: sessionMode },
});
if (!dependent.meta.submission) {
dependent.meta.submission = {};
}
dependent.meta.submission.errors = errors;
dependent.meta.submission.warnings = warnings;
if (!errors.length) {
context.formFieldAdapters[dependent.type].transformFieldValue(dependent, result, context);
}
updateFormField(dependent);
});
}
// evaluate hide
Expand Down Expand Up @@ -212,3 +227,48 @@ function evaluateFieldDependents(field: FormField, values: any, context: FormCon
setForm(formJson);
}
}

export interface ValidatorConfig {
formFields: FormField[];
values: Record<string, any>;
expressionContext: {
patient: fhir.Patient;
mode: SessionMode;
};
}

export function validateFieldValue(
field: FormField,
value: any,
validators: Record<string, FormFieldValidator>,
context: ValidatorConfig,
): { errors: ValidationResult[]; warnings: ValidationResult[] } {
const errors: ValidationResult[] = [];
const warnings: ValidationResult[] = [];

if (field.meta.submission?.unspecified) {
return { errors: [], warnings: [] };
}

try {
field.validators.forEach((validatorConfig) => {
const results = validators[validatorConfig.type]?.validate?.(field, value, {
...validatorConfig,
...context,
});
if (results) {
results.forEach((result) => {
if (result.resultType === 'error') {
errors.push(result);
} else if (result.resultType === 'warning') {
warnings.push(result);
}
});
}
});
} catch (error) {
console.error(error);
}

return { errors, warnings };
}
47 changes: 1 addition & 46 deletions src/components/renderer/field/form-field-renderer.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { getFieldControlWithFallback, getRegisteredControl } from '../../../regi
import styles from './form-field-renderer.scss';
import { isTrue } from '../../../utils/boolean-utils';
import UnspecifiedField from '../../inputs/unspecified/unspecified.component';
import { handleFieldLogic } from './fieldLogic';
import { handleFieldLogic, validateFieldValue } from './fieldLogic';

export interface FormFieldRendererProps {
fieldId: string;
Expand Down Expand Up @@ -221,51 +221,6 @@ function ErrorFallback({ error }) {
);
}

export interface ValidatorConfig {
formFields: FormField[];
values: Record<string, any>;
expressionContext: {
patient: fhir.Patient;
mode: SessionMode;
};
}

function validateFieldValue(
field: FormField,
value: any,
validators: Record<string, FormFieldValidator>,
context: ValidatorConfig,
): { errors: ValidationResult[]; warnings: ValidationResult[] } {
const errors: ValidationResult[] = [];
const warnings: ValidationResult[] = [];

if (field.meta.submission?.unspecified) {
return { errors: [], warnings: [] };
}

try {
field.validators.forEach((validatorConfig) => {
const results = validators[validatorConfig.type]?.validate?.(field, value, {
...validatorConfig,
...context,
});
if (results) {
results.forEach((result) => {
if (result.resultType === 'error') {
errors.push(result);
} else if (result.resultType === 'warning') {
warnings.push(result);
}
});
}
});
} catch (error) {
console.error(error);
}

return { errors, warnings };
}

/**
* Determines whether a field can be unspecified
*/
Expand Down
16 changes: 16 additions & 0 deletions src/form-engine.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,8 @@ describe('Form engine component', () => {

describe('Calculated values', () => {
it('should evaluate BMI', async () => {
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');

await act(async () => renderForm(null, bmiForm));

const bmiField = screen.getByRole('textbox', { name: /bmi/i });
Expand All @@ -694,9 +696,17 @@ describe('Form engine component', () => {
expect(heightField).toHaveValue(150);
expect(weightField).toHaveValue(50);
expect(bmiField).toHaveValue('22.2');

await user.click(screen.getByRole('button', { name: /save/i }));

const encounter = saveEncounterMock.mock.calls[0][1];
expect(encounter.obs.length).toEqual(3);
expect(encounter.obs.find((obs) => obs.formFieldPath === 'rfe-forms-bmi').value).toBe(22.2);
});

it('should evaluate BSA', async () => {
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');

await act(async () => renderForm(null, bsaForm));

const bsaField = screen.getByRole('textbox', { name: /bsa/i });
Expand All @@ -710,6 +720,12 @@ describe('Form engine component', () => {
expect(heightField).toHaveValue(190.5);
expect(weightField).toHaveValue(95);
expect(bsaField).toHaveValue('2.24');

await user.click(screen.getByRole('button', { name: /save/i }));

const encounter = saveEncounterMock.mock.calls[0][1];
expect(encounter.obs.length).toEqual(3);
expect(encounter.obs.find((obs) => obs.formFieldPath === 'rfe-forms-bsa').value).toBe(2.24);
});

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

0 comments on commit a26a78c

Please sign in to comment.