@openmrs/esm-form-engine-lib 2.1.0-pre.1540 → 2.1.0-pre.1542
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/__mocks__/forms/rfe-forms/hide-pages-and-sections-form.json +93 -0
- package/package.json +1 -1
- package/src/components/renderer/field/fieldLogic.ts +3 -3
- package/src/form-engine.test.tsx +44 -0
- package/src/hooks/useEvaluateFormFieldExpressions.ts +2 -1
- package/src/hooks/useFormStateHelpers.ts +2 -1
- package/src/utils/common-utils.ts +13 -1
@@ -0,0 +1,93 @@
|
|
1
|
+
{
|
2
|
+
"encounterType": "e22e39fd-7db2-45e7-80f1-60fa0d5a4378",
|
3
|
+
"name": "Hide Pages and Sections",
|
4
|
+
"processor": "EncounterFormProcessor",
|
5
|
+
"referencedForms": [],
|
6
|
+
"uuid": "7c77485c-7a57-4646-ac21-11d92555a420",
|
7
|
+
"version": "1.0",
|
8
|
+
"pages": [
|
9
|
+
{
|
10
|
+
"label": "Page 1",
|
11
|
+
"sections": [
|
12
|
+
{
|
13
|
+
"label": "Section 1A",
|
14
|
+
"isExpanded": "true",
|
15
|
+
"questions": [
|
16
|
+
{
|
17
|
+
"id": "hideSection1B",
|
18
|
+
"label": "Hide Section 1B",
|
19
|
+
"type": "obs",
|
20
|
+
"questionOptions": {
|
21
|
+
"rendering": "text",
|
22
|
+
"concept": "7aef2620-76e0-4d88-b9cb-c47ba4f67bce"
|
23
|
+
}
|
24
|
+
}
|
25
|
+
]
|
26
|
+
},
|
27
|
+
{
|
28
|
+
"label": "Section 1B",
|
29
|
+
"isExpanded": "true",
|
30
|
+
"hide": {
|
31
|
+
"hideWhenExpression": "isEmpty(hideSection1B)"
|
32
|
+
},
|
33
|
+
"questions": [
|
34
|
+
{
|
35
|
+
"id": "hidePage2",
|
36
|
+
"label": "Hide Page 2",
|
37
|
+
"type": "obs",
|
38
|
+
"questionOptions": {
|
39
|
+
"rendering": "radio",
|
40
|
+
"concept": "1255AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
41
|
+
"answers": [
|
42
|
+
{
|
43
|
+
"concept": "1256AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
44
|
+
"label": "Choice 1",
|
45
|
+
"conceptMappings": []
|
46
|
+
},
|
47
|
+
{
|
48
|
+
"concept": "1258AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
49
|
+
"label": "Choice 2",
|
50
|
+
"conceptMappings": []
|
51
|
+
},
|
52
|
+
{
|
53
|
+
"concept": "1259AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
54
|
+
"label": "Choice 3",
|
55
|
+
"conceptMappings": []
|
56
|
+
}
|
57
|
+
]
|
58
|
+
}
|
59
|
+
}
|
60
|
+
]
|
61
|
+
}
|
62
|
+
]
|
63
|
+
},
|
64
|
+
{
|
65
|
+
"label": "Page 2",
|
66
|
+
"hide": {
|
67
|
+
"hideWhenExpression": "hidePage2 === '1258AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'"
|
68
|
+
},
|
69
|
+
"sections": [
|
70
|
+
{
|
71
|
+
"label": "Section 2A",
|
72
|
+
"isExpanded": "true",
|
73
|
+
"questions": [
|
74
|
+
{
|
75
|
+
"label": "Date",
|
76
|
+
"type": "obs",
|
77
|
+
"required": false,
|
78
|
+
"id": "date",
|
79
|
+
"datePickerFormat": "calendar",
|
80
|
+
"questionOptions": {
|
81
|
+
"rendering": "date",
|
82
|
+
"concept": "159599AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
83
|
+
"conceptMappings": []
|
84
|
+
},
|
85
|
+
"validators": []
|
86
|
+
}
|
87
|
+
]
|
88
|
+
}
|
89
|
+
]
|
90
|
+
}
|
91
|
+
],
|
92
|
+
"description": "Hide Pages and Sections"
|
93
|
+
}
|
package/package.json
CHANGED
@@ -206,9 +206,9 @@ function evaluateFieldDependents(field: FormField, values: any, context: FormCon
|
|
206
206
|
}
|
207
207
|
shouldUpdateForm = true;
|
208
208
|
});
|
209
|
+
}
|
209
210
|
|
210
|
-
|
211
|
-
|
212
|
-
}
|
211
|
+
if (shouldUpdateForm) {
|
212
|
+
setForm(formJson);
|
213
213
|
}
|
214
214
|
}
|
package/src/form-engine.test.tsx
CHANGED
@@ -43,6 +43,7 @@ import nextVisitForm from '__mocks__/forms/rfe-forms/next-visit-test-form.json';
|
|
43
43
|
import viralLoadStatusForm from '__mocks__/forms/rfe-forms/viral-load-status-form.json';
|
44
44
|
import readOnlyValidationForm from '__mocks__/forms/rfe-forms/read-only-validation-form.json';
|
45
45
|
import jsExpressionValidationForm from '__mocks__/forms/rfe-forms/js-expression-validation-form.json';
|
46
|
+
import hidePagesAndSectionsForm from '__mocks__/forms/rfe-forms/hide-pages-and-sections-form.json';
|
46
47
|
|
47
48
|
import FormEngine from './form-engine.component';
|
48
49
|
import { type SessionMode } from './types';
|
@@ -635,6 +636,49 @@ describe('Form engine component', () => {
|
|
635
636
|
});
|
636
637
|
});
|
637
638
|
|
639
|
+
describe('Hide pages and sections', () => {
|
640
|
+
it('should hide/show section based on field value', async () => {
|
641
|
+
await act(async () => renderForm(null, hidePagesAndSectionsForm));
|
642
|
+
|
643
|
+
// assert section "Section 1B" is hidden at initial render
|
644
|
+
try {
|
645
|
+
await screen.findByText('Section 1B');
|
646
|
+
fail('The section named "Section 1B" should be hidden');
|
647
|
+
} catch (err) {
|
648
|
+
expect(err.message.includes('Unable to find an element with the text: Section 1B')).toBeTruthy();
|
649
|
+
}
|
650
|
+
|
651
|
+
// user interactions to make section visible
|
652
|
+
const hideSection1bField = await findTextOrDateInput(screen, 'Hide Section 1B');
|
653
|
+
await user.type(hideSection1bField, 'Some value');
|
654
|
+
|
655
|
+
const section1b = await screen.findByText('Section 1B');
|
656
|
+
expect(section1b).toBeInTheDocument();
|
657
|
+
});
|
658
|
+
|
659
|
+
it('should hide/show page based on field value', async () => {
|
660
|
+
await act(async () => renderForm(null, hidePagesAndSectionsForm));
|
661
|
+
|
662
|
+
// assert page "Page 2" is visible at initial render
|
663
|
+
const page2 = await screen.findByText('Page 2');
|
664
|
+
expect(page2).toBeInTheDocument();
|
665
|
+
|
666
|
+
// user interactions to hide page
|
667
|
+
const hideSection1bField = await findTextOrDateInput(screen, 'Hide Section 1B');
|
668
|
+
await user.type(hideSection1bField, 'Some value');
|
669
|
+
const choice2RadioOption = screen.getByRole('radio', { name: /Choice 2/i });
|
670
|
+
await user.click(choice2RadioOption);
|
671
|
+
|
672
|
+
// assert page is hidden
|
673
|
+
try {
|
674
|
+
await screen.findByText('Page 2');
|
675
|
+
fail('The page named "Page 2" should be hidden');
|
676
|
+
} catch (err) {
|
677
|
+
expect(err.message.includes('Unable to find an element with the text: Page 2')).toBeTruthy();
|
678
|
+
}
|
679
|
+
});
|
680
|
+
});
|
681
|
+
|
638
682
|
describe('Calculated values', () => {
|
639
683
|
it('should evaluate BMI', async () => {
|
640
684
|
await act(async () => renderForm(null, bmiForm));
|
@@ -5,6 +5,7 @@ import { evalConditionalRequired, evaluateConditionalAnswered, evaluateHide } fr
|
|
5
5
|
import { isTrue } from '../utils/boolean-utils';
|
6
6
|
import { isEmpty } from '../validators/form-validator';
|
7
7
|
import { type QuestionAnswerOption } from '../types/schema';
|
8
|
+
import { updateFormSectionReferences } from '../utils/common-utils';
|
8
9
|
|
9
10
|
export const useEvaluateFormFieldExpressions = (
|
10
11
|
formValues: Record<string, any>,
|
@@ -125,7 +126,7 @@ export const useEvaluateFormFieldExpressions = (
|
|
125
126
|
}
|
126
127
|
});
|
127
128
|
});
|
128
|
-
setEvaluatedFormJson(factoryContext.formJson);
|
129
|
+
setEvaluatedFormJson(updateFormSectionReferences(factoryContext.formJson));
|
129
130
|
}, [factoryContext.formJson, formFields]);
|
130
131
|
|
131
132
|
return { evaluatedFormJson, evaluatedFields };
|
@@ -2,6 +2,7 @@ import { type Dispatch, useCallback } from 'react';
|
|
2
2
|
import { type FormField, type FormSchema } from '../types';
|
3
3
|
import { type Action } from '../components/renderer/form/state';
|
4
4
|
import cloneDeep from 'lodash/cloneDeep';
|
5
|
+
import { updateFormSectionReferences } from '../utils/common-utils';
|
5
6
|
|
6
7
|
export function useFormStateHelpers(dispatch: Dispatch<Action>, formFields: FormField[]) {
|
7
8
|
const addFormField = useCallback((field: FormField) => {
|
@@ -35,7 +36,7 @@ export function useFormStateHelpers(dispatch: Dispatch<Action>, formFields: Form
|
|
35
36
|
}, []);
|
36
37
|
|
37
38
|
const setForm = useCallback((formJson: FormSchema) => {
|
38
|
-
dispatch({ type: 'SET_FORM_JSON', value: formJson });
|
39
|
+
dispatch({ type: 'SET_FORM_JSON', value: updateFormSectionReferences(formJson) });
|
39
40
|
}, []);
|
40
41
|
|
41
42
|
return {
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import dayjs from 'dayjs';
|
2
|
-
import { type FormField, type OpenmrsObs, type RenderType } from '../types';
|
2
|
+
import { type FormSchema, type FormField, type OpenmrsObs, type RenderType } from '../types';
|
3
3
|
import { isEmpty } from '../validators/form-validator';
|
4
4
|
import { formatDate, type FormatDateOptions } from '@openmrs/esm-framework';
|
5
5
|
|
@@ -77,3 +77,15 @@ export function formatDateAsDisplayString(field: FormField, date: Date) {
|
|
77
77
|
}
|
78
78
|
return formatDate(date, options);
|
79
79
|
}
|
80
|
+
|
81
|
+
/**
|
82
|
+
* Creates a new copy of `formJson` with updated references at the page and section levels.
|
83
|
+
* This ensures React re-renders properly by providing new references for nested arrays.
|
84
|
+
*/
|
85
|
+
export function updateFormSectionReferences(formJson: FormSchema) {
|
86
|
+
formJson.pages = formJson.pages.map((page) => {
|
87
|
+
page.sections = Array.from(page.sections);
|
88
|
+
return page;
|
89
|
+
});
|
90
|
+
return { ...formJson };
|
91
|
+
}
|