@kenyaemr/esm-patient-registration-app 4.3.0
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/README.md +7 -0
- package/__mocks__/autogenerationoptions.mock.ts +34 -0
- package/__mocks__/react-i18next.js +49 -0
- package/dist/144.js +2 -0
- package/dist/144.js.LICENSE.txt +27 -0
- package/dist/144.js.map +1 -0
- package/dist/207.js +1 -0
- package/dist/207.js.map +1 -0
- package/dist/317.js +2 -0
- package/dist/317.js.LICENSE.txt +6 -0
- package/dist/317.js.map +1 -0
- package/dist/330.js +1 -0
- package/dist/330.js.map +1 -0
- package/dist/574.js +1 -0
- package/dist/59.js +1 -0
- package/dist/59.js.map +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/62.js +1 -0
- package/dist/62.js.map +1 -0
- package/dist/635.js +1 -0
- package/dist/635.js.map +1 -0
- package/dist/68.js +1 -0
- package/dist/68.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/805.js +1 -0
- package/dist/805.js.map +1 -0
- package/dist/807.js +1 -0
- package/dist/821.js +1 -0
- package/dist/821.js.map +1 -0
- package/dist/822.js +1 -0
- package/dist/822.js.map +1 -0
- package/dist/858.js +2 -0
- package/dist/858.js.LICENSE.txt +3 -0
- package/dist/858.js.map +1 -0
- package/dist/887.js +1 -0
- package/dist/887.js.map +1 -0
- package/dist/9.js +2 -0
- package/dist/9.js.LICENSE.txt +9 -0
- package/dist/9.js.map +1 -0
- package/dist/975.js +1 -0
- package/dist/975.js.map +1 -0
- package/dist/main.js +2 -0
- package/dist/main.js.LICENSE.txt +9 -0
- package/dist/main.js.map +1 -0
- package/dist/openmrs-esm-patient-registration-app.js +1 -0
- package/dist/openmrs-esm-patient-registration-app.js.buildmanifest.json +623 -0
- package/dist/openmrs-esm-patient-registration-app.js.map +1 -0
- package/dist/openmrs-esm-patient-registration-app.old +1 -0
- package/docs/images/patient-registration-hierarchy.png +0 -0
- package/package.json +55 -0
- package/src/add-patient-link.scss +3 -0
- package/src/add-patient-link.tsx +21 -0
- package/src/config-schema.ts +405 -0
- package/src/constants.ts +14 -0
- package/src/declarations.d.tsx +4 -0
- package/src/index.ts +131 -0
- package/src/nav-link.tsx +10 -0
- package/src/offline.resources.ts +109 -0
- package/src/offline.ts +90 -0
- package/src/patient-registration/before-save-prompt.tsx +72 -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 +31 -0
- package/src/patient-registration/field/address/address-hierarchy.component.tsx +143 -0
- package/src/patient-registration/field/address/address-hierarchy.test.tsx +181 -0
- package/src/patient-registration/field/address/address-search.component.tsx +98 -0
- package/src/patient-registration/field/address/address-search.scss +53 -0
- package/src/patient-registration/field/custom-field.component.tsx +25 -0
- package/src/patient-registration/field/dob/dob.component.tsx +143 -0
- package/src/patient-registration/field/dob/dob.test.tsx +73 -0
- package/src/patient-registration/field/field.component.tsx +44 -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/gender/gender-field.component.tsx +49 -0
- package/src/patient-registration/field/gender/gender-field.test.tsx +66 -0
- package/src/patient-registration/field/id/id-field.component.tsx +142 -0
- package/src/patient-registration/field/id/identifier-selection-overlay.tsx +194 -0
- package/src/patient-registration/field/id/identifier-selection.scss +37 -0
- package/src/patient-registration/field/name/name-field.component.tsx +109 -0
- package/src/patient-registration/field/obs/obs-field.component.tsx +185 -0
- package/src/patient-registration/field/obs/obs-field.test.tsx +127 -0
- package/src/patient-registration/field/person-attributes/coded-attributes.component.tsx +59 -0
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +68 -0
- package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +81 -0
- package/src/patient-registration/field/person-attributes/person-attributes.resource.tsx +20 -0
- package/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx +57 -0
- package/src/patient-registration/form-manager.test.ts +68 -0
- package/src/patient-registration/form-manager.ts +413 -0
- package/src/patient-registration/input/basic-input/input/input.component.tsx +59 -0
- package/src/patient-registration/input/basic-input/input/input.test.tsx +170 -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 +32 -0
- package/src/patient-registration/input/combo-input/combo-input.component.tsx +76 -0
- package/src/patient-registration/input/combo-input/combo-input.test.tsx +43 -0
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.component.tsx +84 -0
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.scss +53 -0
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +109 -0
- package/src/patient-registration/input/custom-input/estimated-age/estimated-age-input.component.tsx +32 -0
- package/src/patient-registration/input/custom-input/estimated-age/estimated-age-input.test.tsx +36 -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 +110 -0
- package/src/patient-registration/input/custom-input/identifier/utils.ts +19 -0
- package/src/patient-registration/input/custom-input/unidentified-patient/unidentified-patient-input.component.tsx +24 -0
- package/src/patient-registration/input/custom-input/unidentified-patient/unidentified-patient-input.test.tsx +39 -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 +108 -0
- package/src/patient-registration/patient-registration-context.ts +24 -0
- package/src/patient-registration/patient-registration-hooks.ts +320 -0
- package/src/patient-registration/patient-registration-types.tsx +271 -0
- package/src/patient-registration/patient-registration-utils.ts +219 -0
- package/src/patient-registration/patient-registration.component.tsx +250 -0
- package/src/patient-registration/patient-registration.resource.test.tsx +26 -0
- package/src/patient-registration/patient-registration.resource.tsx +296 -0
- package/src/patient-registration/patient-registration.scss +94 -0
- package/src/patient-registration/patient-registration.test.tsx +436 -0
- package/src/patient-registration/section/death-info/death-info-section.component.tsx +30 -0
- package/src/patient-registration/section/death-info/death-info-section.test.tsx +73 -0
- package/src/patient-registration/section/demographics/demographics-section.component.tsx +30 -0
- package/src/patient-registration/section/demographics/demographics-section.test.tsx +84 -0
- package/src/patient-registration/section/generic-section.component.tsx +17 -0
- package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +226 -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/index.tsx +51 -0
- package/src/patient-registration/ui-components/overlay/overlay.scss +63 -0
- package/src/patient-registration/validation/patient-registration-validation.test.tsx +129 -0
- package/src/patient-registration/validation/patient-registration-validation.tsx +46 -0
- package/src/patient-verification/assets/counties.json +236 -0
- package/src/patient-verification/assets/verification-assets.ts +11 -0
- package/src/patient-verification/patient-verification-hook.tsx +156 -0
- package/src/patient-verification/patient-verification-utils.ts +173 -0
- package/src/patient-verification/patient-verification.component.tsx +118 -0
- package/src/patient-verification/patient-verification.scss +30 -0
- package/src/patient-verification/verification-modal/confirm-prompt.component.tsx +69 -0
- package/src/patient-verification/verification-modal/empty-prompt.component.tsx +35 -0
- package/src/patient-verification/verification-types.ts +50 -0
- package/src/resource.ts +12 -0
- package/src/root.component.tsx +66 -0
- package/src/root.scss +7 -0
- package/src/root.test.tsx +32 -0
- package/src/widgets/cancel-patient-edit.component.tsx +37 -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/edit-patient-details-button.component.tsx +34 -0
- package/src/widgets/edit-patient-details-button.scss +3 -0
- package/translations/en.json +108 -0
- package/translations/fr.json +89 -0
- package/translations/km.json +89 -0
- package/tsconfig.json +5 -0
- package/webpack.config.js +1 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { BrowserRouter as Router } from 'react-router-dom';
|
|
3
|
+
import { render, screen, fireEvent, waitFor, within } from '@testing-library/react';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
import { showToast, useConfig, usePatient } from '@openmrs/esm-framework';
|
|
6
|
+
import FormManager from './form-manager';
|
|
7
|
+
import { mockPatient } from '../../../../__mocks__/patient.mock';
|
|
8
|
+
import { saveEncounter, savePatient } from './patient-registration.resource';
|
|
9
|
+
import { Encounter } from './patient-registration-types';
|
|
10
|
+
import { Resources, ResourcesContext } from '../offline.resources';
|
|
11
|
+
import { PatientRegistration } from './patient-registration.component';
|
|
12
|
+
import { RegistrationConfig } from '../config-schema';
|
|
13
|
+
|
|
14
|
+
const mockedUseConfig = useConfig as jest.Mock;
|
|
15
|
+
const mockedUsePatient = usePatient as jest.Mock;
|
|
16
|
+
const mockedSaveEncounter = saveEncounter as jest.Mock;
|
|
17
|
+
const mockedSavePatient = savePatient as jest.Mock;
|
|
18
|
+
const mockedShowToast = showToast as jest.Mock;
|
|
19
|
+
|
|
20
|
+
jest.mock('@openmrs/esm-framework', () => {
|
|
21
|
+
const originalModule = jest.requireActual('@openmrs/esm-framework');
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
...originalModule,
|
|
25
|
+
validator: jest.fn(),
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Mock field.resource using the manual mock (in __mocks__)
|
|
30
|
+
jest.mock('./field/field.resource');
|
|
31
|
+
|
|
32
|
+
jest.mock('react-router-dom', () => ({
|
|
33
|
+
...(jest.requireActual('react-router-dom') as any),
|
|
34
|
+
useLocation: () => ({
|
|
35
|
+
pathname: 'openmrs/spa/patient-registration',
|
|
36
|
+
}),
|
|
37
|
+
useHistory: () => [],
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
jest.mock('./patient-registration.resource', () => {
|
|
41
|
+
const originalModule = jest.requireActual('./patient-registration.resource');
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
...originalModule,
|
|
45
|
+
saveEncounter: jest.fn(),
|
|
46
|
+
savePatient: jest.fn(),
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
jest.mock('@openmrs/esm-framework', () => {
|
|
51
|
+
const originalModule = jest.requireActual('@openmrs/esm-framework');
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
...originalModule,
|
|
55
|
+
validator: jest.fn(),
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const predefinedAddressTemplate = {
|
|
60
|
+
results: [
|
|
61
|
+
{
|
|
62
|
+
value:
|
|
63
|
+
'<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>',
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const mockResourcesContextValue = {
|
|
69
|
+
addressTemplate: predefinedAddressTemplate,
|
|
70
|
+
currentSession: {
|
|
71
|
+
authenticated: true,
|
|
72
|
+
sessionId: 'JSESSION',
|
|
73
|
+
currentProvider: { uuid: 'provider-uuid', identifier: 'PRO-123' },
|
|
74
|
+
},
|
|
75
|
+
relationshipTypes: [],
|
|
76
|
+
identifierTypes: [],
|
|
77
|
+
} as Resources;
|
|
78
|
+
|
|
79
|
+
let mockOpenmrsConfig: RegistrationConfig = {
|
|
80
|
+
sections: ['demographics', 'contact'],
|
|
81
|
+
sectionDefinitions: [
|
|
82
|
+
{ id: 'demographics', name: 'Demographics', fields: ['name', 'gender', 'dob'] },
|
|
83
|
+
{ id: 'contact', name: 'Contact Info', fields: ['address'] },
|
|
84
|
+
{ id: 'relationships', name: 'Relationships', fields: ['relationship'] },
|
|
85
|
+
],
|
|
86
|
+
fieldDefinitions: [],
|
|
87
|
+
fieldConfigurations: {
|
|
88
|
+
name: {
|
|
89
|
+
displayMiddleName: true,
|
|
90
|
+
unidentifiedPatient: true,
|
|
91
|
+
defaultUnknownGivenName: 'UNKNOWN',
|
|
92
|
+
defaultUnknownFamilyName: 'UNKNOWN',
|
|
93
|
+
},
|
|
94
|
+
gender: [
|
|
95
|
+
{
|
|
96
|
+
value: 'Male',
|
|
97
|
+
label: 'Male',
|
|
98
|
+
id: 'male',
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
address: {
|
|
102
|
+
useAddressHierarchy: {
|
|
103
|
+
enabled: true,
|
|
104
|
+
useQuickSearch: true,
|
|
105
|
+
searchAddressByLevel: true,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
concepts: {
|
|
110
|
+
patientPhotoUuid: '736e8771-e501-4615-bfa7-570c03f4bef5',
|
|
111
|
+
},
|
|
112
|
+
links: {
|
|
113
|
+
submitButton: '#',
|
|
114
|
+
},
|
|
115
|
+
defaultPatientIdentifierTypes: [],
|
|
116
|
+
registrationObs: {
|
|
117
|
+
encounterTypeUuid: null,
|
|
118
|
+
encounterProviderRoleUuid: 'asdf',
|
|
119
|
+
registrationFormUuid: null,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const path = `/patient/:patientUuid/edit`;
|
|
124
|
+
|
|
125
|
+
const configWithObs = JSON.parse(JSON.stringify(mockOpenmrsConfig));
|
|
126
|
+
configWithObs.fieldDefinitions = [
|
|
127
|
+
{
|
|
128
|
+
id: 'weight',
|
|
129
|
+
type: 'obs',
|
|
130
|
+
label: null,
|
|
131
|
+
uuid: 'weight-uuid',
|
|
132
|
+
placeholder: '',
|
|
133
|
+
validation: { required: false, matches: null },
|
|
134
|
+
answerConceptSetUuid: null,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: 'chief complaint',
|
|
138
|
+
type: 'obs',
|
|
139
|
+
label: null,
|
|
140
|
+
uuid: 'chief-complaint-uuid',
|
|
141
|
+
placeholder: '',
|
|
142
|
+
validation: { required: false, matches: null },
|
|
143
|
+
answerConceptSetUuid: null,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'nationality',
|
|
147
|
+
type: 'obs',
|
|
148
|
+
label: null,
|
|
149
|
+
uuid: 'nationality-uuid',
|
|
150
|
+
placeholder: '',
|
|
151
|
+
validation: { required: false, matches: null },
|
|
152
|
+
answerConceptSetUuid: null,
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
configWithObs.sectionDefinitions?.push({
|
|
156
|
+
id: 'custom',
|
|
157
|
+
name: 'Custom',
|
|
158
|
+
fields: ['weight', 'chief complaint', 'nationality'],
|
|
159
|
+
});
|
|
160
|
+
configWithObs.sections.push('custom');
|
|
161
|
+
configWithObs.registrationObs.encounterTypeUuid = 'reg-enc-uuid';
|
|
162
|
+
|
|
163
|
+
const fillRequiredFields = async () => {
|
|
164
|
+
const user = userEvent.setup();
|
|
165
|
+
|
|
166
|
+
const demographicsSection = await screen.findByLabelText('Demographics Section');
|
|
167
|
+
const givenNameInput = within(demographicsSection).getByLabelText(/first/i) as HTMLInputElement;
|
|
168
|
+
const familyNameInput = within(demographicsSection).getByLabelText(/family/i) as HTMLInputElement;
|
|
169
|
+
const dateOfBirthInput = within(demographicsSection).getByLabelText(/date of birth/i) as HTMLInputElement;
|
|
170
|
+
const genderInput = within(demographicsSection).getByLabelText(/Male/) as HTMLSelectElement;
|
|
171
|
+
|
|
172
|
+
await user.type(givenNameInput, 'Paul');
|
|
173
|
+
await user.type(familyNameInput, 'Gaihre');
|
|
174
|
+
await user.clear(dateOfBirthInput);
|
|
175
|
+
await user.type(dateOfBirthInput, '02/08/1993');
|
|
176
|
+
fireEvent.blur(dateOfBirthInput);
|
|
177
|
+
fireEvent.click(genderInput);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
describe('patient registration', () => {
|
|
181
|
+
beforeEach(() => {
|
|
182
|
+
mockedUseConfig.mockReturnValue(mockOpenmrsConfig);
|
|
183
|
+
mockedSavePatient.mockReturnValue({ data: { uuid: 'new-pt-uuid' }, ok: true });
|
|
184
|
+
mockedSaveEncounter.mockClear();
|
|
185
|
+
mockedShowToast.mockClear();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it.only('renders without crashing', () => {
|
|
189
|
+
render(
|
|
190
|
+
<ResourcesContext.Provider value={mockResourcesContextValue}>
|
|
191
|
+
<Router>
|
|
192
|
+
<PatientRegistration isOffline={false} savePatientForm={jest.fn()} />
|
|
193
|
+
</Router>
|
|
194
|
+
,
|
|
195
|
+
</ResourcesContext.Provider>,
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('has the expected sections', async () => {
|
|
200
|
+
render(
|
|
201
|
+
<ResourcesContext.Provider value={mockResourcesContextValue}>
|
|
202
|
+
<Router>
|
|
203
|
+
<PatientRegistration isOffline={false} savePatientForm={jest.fn()} />
|
|
204
|
+
</Router>
|
|
205
|
+
</ResourcesContext.Provider>,
|
|
206
|
+
);
|
|
207
|
+
await waitFor(() => expect(screen.getByLabelText(/Demographics Section/)).not.toBeNull());
|
|
208
|
+
expect(screen.getByLabelText(/Contact Info Section/)).not.toBeNull();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('saves the patient without extra info', async () => {
|
|
212
|
+
const user = userEvent.setup();
|
|
213
|
+
|
|
214
|
+
render(
|
|
215
|
+
<ResourcesContext.Provider value={mockResourcesContextValue}>
|
|
216
|
+
<Router>
|
|
217
|
+
<PatientRegistration isOffline={false} savePatientForm={FormManager.savePatientFormOnline} />
|
|
218
|
+
</Router>
|
|
219
|
+
</ResourcesContext.Provider>,
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
await fillRequiredFields();
|
|
223
|
+
await user.click(await screen.findByText('Register Patient'));
|
|
224
|
+
await waitFor(() => {
|
|
225
|
+
expect(mockedSavePatient).toHaveBeenCalledWith(
|
|
226
|
+
expect.anything(),
|
|
227
|
+
expect.objectContaining({
|
|
228
|
+
identifiers: [], //TODO when the identifer story is finished: { identifier: '', identifierType: '05a29f94-c0ed-11e2-94be-8c13b969e334', location: '' }
|
|
229
|
+
// identifiers: [{ identifier: '', identifierType: '05a29f94-c0ed-11e2-94be-8c13b969e334', location: '' }],
|
|
230
|
+
person: {
|
|
231
|
+
addresses: [{}],
|
|
232
|
+
attributes: [],
|
|
233
|
+
birthdate: '1993-8-2',
|
|
234
|
+
birthdateEstimated: false,
|
|
235
|
+
gender: 'M',
|
|
236
|
+
names: [{ givenName: 'Paul', middleName: '', familyName: 'Gaihre', preferred: true, uuid: undefined }],
|
|
237
|
+
dead: false,
|
|
238
|
+
uuid: expect.anything(),
|
|
239
|
+
},
|
|
240
|
+
uuid: expect.anything(),
|
|
241
|
+
}),
|
|
242
|
+
undefined,
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should not save the patient if validation fails', async () => {
|
|
248
|
+
const user = userEvent.setup();
|
|
249
|
+
|
|
250
|
+
const mockedSavePatientForm = jest.fn();
|
|
251
|
+
render(
|
|
252
|
+
<ResourcesContext.Provider value={mockResourcesContextValue}>
|
|
253
|
+
<Router>
|
|
254
|
+
<PatientRegistration isOffline={false} savePatientForm={mockedSavePatientForm} />
|
|
255
|
+
</Router>
|
|
256
|
+
</ResourcesContext.Provider>,
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const givenNameInput = (await screen.findByLabelText('First Name')) as HTMLInputElement;
|
|
260
|
+
|
|
261
|
+
await user.type(givenNameInput, '');
|
|
262
|
+
await user.click(screen.getByText('Register Patient'));
|
|
263
|
+
|
|
264
|
+
expect(mockedSavePatientForm).not.toHaveBeenCalled();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it.skip('edits patient demographics', async () => {
|
|
268
|
+
const user = userEvent.setup();
|
|
269
|
+
|
|
270
|
+
mockedSavePatient.mockResolvedValue({});
|
|
271
|
+
|
|
272
|
+
mockedUsePatient.mockReturnValueOnce({
|
|
273
|
+
isLoading: false,
|
|
274
|
+
patient: mockPatient,
|
|
275
|
+
patientUuid: mockPatient.id,
|
|
276
|
+
error: null,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
render(
|
|
280
|
+
<ResourcesContext.Provider value={mockResourcesContextValue}>
|
|
281
|
+
<Router>
|
|
282
|
+
<PatientRegistration isOffline={false} savePatientForm={FormManager.savePatientFormOnline} />
|
|
283
|
+
</Router>
|
|
284
|
+
</ResourcesContext.Provider>,
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
const givenNameInput = screen.getByLabelText(/First Name/) as HTMLInputElement;
|
|
288
|
+
const familyNameInput = screen.getByLabelText(/Family Name/) as HTMLInputElement;
|
|
289
|
+
const middleNameInput = screen.getByLabelText(/Middle Name/) as HTMLInputElement;
|
|
290
|
+
const dateOfBirthInput = screen.getByLabelText('Date of Birth') as HTMLInputElement;
|
|
291
|
+
const address1 = screen.getByLabelText('Location.address1') as HTMLInputElement;
|
|
292
|
+
|
|
293
|
+
// assert initial values
|
|
294
|
+
expect(givenNameInput.value).toBe('John');
|
|
295
|
+
expect(familyNameInput.value).toBe('Wilson');
|
|
296
|
+
expect(middleNameInput.value).toBeFalsy();
|
|
297
|
+
expect(dateOfBirthInput.value).toBe('04/04/1972');
|
|
298
|
+
|
|
299
|
+
// do some edits
|
|
300
|
+
await user.clear(givenNameInput);
|
|
301
|
+
await user.clear(middleNameInput);
|
|
302
|
+
await user.clear(familyNameInput);
|
|
303
|
+
await user.type(givenNameInput, 'Eric');
|
|
304
|
+
await user.type(middleNameInput, 'Johnson');
|
|
305
|
+
await user.type(familyNameInput, 'Smith');
|
|
306
|
+
await user.type(address1, 'Bom Jesus Street');
|
|
307
|
+
await user.click(screen.getByText('Update Patient'));
|
|
308
|
+
|
|
309
|
+
expect(mockedSavePatient).toHaveBeenCalledWith(
|
|
310
|
+
expect.anything(),
|
|
311
|
+
{
|
|
312
|
+
uuid: '8673ee4f-e2ab-4077-ba55-4980f408773e',
|
|
313
|
+
identifiers: [
|
|
314
|
+
{
|
|
315
|
+
uuid: '1f0ad7a1-430f-4397-b571-59ea654a52db',
|
|
316
|
+
identifier: '100GEJ',
|
|
317
|
+
identifierType: 'e5af9a9c-ff9d-486d-900c-5fbf66a5ba3c',
|
|
318
|
+
preferred: true,
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
uuid: '1f0ad7a1-430f-4397-b571-59ea654a52db',
|
|
322
|
+
identifier: '100732HE',
|
|
323
|
+
identifierType: '3ff0063c-dd45-4d98-8af4-0c094f26166c',
|
|
324
|
+
preferred: false,
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
person: {
|
|
328
|
+
addresses: [
|
|
329
|
+
{
|
|
330
|
+
address1: 'Bom Jesus Street',
|
|
331
|
+
address2: '',
|
|
332
|
+
cityVillage: 'City0351',
|
|
333
|
+
country: 'Country0351',
|
|
334
|
+
postalCode: '60351',
|
|
335
|
+
stateProvince: 'State0351tested',
|
|
336
|
+
},
|
|
337
|
+
],
|
|
338
|
+
attributes: [],
|
|
339
|
+
birthdate: new Date('1972-04-04'),
|
|
340
|
+
birthdateEstimated: false,
|
|
341
|
+
gender: 'M',
|
|
342
|
+
names: [
|
|
343
|
+
{
|
|
344
|
+
uuid: 'efdb246f-4142-4c12-a27a-9be60b9592e9',
|
|
345
|
+
givenName: 'Eric',
|
|
346
|
+
middleName: 'Johnson',
|
|
347
|
+
familyName: 'Smith',
|
|
348
|
+
preferred: true,
|
|
349
|
+
},
|
|
350
|
+
],
|
|
351
|
+
dead: false,
|
|
352
|
+
uuid: '8673ee4f-e2ab-4077-ba55-4980f408773e',
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
'8673ee4f-e2ab-4077-ba55-4980f408773e',
|
|
356
|
+
);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('renders and saves registration obs', async () => {
|
|
360
|
+
const user = userEvent.setup();
|
|
361
|
+
|
|
362
|
+
mockedSaveEncounter.mockResolvedValue({});
|
|
363
|
+
mockedUseConfig.mockReturnValue(configWithObs);
|
|
364
|
+
|
|
365
|
+
render(
|
|
366
|
+
<ResourcesContext.Provider value={mockResourcesContextValue}>
|
|
367
|
+
<Router>
|
|
368
|
+
<PatientRegistration isOffline={false} savePatientForm={FormManager.savePatientFormOnline} />
|
|
369
|
+
</Router>
|
|
370
|
+
</ResourcesContext.Provider>,
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
await fillRequiredFields();
|
|
374
|
+
const customSection = screen.getByLabelText('Custom Section');
|
|
375
|
+
const weight = within(customSection).getByLabelText('Weight (kg)');
|
|
376
|
+
await user.type(weight, '50');
|
|
377
|
+
const complaint = within(customSection).getByLabelText('Chief Complaint');
|
|
378
|
+
await user.type(complaint, 'sad');
|
|
379
|
+
const nationality = within(customSection).getByLabelText('Nationality');
|
|
380
|
+
await user.selectOptions(nationality, 'USA');
|
|
381
|
+
|
|
382
|
+
await user.click(screen.getByText('Register Patient'));
|
|
383
|
+
|
|
384
|
+
await waitFor(() => expect(mockedSavePatient).toHaveBeenCalled());
|
|
385
|
+
await waitFor(() =>
|
|
386
|
+
expect(mockedSaveEncounter).toHaveBeenCalledWith(
|
|
387
|
+
expect.anything(),
|
|
388
|
+
expect.objectContaining<Partial<Encounter>>({
|
|
389
|
+
encounterType: 'reg-enc-uuid',
|
|
390
|
+
patient: 'new-pt-uuid',
|
|
391
|
+
obs: [
|
|
392
|
+
{ concept: 'weight-uuid', value: 50 },
|
|
393
|
+
{ concept: 'chief-complaint-uuid', value: 'sad' },
|
|
394
|
+
{ concept: 'nationality-uuid', value: 'usa' },
|
|
395
|
+
],
|
|
396
|
+
}),
|
|
397
|
+
),
|
|
398
|
+
);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('retries saving registration obs after a failed attempt', async () => {
|
|
402
|
+
const user = userEvent.setup();
|
|
403
|
+
|
|
404
|
+
mockedUseConfig.mockReturnValue(configWithObs);
|
|
405
|
+
|
|
406
|
+
render(
|
|
407
|
+
<ResourcesContext.Provider value={mockResourcesContextValue}>
|
|
408
|
+
<Router>
|
|
409
|
+
<PatientRegistration isOffline={false} savePatientForm={FormManager.savePatientFormOnline} />
|
|
410
|
+
</Router>
|
|
411
|
+
</ResourcesContext.Provider>,
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
await fillRequiredFields();
|
|
415
|
+
const customSection = screen.getByLabelText('Custom Section');
|
|
416
|
+
const weight = within(customSection).getByLabelText('Weight (kg)');
|
|
417
|
+
await user.type(weight, '-999');
|
|
418
|
+
|
|
419
|
+
mockedSaveEncounter.mockRejectedValue({ status: 400, responseBody: { error: { message: 'an error message' } } });
|
|
420
|
+
|
|
421
|
+
await user.click(screen.getByText('Register Patient'));
|
|
422
|
+
|
|
423
|
+
await waitFor(() => expect(mockedSavePatient).toHaveBeenCalledTimes(1));
|
|
424
|
+
await waitFor(() => expect(mockedSaveEncounter).toHaveBeenCalledTimes(1));
|
|
425
|
+
await waitFor(() =>
|
|
426
|
+
expect(mockedShowToast).toHaveBeenCalledWith(expect.objectContaining({ description: 'an error message' })),
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
mockedSaveEncounter.mockResolvedValue({});
|
|
430
|
+
|
|
431
|
+
await user.click(screen.getByText('Register Patient'));
|
|
432
|
+
await waitFor(() => expect(mockedSavePatient).toHaveBeenCalledTimes(1));
|
|
433
|
+
await waitFor(() => expect(mockedSaveEncounter).toHaveBeenCalledTimes(2));
|
|
434
|
+
await waitFor(() => expect(mockedShowToast).toHaveBeenCalledWith(expect.objectContaining({ kind: 'success' })));
|
|
435
|
+
});
|
|
436
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Input } from '../../input/basic-input/input/input.component';
|
|
4
|
+
import { SelectInput } from '../../input/basic-input/select/select-input.component';
|
|
5
|
+
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
6
|
+
import styles from './../section.scss';
|
|
7
|
+
|
|
8
|
+
export const DeathInfoSection = () => {
|
|
9
|
+
const { values } = React.useContext(PatientRegistrationContext);
|
|
10
|
+
const { t } = useTranslation();
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<section className={styles.formSection} aria-label="Death Info Section">
|
|
14
|
+
<h5 className={`omrs-type-title-5 ${styles.formSectionTitle}`}>Death Info</h5>
|
|
15
|
+
<section className={styles.fieldGroup}>
|
|
16
|
+
<Input labelText={t('isDeadInputLabel', 'Is Dead')} name="isDead" id="isDead" />
|
|
17
|
+
{values.isDead && (
|
|
18
|
+
<>
|
|
19
|
+
<Input labelText={t('deathDateInputLabel', 'Date of Death')} name="deathDate" id="deathDate" />
|
|
20
|
+
<SelectInput
|
|
21
|
+
options={[t('unknown', 'Unknown'), t('stroke', 'Stroke')]}
|
|
22
|
+
label={t('causeOfDeathInputLabel', 'Cause of Death')}
|
|
23
|
+
name="deathCause"
|
|
24
|
+
/>
|
|
25
|
+
</>
|
|
26
|
+
)}
|
|
27
|
+
</section>
|
|
28
|
+
</section>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { Formik, Form } from 'formik';
|
|
4
|
+
import { initialFormValues } from '../../patient-registration.component';
|
|
5
|
+
import { DeathInfoSection } from './death-info-section.component';
|
|
6
|
+
import { FormValues } from '../../patient-registration-types';
|
|
7
|
+
|
|
8
|
+
jest.mock('@openmrs/esm-framework', () => {
|
|
9
|
+
const originalModule = jest.requireActual('@openmrs/esm-framework');
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
...originalModule,
|
|
13
|
+
validator: jest.fn(),
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// TODO: Implement feature and get tests to pass
|
|
18
|
+
describe.skip('death info section', () => {
|
|
19
|
+
const formValues: FormValues = initialFormValues;
|
|
20
|
+
|
|
21
|
+
const setupSection = async (isDead?: boolean) => {
|
|
22
|
+
render(
|
|
23
|
+
<Formik initialValues={{ ...initialFormValues, isDead }} onSubmit={null}>
|
|
24
|
+
<Form>
|
|
25
|
+
<DeathInfoSection />
|
|
26
|
+
</Form>
|
|
27
|
+
</Formik>,
|
|
28
|
+
);
|
|
29
|
+
const allInputs = screen.queryAllByLabelText(
|
|
30
|
+
(content, element) => element.tagName.toLowerCase() === 'input',
|
|
31
|
+
) as Array<HTMLInputElement>;
|
|
32
|
+
const allSelects = screen.queryAllByRole('combobox') as Array<HTMLInputElement>;
|
|
33
|
+
let inputAndSelectNames = [];
|
|
34
|
+
allInputs.forEach((input) => inputAndSelectNames.push(input.name));
|
|
35
|
+
allSelects.forEach((select) => inputAndSelectNames.push(select.name));
|
|
36
|
+
return inputAndSelectNames;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
it('has the correct number of inputs if is dead is checked', async () => {
|
|
40
|
+
const inputAndSelectNames = await setupSection(true);
|
|
41
|
+
expect(inputAndSelectNames.length).toBe(3);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('has the correct number of inputs if is dead is not checked', async () => {
|
|
45
|
+
const inputAndSelectNames = await setupSection(false);
|
|
46
|
+
expect(inputAndSelectNames.length).toBe(1);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('has isDead checkbox', async () => {
|
|
50
|
+
const inputAndSelectNames = await setupSection();
|
|
51
|
+
expect(inputAndSelectNames).toContain('isDead');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('has death date if is dead is checked', async () => {
|
|
55
|
+
const inputAndSelectNames = await setupSection(true);
|
|
56
|
+
expect(inputAndSelectNames).toContain('deathDate');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('has no death date if is dead is not checked', async () => {
|
|
60
|
+
const inputAndSelectNames = await setupSection(false);
|
|
61
|
+
expect(inputAndSelectNames).not.toContain('deathDate');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('has death cause if is dead is checked', async () => {
|
|
65
|
+
const inputAndSelectNames = await setupSection(true);
|
|
66
|
+
expect(inputAndSelectNames).toContain('deathCause');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('has no death cause if is dead is not checked', async () => {
|
|
70
|
+
const inputAndSelectNames = await setupSection(false);
|
|
71
|
+
expect(inputAndSelectNames).not.toContain('deathCause');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React, { useContext, useEffect } from 'react';
|
|
2
|
+
import styles from './../section.scss';
|
|
3
|
+
import { useField } from 'formik';
|
|
4
|
+
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
5
|
+
import { Field } from '../../field/field.component';
|
|
6
|
+
|
|
7
|
+
export interface DemographicsSectionProps {
|
|
8
|
+
fields: Array<string>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const DemographicsSection: React.FC<DemographicsSectionProps> = ({ fields }) => {
|
|
12
|
+
const [field, meta] = useField('addNameInLocalLanguage');
|
|
13
|
+
const { setFieldValue } = useContext(PatientRegistrationContext);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!field.value && meta.touched) {
|
|
17
|
+
setFieldValue('additionalGivenName', '');
|
|
18
|
+
setFieldValue('additionalMiddleName', '');
|
|
19
|
+
setFieldValue('additionalFamilyName', '');
|
|
20
|
+
}
|
|
21
|
+
}, [field.value, meta.touched]);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<section className={styles.formSection} aria-label="Demographics Section">
|
|
25
|
+
{fields.map((field) => (
|
|
26
|
+
<Field key={`demographics-${field}`} name={field} />
|
|
27
|
+
))}
|
|
28
|
+
</section>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { Formik, Form } from 'formik';
|
|
4
|
+
import { initialFormValues } from '../../patient-registration.component';
|
|
5
|
+
import { DemographicsSection } from './demographics-section.component';
|
|
6
|
+
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
7
|
+
import { FormValues } from '../../patient-registration-types';
|
|
8
|
+
|
|
9
|
+
jest.mock('@openmrs/esm-framework', () => {
|
|
10
|
+
const originalModule = jest.requireActual('@openmrs/esm-framework');
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
...originalModule,
|
|
14
|
+
validator: jest.fn(),
|
|
15
|
+
useConfig: jest.fn().mockImplementation(() => ({
|
|
16
|
+
fieldConfigurations: { dateOfBirth: { useEstimatedDateOfBirth: { enabled: true, dayOfMonth: 0, month: 0 } } },
|
|
17
|
+
})),
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
jest.mock('../../field/name/name-field.component', () => {
|
|
22
|
+
return {
|
|
23
|
+
NameField: () => (
|
|
24
|
+
<div>
|
|
25
|
+
<input type="text" name="name" />
|
|
26
|
+
</div>
|
|
27
|
+
),
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
jest.mock('../../field/gender/gender-field.component', () => {
|
|
32
|
+
return {
|
|
33
|
+
GenderField: () => (
|
|
34
|
+
<div>
|
|
35
|
+
<input type="text" name="name" />
|
|
36
|
+
</div>
|
|
37
|
+
),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
jest.mock('../../field/id/id-field.component', () => {
|
|
42
|
+
return {
|
|
43
|
+
IdField: () => (
|
|
44
|
+
<div>
|
|
45
|
+
<input type="text" name="name" />
|
|
46
|
+
</div>
|
|
47
|
+
),
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('demographics section', () => {
|
|
52
|
+
const formValues: FormValues = initialFormValues;
|
|
53
|
+
|
|
54
|
+
const setupSection = async (birthdateEstimated?: boolean, addNameInLocalLanguage?: boolean) => {
|
|
55
|
+
render(
|
|
56
|
+
<Formik initialValues={{ ...initialFormValues, birthdateEstimated, addNameInLocalLanguage }} onSubmit={null}>
|
|
57
|
+
<Form>
|
|
58
|
+
<PatientRegistrationContext.Provider
|
|
59
|
+
value={{
|
|
60
|
+
initialFormValues: null,
|
|
61
|
+
identifierTypes: [],
|
|
62
|
+
validationSchema: {},
|
|
63
|
+
setValidationSchema: () => {},
|
|
64
|
+
values: { ...initialFormValues, birthdateEstimated, addNameInLocalLanguage },
|
|
65
|
+
inEditMode: false,
|
|
66
|
+
setFieldValue: () => {},
|
|
67
|
+
currentPhoto: 'TEST',
|
|
68
|
+
isOffline: true,
|
|
69
|
+
setCapturePhotoProps: (value) => {},
|
|
70
|
+
}}>
|
|
71
|
+
<DemographicsSection fields={['name', 'gender', 'dob']} id="demographics" />
|
|
72
|
+
</PatientRegistrationContext.Provider>
|
|
73
|
+
</Form>
|
|
74
|
+
</Formik>,
|
|
75
|
+
);
|
|
76
|
+
const allInputs = screen.getAllByRole('textbox') as Array<HTMLInputElement>;
|
|
77
|
+
return allInputs.map((input) => input.name);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
it('inputs corresponding to number of fields', async () => {
|
|
81
|
+
const inputNames = await setupSection();
|
|
82
|
+
expect(inputNames.length).toBe(3);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { SectionDefinition } from '../../config-schema';
|
|
3
|
+
import { Field } from '../field/field.component';
|
|
4
|
+
|
|
5
|
+
export interface GenericSectionProps {
|
|
6
|
+
sectionDefinition: SectionDefinition;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const GenericSection = ({ sectionDefinition }: GenericSectionProps) => {
|
|
10
|
+
return (
|
|
11
|
+
<section aria-label={`${sectionDefinition.name} Section`}>
|
|
12
|
+
{sectionDefinition.fields.map((name) => (
|
|
13
|
+
<Field key={`${sectionDefinition.name}-${name}`} name={name} />
|
|
14
|
+
))}
|
|
15
|
+
</section>
|
|
16
|
+
);
|
|
17
|
+
};
|