@kenyaemr/esm-patient-registration-app 5.2.2 → 6.0.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.
Files changed (122) hide show
  1. package/dist/130.js +1 -1
  2. package/dist/130.js.map +1 -1
  3. package/dist/271.js +1 -0
  4. package/dist/319.js +1 -1
  5. package/dist/330.js +1 -1
  6. package/dist/460.js +1 -1
  7. package/dist/537.js +1 -1
  8. package/dist/574.js +1 -1
  9. package/dist/59.js +1 -1
  10. package/dist/59.js.map +1 -1
  11. package/dist/619.js +1 -0
  12. package/dist/619.js.map +1 -0
  13. package/dist/644.js +1 -0
  14. package/dist/735.js +1 -1
  15. package/dist/757.js +1 -1
  16. package/dist/784.js +1 -1
  17. package/dist/788.js +1 -1
  18. package/dist/807.js +1 -1
  19. package/dist/833.js +1 -1
  20. package/dist/895.js +2 -0
  21. package/dist/{388.js.LICENSE.txt → 895.js.LICENSE.txt} +4 -2
  22. package/dist/895.js.map +1 -0
  23. package/dist/{openmrs-esm-patient-registration-app.js → kenyaemr-esm-patient-registration-app.js} +1 -1
  24. package/dist/{openmrs-esm-patient-registration-app.js.buildmanifest.json → kenyaemr-esm-patient-registration-app.js.buildmanifest.json} +117 -73
  25. package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -0
  26. package/dist/main.js +1 -1
  27. package/dist/main.js.LICENSE.txt +4 -2
  28. package/dist/main.js.map +1 -1
  29. package/dist/routes.json +1 -1
  30. package/package.json +6 -5
  31. package/src/add-patient-link.test.tsx +9 -7
  32. package/src/config-schema.ts +31 -38
  33. package/src/constants.ts +1 -1
  34. package/src/offline.resources.ts +13 -18
  35. package/src/offline.ts +8 -6
  36. package/src/patient-registration/before-save-prompt.tsx +2 -1
  37. package/src/patient-registration/field/__mocks__/field.resource.ts +1 -1
  38. package/src/patient-registration/field/address/address-field.component.tsx +5 -4
  39. package/src/patient-registration/field/address/address-hierarchy.resource.tsx +2 -2
  40. package/src/patient-registration/field/address/address-search.component.tsx +1 -14
  41. package/src/patient-registration/field/address/custom-address-field.component.tsx +1 -1
  42. package/src/patient-registration/field/address/tests/address-hierarchy.test.tsx +3 -3
  43. package/src/patient-registration/field/address/tests/address-search-component.test.tsx +14 -7
  44. package/src/patient-registration/field/custom-field.component.tsx +1 -1
  45. package/src/patient-registration/field/dob/dob.component.tsx +2 -2
  46. package/src/patient-registration/field/dob/dob.test.tsx +0 -3
  47. package/src/patient-registration/field/field.component.tsx +4 -1
  48. package/src/patient-registration/field/field.resource.ts +4 -4
  49. package/src/patient-registration/field/field.test.tsx +98 -95
  50. package/src/patient-registration/field/gender/gender-field.component.tsx +4 -4
  51. package/src/patient-registration/field/gender/gender-field.test.tsx +7 -14
  52. package/src/patient-registration/field/id/id-field.component.tsx +6 -6
  53. package/src/patient-registration/field/id/id-field.test.tsx +9 -7
  54. package/src/patient-registration/field/id/identifier-selection-overlay.component.tsx +1 -1
  55. package/src/patient-registration/field/name/name-field.component.tsx +1 -1
  56. package/src/patient-registration/field/obs/obs-field.component.tsx +35 -33
  57. package/src/patient-registration/field/obs/obs-field.test.tsx +106 -28
  58. package/src/patient-registration/field/person-attributes/coded-attributes.component.tsx +1 -1
  59. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +70 -24
  60. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +54 -30
  61. package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +4 -3
  62. package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +1 -1
  63. package/src/patient-registration/field/person-attributes/{person-attributes.resource.tsx → person-attributes.resource.ts} +3 -3
  64. package/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx +1 -1
  65. package/src/patient-registration/field/phone/phone-field.component.tsx +16 -0
  66. package/src/patient-registration/form-manager.test.ts +1 -1
  67. package/src/patient-registration/form-manager.ts +22 -24
  68. package/src/patient-registration/input/basic-input/select/select-input.test.tsx +3 -3
  69. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.component.tsx +5 -5
  70. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +70 -58
  71. package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +2 -2
  72. package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +2 -5
  73. package/src/patient-registration/input/custom-input/identifier/utils.ts +1 -1
  74. package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +1 -1
  75. package/src/patient-registration/input/dummy-data/dummy-data-input.test.tsx +6 -6
  76. package/src/patient-registration/patient-registration-context.ts +3 -4
  77. package/src/patient-registration/patient-registration-hooks.ts +25 -20
  78. package/src/patient-registration/patient-registration-utils.ts +7 -7
  79. package/src/patient-registration/patient-registration.component.tsx +20 -10
  80. package/src/patient-registration/patient-registration.resource.test.tsx +3 -3
  81. package/src/patient-registration/{patient-registration.resource.tsx → patient-registration.resource.ts} +15 -15
  82. package/src/patient-registration/patient-registration.test.tsx +270 -251
  83. package/src/patient-registration/{patient-registration.types.tsx → patient-registration.types.ts} +12 -3
  84. package/src/patient-registration/section/death-info/death-info-section.test.tsx +33 -45
  85. package/src/patient-registration/section/demographics/demographics-section.test.tsx +1 -2
  86. package/src/patient-registration/section/generic-section.component.tsx +1 -1
  87. package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +3 -3
  88. package/src/patient-registration/section/patient-relationships/relationships-section.test.tsx +17 -5
  89. package/src/patient-registration/section/patient-relationships/relationships.resource.tsx +4 -4
  90. package/src/patient-registration/section/section-wrapper.component.tsx +1 -1
  91. package/src/patient-registration/section/section.component.tsx +1 -1
  92. package/src/patient-registration/validation/patient-registration-validation.test.tsx +140 -126
  93. package/src/patient-registration/validation/patient-registration-validation.tsx +54 -46
  94. package/src/patient-verification/patient-verification-hook.tsx +13 -4
  95. package/src/patient-verification/patient-verification-utils.ts +20 -12
  96. package/src/patient-verification/patient-verification.component.tsx +13 -6
  97. package/src/patient-verification/patient-verification.scss +0 -1
  98. package/src/patient-verification/verification-modal/confirm-prompt.component.tsx +2 -11
  99. package/src/routes.json +1 -0
  100. package/src/widgets/cancel-patient-edit.test.tsx +7 -4
  101. package/src/widgets/delete-identifier-confirmation-modal.test.tsx +7 -4
  102. package/src/widgets/display-photo.test.tsx +1 -1
  103. package/src/widgets/edit-patient-details-button.test.tsx +12 -7
  104. package/translations/am.json +30 -14
  105. package/translations/ar.json +30 -14
  106. package/translations/en.json +11 -11
  107. package/translations/es.json +34 -22
  108. package/translations/fr.json +48 -40
  109. package/translations/he.json +22 -2
  110. package/translations/km.json +22 -2
  111. package/translations/zh.json +97 -0
  112. package/translations/zh_CN.json +97 -0
  113. package/tsconfig.json +1 -1
  114. package/__mocks__/autogenerationoptions.mock.ts +0 -34
  115. package/dist/388.js +0 -2
  116. package/dist/388.js.map +0 -1
  117. package/dist/598.js +0 -1
  118. package/dist/598.js.map +0 -1
  119. package/dist/openmrs-esm-patient-registration-app.js.map +0 -1
  120. package/src/patient-registration/field/__mocks__/identifier-types.mock.ts +0 -76
  121. package/src/patient-registration/field/__mocks__/identifiers.mock.ts +0 -27
  122. package/src/patient-registration/field/address/tests/mocks.ts +0 -98
