@openmrs/esm-form-engine-lib 2.1.0-pre.1511 → 2.1.0-pre.1517
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/sample_unspecified-form.json +39 -0
- package/package.json +1 -1
- package/src/components/inputs/multi-select/multi-select.component.tsx +3 -3
- package/src/components/inputs/unspecified/unspecified.component.tsx +9 -6
- package/src/components/inputs/unspecified/unspecified.test.tsx +142 -50
- package/src/components/renderer/field/form-field-renderer.component.tsx +4 -0
- package/src/utils/common-utils.ts +1 -0
- package/22ec231da647cd2c/22ec231da647cd2c.gz +0 -0
- package/3a3e1d216bd6470d/3a3e1d216bd6470d.gz +0 -0
- package/485e0040f135cee8/485e0040f135cee8.gz +0 -0
- package/b3059e748360776a/b3059e748360776a.gz +0 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
{
|
2
|
+
"name": "Sample Unspecified Form",
|
3
|
+
"pages": [
|
4
|
+
{
|
5
|
+
"label": "Page 1",
|
6
|
+
"sections": [
|
7
|
+
{
|
8
|
+
"label": "Section 1",
|
9
|
+
"isExpanded": "true",
|
10
|
+
"questions": [
|
11
|
+
{
|
12
|
+
"label": "Body Weight",
|
13
|
+
"type": "obs",
|
14
|
+
"questionOptions": {
|
15
|
+
"rendering": "number",
|
16
|
+
"concept": "560555AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
17
|
+
"conceptMappings": [
|
18
|
+
{
|
19
|
+
"type": "CIEL",
|
20
|
+
"value": "160555"
|
21
|
+
}
|
22
|
+
]
|
23
|
+
},
|
24
|
+
"id": "bodyWeight",
|
25
|
+
"validators": [],
|
26
|
+
"required": true,
|
27
|
+
"unspecified": true
|
28
|
+
}
|
29
|
+
]
|
30
|
+
}
|
31
|
+
]
|
32
|
+
}
|
33
|
+
],
|
34
|
+
"availableIntents": [],
|
35
|
+
"processor": "EncounterFormProcessor",
|
36
|
+
"uuid": "na24c540-cc83-43bc-978f-c1ef180a597f",
|
37
|
+
"referencedForms": [],
|
38
|
+
"encounterType": "b9c1f50f-f77d-42e2-ad2a-d29304dde2fv"
|
39
|
+
}
|
package/package.json
CHANGED
@@ -104,7 +104,7 @@ const MultiSelect: React.FC<FormFieldInputProps> = ({ field, value, errors, warn
|
|
104
104
|
initialSelectedItems={initiallySelectedQuestionItems}
|
105
105
|
label={''}
|
106
106
|
titleText={label}
|
107
|
-
key={
|
107
|
+
key={field.id}
|
108
108
|
itemToString={(item) => (item ? item.label : ' ')}
|
109
109
|
disabled={field.isDisabled}
|
110
110
|
invalid={errors.length > 0}
|
@@ -118,10 +118,10 @@ const MultiSelect: React.FC<FormFieldInputProps> = ({ field, value, errors, warn
|
|
118
118
|
{field.questionOptions.answers?.map((value, index) => {
|
119
119
|
return (
|
120
120
|
<Checkbox
|
121
|
-
key={value.concept}
|
121
|
+
key={`${field.id}-${value.concept}`}
|
122
122
|
className={styles.checkbox}
|
123
123
|
labelText={value.label}
|
124
|
-
id={value.concept}
|
124
|
+
id={`${field.id}-${value.concept}`}
|
125
125
|
onChange={() => {
|
126
126
|
handleSelectCheckbox(value);
|
127
127
|
}}
|
@@ -7,7 +7,7 @@ import { isTrue } from '../../../utils/boolean-utils';
|
|
7
7
|
|
8
8
|
import styles from './unspecified.scss';
|
9
9
|
import { useFormProviderContext } from '../../../provider/form-provider';
|
10
|
-
import { isViewMode } from '../../../utils/common-utils';
|
10
|
+
import { clearSubmission, isViewMode } from '../../../utils/common-utils';
|
11
11
|
|
12
12
|
interface UnspecifiedFieldProps {
|
13
13
|
field: FormField;
|
@@ -29,25 +29,28 @@ const UnspecifiedField: React.FC<UnspecifiedFieldProps> = ({ field, fieldValue,
|
|
29
29
|
}, []);
|
30
30
|
|
31
31
|
useEffect(() => {
|
32
|
-
if (field.meta.submission?.unspecified && field.meta.submission.newValue) {
|
32
|
+
if (field.meta.submission?.unspecified && (field.meta.submission.newValue || !isEmpty(fieldValue))) {
|
33
33
|
setIsUnspecified(false);
|
34
34
|
field.meta.submission.unspecified = false;
|
35
35
|
updateFormField(field);
|
36
36
|
}
|
37
|
-
}, [field.meta?.submission]);
|
37
|
+
}, [field.meta?.submission, fieldValue]);
|
38
38
|
|
39
39
|
const handleOnChange = useCallback(
|
40
40
|
(value) => {
|
41
41
|
const rendering = field.questionOptions.rendering;
|
42
42
|
if (value.target.checked) {
|
43
|
-
const emptyValue = rendering === 'checkbox' ? [] : '';
|
44
|
-
field.meta.submission = { ...field.meta.submission, unspecified: true };
|
45
|
-
updateFormField({ ...field });
|
46
43
|
setIsUnspecified(true);
|
44
|
+
const emptyValue = rendering === 'checkbox' ? [] : '';
|
45
|
+
clearSubmission(field);
|
46
|
+
field.meta.submission.unspecified = true;
|
47
|
+
updateFormField(field);
|
47
48
|
setFieldValue(emptyValue);
|
48
49
|
onAfterChange(emptyValue);
|
49
50
|
} else {
|
50
51
|
setIsUnspecified(false);
|
52
|
+
field.meta.submission.unspecified = false;
|
53
|
+
updateFormField(field);
|
51
54
|
}
|
52
55
|
},
|
53
56
|
[field.questionOptions.rendering],
|
@@ -1,74 +1,166 @@
|
|
1
1
|
import React from 'react';
|
2
|
-
import
|
3
|
-
import {
|
4
|
-
import {
|
5
|
-
import {
|
6
|
-
import
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
2
|
+
import { act, render, screen } from '@testing-library/react';
|
3
|
+
import { usePatient, useSession } from '@openmrs/esm-framework';
|
4
|
+
import { type FormSchema, type SessionMode } from '../../../types';
|
5
|
+
import { findNumberInput } from '../../../utils/test-utils';
|
6
|
+
import unspecifiedForm from '../../../../__mocks__/forms/rfe-forms/sample_unspecified-form.json';
|
7
|
+
import { FormEngine } from '../../..';
|
8
|
+
import { mockPatient } from '../../../../__mocks__/patient.mock';
|
9
|
+
import { mockSessionDataResponse } from '../../../../__mocks__/session.mock';
|
10
|
+
import userEvent from '@testing-library/user-event';
|
11
|
+
import * as api from '../../../api';
|
12
|
+
|
13
|
+
const mockUsePatient = jest.mocked(usePatient);
|
14
|
+
const mockUseSession = jest.mocked(useSession);
|
15
|
+
|
16
|
+
global.ResizeObserver = require('resize-observer-polyfill');
|
17
|
+
|
18
|
+
jest.mock('../../../api', () => {
|
19
|
+
const originalModule = jest.requireActual('../../../api');
|
20
|
+
return {
|
21
|
+
...originalModule,
|
22
|
+
getPreviousEncounter: jest.fn().mockImplementation(() => Promise.resolve(null)),
|
23
|
+
getConcept: jest.fn().mockImplementation(() => Promise.resolve(null)),
|
24
|
+
saveEncounter: jest.fn(),
|
25
|
+
};
|
21
26
|
});
|
22
27
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
},
|
31
|
-
|
32
|
-
};
|
28
|
+
jest.mock('../../../hooks/useConcepts', () => ({
|
29
|
+
useConcepts: jest.fn().mockImplementation((references: Set<string>) => {
|
30
|
+
return {
|
31
|
+
isLoading: false,
|
32
|
+
concepts: [],
|
33
|
+
error: undefined,
|
34
|
+
};
|
35
|
+
}),
|
36
|
+
}));
|
33
37
|
|
34
|
-
|
35
|
-
|
38
|
+
jest.mock('../../../hooks/useEncounterRole', () => ({
|
39
|
+
useEncounterRole: jest.fn().mockReturnValue({
|
40
|
+
isLoading: false,
|
41
|
+
encounterRole: { name: 'Clinician', uuid: 'clinician-uuid' },
|
42
|
+
error: undefined,
|
43
|
+
}),
|
44
|
+
}));
|
45
|
+
|
46
|
+
jest.mock('../../../hooks/useEncounter', () => ({
|
47
|
+
useEncounter: jest.fn().mockImplementation((formJson: FormSchema) => {
|
48
|
+
return {
|
49
|
+
encounter: formJson.encounter
|
50
|
+
? {
|
51
|
+
uuid: 'encounter-uuid',
|
52
|
+
obs: [],
|
53
|
+
}
|
54
|
+
: null,
|
55
|
+
isLoading: false,
|
56
|
+
error: undefined,
|
57
|
+
};
|
58
|
+
}),
|
59
|
+
}));
|
60
|
+
|
61
|
+
const renderForm = async (mode: SessionMode = 'enter') => {
|
62
|
+
await act(async () => {
|
63
|
+
render(
|
64
|
+
<FormEngine
|
65
|
+
formJson={unspecifiedForm as FormSchema}
|
66
|
+
patientUUID="8673ee4f-e2ab-4077-ba55-4980f408773e"
|
67
|
+
mode={mode}
|
68
|
+
encounterUUID={mode === 'edit' ? 'encounter-uuid' : null}
|
69
|
+
/>,
|
70
|
+
);
|
71
|
+
});
|
36
72
|
};
|
37
73
|
|
38
|
-
describe
|
39
|
-
|
40
|
-
|
41
|
-
|
74
|
+
describe('Unspecified', () => {
|
75
|
+
const user = userEvent.setup();
|
76
|
+
|
77
|
+
beforeEach(() => {
|
78
|
+
Object.defineProperty(window, 'i18next', {
|
79
|
+
writable: true,
|
80
|
+
configurable: true,
|
81
|
+
value: {
|
82
|
+
language: 'en',
|
83
|
+
t: jest.fn(),
|
84
|
+
},
|
85
|
+
});
|
86
|
+
|
87
|
+
mockUsePatient.mockImplementation(() => ({
|
88
|
+
patient: mockPatient,
|
89
|
+
isLoading: false,
|
90
|
+
error: undefined,
|
91
|
+
patientUuid: mockPatient.id,
|
92
|
+
}));
|
93
|
+
|
94
|
+
mockUseSession.mockImplementation(() => mockSessionDataResponse.data);
|
95
|
+
});
|
96
|
+
|
97
|
+
it('Should clear field value when the "Unspecified" checkbox is clicked', async () => {
|
98
|
+
//setup
|
99
|
+
await renderForm();
|
42
100
|
const unspecifiedCheckbox = screen.getByRole('checkbox', { name: /Unspecified/ });
|
101
|
+
const bodyWeightField = await findNumberInput(screen, 'Body Weight *');
|
43
102
|
|
44
103
|
// assert initial state
|
45
104
|
expect(unspecifiedCheckbox).not.toBeChecked();
|
105
|
+
expect(bodyWeightField.value).toBe('');
|
46
106
|
|
47
|
-
|
48
|
-
fireEvent.click(unspecifiedCheckbox);
|
49
|
-
expect(unspecifiedCheckbox).toBeChecked();
|
107
|
+
await user.type(bodyWeightField, '55');
|
50
108
|
|
51
|
-
// assert
|
52
|
-
|
53
|
-
|
109
|
+
// assert new value
|
110
|
+
expect(bodyWeightField.value).toBe('55');
|
111
|
+
|
112
|
+
// mark as unspecified
|
113
|
+
await user.click(unspecifiedCheckbox);
|
114
|
+
expect(unspecifiedCheckbox).toBeChecked();
|
115
|
+
expect(bodyWeightField.value).toBe('');
|
54
116
|
});
|
55
117
|
|
56
|
-
it('Should
|
118
|
+
it('Should bypass form validation when the "Unspecified" checkbox is clicked', async () => {
|
57
119
|
//setup
|
58
|
-
|
120
|
+
const mockSaveEncounter = jest.spyOn(api, 'saveEncounter');
|
121
|
+
await renderForm();
|
59
122
|
const unspecifiedCheckbox = screen.getByRole('checkbox', { name: /Unspecified/ });
|
60
|
-
const
|
123
|
+
const bodyWeightField = await findNumberInput(screen, 'Body Weight *');
|
61
124
|
|
62
125
|
// assert initial state
|
63
126
|
expect(unspecifiedCheckbox).not.toBeChecked();
|
64
|
-
expect(
|
127
|
+
expect(bodyWeightField.value).toBe('');
|
128
|
+
|
129
|
+
// attempt to submit the form
|
130
|
+
await user.click(screen.getByRole('button', { name: /Save/ }));
|
131
|
+
expect(screen.getByText(/Field is mandatory/)).toBeInTheDocument();
|
132
|
+
expect(mockSaveEncounter).not.toHaveBeenCalled();
|
133
|
+
|
134
|
+
// mark as unspecified
|
135
|
+
await user.click(unspecifiedCheckbox);
|
136
|
+
expect(unspecifiedCheckbox).toBeChecked();
|
137
|
+
expect(bodyWeightField.value).toBe('');
|
138
|
+
|
139
|
+
// submit the form again
|
140
|
+
await user.click(screen.getByRole('button', { name: /Save/ }));
|
141
|
+
expect(mockSaveEncounter).toHaveBeenCalled();
|
142
|
+
});
|
65
143
|
|
66
|
-
|
144
|
+
it('Should mark fields with null values as unspecified when in edit mode', async () => {
|
145
|
+
// setup
|
146
|
+
await renderForm('edit');
|
147
|
+
const unspecifiedCheckbox = screen.getByRole('checkbox', { name: /Unspecified/ });
|
148
|
+
const bodyWeightField = await findNumberInput(screen, 'Body Weight *');
|
67
149
|
|
68
|
-
// assert
|
69
|
-
fireEvent.click(unspecifiedCheckbox);
|
150
|
+
// assert initial state
|
70
151
|
expect(unspecifiedCheckbox).toBeChecked();
|
71
|
-
|
72
|
-
|
152
|
+
expect(bodyWeightField.value).toBe('');
|
153
|
+
});
|
154
|
+
|
155
|
+
it('Should not display the unspecified checkbox in view mode', async () => {
|
156
|
+
// setup
|
157
|
+
await renderForm('view');
|
158
|
+
|
159
|
+
try {
|
160
|
+
screen.getByRole('checkbox', { name: /Unspecified/ });
|
161
|
+
fail('Unspecified checkbox should not be displayed');
|
162
|
+
} catch (error) {
|
163
|
+
expect(error).toBeDefined();
|
164
|
+
}
|
73
165
|
});
|
74
166
|
});
|
@@ -105,6 +105,10 @@ export const FormFieldRenderer = ({ fieldId, valueAdapter, repeatOptions }: Form
|
|
105
105
|
if (field.meta.submission?.warnings) {
|
106
106
|
setWarnings(field.meta.submission.warnings);
|
107
107
|
}
|
108
|
+
if (field.meta.submission?.unspecified) {
|
109
|
+
setErrors([]);
|
110
|
+
removeInvalidField(field.id);
|
111
|
+
}
|
108
112
|
}, [field.meta.submission]);
|
109
113
|
|
110
114
|
const onAfterChange = (value: any) => {
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|