@openmrs/esm-form-engine-lib 2.1.0-pre.1401 → 2.1.0-pre.1404
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/12ea0b64ac1d74d9/12ea0b64ac1d74d9.gz +0 -0
- package/ff6bbdac1639f6d1/ff6bbdac1639f6d1.gz +0 -0
- package/package.json +1 -1
- package/src/components/inputs/select/dropdown.component.tsx +22 -11
- package/src/components/inputs/select/dropdown.test.tsx +38 -1
- package/src/constants.ts +1 -0
- package/src/hooks/useInitialValues.test.ts +76 -0
- package/src/hooks/useInitialValues.ts +1 -0
- package/8f75299d4c303e6e/8f75299d4c303e6e.gz +0 -0
- package/98a5f82581f0d1ed/98a5f82581f0d1ed.gz +0 -0
- /package/{ca0db33c95d8679b/ca0db33c95d8679b.gz → b39f834950646b97/b39f834950646b97.gz} +0 -0
- /package/{afdf83d2ad7bbe00/afdf83d2ad7bbe00.gz → f7899165a77a3934/f7899165a77a3934.gz} +0 -0
Binary file
|
Binary file
|
package/package.json
CHANGED
@@ -6,29 +6,43 @@ import { isTrue } from '../../../utils/boolean-utils';
|
|
6
6
|
import { type FormFieldInputProps } from '../../../types';
|
7
7
|
import FieldValueView from '../../value/view/field-value-view.component';
|
8
8
|
import FieldLabel from '../../field-label/field-label.component';
|
9
|
-
|
10
|
-
import styles from './dropdown.scss';
|
11
9
|
import { useFormProviderContext } from '../../../provider/form-provider';
|
10
|
+
import { NullSelectOption } from '../../../constants';
|
11
|
+
import { isEmpty } from '../../../validators/form-validator';
|
12
|
+
import styles from './dropdown.scss';
|
12
13
|
|
13
14
|
const Dropdown: React.FC<FormFieldInputProps> = ({ field, value, errors, warnings, setFieldValue }) => {
|
14
15
|
const { t } = useTranslation();
|
15
16
|
const { layoutType, sessionMode, workspaceLayout, formFieldAdapters } = useFormProviderContext();
|
16
17
|
|
17
18
|
const handleChange = useCallback(
|
18
|
-
(
|
19
|
-
setFieldValue(
|
19
|
+
({ selectedItem }) => {
|
20
|
+
setFieldValue(selectedItem === NullSelectOption ? null : selectedItem);
|
20
21
|
},
|
21
22
|
[setFieldValue],
|
22
23
|
);
|
23
24
|
|
24
25
|
const itemToString = useCallback(
|
25
26
|
(item) => {
|
26
|
-
|
27
|
+
let answer = field.questionOptions.answers.find((opt) => {
|
28
|
+
return opt.value ? opt.value == item : opt.concept == item;
|
29
|
+
});
|
27
30
|
return answer?.label;
|
28
31
|
},
|
29
32
|
[field.questionOptions.answers],
|
30
33
|
);
|
31
34
|
|
35
|
+
const items = useMemo(() => {
|
36
|
+
const options = field.questionOptions.answers;
|
37
|
+
if (!options.some((option) => option.value === NullSelectOption)) {
|
38
|
+
options.unshift({
|
39
|
+
value: NullSelectOption,
|
40
|
+
label: t('chooseAnOption', 'Choose an option'),
|
41
|
+
});
|
42
|
+
}
|
43
|
+
return options.filter((option) => !option.isHidden).map((item) => item.value || item.concept);
|
44
|
+
}, [field.questionOptions.answers]);
|
45
|
+
|
32
46
|
const isInline = useMemo(() => {
|
33
47
|
if (['view', 'embedded-view'].includes(sessionMode) || isTrue(field.readonly)) {
|
34
48
|
return shouldUseInlineLayout(field.inlineRendering, layoutType, workspaceLayout, sessionMode);
|
@@ -50,13 +64,10 @@ const Dropdown: React.FC<FormFieldInputProps> = ({ field, value, errors, warning
|
|
50
64
|
<DropdownInput
|
51
65
|
id={field.id}
|
52
66
|
titleText={<FieldLabel field={field} />}
|
53
|
-
|
54
|
-
items={field.questionOptions.answers
|
55
|
-
.filter((answer) => !answer.isHidden)
|
56
|
-
.map((item) => item.value || item.concept)}
|
67
|
+
items={items}
|
57
68
|
itemToString={itemToString}
|
58
|
-
selectedItem={value}
|
59
|
-
onChange={
|
69
|
+
selectedItem={isEmpty(value) ? NullSelectOption : value}
|
70
|
+
onChange={handleChange}
|
60
71
|
disabled={field.isDisabled}
|
61
72
|
readOnly={field.readonly}
|
62
73
|
invalid={errors.length > 0}
|
@@ -117,4 +117,41 @@ describe.skip('dropdown input field', () => {
|
|
117
117
|
});
|
118
118
|
});
|
119
119
|
});
|
120
|
-
|
120
|
+
|
121
|
+
it('should clear selection when empty option is selected', async () => {
|
122
|
+
// setup
|
123
|
+
question.meta.previousValue = {
|
124
|
+
uuid: '305ed1fc-c1fd-11eb-8529-0242ac130003',
|
125
|
+
person: '833db896-c1f0-11eb-8529-0242ac130003',
|
126
|
+
obsDatetime: encounterContext.encounterDate,
|
127
|
+
concept: '1c43b05b-b6d8-4eb5-8f37-0b14f5347568',
|
128
|
+
location: { uuid: '41e6e516-c1f0-11eb-8529-0242ac130003' },
|
129
|
+
order: null,
|
130
|
+
groupMembers: [],
|
131
|
+
voided: false,
|
132
|
+
value: '6ddd933a-e65c-4f35-8884-c555b50c55e1',
|
133
|
+
};
|
134
|
+
await renderForm({ 'patient-past-program': question.meta.previousValue.value });
|
135
|
+
const dropdownWidget = screen.getByRole('combobox', { name: /Patient past program./ });
|
136
|
+
|
137
|
+
// select an option first
|
138
|
+
fireEvent.click(dropdownWidget);
|
139
|
+
const oncologyScreeningOption = screen.getByText('Oncology Screening and Diagnosis Program');
|
140
|
+
fireEvent.click(oncologyScreeningOption);
|
141
|
+
|
142
|
+
// clear the selection
|
143
|
+
fireEvent.click(dropdownWidget);
|
144
|
+
const clearOption = screen.getByText('Select an option');
|
145
|
+
fireEvent.click(clearOption);
|
146
|
+
|
147
|
+
// verify
|
148
|
+
await act(async () => {
|
149
|
+
expect(question.meta.submission.newValue).toEqual({
|
150
|
+
uuid: '305ed1fc-c1fd-11eb-8529-0242ac130003',
|
151
|
+
value: null,
|
152
|
+
formFieldNamespace: 'rfe-forms',
|
153
|
+
formFieldPath: 'rfe-forms-patient-past-program',
|
154
|
+
});
|
155
|
+
});
|
156
|
+
});
|
157
|
+
});
|
package/src/constants.ts
CHANGED
@@ -10,3 +10,4 @@ export const encounterRepresentation =
|
|
10
10
|
export const FormsStore = 'forms-engine-store';
|
11
11
|
export const PatientChartWorkspaceHeaderSlot = 'patient-chart-workspace-header-slot';
|
12
12
|
export const codedTypes = ['radio', 'checkbox', 'select', 'content-switcher'];
|
13
|
+
export const NullSelectOption = 'OPTION_NULL';
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import { act, renderHook, waitFor } from '@testing-library/react';
|
2
|
+
|
3
|
+
import useInitialValues from './useInitialValues';
|
4
|
+
import { type FormField } from '../types';
|
5
|
+
|
6
|
+
export const mockFormFields: FormField[] = [
|
7
|
+
{
|
8
|
+
label: 'First Name',
|
9
|
+
type: 'text',
|
10
|
+
id: 'firstName',
|
11
|
+
questionOptions: {
|
12
|
+
rendering: 'text',
|
13
|
+
},
|
14
|
+
value: '',
|
15
|
+
isRequired: true,
|
16
|
+
validators: [{ required: true }],
|
17
|
+
},
|
18
|
+
{
|
19
|
+
label: 'Date of Birth',
|
20
|
+
type: 'date',
|
21
|
+
questionOptions: {
|
22
|
+
rendering: 'date',
|
23
|
+
},
|
24
|
+
datePickerFormat: 'both',
|
25
|
+
id: 'dob',
|
26
|
+
value: null,
|
27
|
+
isRequired: true,
|
28
|
+
validators: [{ required: true }],
|
29
|
+
},
|
30
|
+
];
|
31
|
+
|
32
|
+
describe('useInitialValues', () => {
|
33
|
+
let formProcessor;
|
34
|
+
let context;
|
35
|
+
|
36
|
+
beforeEach(() => {
|
37
|
+
formProcessor = {
|
38
|
+
getInitialValues: jest.fn(),
|
39
|
+
};
|
40
|
+
context = {
|
41
|
+
formFields: mockFormFields,
|
42
|
+
formFieldAdapters: { adapter1: {}, adapter2: {} },
|
43
|
+
};
|
44
|
+
});
|
45
|
+
|
46
|
+
it('should not call getInitialValues if context dependencies are not loaded', () => {
|
47
|
+
const { result } = renderHook(() => useInitialValues(formProcessor, true, context));
|
48
|
+
|
49
|
+
expect(result.current.isLoadingInitialValues).toBe(true);
|
50
|
+
expect(formProcessor.getInitialValues).not.toHaveBeenCalled();
|
51
|
+
});
|
52
|
+
|
53
|
+
it('should set error if getInitialValues rejects', async () => {
|
54
|
+
const mockError = new Error('Failed to fetch initial values');
|
55
|
+
formProcessor.getInitialValues.mockRejectedValue(mockError);
|
56
|
+
|
57
|
+
const { result } = await act(() => renderHook(() => useInitialValues(formProcessor, false, context)));
|
58
|
+
|
59
|
+
expect(result.current.error).toEqual(mockError);
|
60
|
+
expect(result.current.isLoadingInitialValues).toBe(false);
|
61
|
+
});
|
62
|
+
|
63
|
+
it('should set initial values when dependencies are loaded', async () => {
|
64
|
+
const mockInitialValues = { firstName: 'John Doe', dob: '1992-10-10' };
|
65
|
+
formProcessor.getInitialValues.mockResolvedValue(mockInitialValues);
|
66
|
+
|
67
|
+
const { result } = renderHook(() => useInitialValues(formProcessor, false, context));
|
68
|
+
|
69
|
+
await waitFor(() => expect(result.current.isLoadingInitialValues).toBe(true));
|
70
|
+
|
71
|
+
expect(formProcessor.getInitialValues).toHaveBeenCalledWith(context);
|
72
|
+
expect(result.current.initialValues).toEqual(mockInitialValues);
|
73
|
+
expect(result.current.isLoadingInitialValues).toBe(false);
|
74
|
+
expect(result.current.error).toBe(null);
|
75
|
+
});
|
76
|
+
});
|
Binary file
|
Binary file
|
File without changes
|
File without changes
|