@@ -1,16 +1,16 @@
1
1
  import React from 'react';
2
2
  import { BrowserRouter as Router, useParams } from 'react-router-dom';
3
- import { render, screen, waitFor, within } from '@testing-library/react';
3
+ import { render, screen, within } from '@testing-library/react';
4
4
  import userEvent from '@testing-library/user-event';
5
5
  import { showSnackbar, useConfig, usePatient } from '@openmrs/esm-framework';
6
+ import type { AddressTemplate, Encounter } from './patient-registration.types';
7
+ import { type RegistrationConfig } from '../config-schema';
6
8
  import { FormManager } from './form-manager';
7
- import { saveEncounter, savePatient } from './patient-registration.resource';
8
- import type { Encounter } from './patient-registration.types';
9
- import { Resources, ResourcesContext } from '../offline.resources';
9
+ import { ResourcesContext } from '../offline.resources';
10
10
  import { PatientRegistration } from './patient-registration.component';
11
- import { RegistrationConfig } from '../config-schema';
12
- import { mockedAddressTemplate } from './field/address/tests/mocks';
13
- import { mockPatient } from '../../../../tools/test-helpers';
11
+ import { saveEncounter, savePatient } from './patient-registration.resource';
12
+ import { mockedAddressTemplate } from '__mocks__';
13
+ import { mockPatient } from 'tools';
14
14
 
