diff --git a/__mocks__/forms/rfe-forms/js-expression-validation-form.json b/__mocks__/forms/rfe-forms/js-expression-validation-form.json new file mode 100644 index 000000000..508088668 --- /dev/null +++ b/__mocks__/forms/rfe-forms/js-expression-validation-form.json @@ -0,0 +1,54 @@ +{ + "name": "Js expression validation", + "uuid": "xxxx", + "EncounterType": "xxxx", + "referencedForms": [], + "processor": "EncounterFormProcessor", + "pages": [ + { + "label": "Page 1", + "sections": [ + { + "label": "Section 1", + "questions": [ + { + "label": "Question 1", + "type": "obs", + "required": false, + "id": "question1", + "questionOptions": { + "rendering": "text", + "concept": "166103AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "conceptMappings": [ + { + "relationship": "SAME-AS", + "type": "PIH", + "value": "2724" + }, + { + "relationship": "SAME-AS", + "type": "SNOMED CT", + "value": "397678008" + }, + { + "relationship": "SAME-AS", + "type": "CIEL", + "value": "166103" + } + ], + "showDate": true + }, + "validators": [ + { + "type": "js_expression", + "failsWhenExpression": "isEmpty(myValue)", + "message": "Empty value not allowed!" + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/form-engine.test.tsx b/src/form-engine.test.tsx index c7b3894c6..85a071929 100644 --- a/src/form-engine.test.tsx +++ b/src/form-engine.test.tsx @@ -12,7 +12,7 @@ import { } from '@openmrs/esm-framework'; import { when } from 'jest-when'; import * as api from './api'; -import { assertFormHasAllFields, findCheckboxGroup, findSelectInput } from './utils/test-utils'; +import { assertFormHasAllFields, findCheckboxGroup, findSelectInput, findTextOrDateInput } from './utils/test-utils'; import { evaluatePostSubmissionExpression } from './utils/post-submission-action-helper'; import { mockPatient } from '__mocks__/patient.mock'; import { mockSessionDataResponse } from '__mocks__/session.mock'; @@ -42,6 +42,7 @@ import monthsOnArtForm from '__mocks__/forms/rfe-forms/months-on-art-form.json'; import nextVisitForm from '__mocks__/forms/rfe-forms/next-visit-test-form.json'; import viralLoadStatusForm from '__mocks__/forms/rfe-forms/viral-load-status-form.json'; import readOnlyValidationForm from '__mocks__/forms/rfe-forms/read-only-validation-form.json'; +import jsExpressionValidationForm from '__mocks__/forms/rfe-forms/js-expression-validation-form.json'; import FormEngine from './form-engine.component'; import { type SessionMode } from './types'; @@ -262,6 +263,21 @@ describe('Form engine component', () => { }); }); + describe('js-expression based validation', () => { + it('should invoke validation when field value changes', async () => { + await act(async () => { + renderForm(null, jsExpressionValidationForm); + }); + + const textField = await findTextOrDateInput(screen, 'Question 1'); + await user.type(textField, 'Some value'); + // clear value + await user.clear(textField); + const errorMessage = await screen.findByText(/Empty value not allowed!/i); + expect(errorMessage).toBeInTheDocument(); + }); + }); + describe('historical expressions', () => { it('should ascertain getPreviousEncounter() returns an encounter and the historical expression displays on the UI', async () => { renderForm(null, historicalExpressionsForm, 'COVID Assessment'); diff --git a/src/provider/form-factory-helper.ts b/src/provider/form-factory-helper.ts index c2480139c..7af8c60d5 100644 --- a/src/provider/form-factory-helper.ts +++ b/src/provider/form-factory-helper.ts @@ -24,7 +24,7 @@ export function validateForm(context: FormContextProps) { const validator = formFieldValidators[validatorConfig.type]; if (validator) { const validationResults = validator.validate(field, values[field.id], { - fields: formFields, + formFields, values, expressionContext: { patient, diff --git a/src/validators/js-expression-validator.test.ts b/src/validators/js-expression-validator.test.ts index eeadafc78..903c6081d 100644 --- a/src/validators/js-expression-validator.test.ts +++ b/src/validators/js-expression-validator.test.ts @@ -20,22 +20,22 @@ describe('ExpressionValidator - validate', () => { it('should evaluate js expressions', () => { // setup const field = allFields.find((f) => f.id == 'htsProviderRemarks'); - const failsWhenExpression = '!isEmpty(myValue) && isEmpty(`referredToPreventionServices`)'; + const failsWhenExpression = '!isEmpty(myValue) && isEmpty(referredToPreventionServices)'; // replay let errors = ExpressionValidator.validate(field, 'Remarks..', { failsWhenExpression, expressionContext, values, - message: 'Atleast one type of Prevention Services must be selected', - fields: allFields, + message: 'At least one type of Prevention Services must be selected', + formFields: allFields, }); // verify expect(errors).toEqual([ { errCode: 'value.invalid', - message: 'Atleast one type of Prevention Services must be selected', + message: 'At least one type of Prevention Services must be selected', resultType: 'error', }, ]); @@ -49,14 +49,14 @@ describe('ExpressionValidator - validate', () => { expressionContext, values, message: 'Atleast one type of Prevention Services must be selected', - fields: allFields, + formFields: allFields, }); // verify expect(errors).toEqual([]); }); - fit('should fail if date value is not within the configured bounds', () => { + it('should fail if date value is not within the configured bounds', () => { // setup const dateField: FormField = { label: 'Test Date', @@ -81,7 +81,7 @@ describe('ExpressionValidator - validate', () => { ...dateField.validators[0], expressionContext, values, - fields: allFields, + formFields: allFields, }); // verify @@ -96,7 +96,7 @@ describe('ExpressionValidator - validate', () => { ...dateField.validators[0], expressionContext, values, - fields: allFields, + formFields: allFields, }); // verify @@ -109,7 +109,7 @@ describe('ExpressionValidator - validate', () => { ...dateField.validators[0], expressionContext, values, - fields: allFields, + formFields: allFields, }); // verify diff --git a/src/validators/js-expression-validator.ts b/src/validators/js-expression-validator.ts index 5ce944433..6d9bbc69c 100644 --- a/src/validators/js-expression-validator.ts +++ b/src/validators/js-expression-validator.ts @@ -5,7 +5,7 @@ interface ExpressionValidatorConfig { failsWhenExpression?: string; warnsWhenExpression?: string; message: string; - fields: FormField[]; + formFields: FormField[]; expressionContext: ExpressionContext; values: Record; } @@ -23,7 +23,7 @@ export const ExpressionValidator: FormFieldValidator = { return evaluateExpression( config[key], { value: field, type: 'field' }, - config.fields, + config.formFields, { ...config.values, [field.id]: value }, config.expressionContext, )