@openmrs/esm-form-engine-lib 3.1.3-pre.1764 → 3.1.3

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
  {
2
2
  "name": "@openmrs/esm-form-engine-lib",
3
- "version": "3.1.3-pre.1764",
3
+ "version": "3.1.3",
4
4
  "description": "React Form Engine for O3",
5
5
  "browser": "dist/openmrs-esm-form-engine-lib.js",
6
6
  "main": "src/index.ts",
@@ -102,6 +102,5 @@
102
102
  "*.{ts,tsx}": "eslint --cache --fix --max-warnings 0",
103
103
  "*.{css,scss,ts,tsx}": "prettier --write --list-different"
104
104
  },
105
- "packageManager": "yarn@4.6.0",
106
- "stableVersion": "3.1.2"
105
+ "packageManager": "yarn@4.6.0"
107
106
  }
@@ -0,0 +1,151 @@
1
+ import { handleFieldLogic, validateFieldValue } from './fieldLogic';
2
+ import { evaluateAsyncExpression, evaluateExpression } from '../../../utils/expression-runner';
3
+ import { type FormField } from '../../../types';
4
+ import { type FormContextProps } from '../../../provider/form-provider';
5
+
6
+ jest.mock('../../../utils/expression-runner', () => ({
7
+ evaluateExpression: jest.fn(),
8
+ evaluateAsyncExpression: jest.fn().mockResolvedValue({ result: 'mockedResult' }),
9
+ }));
10
+
11
+ describe('handleFieldLogic', () => {
12
+ let mockContext: FormContextProps;
13
+ let mockFieldCoded: FormField;
14
+
15
+ beforeEach(() => {
16
+ mockContext = {
17
+ methods: {
18
+ getValues: jest.fn().mockReturnValue({}),
19
+ setValue: jest.fn(),
20
+ },
21
+ formFields: [],
22
+ sessionMode: 'edit',
23
+ patient: {},
24
+ formFieldValidators: {},
25
+ formFieldAdapters: {
26
+ obs: {
27
+ transformFieldValue: jest.fn(),
28
+ }
29
+ },
30
+ formJson: { pages: [] },
31
+ updateFormField: jest.fn(),
32
+ setForm: jest.fn(),
33
+ } as unknown as FormContextProps;
34
+
35
+ mockFieldCoded = {
36
+ id: 'testField',
37
+ label: 'Test Field',
38
+ type: 'obs',
39
+ questionOptions: {
40
+ rendering: 'radio',
41
+ answers: [
42
+ {
43
+ label: 'Test Answer',
44
+ concept: 'testConcept',
45
+ disable: {
46
+ disableWhenExpression: 'myValue > 10',
47
+ },
48
+ },
49
+ ],
50
+ },
51
+ fieldDependents: [],
52
+ sectionDependents: [],
53
+ pageDependents: [],
54
+ validators: [],
55
+ } as unknown as FormField;
56
+ });
57
+
58
+ it('should evaluate field answer disabled logic', () => {
59
+ (evaluateExpression as jest.Mock).mockReturnValue(true);
60
+
61
+ handleFieldLogic(mockFieldCoded, mockContext);
62
+
63
+ expect(evaluateExpression).toHaveBeenCalledWith(
64
+ 'myValue > 10',
65
+ { value: mockFieldCoded, type: 'field' },
66
+ mockContext.formFields,
67
+ mockContext.methods.getValues(),
68
+ {
69
+ mode: mockContext.sessionMode,
70
+ patient: mockContext.patient,
71
+ },
72
+ );
73
+ expect(mockFieldCoded.questionOptions.answers[0].disable.isDisabled).toBe(true);
74
+ });
75
+
76
+ it('should handle field dependents logic', () => {
77
+ mockFieldCoded.fieldDependents = new Set(['dependentField']);
78
+ mockContext.formFields = [
79
+ {
80
+ id: 'dependentField',
81
+ type: 'obs',
82
+ questionOptions: {
83
+ calculate: {
84
+ calculateExpression: '2 + 2',
85
+ },
86
+ },
87
+ validators: [],
88
+ meta: {},
89
+ } as unknown as FormField,
90
+ ];
91
+ handleFieldLogic(mockFieldCoded, mockContext);
92
+
93
+ expect(mockContext.updateFormField).toHaveBeenCalled();
94
+ });
95
+ });
96
+
97
+ describe('validateFieldValue', () => {
98
+ let mockField: FormField;
99
+ let mockValidators: Record<string, any>;
100
+ let mockContext: any;
101
+
102
+ beforeEach(() => {
103
+ mockField = {
104
+ id: 'testField',
105
+ validators: [
106
+ {
107
+ type: 'required',
108
+ },
109
+ ],
110
+ meta: {},
111
+ } as unknown as FormField;
112
+
113
+ mockValidators = {
114
+ required: {
115
+ validate: jest.fn().mockReturnValue([
116
+ { resultType: 'error', message: 'Field is required' },
117
+ ]),
118
+ },
119
+ };
120
+
121
+ mockContext = {
122
+ formFields: [],
123
+ values: {},
124
+ expressionContext: {
125
+ patient: {},
126
+ mode: 'edit',
127
+ },
128
+ };
129
+ });
130
+
131
+ it('should validate field value and return errors and warnings', () => {
132
+ const result = validateFieldValue(mockField, '', mockValidators, mockContext);
133
+
134
+ expect(mockValidators.required.validate).toHaveBeenCalledWith(
135
+ mockField,
136
+ '',
137
+ expect.objectContaining(mockContext),
138
+ );
139
+ expect(result.errors).toEqual([{ resultType: 'error', message: 'Field is required' }]);
140
+ expect(result.warnings).toEqual([]);
141
+ });
142
+
143
+ it('should return empty errors and warnings if field submission is unspecified', () => {
144
+ mockField.meta.submission = { unspecified: true };
145
+
146
+ const result = validateFieldValue(mockField, '', mockValidators, mockContext);
147
+
148
+ expect(result.errors).toEqual([]);
149
+ expect(result.warnings).toEqual([]);
150
+ });
151
+ });
@@ -1,10 +1,9 @@
1
1
  import { codedTypes } from '../../../constants';