15
15
  const mockedUseConfig = useConfig as jest.Mock;
16
16
  const mockedUsePatient = usePatient as jest.Mock;
@@ -18,7 +18,62 @@ const mockedSaveEncounter = saveEncounter as jest.Mock;
18
18
  const mockedSavePatient = savePatient as jest.Mock;
19
19
  const mockedShowSnackbar = showSnackbar as jest.Mock;
20
20
 
21
- jest.setTimeout(10000);
21
+ jest.mock('./field/field.resource', () => ({
22
+ useConcept: jest.fn().mockImplementation((uuid: string) => {
23
+ let data;
24
+ if (uuid == 'weight-uuid') {
25
+ data = {
26
+ uuid: 'weight-uuid',
27
+ display: 'Weight (kg)',
28
+ datatype: { display: 'Numeric', uuid: 'num' },
29
+ answers: [],
30
+ setMembers: [],
31
+ };
32
+ } else if (uuid == 'chief-complaint-uuid') {
33
+ data = {
34
+ uuid: 'chief-complaint-uuid',
35
+ display: 'Chief Complaint',
36
+ datatype: { display: 'Text', uuid: 'txt' },
37
+ answers: [],
38
+ setMembers: [],
39
+ };
40
+ } else if (uuid == 'nationality-uuid') {
41
+ data = {
42
+ uuid: 'nationality-uuid',
43
+ display: 'Nationality',
44
+ datatype: { display: 'Coded', uuid: 'cdd' },
45
+ answers: [
46
+ { display: 'USA', uuid: 'usa' },
47
+ { display: 'Mexico', uuid: 'mex' },
48
+ ],
49
+ setMembers: [],
50
+ };
51
+ }
52
+ return {
53
+ data: data ?? null,
54
+ isLoading: !data,
55
+ };
56
+ }),
57
+ useConceptAnswers: jest.fn().mockImplementation((uuid: string) => {
58
+ if (uuid == 'nationality-uuid') {
59
+ return {
60
+ data: [
61
+ { display: 'USA', uuid: 'usa' },
62
+ { display: 'Mexico', uuid: 'mex' },
63
+ ],
64
+ isLoading: false,
65
+ };
66
+ } else if (uuid == 'other-countries-uuid') {
67
+ return {
68
+ data: [
69
+ { display: 'Kenya', uuid: 'ke' },
70
+ { display: 'Uganda', uuid: 'ug' },
71
+ ],
72
+ isLoading: false,
73
+ };
74
+ }
75
+ }),
76
+ }));
22
77
 
