@ampath/esm-patient-registration-app 6.0.1-pre.6
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/.turbo/turbo-build.log +41 -0
- package/README.md +7 -0
- package/dist/130.js +2 -0
- package/dist/130.js.LICENSE.txt +3 -0
- package/dist/130.js.map +1 -0
- package/dist/152.js +1 -0
- package/dist/152.js.map +1 -0
- package/dist/249.js +2 -0
- package/dist/249.js.LICENSE.txt +46 -0
- package/dist/249.js.map +1 -0
- package/dist/255.js +2 -0
- package/dist/255.js.LICENSE.txt +9 -0
- package/dist/255.js.map +1 -0
- package/dist/271.js +1 -0
- package/dist/303.js +1 -0
- package/dist/303.js.map +1 -0
- package/dist/319.js +1 -0
- package/dist/365.js +1 -0
- package/dist/365.js.map +1 -0
- package/dist/460.js +1 -0
- package/dist/525.js +1 -0
- package/dist/525.js.map +1 -0
- package/dist/537.js +1 -0
- package/dist/537.js.map +1 -0
- package/dist/574.js +1 -0
- package/dist/591.js +2 -0
- package/dist/591.js.LICENSE.txt +32 -0
- package/dist/591.js.map +1 -0
- package/dist/621.js +1 -0
- package/dist/621.js.map +1 -0
- package/dist/644.js +1 -0
- package/dist/729.js +1 -0
- package/dist/729.js.map +1 -0
- package/dist/735.js +1 -0
- package/dist/735.js.map +1 -0
- package/dist/757.js +1 -0
- package/dist/784.js +2 -0
- package/dist/784.js.LICENSE.txt +9 -0
- package/dist/784.js.map +1 -0
- package/dist/788.js +1 -0
- package/dist/807.js +1 -0
- package/dist/833.js +1 -0
- package/dist/879.js +1 -0
- package/dist/879.js.map +1 -0
- package/dist/ampath-esm-patient-registration-app.js +1 -0
- package/dist/ampath-esm-patient-registration-app.js.buildmanifest.json +649 -0
- package/dist/ampath-esm-patient-registration-app.js.map +1 -0
- package/dist/main.js +2 -0
- package/dist/main.js.LICENSE.txt +56 -0
- package/dist/main.js.map +1 -0
- package/dist/routes.json +1 -0
- package/docs/images/patient-registration-hierarchy.png +0 -0
- package/jest.config.js +3 -0
- package/package.json +61 -0
- package/src/add-patient-link.scss +3 -0
- package/src/add-patient-link.test.tsx +20 -0
- package/src/add-patient-link.tsx +21 -0
- package/src/config-schema.ts +410 -0
- package/src/constants.ts +14 -0
- package/src/declarations.d.ts +6 -0
- package/src/index.ts +71 -0
- package/src/nav-link.test.tsx +13 -0
- package/src/nav-link.tsx +10 -0
- package/src/offline.resources.ts +155 -0
- package/src/offline.ts +91 -0
- package/src/patient-registration/before-save-prompt.tsx +73 -0
- package/src/patient-registration/date-util.ts +52 -0
- package/src/patient-registration/field/__mocks__/field.resource.ts +60 -0
- package/src/patient-registration/field/address/address-field.component.tsx +153 -0
- package/src/patient-registration/field/address/address-hierarchy-levels.component.tsx +73 -0
- package/src/patient-registration/field/address/address-hierarchy.resource.tsx +157 -0
- package/src/patient-registration/field/address/address-search.component.tsx +85 -0
- package/src/patient-registration/field/address/address-search.scss +53 -0
- package/src/patient-registration/field/address/custom-address-field.component.tsx +31 -0
- package/src/patient-registration/field/address/tests/address-hierarchy.test.tsx +214 -0
- package/src/patient-registration/field/address/tests/address-search-component.test.tsx +135 -0
- package/src/patient-registration/field/custom-field.component.tsx +25 -0
- package/src/patient-registration/field/dob/dob.component.tsx +159 -0
- package/src/patient-registration/field/dob/dob.test.tsx +75 -0
- package/src/patient-registration/field/field.component.tsx +47 -0
- package/src/patient-registration/field/field.resource.ts +35 -0
- package/src/patient-registration/field/field.scss +127 -0
- package/src/patient-registration/field/field.test.tsx +294 -0
- package/src/patient-registration/field/gender/gender-field.component.tsx +49 -0
- package/src/patient-registration/field/gender/gender-field.test.tsx +59 -0
- package/src/patient-registration/field/id/id-field.component.tsx +144 -0
- package/src/patient-registration/field/id/id-field.test.tsx +107 -0
- package/src/patient-registration/field/id/identifier-selection-overlay.component.tsx +198 -0
- package/src/patient-registration/field/id/identifier-selection.scss +37 -0
- package/src/patient-registration/field/name/name-field.component.tsx +142 -0
- package/src/patient-registration/field/obs/obs-field.component.tsx +204 -0
- package/src/patient-registration/field/obs/obs-field.test.tsx +205 -0
- package/src/patient-registration/field/person-attributes/coded-attributes.component.tsx +60 -0
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +116 -0
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +127 -0
- package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +88 -0
- package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +187 -0
- package/src/patient-registration/field/person-attributes/person-attributes.resource.ts +20 -0
- package/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx +58 -0
- package/src/patient-registration/field/person-attributes/text-person-attribute-field.test.tsx +88 -0
- package/src/patient-registration/field/phone/phone-field.component.tsx +16 -0
- package/src/patient-registration/form-manager.test.ts +67 -0
- package/src/patient-registration/form-manager.ts +414 -0
- package/src/patient-registration/input/basic-input/input/input.component.tsx +179 -0
- package/src/patient-registration/input/basic-input/input/input.test.tsx +72 -0
- package/src/patient-registration/input/basic-input/select/select-input.component.tsx +32 -0
- package/src/patient-registration/input/basic-input/select/select-input.test.tsx +49 -0
- package/src/patient-registration/input/combo-input/combo-input.component.tsx +128 -0
- package/src/patient-registration/input/combo-input/selection-tick.component.tsx +20 -0
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.component.tsx +187 -0
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.scss +62 -0
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +132 -0
- package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +156 -0
- package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +107 -0
- package/src/patient-registration/input/custom-input/identifier/utils.test.ts +81 -0
- package/src/patient-registration/input/custom-input/identifier/utils.ts +19 -0
- package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +53 -0
- package/src/patient-registration/input/dummy-data/dummy-data-input.test.tsx +43 -0
- package/src/patient-registration/input/input.scss +118 -0
- package/src/patient-registration/patient-registration-context.ts +24 -0
- package/src/patient-registration/patient-registration-hooks.ts +287 -0
- package/src/patient-registration/patient-registration-utils.ts +216 -0
- package/src/patient-registration/patient-registration.component.tsx +240 -0
- package/src/patient-registration/patient-registration.resource.test.tsx +26 -0
- package/src/patient-registration/patient-registration.resource.ts +250 -0
- package/src/patient-registration/patient-registration.scss +122 -0
- package/src/patient-registration/patient-registration.test.tsx +471 -0
- package/src/patient-registration/patient-registration.types.ts +318 -0
- package/src/patient-registration/section/death-info/death-info-section.component.tsx +31 -0
- package/src/patient-registration/section/death-info/death-info-section.test.tsx +64 -0
- package/src/patient-registration/section/demographics/demographics-section.component.tsx +30 -0
- package/src/patient-registration/section/demographics/demographics-section.test.tsx +83 -0
- package/src/patient-registration/section/generic-section.component.tsx +17 -0
- package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +235 -0
- package/src/patient-registration/section/patient-relationships/relationships-section.test.tsx +100 -0
- package/src/patient-registration/section/patient-relationships/relationships.resource.tsx +78 -0
- package/src/patient-registration/section/patient-relationships/relationships.scss +35 -0
- package/src/patient-registration/section/section-wrapper.component.tsx +40 -0
- package/src/patient-registration/section/section.component.tsx +23 -0
- package/src/patient-registration/section/section.scss +1 -0
- package/src/patient-registration/ui-components/overlay/overlay.component.tsx +51 -0
- package/src/patient-registration/ui-components/overlay/overlay.scss +63 -0
- package/src/patient-registration/validation/patient-registration-validation.test.tsx +157 -0
- package/src/patient-registration/validation/patient-registration-validation.tsx +60 -0
- package/src/patient-verification/client-registry-constants.ts +13 -0
- package/src/patient-verification/client-registry.component.tsx +66 -0
- package/src/patient-verification/client-registry.scss +1 -0
- package/src/patient-verification/utils.tsx +56 -0
- package/src/patient-verification/verification-modal.scss +20 -0
- package/src/patient-verification/verification.component.tsx +48 -0
- package/src/resource.ts +12 -0
- package/src/root.component.tsx +63 -0
- package/src/root.scss +7 -0
- package/src/root.test.tsx +32 -0
- package/src/routes.json +66 -0
- package/src/widgets/cancel-patient-edit.component.tsx +37 -0
- package/src/widgets/cancel-patient-edit.test.tsx +27 -0
- package/src/widgets/delete-identifier-confirmation-modal.test.tsx +34 -0
- package/src/widgets/delete-identifier-confirmation-modal.tsx +41 -0
- package/src/widgets/delete-identifier-modal.scss +34 -0
- package/src/widgets/display-photo.component.tsx +30 -0
- package/src/widgets/display-photo.test.tsx +37 -0
- package/src/widgets/edit-patient-details-button.component.tsx +34 -0
- package/src/widgets/edit-patient-details-button.scss +3 -0
- package/src/widgets/edit-patient-details-button.test.tsx +41 -0
- package/translations/am.json +97 -0
- package/translations/ar.json +97 -0
- package/translations/en.json +103 -0
- package/translations/es.json +97 -0
- package/translations/fr.json +97 -0
- package/translations/he.json +97 -0
- package/translations/km.json +97 -0
- package/translations/zh.json +89 -0
- package/translations/zh_CN.json +89 -0
- package/tsconfig.json +5 -0
- package/webpack.config.js +1 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Form, Formik } from 'formik';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { useConfig } from '@openmrs/esm-framework';
|
|
5
|
+
import { Field } from './field.component';
|
|
6
|
+
import type { AddressTemplate, FormValues } from '../patient-registration.types';
|
|
7
|
+
import { type Resources, ResourcesContext } from '../../offline.resources';
|
|
8
|
+
import { PatientRegistrationContext } from '../patient-registration-context';
|
|
9
|
+
|
|
10
|
+
jest.mock('@openmrs/esm-framework', () => ({
|
|
11
|
+
...jest.requireActual('@openmrs/esm-framework'),
|
|
12
|
+
useConfig: jest.fn(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
const predefinedAddressTemplate = {
|
|
16
|
+
uuid: 'test-address-template-uuid',
|
|
17
|
+
property: 'layout.address.format',
|
|
18
|
+
description: 'Test Address Template',
|
|
19
|
+
display:
|
|
20
|
+
'Layout - Address Format = <org.openmrs.layout.address.AddressTemplate>\n <nameMappings class="properties">\n <property name="postalCode" value="Location.postalCode"/>\n <property name="address2" value="Location.address2"/>\n <property name="address1" value="Location.address1"/>\n <property name="country" value="Location.country"/>\n <property name="stateProvince" value="Location.stateProvince"/>\n <property name="cityVillage" value="Location.cityVillage"/>\n </nameMappings>\n <sizeMappings class="properties">\n <property name="postalCode" value="10"/>\n <property name="address2" value="40"/>\n <property name="address1" value="40"/>\n <property name="country" value="10"/>\n <property name="stateProvince" value="10"/>\n <property name="cityVillage" value="10"/>\n </sizeMappings>\n <lineByLineFormat>\n <string>address1</string>\n <string>address2</string>\n <string>cityVillage stateProvince country postalCode</string>\n </lineByLineFormat>\n </org.openmrs.layout.address.AddressTemplate>',
|
|
21
|
+
value:
|
|
22
|
+
'<org.openmrs.layout.address.AddressTemplate>\r\n <nameMappings class="properties">\r\n <property name="postalCode" value="Location.postalCode"/>\r\n <property name="address2" value="Location.address2"/>\r\n <property name="address1" value="Location.address1"/>\r\n <property name="country" value="Location.country"/>\r\n <property name="stateProvince" value="Location.stateProvince"/>\r\n <property name="cityVillage" value="Location.cityVillage"/>\r\n </nameMappings>\r\n <sizeMappings class="properties">\r\n <property name="postalCode" value="4"/>\r\n <property name="address1" value="40"/>\r\n <property name="address2" value="40"/>\r\n <property name="country" value="10"/>\r\n <property name="stateProvince" value="10"/>\r\n <property name="cityVillage" value="10"/>\r\n <asset name="cityVillage" value="10"/>\r\n </sizeMappings>\r\n <lineByLineFormat>\r\n <string>address1 address2</string>\r\n <string>cityVillage stateProvince postalCode</string>\r\n <string>country</string>\r\n </lineByLineFormat>\r\n <elementDefaults class="properties">\r\n <property name="country" value=""/>\r\n </elementDefaults>\r\n <elementRegex class="properties">\r\n <property name="address1" value="[a-zA-Z]+$"/>\r\n </elementRegex>\r\n <elementRegexFormats class="properties">\r\n <property name="address1" value="Countries can only be letters"/>\r\n </elementRegexFormats>\r\n </org.openmrs.layout.address.AddressTemplate>',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const mockedIdentifierTypes = [
|
|
26
|
+
{
|
|
27
|
+
fieldName: 'openMrsId',
|
|
28
|
+
format: '',
|
|
29
|
+
identifierSources: [
|
|
30
|
+
{
|
|
31
|
+
uuid: '8549f706-7e85-4c1d-9424-217d50a2988b',
|
|
32
|
+
name: 'Generator for OpenMRS ID',
|
|
33
|
+
description: 'Generator for OpenMRS ID',
|
|
34
|
+
baseCharacterSet: '0123456789ACDEFGHJKLMNPRTUVWXY',
|
|
35
|
+
prefix: '',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
isPrimary: true,
|
|
39
|
+
name: 'OpenMRS ID',
|
|
40
|
+
required: true,
|
|
41
|
+
uniquenessBehavior: 'UNIQUE' as const,
|
|
42
|
+
uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
fieldName: 'idCard',
|
|
46
|
+
format: '',
|
|
47
|
+
identifierSources: [],
|
|
48
|
+
isPrimary: false,
|
|
49
|
+
name: 'ID Card',
|
|
50
|
+
required: false,
|
|
51
|
+
uniquenessBehavior: 'UNIQUE' as const,
|
|
52
|
+
uuid: 'b4143563-16cd-4439-b288-f83d61670fc8',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
fieldName: 'legacyId',
|
|
56
|
+
format: '',
|
|
57
|
+
identifierSources: [],
|
|
58
|
+
isPrimary: false,
|
|
59
|
+
name: 'Legacy ID',
|
|
60
|
+
required: false,
|
|
61
|
+
uniquenessBehavior: null,
|
|
62
|
+
uuid: '22348099-3873-459e-a32e-d93b17eda533',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
fieldName: 'oldIdentificationNumber',
|
|
66
|
+
format: '',
|
|
67
|
+
identifierSources: [],
|
|
68
|
+
isPrimary: false,
|
|
69
|
+
name: 'Old Identification Number',
|
|
70
|
+
required: false,
|
|
71
|
+
uniquenessBehavior: null,
|
|
72
|
+
uuid: '8d79403a-c2cc-11de-8d13-0010c6dffd0f',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
fieldName: 'openMrsIdentificationNumber',
|
|
76
|
+
format: '',
|
|
77
|
+
identifierSources: [],
|
|
78
|
+
isPrimary: false,
|
|
79
|
+
name: 'OpenMRS Identification Number',
|
|
80
|
+
required: false,
|
|
81
|
+
uniquenessBehavior: null,
|
|
82
|
+
uuid: '8d793bee-c2cc-11de-8d13-0010c6dffd0f',
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const mockResourcesContextValue: Resources = {
|
|
87
|
+
addressTemplate: predefinedAddressTemplate as unknown as AddressTemplate,
|
|
88
|
+
currentSession: {
|
|
89
|
+
authenticated: true,
|
|
90
|
+
sessionId: 'JSESSION',
|
|
91
|
+
currentProvider: { uuid: 'provider-uuid', identifier: 'PRO-123' },
|
|
92
|
+
},
|
|
93
|
+
relationshipTypes: [],
|
|
94
|
+
identifierTypes: [...mockedIdentifierTypes],
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const initialContextValues = {
|
|
98
|
+
currentPhoto: 'data:image/png;base64,1234567890',
|
|
99
|
+
identifierTypes: [],
|
|
100
|
+
inEditMode: false,
|
|
101
|
+
initialFormValues: {} as FormValues,
|
|
102
|
+
isOffline: false,
|
|
103
|
+
setCapturePhotoProps: jest.fn(),
|
|
104
|
+
setFieldValue: jest.fn(),
|
|
105
|
+
setInitialFormValues: jest.fn(),
|
|
106
|
+
validationSchema: null,
|
|
107
|
+
values: {} as FormValues,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
describe('Field', () => {
|
|
111
|
+
let ContextWrapper;
|
|
112
|
+
|
|
113
|
+
beforeEach(() => {
|
|
114
|
+
ContextWrapper = ({ children }) => (
|
|
115
|
+
<ResourcesContext.Provider value={mockResourcesContextValue}>
|
|
116
|
+
<Formik initialValues={{}} onSubmit={jest.fn()}>
|
|
117
|
+
<Form>
|
|
118
|
+
<PatientRegistrationContext.Provider value={initialContextValues}>
|
|
119
|
+
{children}
|
|
120
|
+
</PatientRegistrationContext.Provider>
|
|
121
|
+
</Form>
|
|
122
|
+
</Formik>
|
|
123
|
+
</ResourcesContext.Provider>
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
afterEach(() => {
|
|
128
|
+
jest.clearAllMocks();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should render NameField component when name prop is "name"', () => {
|
|
132
|
+
(useConfig as jest.Mock).mockImplementation(() => ({
|
|
133
|
+
fieldConfigurations: {
|
|
134
|
+
name: {
|
|
135
|
+
displayMiddleName: true,
|
|
136
|
+
unidentifiedPatient: true,
|
|
137
|
+
defaultUnknownGivenName: 'UNKNOWN',
|
|
138
|
+
defaultUnknownFamilyName: 'UNKNOWN',
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
}));
|
|
142
|
+
|
|
143
|
+
render(<Field name="name" />, { wrapper: ContextWrapper });
|
|
144
|
+
|
|
145
|
+
expect(screen.getByText('Full Name')).toBeInTheDocument();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should render GenderField component when name prop is "gender"', () => {
|
|
149
|
+
(useConfig as jest.Mock).mockImplementation(() => ({
|
|
150
|
+
fieldConfigurations: {
|
|
151
|
+
gender: [
|
|
152
|
+
{
|
|
153
|
+
value: 'Male',
|
|
154
|
+
label: 'Male',
|
|
155
|
+
id: 'male',
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
render(<Field name="gender" />, { wrapper: ContextWrapper });
|
|
162
|
+
|
|
163
|
+
expect(screen.getByLabelText('Male')).toBeInTheDocument();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should render DobField component when name prop is "dob"', () => {
|
|
167
|
+
(useConfig as jest.Mock).mockImplementation(() => ({
|
|
168
|
+
fieldConfigurations: {
|
|
169
|
+
dob: {
|
|
170
|
+
minAgeLimit: 0,
|
|
171
|
+
maxAgeLimit: 120,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
}));
|
|
175
|
+
render(<Field name="dob" />, { wrapper: ContextWrapper });
|
|
176
|
+
expect(screen.getByText('Birth')).toBeInTheDocument();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should render AddressComponent component when name prop is "address"', () => {
|
|
180
|
+
jest.mock('./address/address-hierarchy.resource', () => ({
|
|
181
|
+
...(jest.requireActual('../address-hierarchy.resource') as jest.Mock),
|
|
182
|
+
useOrderedAddressHierarchyLevels: jest.fn(),
|
|
183
|
+
}));
|
|
184
|
+
(useConfig as jest.Mock).mockImplementation(() => ({
|
|
185
|
+
fieldConfigurations: {
|
|
186
|
+
address: {
|
|
187
|
+
useAddressHierarchy: {
|
|
188
|
+
enabled: false,
|
|
189
|
+
useQuickSearch: false,
|
|
190
|
+
searchAddressByLevel: false,
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
}));
|
|
195
|
+
|
|
196
|
+
render(<Field name="address" />, { wrapper: ContextWrapper });
|
|
197
|
+
|
|
198
|
+
expect(screen.getByText('Address')).toBeInTheDocument();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should render Identifiers component when name prop is "id"', () => {
|
|
202
|
+
(useConfig as jest.Mock).mockImplementation(() => ({
|
|
203
|
+
defaultPatientIdentifierTypes: ['OpenMRS ID'],
|
|
204
|
+
}));
|
|
205
|
+
|
|
206
|
+
const openmrsID = {
|
|
207
|
+
name: 'OpenMRS ID',
|
|
208
|
+
fieldName: 'openMrsId',
|
|
209
|
+
required: true,
|
|
210
|
+
uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
|
|
211
|
+
format: null,
|
|
212
|
+
isPrimary: true,
|
|
213
|
+
identifierSources: [
|
|
214
|
+
{
|
|
215
|
+
uuid: '691eed12-c0f1-11e2-94be-8c13b969e334',
|
|
216
|
+
name: 'Generator 1 for OpenMRS ID',
|
|
217
|
+
autoGenerationOption: {
|
|
218
|
+
manualEntryEnabled: false,
|
|
219
|
+
automaticGenerationEnabled: true,
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
uuid: '01af8526-cea4-4175-aa90-340acb411771',
|
|
224
|
+
name: 'Generator 2 for OpenMRS ID',
|
|
225
|
+
autoGenerationOption: {
|
|
226
|
+
manualEntryEnabled: true,
|
|
227
|
+
automaticGenerationEnabled: true,
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
identifierUuid: 'openmrs-identifier-uuid',
|
|
232
|
+
identifierTypeUuid: 'openmrs-id-identifier-type-uuid',
|
|
233
|
+
initialValue: '12345',
|
|
234
|
+
identifierValue: '12345',
|
|
235
|
+
identifierName: 'OpenMRS ID',
|
|
236
|
+
preferred: true,
|
|
237
|
+
selectedSource: {
|
|
238
|
+
uuid: 'openmrs-id-selected-source-uuid',
|
|
239
|
+
name: 'Generator 1 for OpenMRS ID',
|
|
240
|
+
autoGenerationOption: {
|
|
241
|
+
manualEntryEnabled: false,
|
|
242
|
+
automaticGenerationEnabled: true,
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
autoGenerationSource: null,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const updatedContextValues = {
|
|
249
|
+
currentPhoto: 'data:image/png;base64,1234567890',
|
|
250
|
+
identifierTypes: [],
|
|
251
|
+
inEditMode: false,
|
|
252
|
+
initialFormValues: { identifiers: { openmrsID } } as unknown as FormValues,
|
|
253
|
+
isOffline: false,
|
|
254
|
+
setCapturePhotoProps: jest.fn(),
|
|
255
|
+
setFieldValue: jest.fn(),
|
|
256
|
+
setInitialFormValues: jest.fn(),
|
|
257
|
+
validationSchema: null,
|
|
258
|
+
values: { identifiers: { openmrsID } } as unknown as FormValues,
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
render(
|
|
262
|
+
<ResourcesContext.Provider value={mockResourcesContextValue}>
|
|
263
|
+
<Formik initialValues={{}} onSubmit={jest.fn()}>
|
|
264
|
+
<Form>
|
|
265
|
+
<PatientRegistrationContext.Provider value={updatedContextValues}>
|
|
266
|
+
<Field name="id" />
|
|
267
|
+
</PatientRegistrationContext.Provider>
|
|
268
|
+
</Form>
|
|
269
|
+
</Formik>
|
|
270
|
+
</ResourcesContext.Provider>,
|
|
271
|
+
);
|
|
272
|
+
expect(screen.getByText('Identifiers')).toBeInTheDocument();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should return null and report an error for an invalid field name', () => {
|
|
276
|
+
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
277
|
+
|
|
278
|
+
(useConfig as jest.Mock).mockImplementation(() => ({
|
|
279
|
+
fieldDefinitions: [{ id: 'weight' }],
|
|
280
|
+
}));
|
|
281
|
+
let error = null;
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
render(<Field name="invalidField" />);
|
|
285
|
+
} catch (err) {
|
|
286
|
+
error = err;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
expect(error).toMatch(/Invalid field name 'invalidField'. Valid options are /);
|
|
290
|
+
expect(screen.queryByTestId('invalid-field')).not.toBeInTheDocument();
|
|
291
|
+
|
|
292
|
+
consoleError.mockRestore();
|
|
293
|
+
});
|
|
294
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
|
+
import { RadioButton, RadioButtonGroup } from '@carbon/react';
|
|
3
|
+
import styles from '../field.scss';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
6
|
+
import { useField } from 'formik';
|
|
7
|
+
import { type RegistrationConfig } from '../../../config-schema';
|
|
8
|
+
import { useConfig } from '@openmrs/esm-framework';
|
|
9
|
+
|
|
10
|
+
export const GenderField: React.FC = () => {
|
|
11
|
+
const { fieldConfigurations } = useConfig() as RegistrationConfig;
|
|
12
|
+
const { t } = useTranslation();
|
|
13
|
+
const [field, meta] = useField('gender');
|
|
14
|
+
const { setFieldValue } = useContext(PatientRegistrationContext);
|
|
15
|
+
const fieldConfigs = fieldConfigurations?.gender;
|
|
16
|
+
|
|
17
|
+
const setGender = (gender: string) => {
|
|
18
|
+
setFieldValue('gender', gender);
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* DO NOT REMOVE THIS COMMENT HERE, ADDS TRANSLATION FOR SEX OPTIONS
|
|
22
|
+
* t('male', 'Male')
|
|
23
|
+
* t('female', 'Female')
|
|
24
|
+
* t('other', 'Other')
|
|
25
|
+
* t('unknown', 'Unknown')
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className={styles.halfWidthInDesktopView}>
|
|
30
|
+
<h4 className={styles.productiveHeading02Light}>{t('sexFieldLabelText', 'Sex')}</h4>
|
|
31
|
+
<div className={styles.sexField}>
|
|
32
|
+
<p className="cds--label">{t('genderLabelText', 'Sex')}</p>
|
|
33
|
+
<RadioButtonGroup name="gender" orientation="vertical" onChange={setGender} valueSelected={field.value}>
|
|
34
|
+
{fieldConfigs.map((option) => (
|
|
35
|
+
<RadioButton
|
|
36
|
+
key={option.label ?? option.value}
|
|
37
|
+
id={`gender-option-${option.value}`}
|
|
38
|
+
value={option.value}
|
|
39
|
+
labelText={t(option.label ?? option.value, option.label ?? option.value)}
|
|
40
|
+
/>
|
|
41
|
+
))}
|
|
42
|
+
</RadioButtonGroup>
|
|
43
|
+
{meta.touched && meta.error && (
|
|
44
|
+
<div className={styles.radioFieldError}>{t(meta.error, 'Gender is required')}</div>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { Formik, Form } from 'formik';
|
|
4
|
+
import { render } from '@testing-library/react';
|
|
5
|
+
import { GenderField } from './gender-field.component';
|
|
6
|
+
|
|
7
|
+
jest.mock('@openmrs/esm-framework', () => ({
|
|
8
|
+
...(jest.requireActual('@openmrs/esm-framework') as any),
|
|
9
|
+
useConfig: jest.fn(() => ({
|
|
10
|
+
fieldConfigurations: {
|
|
11
|
+
gender: [
|
|
12
|
+
{
|
|
13
|
+
value: 'male',
|
|
14
|
+
label: 'Male',
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
name: {
|
|
18
|
+
displayMiddleName: false,
|
|
19
|
+
unidentifiedPatient: false,
|
|
20
|
+
defaultUnknownGivenName: '',
|
|
21
|
+
defaultUnknownFamilyName: '',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
})),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
jest.mock('react', () => ({
|
|
28
|
+
...(jest.requireActual('react') as any),
|
|
29
|
+
useContext: jest.fn(() => ({
|
|
30
|
+
setFieldValue: jest.fn(),
|
|
31
|
+
})),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
jest.mock('formik', () => ({
|
|
35
|
+
...(jest.requireActual('formik') as any),
|
|
36
|
+
useField: jest.fn(() => [{}, {}]),
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
describe('GenderField', () => {
|
|
40
|
+
const renderComponent = () => {
|
|
41
|
+
return render(
|
|
42
|
+
<Formik initialValues={{}} onSubmit={null}>
|
|
43
|
+
<Form>
|
|
44
|
+
<GenderField />
|
|
45
|
+
</Form>
|
|
46
|
+
</Formik>,
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
it('has a label', () => {
|
|
51
|
+
expect(renderComponent().getAllByText('Sex')).toBeTruthy();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('checks an option', async () => {
|
|
55
|
+
const user = userEvent.setup();
|
|
56
|
+
const component = renderComponent();
|
|
57
|
+
expect(component.getByLabelText('Male').getAttribute('value')).toBe('male');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Button, SkeletonText } from '@carbon/react';
|
|
4
|
+
import { ArrowRight } from '@carbon/react/icons';
|
|
5
|
+
import { useLayoutType, useConfig, isDesktop, UserHasAccess } from '@openmrs/esm-framework';
|
|
6
|
+
import IdentifierSelectionOverlay from './identifier-selection-overlay.component';
|
|
7
|
+
import { IdentifierInput } from '../../input/custom-input/identifier/identifier-input.component';
|
|
8
|
+
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
9
|
+
import {
|
|
10
|
+
type FormValues,
|
|
11
|
+
type IdentifierSource,
|
|
12
|
+
type PatientIdentifierType,
|
|
13
|
+
type PatientIdentifierValue,
|
|
14
|
+
} from '../../patient-registration.types';
|
|
15
|
+
import { ResourcesContext } from '../../../offline.resources';
|
|
16
|
+
import styles from '../field.scss';
|
|
17
|
+
|
|
18
|
+
export function setIdentifierSource(
|
|
19
|
+
identifierSource: IdentifierSource,
|
|
20
|
+
identifierValue: string,
|
|
21
|
+
initialValue: string,
|
|
22
|
+
): {
|
|
23
|
+
identifierValue: string;
|
|
24
|
+
autoGeneration: boolean;
|
|
25
|
+
selectedSource: IdentifierSource;
|
|
26
|
+
} {
|
|
27
|
+
const autoGeneration = identifierSource?.autoGenerationOption?.automaticGenerationEnabled;
|
|
28
|
+
return {
|
|
29
|
+
selectedSource: identifierSource,
|
|
30
|
+
autoGeneration,
|
|
31
|
+
identifierValue: autoGeneration
|
|
32
|
+
? 'auto-generated'
|
|
33
|
+
: identifierValue !== 'auto-generated'
|
|
34
|
+
? identifierValue
|
|
35
|
+
: initialValue,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function initializeIdentifier(identifierType: PatientIdentifierType, identifierProps): PatientIdentifierValue {
|
|
40
|
+
return {
|
|
41
|
+
identifierTypeUuid: identifierType.uuid,
|
|
42
|
+
identifierName: identifierType.name,
|
|
43
|
+
preferred: identifierType.isPrimary,
|
|
44
|
+
initialValue: '',
|
|
45
|
+
required: identifierType.isPrimary || identifierType.required,
|
|
46
|
+
...identifierProps,
|
|
47
|
+
...setIdentifierSource(
|
|
48
|
+
identifierProps?.selectedSource ?? identifierType.identifierSources?.[0],
|
|
49
|
+
identifierProps?.identifierValue,
|
|
50
|
+
identifierProps?.initialValue ?? '',
|
|
51
|
+
),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function deleteIdentifierType(identifiers: FormValues['identifiers'], identifierFieldName) {
|
|
56
|
+
return Object.fromEntries(Object.entries(identifiers).filter(([fieldName]) => fieldName !== identifierFieldName));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const Identifiers: React.FC = () => {
|
|
60
|
+
const { identifierTypes } = useContext(ResourcesContext);
|
|
61
|
+
const isLoading = !identifierTypes;
|
|
62
|
+
const { values, setFieldValue, initialFormValues, isOffline } = useContext(PatientRegistrationContext);
|
|
63
|
+
const { t } = useTranslation();
|
|
64
|
+
const layout = useLayoutType();
|
|
65
|
+
const [showIdentifierOverlay, setShowIdentifierOverlay] = useState(false);
|
|
66
|
+
const config = useConfig();
|
|
67
|
+
const { defaultPatientIdentifierTypes } = config;
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
// Initialization
|
|
71
|
+
if (identifierTypes) {
|
|
72
|
+
const identifiers = {};
|
|
73
|
+
identifierTypes
|
|
74
|
+
.filter(
|
|
75
|
+
(type) =>
|
|
76
|
+
type.isPrimary ||
|
|
77
|
+
type.required ||
|
|
78
|
+
!!defaultPatientIdentifierTypes?.find(
|
|
79
|
+
(defaultIdentifierTypeUuid) => defaultIdentifierTypeUuid === type.uuid,
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
.filter((type) => !values.identifiers[type.fieldName])
|
|
83
|
+
.forEach((type) => {
|
|
84
|
+
identifiers[type.fieldName] = initializeIdentifier(
|
|
85
|
+
type,
|
|
86
|
+
values.identifiers[type.uuid] ?? initialFormValues.identifiers[type.uuid] ?? {},
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
/*
|
|
90
|
+
Identifier value should only be updated if there is any update in the
|
|
91
|
+
identifier values, otherwise, if the below 'if' clause is removed, it will
|
|
92
|
+
fall into an infinite run.
|
|
93
|
+
*/
|
|
94
|
+
if (Object.keys(identifiers).length) {
|
|
95
|
+
setFieldValue('identifiers', {
|
|
96
|
+
...values.identifiers,
|
|
97
|
+
...identifiers,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
102
|
+
}, [identifierTypes, setFieldValue, defaultPatientIdentifierTypes, values.identifiers, initializeIdentifier]);
|
|
103
|
+
|
|
104
|
+
const closeIdentifierSelectionOverlay = useCallback(
|
|
105
|
+
() => setShowIdentifierOverlay(false),
|
|
106
|
+
[setShowIdentifierOverlay],
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (isLoading && !isOffline) {
|
|
110
|
+
return (
|
|
111
|
+
<div data-testid="loading-skeleton" className={styles.halfWidthInDesktopView}>
|
|
112
|
+
<div className={styles.identifierLabelText}>
|
|
113
|
+
<h4 className={styles.productiveHeading02Light}>{t('idFieldLabelText', 'Identifiers')}</h4>
|
|
114
|
+
</div>
|
|
115
|
+
<SkeletonText />
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div className={styles.halfWidthInDesktopView}>
|
|
122
|
+
<UserHasAccess privilege={['Get Identifier Types', 'Add Patient Identifiers']}>
|
|
123
|
+
<div className={styles.identifierLabelText}>
|
|
124
|
+
<h4 className={styles.productiveHeading02Light}>{t('idFieldLabelText', 'Identifiers')}</h4>
|
|
125
|
+
<Button
|
|
126
|
+
kind="ghost"
|
|
127
|
+
className={styles.setIDNumberButton}
|
|
128
|
+
onClick={() => setShowIdentifierOverlay(true)}
|
|
129
|
+
size={isDesktop(layout) ? 'sm' : 'md'}>
|
|
130
|
+
{t('configure', 'Configure')} <ArrowRight size={16} />
|
|
131
|
+
</Button>
|
|
132
|
+
</div>
|
|
133
|
+
</UserHasAccess>
|
|
134
|
+
<div>
|
|
135
|
+
{Object.entries(values.identifiers).map(([fieldName, identifier]) => (
|
|
136
|
+
<IdentifierInput key={fieldName} fieldName={fieldName} patientIdentifier={identifier} />
|
|
137
|
+
))}
|
|
138
|
+
{showIdentifierOverlay && (
|
|
139
|
+
<IdentifierSelectionOverlay setFieldValue={setFieldValue} closeOverlay={closeIdentifierSelectionOverlay} />
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { Identifiers } from './id-field.component';
|
|
5
|
+
import { type Resources, ResourcesContext } from '../../../offline.resources';
|
|
6
|
+
import { Form, Formik } from 'formik';
|
|
7
|
+
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
8
|
+
import { openmrsID, mockedIdentifierTypes } from '__mocks__';
|
|
9
|
+
|
|
10
|
+
jest.mock('@openmrs/esm-framework', () => ({
|
|
11
|
+
...jest.requireActual('@openmrs/esm-framework'),
|
|
12
|
+
useConfig: jest.fn().mockImplementation(() => ({
|
|
13
|
+
defaultPatientIdentifierTypes: ['OpenMRS ID'],
|
|
14
|
+
})),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
describe('Identifiers', () => {
|
|
18
|
+
const mockResourcesContextValue = {
|
|
19
|
+
addressTemplate: {},
|
|
20
|
+
currentSession: {
|
|
21
|
+
authenticated: true,
|
|
22
|
+
sessionId: 'JSESSION',
|
|
23
|
+
currentProvider: { uuid: 'provider-uuid', identifier: 'PRO-123' },
|
|
24
|
+
},
|
|
25
|
+
relationshipTypes: [],
|
|
26
|
+
identifierTypes: [...mockedIdentifierTypes],
|
|
27
|
+
} as Resources;
|
|
28
|
+
|
|
29
|
+
it('should render loading skeleton when identifier types are loading', () => {
|
|
30
|
+
render(
|
|
31
|
+
<ResourcesContext.Provider value={[]}>
|
|
32
|
+
<Formik initialValues={{}} onSubmit={null}>
|
|
33
|
+
<Form>
|
|
34
|
+
<PatientRegistrationContext.Provider
|
|
35
|
+
value={{
|
|
36
|
+
setFieldValue: jest.fn(),
|
|
37
|
+
initialFormValues: { identifiers: { ...mockedIdentifierTypes[0] } },
|
|
38
|
+
setInitialFormValues: jest.fn(),
|
|
39
|
+
values: {
|
|
40
|
+
identifiers: { openmrsID },
|
|
41
|
+
},
|
|
42
|
+
}}>
|
|
43
|
+
<Identifiers />
|
|
44
|
+
</PatientRegistrationContext.Provider>
|
|
45
|
+
</Form>
|
|
46
|
+
</Formik>
|
|
47
|
+
</ResourcesContext.Provider>,
|
|
48
|
+
);
|
|
49
|
+
expect(screen.getByTestId('loading-skeleton')).toBeInTheDocument();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should render identifier inputs when identifier types are loaded', () => {
|
|
53
|
+
render(
|
|
54
|
+
<ResourcesContext.Provider value={mockResourcesContextValue}>
|
|
55
|
+
<Formik initialValues={{}} onSubmit={null}>
|
|
56
|
+
<Form>
|
|
57
|
+
<PatientRegistrationContext.Provider
|
|
58
|
+
value={{
|
|
59
|
+
setFieldValue: jest.fn(),
|
|
60
|
+
initialFormValues: { identifiers: { ...mockedIdentifierTypes[0] } },
|
|
61
|
+
setInitialFormValues: jest.fn(),
|
|
62
|
+
values: {
|
|
63
|
+
identifiers: { openmrsID },
|
|
64
|
+
},
|
|
65
|
+
}}>
|
|
66
|
+
<Identifiers />
|
|
67
|
+
</PatientRegistrationContext.Provider>
|
|
68
|
+
</Form>
|
|
69
|
+
</Formik>
|
|
70
|
+
</ResourcesContext.Provider>,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
expect(screen.getByText('Identifiers')).toBeInTheDocument();
|
|
74
|
+
const configureButton = screen.getByRole('button', { name: 'Configure' });
|
|
75
|
+
expect(configureButton).toBeInTheDocument();
|
|
76
|
+
expect(configureButton).toBeEnabled();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should open identifier selection overlay when "Configure" button is clicked', async () => {
|
|
80
|
+
const user = userEvent.setup();
|
|
81
|
+
|
|
82
|
+
render(
|
|
83
|
+
<ResourcesContext.Provider value={mockResourcesContextValue}>
|
|
84
|
+
<Formik initialValues={{}} onSubmit={null}>
|
|
85
|
+
<Form>
|
|
86
|
+
<PatientRegistrationContext.Provider
|
|
87
|
+
value={{
|
|
88
|
+
setFieldValue: jest.fn(),
|
|
89
|
+
initialFormValues: { identifiers: { ...mockedIdentifierTypes[0] } },
|
|
90
|
+
setInitialFormValues: jest.fn(),
|
|
91
|
+
values: {
|
|
92
|
+
identifiers: { openmrsID },
|
|
93
|
+
},
|
|
94
|
+
}}>
|
|
95
|
+
<Identifiers />
|
|
96
|
+
</PatientRegistrationContext.Provider>
|
|
97
|
+
</Form>
|
|
98
|
+
</Formik>
|
|
99
|
+
</ResourcesContext.Provider>,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const configureButton = screen.getByRole('button', { name: 'Configure' });
|
|
103
|
+
await user.click(configureButton);
|
|
104
|
+
|
|
105
|
+
expect(screen.getByRole('button', { name: 'Close overlay' })).toBeInTheDocument();
|
|
106
|
+
});
|
|
107
|
+
});
|