2
2
  import { type FormContextProps } from '../../../provider/form-provider';
3
3
  import { type FormFieldValidator, type SessionMode, type ValidationResult, type FormField } from '../../../types';
4
- import { isTrue } from '../../../utils/boolean-utils';
5
4
  import { hasRendering } from '../../../utils/common-utils';
6
5
  import { evaluateAsyncExpression, evaluateExpression } from '../../../utils/expression-runner';
7
- import { evalConditionalRequired, evaluateDisabled, evaluateHide } from '../../../utils/form-helper';
6
+ import { evalConditionalRequired, evaluateDisabled, evaluateHide, findFieldSection } from '../../../utils/form-helper';
8
7
  import { isEmpty } from '../../../validators/form-validator';
9
8
  import { reportError } from '../../../utils/error-utils';
10
9
 
@@ -49,6 +48,8 @@ function evaluateFieldDependents(field: FormField, values: any, context: FormCon
49
48
  updateFormField,
50
49
  setForm,
51
50
  } = context;
51
+
52
+ let shouldUpdateForm = false;
52
53
  // handle fields
53
54
  if (field.fieldDependents) {
54
55
  field.fieldDependents.forEach((dep) => {
@@ -89,6 +90,9 @@ function evaluateFieldDependents(field: FormField, values: any, context: FormCon
89
90
  }
90
91
  // evaluate hide
91
92
  if (dependent.hide) {
93
+ const targetSection = findFieldSection(formJson, dependent);
94
+ const isSectionVisible = targetSection?.questions.some((question) => !question.isHidden);
95
+
92
96
  evaluateHide(
93
97
  { value: dependent, type: 'field' },
94
98
  formFields,
@@ -98,6 +102,27 @@ function evaluateFieldDependents(field: FormField, values: any, context: FormCon
98
102
  evaluateExpression,
99
103
  updateFormField,
100
104
  );
105
+
106
+ if (targetSection) {
107
+ targetSection.questions = targetSection?.questions.map((question) => {
108
+ if (question.id === dependent.id) {
109
+ return dependent;
110
+ }
111
+ return question;
112
+ });
113
+ const isDependentFieldHidden = dependent.isHidden;
114
+ const sectionHasVisibleFieldAfterEvaluation = [...targetSection.questions, dependent].some(
115
+ (field) => !field.isHidden,
116
+ );
117
+
118
+ if (!isSectionVisible && !isDependentFieldHidden) {
119
+ targetSection.isHidden = false;
120
+ shouldUpdateForm = true;
121
+ } else if (isSectionVisible && !sectionHasVisibleFieldAfterEvaluation) {
122
+ targetSection.isHidden = true;
123
+ shouldUpdateForm = true;
124
+ }
125
+ }
101
126
  }
102
127
  // evaluate disabled
103
128
  if (typeof dependent.disabled === 'object' && dependent.disabled.disableWhenExpression) {
@@ -192,8 +217,6 @@ function evaluateFieldDependents(field: FormField, values: any, context: FormCon
192
217
  });
193
218
  }
194
219
 
195
- let shouldUpdateForm = false;
196
-
197
220
  // handle sections
198
221
  if (field.sectionDependents) {
199
222
  field.sectionDependents.forEach((sectionId) => {
@@ -1,5 +1,5 @@
1
1
  import { type LayoutType } from '@openmrs/esm-framework';
2
- import type { FormField, FormPage, FormSection, SessionMode, FHIRObsResource, RenderType } from '../types';
2
+ import type { FormField, FormPage, FormSection, SessionMode, FHIRObsResource, RenderType, FormSchema } from '../types';
3
3
  import { isEmpty } from '../validators/form-validator';
4
4
  import { parseToLocalDateTime } from './common-utils';
5
5
  import dayjs from 'dayjs';
@@ -271,3 +271,13 @@ function extractFHIRObsValue(fhirObs: FHIRObsResource, rendering: RenderType) {
271
271
  return fhirObs.valueString;
272
272
  }
273
273
  }
274
+
275
+ /**
276
+ * Find formField section
277
+ * @param formJson FormSchema
278
+ * @param field FormField
279
+ */
280
+ export function findFieldSection(formJson: FormSchema, field: FormField) {
281
+ let page = formJson.pages.find((page) => field.meta.pageId === page.id);
282
+ return page.sections.find((section) => section.questions.find((question) => question.id === field.id));
283
+ }