23
78
  jest.mock('@openmrs/esm-framework', () => {
24
79
  const originalModule = jest.requireActual('@openmrs/esm-framework');
@@ -29,9 +84,6 @@ jest.mock('@openmrs/esm-framework', () => {
29
84
  };
30
85
  });
31
86
 
32
- // Mock field.resource using the manual mock (in __mocks__)
33
- jest.mock('./field/field.resource');
34
-
35
87
  jest.mock('react-router-dom', () => ({
36
88
  ...(jest.requireActual('react-router-dom') as any),
37
89
  useLocation: () => ({
@@ -62,7 +114,7 @@ jest.mock('@openmrs/esm-framework', () => {
62
114
  });
63
115
 
64
116
  const mockResourcesContextValue = {
65
- addressTemplate: mockedAddressTemplate,
117
+ addressTemplate: mockedAddressTemplate as AddressTemplate,
66
118
  currentSession: {
67
119
  authenticated: true,
68
120
  sessionId: 'JSESSION',
@@ -70,7 +122,7 @@ const mockResourcesContextValue = {
70
122
  },
71
123
  relationshipTypes: [],
72
124
  identifierTypes: [],
73
- } as Resources;
125
+ };
74
126
 
75
127
  let mockOpenmrsConfig: RegistrationConfig = {
76
128
  sections: ['demographics', 'contact'],
@@ -99,9 +151,8 @@ let mockOpenmrsConfig: RegistrationConfig = {
99
151
  },
100
152
  gender: [
101
153
  {
102
- value: 'Male',
154
+ value: 'male',
103
155
  label: 'Male',
104
- id: 'male',
105
156
  },
106
157
  ],
107
158
  address: {
@@ -138,6 +189,7 @@ configWithObs.fieldDefinitions = [
138
189
  placeholder: '',
139
190
  validation: { required: false, matches: null },
140
191
  answerConceptSetUuid: null,
192
+ customConceptAnswers: [],
141
193
  },
142
194
  {
143
195
  id: 'chief complaint',
@@ -147,6 +199,7 @@ configWithObs.fieldDefinitions = [
147
199
  placeholder: '',
148
200
  validation: { required: false, matches: null },
149
201
  answerConceptSetUuid: null,
202
+ customConceptAnswers: [],
150
203
  },
151
204
  {
152
205
  id: 'nationality',
@@ -156,8 +209,10 @@ configWithObs.fieldDefinitions = [
156
209
  placeholder: '',
157
210
  validation: { required: false, matches: null },
158
211
  answerConceptSetUuid: null,
212
+ customConceptAnswers: [],
159
213
  },
160
214
  ];
215
+
161
216
  configWithObs.sectionDefinitions?.push({
162
217
  id: 'custom',
163
218
  name: 'Custom',
@@ -182,271 +237,235 @@ const fillRequiredFields = async () => {
182
237
  user.click(genderInput);
183
238
  };
184
239
 
185
- describe('patient registration component', () => {
186
- describe('when registering a new patient', () => {
187
- beforeEach(() => {
188
- mockedUseConfig.mockReturnValue(mockOpenmrsConfig);
189
- mockedSavePatient.mockReturnValue({ data: { uuid: 'new-pt-uuid' }, ok: true });
190
- mockedSaveEncounter.mockClear();
191
- mockedShowSnackbar.mockClear();
192
- jest.clearAllMocks();
193
- });
240
+ function Wrapper({ children }) {
241
+ return (
242
+ <ResourcesContext.Provider value={mockResourcesContextValue}>
243
+ <Router>{children}</Router>
244
+ </ResourcesContext.Provider>
245
+ );
246
+ }
247
+
248
+ describe('Registering a new patient', () => {
249
+ beforeEach(() => {
250
+ mockedUseConfig.mockReturnValue(mockOpenmrsConfig);
251
+ mockedSavePatient.mockReturnValue({ data: { uuid: 'new-pt-uuid' }, ok: true });
252
+ mockedSaveEncounter.mockClear();
253
+ mockedShowSnackbar.mockClear();
254
+ jest.clearAllMocks();
255
+ });
194
256
 
195
- it('renders without crashing', () => {
196
- render(
197
- <ResourcesContext.Provider value={mockResourcesContextValue}>
198
- <Router>
199
- <PatientRegistration isOffline={false} savePatientForm={jest.fn()} />
200
- </Router>
201
- ,
202
- </ResourcesContext.Provider>,
203
- );
204
- });
257
+ it('renders without crashing', () => {
258
+ render(<PatientRegistration isOffline={false} savePatientForm={jest.fn()} />, { wrapper: Wrapper });
259
+ });
205
260
 
206
- it('has the expected sections', async () => {
207
- render(
208
- <ResourcesContext.Provider value={mockResourcesContextValue}>
209
- <Router>
210
- <PatientRegistration isOffline={false} savePatientForm={jest.fn()} />
211
- </Router>
212
- </ResourcesContext.Provider>,
213
- );
214
- await waitFor(() => expect(screen.getByLabelText(/Demographics Section/)).not.toBeNull());
215
- expect(screen.getByLabelText(/Contact Info Section/)).not.toBeNull();
216
- });
261
+ it('has the expected sections', async () => {
262
+ render(<PatientRegistration isOffline={false} savePatientForm={jest.fn()} />, { wrapper: Wrapper });
217
263
 
218
- it('saves the patient without extra info', async () => {
219
- const user = userEvent.setup();
220
-
221
- render(
222
- <ResourcesContext.Provider value={mockResourcesContextValue}>
223
- <Router>
224
- <PatientRegistration isOffline={false} savePatientForm={FormManager.savePatientFormOnline} />
225
- </Router>
226
- </ResourcesContext.Provider>,
227
- );
228
-
229
- await fillRequiredFields();
230
- await user.click(await screen.findByText('Register Patient'));
231
- await waitFor(() => {
232
- expect(mockedSavePatient).toHaveBeenCalledWith(
233
- expect.objectContaining({
234
- identifiers: [], //TODO when the identifer story is finished: { identifier: '', identifierType: '05a29f94-c0ed-11e2-94be-8c13b969e334', location: '' },
235
- person: {
236
- addresses: expect.arrayContaining([expect.any(Object)]),
237
- attributes: [],
238
- birthdate: '1993-8-2',
239
- birthdateEstimated: false,
240
- gender: 'M',
241
- names: [{ givenName: 'Paul', middleName: '', familyName: 'Gaihre', preferred: true, uuid: undefined }],
242
- dead: false,
243
- uuid: expect.anything(),
244
- },
245
- uuid: expect.anything(),
246
- }),
247
- undefined,
248
- );
249
- });
264
+ expect(screen.getByLabelText(/Demographics Section/)).not.toBeNull();
265
+ expect(screen.getByLabelText(/Contact Info Section/)).not.toBeNull();
266
+ });
267
+
268
+ it('saves the patient without extra info', async () => {
269
+ const user = userEvent.setup();
270
+
271
+ render(<PatientRegistration isOffline={false} savePatientForm={FormManager.savePatientFormOnline} />, {
272
+ wrapper: Wrapper,
250
273
  });
251
274
 
252
- it('should not save the patient if validation fails', async () => {
253
- const user = userEvent.setup();
275
+ await fillRequiredFields();
276
+ await user.click(await screen.findByText('Register Patient'));
277
+ expect(mockedSavePatient).toHaveBeenCalledWith(
278
+ expect.objectContaining({
279
+ identifiers: [], //TODO when the identifer story is finished: { identifier: '', identifierType: '05a29f94-c0ed-11e2-94be-8c13b969e334', location: '' },
280
+ person: {
281
+ addresses: expect.arrayContaining([expect.any(Object)]),
282
+ attributes: [],
283
+ birthdate: '1993-8-2',
284
+ birthdateEstimated: false,
285
+ gender: expect.stringMatching(/^M$/),
286
+ names: [{ givenName: 'Paul', middleName: '', familyName: 'Gaihre', preferred: true, uuid: undefined }],
287
+ dead: false,
288
+ uuid: expect.anything(),
289
+ },
290
+ uuid: expect.anything(),
291
+ }),
292
+ undefined,
293
+ );
294
+ });
254
295
 
255
- const mockedSavePatientForm = jest.fn();
256
- render(
257
- <ResourcesContext.Provider value={mockResourcesContextValue}>
258
- <Router>
259
- <PatientRegistration isOffline={false} savePatientForm={mockedSavePatientForm} />
260
- </Router>
261
- </ResourcesContext.Provider>,
262
- );
296
+ it('should not save the patient if validation fails', async () => {
297
+ const user = userEvent.setup();
263
298
 
264
- const givenNameInput = (await screen.findByLabelText('First Name')) as HTMLInputElement;
299
+ const mockedSavePatientForm = jest.fn();
300
+ render(<PatientRegistration isOffline={false} savePatientForm={mockedSavePatientForm} />, { wrapper: Wrapper });
265
301
 
266
- await user.type(givenNameInput, '5');
267
- await user.click(screen.getByText('Register Patient'));
302
+ const givenNameInput = (await screen.findByLabelText('First Name')) as HTMLInputElement;
268
303
 
269
- expect(mockedSavePatientForm).not.toHaveBeenCalled();
270
- });
304
+ await user.type(givenNameInput, '5');
305
+ await user.click(screen.getByText('Register Patient'));
271
306
 
272
- it('renders and saves registration obs', async () => {
273
- const user = userEvent.setup();
307
+ expect(mockedSavePatientForm).not.toHaveBeenCalled();
308
+ });
274
309
 
275
- mockedSaveEncounter.mockResolvedValue({});
276
- mockedUseConfig.mockReturnValue(configWithObs);
277
-
278
- render(
279
- <ResourcesContext.Provider value={mockResourcesContextValue}>
280
- <Router>
281
- <PatientRegistration isOffline={false} savePatientForm={FormManager.savePatientFormOnline} />
282
- </Router>
283
- </ResourcesContext.Provider>,
284
- );
285
-
286
- await fillRequiredFields();
287
- const customSection = screen.getByLabelText('Custom Section');
288
- const weight = within(customSection).getByLabelText('Weight (kg) (optional)');
289
- await user.type(weight, '50');
290
- const complaint = within(customSection).getByLabelText('Chief Complaint (optional)');
291
- await user.type(complaint, 'sad');
292
- const nationality = within(customSection).getByLabelText('Nationality');
293
- await user.selectOptions(nationality, 'USA');
294
-
295
- await user.click(screen.getByText('Register Patient'));
296
-
297
- await waitFor(() => expect(mockedSavePatient).toHaveBeenCalled());
298
- await waitFor(() =>
299
- expect(mockedSaveEncounter).toHaveBeenCalledWith(
300
- expect.objectContaining<Partial<Encounter>>({
301
- encounterType: 'reg-enc-uuid',
302
- patient: 'new-pt-uuid',
303
- obs: [
304
- { concept: 'weight-uuid', value: 50 },
305
- { concept: 'chief-complaint-uuid', value: 'sad' },
306
- { concept: 'nationality-uuid', value: 'usa' },
307
- ],
308
- }),
309
- ),
310
- );
310
+ it('renders and saves registration obs', async () => {
311
+ const user = userEvent.setup();
312
+
313
+ mockedSaveEncounter.mockResolvedValue({});
314
+ mockedUseConfig.mockReturnValue(configWithObs);
315
+
316
+ render(<PatientRegistration isOffline={false} savePatientForm={FormManager.savePatientFormOnline} />, {
317
+ wrapper: Wrapper,
311
318
  });
312
319
 
313
- it('retries saving registration obs after a failed attempt', async () => {
314
- const user = userEvent.setup();
320
+ await fillRequiredFields();
321
+ const customSection = screen.getByLabelText('Custom Section');
322
+ const weight = within(customSection).getByLabelText('Weight (kg) (optional)');
323
+ await user.type(weight, '50');
324
+ const complaint = within(customSection).getByLabelText('Chief Complaint (optional)');
325
+ await user.type(complaint, 'sad');
326
+ const nationality = within(customSection).getByLabelText('Nationality');
327
+ await user.selectOptions(nationality, 'USA');
328
+
329
+ await user.click(screen.getByText('Register Patient'));
330
+
331
+ expect(mockedSavePatient).toHaveBeenCalled();
332
+
333
+ expect(mockedSaveEncounter).toHaveBeenCalledWith(
334
+ expect.objectContaining<Partial<Encounter>>({
335
+ encounterType: 'reg-enc-uuid',
336
+ patient: 'new-pt-uuid',
337
+ obs: [
338
+ { concept: 'weight-uuid', value: 50 },
339
+ { concept: 'chief-complaint-uuid', value: 'sad' },
340
+ { concept: 'nationality-uuid', value: 'usa' },
341
+ ],
342
+ }),
343
+ );
344
+ });
345
+
346
+ it('retries saving registration obs after a failed attempt', async () => {
347
+ const user = userEvent.setup();
315
348
 
316
- mockedUseConfig.mockReturnValue(configWithObs);
349
+ mockedUseConfig.mockReturnValue(configWithObs);
350
+
351
+ render(<PatientRegistration isOffline={false} savePatientForm={FormManager.savePatientFormOnline} />, {
352
+ wrapper: Wrapper,
353
+ });
317
354
 
318
- render(
319
- <ResourcesContext.Provider value={mockResourcesContextValue}>
320
- <Router>
321
- <PatientRegistration isOffline={false} savePatientForm={FormManager.savePatientFormOnline} />
322
- </Router>
323
- </ResourcesContext.Provider>,
324
- );
355
+ await fillRequiredFields();
356
+ const customSection = screen.getByLabelText('Custom Section');
357
+ const weight = within(customSection).getByLabelText('Weight (kg) (optional)');
358
+ await user.type(weight, '-999');
325
359
 
326
- await fillRequiredFields();
327
- const customSection = screen.getByLabelText('Custom Section');
328
- const weight = within(customSection).getByLabelText('Weight (kg) (optional)');
329
- await user.type(weight, '-999');
360
+ mockedSaveEncounter.mockRejectedValue({ status: 400, responseBody: { error: { message: 'an error message' } } });
330
361
 
331
- mockedSaveEncounter.mockRejectedValue({ status: 400, responseBody: { error: { message: 'an error message' } } });
362
+ const registerPatientButton = screen.getByText('Register Patient');
332
363
 
333
- await user.click(screen.getByText('Register Patient'));
364
+ await user.click(registerPatientButton);
334
365
 
335
- await waitFor(() => expect(mockedSavePatient).toHaveBeenCalledTimes(1));
336
- await waitFor(() => expect(mockedSaveEncounter).toHaveBeenCalledTimes(1));
337
- await waitFor(() =>
338
- expect(mockedShowSnackbar).toHaveBeenCalledWith(expect.objectContaining({ subtitle: 'an error message' })),
339
- );
366
+ expect(mockedSavePatient).toHaveBeenCalledTimes(1);
367
+ expect(mockedSaveEncounter).toHaveBeenCalledTimes(1);
340
368
 
369
+ expect(mockedShowSnackbar).toHaveBeenCalledWith(expect.objectContaining({ subtitle: 'an error message' })),
341
370
  mockedSaveEncounter.mockResolvedValue({});
342
371
 
343
- await waitFor(() => user.click(screen.getByText('Register Patient')));
344
- await waitFor(() => expect(mockedSavePatient).toHaveBeenCalledTimes(2));
345
- await waitFor(() => expect(mockedSaveEncounter).toHaveBeenCalledTimes(2));
346
- await waitFor(() =>
347
- expect(mockedShowSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'success' })),
348
- );
349
- });
372
+ await user.click(registerPatientButton);
373
+ expect(mockedSavePatient).toHaveBeenCalledTimes(2);
374
+ expect(mockedSaveEncounter).toHaveBeenCalledTimes(2);
375
+
376
+ expect(mockedShowSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'success' }));
350
377
  });
378
+ });
351
379
 
352
- describe('when updating an existing patient details', () => {
353
- beforeEach(() => {
354
- mockedUseConfig.mockReturnValue(mockOpenmrsConfig);
355
- mockedSavePatient.mockReturnValue({ data: { uuid: 'new-pt-uuid' }, ok: true });
356
- mockedSaveEncounter.mockClear();
357
- mockedShowSnackbar.mockClear();
358
- jest.clearAllMocks();
359
- });
380
+ describe('Updating an existing patient record', () => {
381
+ beforeEach(() => {
382
+ mockedUseConfig.mockReturnValue(mockOpenmrsConfig);
383
+ mockedSavePatient.mockReturnValue({ data: { uuid: 'new-pt-uuid' }, ok: true });
384
+ mockedSaveEncounter.mockClear();
385
+ mockedShowSnackbar.mockClear();
386
+ jest.clearAllMocks();
387
+ });
360
388
 
361
- it('edits patient demographics', async () => {
362
- const user = userEvent.setup();
389
+ it('edits patient demographics', async () => {
390
+ const user = userEvent.setup();
363
391
 
364
- mockedSavePatient.mockResolvedValue({});
392
+ mockedSavePatient.mockResolvedValue({});
365
393
 
366
- const mockedUseParams = useParams as jest.Mock;
394
+ const mockedUseParams = useParams as jest.Mock;
367
395
 
368
- mockedUseParams.mockReturnValue({ patientUuid: mockPatient.id });
396
+ mockedUseParams.mockReturnValue({ patientUuid: mockPatient.id });
369
397
 
370
- mockedUsePatient.mockReturnValue({
371
- isLoading: false,
372
- patient: mockPatient,
373
- patientUuid: mockPatient.id,
374
- error: null,
375
- });
376
-
377
- render(
378
- <ResourcesContext.Provider value={mockResourcesContextValue}>
379
- <Router>
380
- <PatientRegistration isOffline={false} savePatientForm={mockedSavePatient} />
381
- </Router>
382
- </ResourcesContext.Provider>,
383
- );
384
-
385
- const givenNameInput: HTMLInputElement = screen.getByLabelText(/First Name/);
386
- const familyNameInput: HTMLInputElement = screen.getByLabelText(/Family Name/);
387
- const middleNameInput: HTMLInputElement = screen.getByLabelText(/Middle Name/);
388
- const dateOfBirthInput: HTMLInputElement = screen.getByLabelText('Date of Birth');
389
- const genderInput: HTMLInputElement = screen.getByLabelText(/Male/);
390
-
391
- // assert initial values
392
- expect(givenNameInput.value).toBe('John');
393
- expect(familyNameInput.value).toBe('Wilson');
394
- expect(middleNameInput.value).toBeFalsy();
395
- expect(dateOfBirthInput.value).toBe('4/4/1972');
396
- expect(genderInput.value).toBe('Male');
397
-
398
- // do some edits
399
- await user.clear(givenNameInput);
400
- await user.clear(middleNameInput);
401
- await user.clear(familyNameInput);
402
- await user.type(givenNameInput, 'Eric');
403
- await user.type(middleNameInput, 'Johnson');
404
- await user.type(familyNameInput, 'Smith');
405
- await user.click(screen.getByText('Update Patient'));
406
-
407
- await waitFor(() =>
408
- expect(mockedSavePatient).toHaveBeenCalledWith(
409
- false,
410
- {
411
- '0': {
412
- oldIdentificationNumber: '100732HE',
413
- },
414
- '1': {
415
- openMrsId: '100GEJ',
416
- },
417
- addNameInLocalLanguage: undefined,
418
- additionalFamilyName: '',
419
- additionalGivenName: '',
420
- additionalMiddleName: '',
421
- address: {},
422
- birthdate: new Date('1972-04-04T00:00:00.000Z'),
423
- birthdateEstimated: false,
424
- deathCause: '',
425
- deathDate: '',
426
- familyName: 'Smith',
427
- gender: 'Male',
428
- givenName: 'Eric',
429
- identifiers: {},
430
- isDead: false,
431
- middleName: 'Johnson',
432
- monthsEstimated: 0,
433
- patientUuid: '8673ee4f-e2ab-4077-ba55-4980f408773e',
434
- relationships: [],
435
- telephoneNumber: '',
436
- unidentifiedPatient: undefined,
437
- yearsEstimated: 0,
438
- },
439
- expect.anything(),
440
- expect.anything(),
441
- null,
442
- undefined,
443
- expect.anything(),
444
- expect.anything(),
445
- expect.anything(),
446
- { patientSaved: false },
447
- expect.anything(),
448
- ),
449
- );
398
+ mockedUsePatient.mockReturnValue({
399
+ isLoading: false,
400
+ patient: mockPatient,
401
+ patientUuid: mockPatient.id,
402
+ error: null,
450
403
  });
404
+
405
+ render(<PatientRegistration isOffline={false} savePatientForm={mockedSavePatient} />, { wrapper: Wrapper });
406
+
407
+ const givenNameInput: HTMLInputElement = screen.getByLabelText(/First Name/);
408
+ const familyNameInput: HTMLInputElement = screen.getByLabelText(/Family Name/);
409
+ const middleNameInput: HTMLInputElement = screen.getByLabelText(/Middle Name/);
410
+ const dateOfBirthInput: HTMLInputElement = screen.getByLabelText('Date of Birth');
411
+ const genderInput: HTMLInputElement = screen.getByLabelText(/Male/);
412
+
413
+ // assert initial values
414
+ expect(givenNameInput.value).toBe('John');
415
+ expect(familyNameInput.value).toBe('Wilson');
416
+ expect(middleNameInput.value).toBeFalsy();
417
+ expect(dateOfBirthInput.value).toBe('4/4/1972');
418
+ expect(genderInput.value).toBe('male');
419
+
420
+ // do some edits
421
+ await user.clear(givenNameInput);
422
+ await user.clear(middleNameInput);
423
+ await user.clear(familyNameInput);
424
+ await user.type(givenNameInput, 'Eric');
425
+ await user.type(middleNameInput, 'Johnson');
426
+ await user.type(familyNameInput, 'Smith');
427
+ await user.click(screen.getByText('Update Patient'));
428
+
429
+ expect(mockedSavePatient).toHaveBeenCalledWith(
430
+ false,
431
+ {
432
+ '0': {
433
+ oldIdentificationNumber: '100732HE',
434
+ },
435
+ '1': {
436
+ openMrsId: '100GEJ',
437
+ },
438
+ addNameInLocalLanguage: undefined,
439
+ additionalFamilyName: '',
440
+ additionalGivenName: '',
441
+ additionalMiddleName: '',
442
+ address: {},
443
+ birthdate: new Date('1972-04-04T00:00:00.000Z'),
444
+ birthdateEstimated: false,
445
+ deathCause: '',
446
+ deathDate: '',
447
+ familyName: 'Smith',
448
+ gender: expect.stringMatching(/male/i),
449
+ givenName: 'Eric',
450
+ identifiers: {},
451
+ isDead: false,
452
+ middleName: 'Johnson',
453
+ monthsEstimated: 0,
454
+ patientUuid: '8673ee4f-e2ab-4077-ba55-4980f408773e',
455
+ relationships: [],
456
+ telephoneNumber: '',
457
+ unidentifiedPatient: undefined,
458
+ yearsEstimated: 0,
459
+ },
460
+ expect.anything(),
461
+ expect.anything(),
462
+ null,
463
+ undefined,
464
+ expect.anything(),
465
+ expect.anything(),
466
+ expect.anything(),
467
+ { patientSaved: false },
468
+ expect.anything(),
469
+ );
451
470
  });
452
471
  });
@@ -1,6 +1,6 @@
1
- import { OpenmrsResource, Session } from '@openmrs/esm-framework';
2
- import { RegistrationConfig } from '../config-schema';
3
- import { SavePatientTransactionManager } from './form-manager';
1
+ import { type OpenmrsResource, type Session } from '@openmrs/esm-framework';
2
+ import { type RegistrationConfig } from '../config-schema';
3
+ import { type SavePatientTransactionManager } from './form-manager';
4
4
 
5
5
  interface NameValue {
6
6
  uuid: string;
@@ -310,6 +310,15 @@ export interface AddressTemplate {
310
310
  elementRegexFormats: ExtensibleAddressProperties;
311
311
  requiredElements: Array<AddressProperties> | null;
312
312
  }
313
+
314
+ // https://rest.openmrs.org/#address-template
315
+ export interface RestAddressTemplate {
316
+ uuid: string;
317
+ description: string;
318
+ property: string;
319
+ display: string;
320
+ value: string;
321
+ }
313
322
  export interface ObsResponse {
314
323
  results: Array<{ obs: Array<{ uuid: string; display: string; value: OpenmrsResource; concept: OpenmrsResource }> }>;
315
324
  }