@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,28 +1,31 @@
1
1
  import React from 'react';
2
+ import { Form, Formik } from 'formik';
2
3
  import { render, screen } from '@testing-library/react';
3
- import { Field } from './field.component';
4
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';
5
8
  import { PatientRegistrationContext } from '../patient-registration-context';
6
- import { Resources, ResourcesContext } from '../../offline.resources';
7
- import { Form, Formik } from 'formik';
8
9
 
9
10
  jest.mock('@openmrs/esm-framework', () => ({
10
11
  ...jest.requireActual('@openmrs/esm-framework'),
11
12
  useConfig: jest.fn(),
12
13
  }));
14
+
13
15
  const predefinedAddressTemplate = {
14
- results: [
15
- {
16
- value:
17
- '<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>',
18
- },
19
- ],
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>',
20
23
  };
21
24
 
22
25
  const mockedIdentifierTypes = [
23
26
  {
24
27
  fieldName: 'openMrsId',
25
- format: null,
28
+ format: '',
26
29
  identifierSources: [
27
30
  {
28
31
  uuid: '8549f706-7e85-4c1d-9424-217d50a2988b',
@@ -35,22 +38,22 @@ const mockedIdentifierTypes = [
35
38
  isPrimary: true,
36
39
  name: 'OpenMRS ID',
37
40
  required: true,
38
- uniquenessBehavior: 'UNIQUE',
41
+ uniquenessBehavior: 'UNIQUE' as const,
39
42
  uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
40
43
  },
41
44
  {
42
45
  fieldName: 'idCard',
43
- format: null,
46
+ format: '',
44
47
  identifierSources: [],
45
48
  isPrimary: false,
46
49
  name: 'ID Card',
47
50
  required: false,
48
- uniquenessBehavior: 'UNIQUE',
51
+ uniquenessBehavior: 'UNIQUE' as const,
49
52
  uuid: 'b4143563-16cd-4439-b288-f83d61670fc8',
50
53
  },
51
54
  {
52
55
  fieldName: 'legacyId',
53
- format: null,
56
+ format: '',
54
57
  identifierSources: [],
55
58
  isPrimary: false,
56
59
  name: 'Legacy ID',
@@ -79,8 +82,9 @@ const mockedIdentifierTypes = [
79
82
  uuid: '8d793bee-c2cc-11de-8d13-0010c6dffd0f',
80
83
  },
81
84
  ];
82
- const mockResourcesContextValue = {
83
- addressTemplate: predefinedAddressTemplate,
85
+
86
+ const mockResourcesContextValue: Resources = {
87
+ addressTemplate: predefinedAddressTemplate as unknown as AddressTemplate,
84
88
  currentSession: {
85
89
  authenticated: true,
86
90
  sessionId: 'JSESSION',
@@ -88,10 +92,39 @@ const mockResourcesContextValue = {
88
92
  },
89
93
  relationshipTypes: [],
90
94
  identifierTypes: [...mockedIdentifierTypes],
91
- } as Resources;
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
+ };
92
109
 
93
110
  describe('Field', () => {
111
+ let ContextWrapper;
112
+
94
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(() => {
95
128
  jest.clearAllMocks();
96
129
  });
97
130
 
@@ -106,22 +139,9 @@ describe('Field', () => {
106
139
  },
107
140
  },
108
141
  }));
109
- render(
110
- <ResourcesContext.Provider value={mockResourcesContextValue}>
111
- <Formik initialValues={{}} onSubmit={null}>
112
- <Form>
113
- <PatientRegistrationContext.Provider
114
- value={{
115
- setFieldValue: jest.fn(),
116
- setInitialFormValues: jest.fn(),
117
- values: {},
118
- }}>
119
- <Field name="name" />
120
- </PatientRegistrationContext.Provider>
121
- </Form>
122
- </Formik>
123
- </ResourcesContext.Provider>,
124
- );
142
+
143
+ render(<Field name="name" />, { wrapper: ContextWrapper });
144
+
125
145
  expect(screen.getByText('Full Name')).toBeInTheDocument();
126
146
  });
127
147
 
@@ -137,22 +157,9 @@ describe('Field', () => {
137
157
  ],
138
158
  },
139
159
  }));
140
- render(
141
- <ResourcesContext.Provider value={mockResourcesContextValue}>
142
- <Formik initialValues={{}} onSubmit={null}>
143
- <Form>
144
- <PatientRegistrationContext.Provider
145
- value={{
146
- setFieldValue: jest.fn(),
147
- setInitialFormValues: jest.fn(),
148
- values: {},
149
- }}>
150
- <Field name="gender" />
151
- </PatientRegistrationContext.Provider>
152
- </Form>
153
- </Formik>
154
- </ResourcesContext.Provider>,
155
- );
160
+
161
+ render(<Field name="gender" />, { wrapper: ContextWrapper });
162
+
156
163
  expect(screen.getByLabelText('Male')).toBeInTheDocument();
157
164
  });
158
165
 
@@ -165,22 +172,7 @@ describe('Field', () => {
165
172
  },
166
173
  },
167
174
  }));
168
- render(
169
- <ResourcesContext.Provider value={mockResourcesContextValue}>
170
- <Formik initialValues={{}} onSubmit={null}>
171
- <Form>
172
- <PatientRegistrationContext.Provider
173
- value={{
174
- setFieldValue: jest.fn(),
175
- setInitialFormValues: jest.fn(),
176
- values: {},
177
- }}>
178
- <Field name="dob" />
179
- </PatientRegistrationContext.Provider>
180
- </Form>
181
- </Formik>
182
- </ResourcesContext.Provider>,
183
- );
175
+ render(<Field name="dob" />, { wrapper: ContextWrapper });
184
176
  expect(screen.getByText('Birth')).toBeInTheDocument();
185
177
  });
186
178
 
@@ -200,22 +192,9 @@ describe('Field', () => {
200
192
  },
201
193
  },
202
194
  }));
203
- render(
204
- <ResourcesContext.Provider value={mockResourcesContextValue}>
205
- <Formik initialValues={{}} onSubmit={null}>
206
- <Form>
207
- <PatientRegistrationContext.Provider
208
- value={{
209
- setFieldValue: jest.fn(),
210
- setInitialFormValues: jest.fn(),
211
- values: {},
212
- }}>
213
- <Field name="address" />
214
- </PatientRegistrationContext.Provider>
215
- </Form>
216
- </Formik>
217
- </ResourcesContext.Provider>,
218
- );
195
+
196
+ render(<Field name="address" />, { wrapper: ContextWrapper });
197
+
219
198
  expect(screen.getByText('Address')).toBeInTheDocument();
220
199
  });
221
200
 
@@ -223,7 +202,7 @@ describe('Field', () => {
223
202
  (useConfig as jest.Mock).mockImplementation(() => ({
224
203
  defaultPatientIdentifierTypes: ['OpenMRS ID'],
225
204
  }));
226
- // initial value for the identifiers field
205
+
227
206
  const openmrsID = {
228
207
  name: 'OpenMRS ID',
229
208
  fieldName: 'openMrsId',
@@ -249,21 +228,41 @@ describe('Field', () => {
249
228
  },
250
229
  },
251
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
+ },
252
245
  autoGenerationSource: null,
253
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
+
254
261
  render(
255
262
  <ResourcesContext.Provider value={mockResourcesContextValue}>
256
- <Formik initialValues={{}} onSubmit={null}>
263
+ <Formik initialValues={{}} onSubmit={jest.fn()}>
257
264
  <Form>
258
- <PatientRegistrationContext.Provider
259
- value={{
260
- setFieldValue: jest.fn(),
261
- initialFormValues: { identifiers: { openmrsID } },
262
- setInitialFormValues: jest.fn(),
263
- values: {
264
- identifiers: { openmrsID },
265
- },
266
- }}>
265
+ <PatientRegistrationContext.Provider value={updatedContextValues}>
267
266
  <Field name="id" />
268
267
  </PatientRegistrationContext.Provider>
269
268
  </Form>
@@ -274,18 +273,22 @@ describe('Field', () => {
274
273
  });
275
274
 
276
275
  it('should return null and report an error for an invalid field name', () => {
276
+ const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
277
+
277
278
  (useConfig as jest.Mock).mockImplementation(() => ({
278
279
  fieldDefinitions: [{ id: 'weight' }],
279
280
  }));
280
281
  let error = null;
282
+
281
283
  try {
282
284
  render(<Field name="invalidField" />);
283
285
  } catch (err) {
284
286
  error = err;
285
287
  }
286
- expect(error).toBe(
287
- "Invalid field name 'invalidField'. Valid options are 'weight', 'name', 'gender', 'dob', 'address', 'id', 'phone & email'.",
288
- );
288
+
289
+ expect(error).toMatch(/Invalid field name 'invalidField'. Valid options are /);
289
290
  expect(screen.queryByTestId('invalid-field')).not.toBeInTheDocument();
291
+
292
+ consoleError.mockRestore();
290
293
  });
291
294
  });
@@ -4,7 +4,7 @@ import styles from '../field.scss';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { PatientRegistrationContext } from '../../patient-registration-context';
6
6
  import { useField } from 'formik';
7
- import { RegistrationConfig } from '../../../config-schema';
7
+ import { type RegistrationConfig } from '../../../config-schema';
8
8
  import { useConfig } from '@openmrs/esm-framework';
9
9
 
10
10
  export const GenderField: React.FC = () => {
@@ -33,10 +33,10 @@ export const GenderField: React.FC = () => {
33
33
  <RadioButtonGroup name="gender" orientation="vertical" onChange={setGender} valueSelected={field.value}>
34
34
  {fieldConfigs.map((option) => (
35
35
  <RadioButton
36
- key={option.label}
37
- id={option.id}
36
+ key={option.label ?? option.value}
37
+ id={`gender-option-${option.value}`}
38
38
  value={option.value}
39
- labelText={t(`${option.label}`, `${option.label}`)}
39
+ labelText={t(option.label ?? option.value, option.label ?? option.value)}
40
40
  />
41
41
  ))}
42
42
  </RadioButtonGroup>
@@ -1,9 +1,7 @@
1
- import React, { useContext } from 'react';
2
- import { render, fireEvent } from '@testing-library/react';
3
- import '@testing-library/jest-dom/extend-expect';
4
- import '@testing-library/jest-dom';
1
+ import React from 'react';
2
+ import userEvent from '@testing-library/user-event';
5
3
  import { Formik, Form } from 'formik';
6
-
4
+ import { render } from '@testing-library/react';
7
5
  import { GenderField } from './gender-field.component';
8
6
 
9
7
  jest.mock('@openmrs/esm-framework', () => ({
@@ -12,9 +10,8 @@ jest.mock('@openmrs/esm-framework', () => ({
12
10
  fieldConfigurations: {
13
11
  gender: [
14
12
  {
15
- value: 'Male',
13
+ value: 'male',
16
14
  label: 'Male',
17
- id: 'male',
18
15
  },
19
16
  ],
20
17
  name: {
@@ -50,17 +47,13 @@ describe('GenderField', () => {
50
47
  );
51
48
  };
52
49
 
53
- it('renders', () => {
54
- expect(renderComponent()).not.toBeNull();
55
- });
56
-
57
50
  it('has a label', () => {
58
51
  expect(renderComponent().getAllByText('Sex')).toBeTruthy();
59
52
  });
60
53
 
61
- it('checks an option', () => {
54
+ it('checks an option', async () => {
55
+ const user = userEvent.setup();
62
56
  const component = renderComponent();
63
- fireEvent.click(component.getByLabelText('Male'));
64
- expect(component.getByLabelText('Male')).toBeChecked();
57
+ expect(component.getByLabelText('Male').getAttribute('value')).toBe('male');
65
58
  });
66
59
  });
@@ -7,10 +7,10 @@ import IdentifierSelectionOverlay from './identifier-selection-overlay.component
7
7
  import { IdentifierInput } from '../../input/custom-input/identifier/identifier-input.component';
8
8
  import { PatientRegistrationContext } from '../../patient-registration-context';
9
9
  import {
10
- FormValues,
11
- IdentifierSource,
12
- PatientIdentifierType,
13
- PatientIdentifierValue,
10
+ type FormValues,
11
+ type IdentifierSource,
12
+ type PatientIdentifierType,
13
+ type PatientIdentifierValue,
14
14
  } from '../../patient-registration.types';
15
15
  import { ResourcesContext } from '../../../offline.resources';
16
16
  import styles from '../field.scss';
@@ -31,8 +31,8 @@ export function setIdentifierSource(
31
31
  identifierValue: autoGeneration
32
32
  ? 'auto-generated'
33
33
  : identifierValue !== 'auto-generated'
34
- ? identifierValue
35
- : initialValue,
34
+ ? identifierValue
35
+ : initialValue,
36
36
  };
37
37
  }
38
38
 
@@ -1,11 +1,11 @@
1
1
  import React from 'react';
2
- import { render, screen, fireEvent } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { render, screen } from '@testing-library/react';
3
4
  import { Identifiers } from './id-field.component';
4
- import { Resources, ResourcesContext } from '../../../offline.resources';
5
+ import { type Resources, ResourcesContext } from '../../../offline.resources';
5
6
  import { Form, Formik } from 'formik';
6
7
  import { PatientRegistrationContext } from '../../patient-registration-context';
7
- import { openmrsID } from '../__mocks__/identifiers.mock';
8
- import { mockedIdentifierTypes } from '../__mocks__/identifier-types.mock';
8
+ import { openmrsID, mockedIdentifierTypes } from '__mocks__';
9
9
 
10
10
  jest.mock('@openmrs/esm-framework', () => ({
11
11
  ...jest.requireActual('@openmrs/esm-framework'),
@@ -16,7 +16,7 @@ jest.mock('@openmrs/esm-framework', () => ({
16
16
 
17
17
  describe('Identifiers', () => {
18
18
  const mockResourcesContextValue = {
19
- addressTemplate: [],
19
+ addressTemplate: {},
20
20
  currentSession: {
21
21
  authenticated: true,
22
22
  sessionId: 'JSESSION',
@@ -76,7 +76,9 @@ describe('Identifiers', () => {
76
76
  expect(configureButton).toBeEnabled();
77
77
  });
78
78
 
79
- it('should open identifier selection overlay when "Configure" button is clicked', () => {
79
+ it('should open identifier selection overlay when "Configure" button is clicked', async () => {
80
+ const user = userEvent.setup();
81
+
80
82
  render(
81
83
  <ResourcesContext.Provider value={mockResourcesContextValue}>
82
84
  <Formik initialValues={{}} onSubmit={null}>
@@ -98,7 +100,7 @@ describe('Identifiers', () => {
98
100
  );
99
101
 
100
102
  const configureButton = screen.getByRole('button', { name: 'Configure' });
101
- fireEvent.click(configureButton);
103
+ await user.click(configureButton);
102
104
 
103
105
  expect(screen.getByRole('button', { name: 'Close overlay' })).toBeInTheDocument();
104
106
  });
@@ -2,7 +2,7 @@ import React, { useMemo, useCallback, useEffect, useState, useContext } from 're
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { Button, ButtonSet, Checkbox, Search, RadioButtonGroup, RadioButton } from '@carbon/react';
4
4
  import { isDesktop, useConfig, useLayoutType } from '@openmrs/esm-framework';
5
- import { FormValues, PatientIdentifierType, PatientIdentifierValue } from '../../patient-registration.types';
5
+ import { type FormValues, type PatientIdentifierType, PatientIdentifierValue } from '../../patient-registration.types';
6
6
  import Overlay from '../../ui-components/overlay/overlay.component';
7
7
  import { ResourcesContext } from '../../../offline.resources';
8
8
  import { PatientRegistrationContext } from '../../patient-registration-context';
@@ -6,7 +6,7 @@ import { ExtensionSlot, useConfig } from '@openmrs/esm-framework';
6
6
  import { Input } from '../../input/basic-input/input/input.component';
7
7
  import { PatientRegistrationContext } from '../../patient-registration-context';
8
8
  import styles from '../field.scss';
9
- import { RegistrationConfig } from '../../../config-schema';
9
+ import { type RegistrationConfig } from '../../../config-schema';
10
10
 
11
11
  export const unidentifiedPatientAttributeTypeUuid = '8b56eac7-5c76-4b9c-8c6f-1deab8d3fc47';
12
12
  const containsNoNumbers = /^([^0-9]*)$/;
@@ -1,11 +1,11 @@
1
- import React from 'react';
1
+ import React, { useMemo } from 'react';
2
2
  import classNames from 'classnames';
3
3
  import { Field } from 'formik';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { InlineNotification, Layer, Select, SelectItem } from '@carbon/react';
6
6
  import { useConfig } from '@openmrs/esm-framework';
7
- import { ConceptResponse } from '../../patient-registration.types';
8
- import { FieldDefinition, RegistrationConfig } from '../../../config-schema';
7
+ import { type ConceptResponse } from '../../patient-registration.types';
8
+ import { type FieldDefinition, type RegistrationConfig } from '../../../config-schema';
9
9
  import { Input } from '../../input/basic-input/input/input.component';
10
10
  import { useConcept, useConceptAnswers } from '../field.resource';
11
11
  import styles from './../field.scss';
@@ -15,8 +15,10 @@ export interface ObsFieldProps {
15
15
  }
16
16
 
17
17
  export function ObsField({ fieldDefinition }: ObsFieldProps) {
18
+ const { t } = useTranslation();
18
19
  const { data: concept, isLoading } = useConcept(fieldDefinition.uuid);
19
- const config = useConfig() as RegistrationConfig;
20
+
21
+ const config = useConfig<RegistrationConfig>();
20
22
 
21
23
  if (!config.registrationObs.encounterTypeUuid) {
22
24
  console.error(
@@ -30,6 +32,7 @@ export function ObsField({ fieldDefinition }: ObsFieldProps) {
30
32
  if (isLoading) {
31
33
  return null;
32
34
  }
35
+
33
36
  switch (concept.datatype.display) {
34
37
  case 'Text':
35
38
  return (
@@ -55,12 +58,17 @@ export function ObsField({ fieldDefinition }: ObsFieldProps) {
55
58
  answerConceptSetUuid={fieldDefinition.answerConceptSetUuid}
56
59
  label={fieldDefinition.label}
57
60
  required={fieldDefinition.validation.required}
61
+ customConceptAnswers={fieldDefinition.customConceptAnswers}
58
62
  />
59
63
  );
60
64
  default:
61
65
  return (
62
66
  <InlineNotification kind="error" title="Error">
63
- Concept has unknown datatype "{concept.datatype.display}"
67
+ {t(
68
+ 'obsFieldUnknownDatatype',
69
+ `Concept for obs field '{{fieldDefinitionId}}' has unknown datatype '{{datatypeName}}'`,
70
+ { fieldDefinitionId: fieldDefinition.id, datatypeName: concept.datatype.display },
71
+ )}
64
72
  </InlineNotification>
65
73
  );
66
74
  }
@@ -115,8 +123,6 @@ interface NumericObsFieldProps {
115
123
  }
116
124
 
117
125
  function NumericObsField({ concept, label, required }: NumericObsFieldProps) {
118
- const { t } = useTranslation();
119
-
120
126
  const fieldName = `obs.${concept.uuid}`;
121
127
 
122
128
  return (
@@ -144,40 +150,32 @@ interface CodedObsFieldProps {
144
150
  answerConceptSetUuid?: string;
145
151
  label?: string;
146
152
  required?: boolean;
153
+ customConceptAnswers: Array<{ uuid: string; label?: string }>;
147
154
  }
148
155
 
149
- function CodedObsField({ concept, answerConceptSetUuid, label, required }: CodedObsFieldProps) {
150
- const config = useConfig() as RegistrationConfig;
156
+ function CodedObsField({ concept, answerConceptSetUuid, label, required, customConceptAnswers }: CodedObsFieldProps) {
157
+ const { t } = useTranslation();
158
+ const fieldName = `obs.${concept.uuid}`;
159
+
151
160
  const { data: conceptAnswers, isLoading: isLoadingConceptAnswers } = useConceptAnswers(
152
- answerConceptSetUuid ?? concept.uuid,
161
+ customConceptAnswers.length ? '' : answerConceptSetUuid ?? concept.uuid,
153
162
  );
154
163
 
155
- const fieldName = `obs.${concept.uuid}`;
156
- const fieldDefinition = config?.fieldDefinitions?.filter((def) => def.type === 'obs' && def.uuid === concept.uuid)[0];
164
+ const answers = useMemo(
165
+ () =>
166
+ customConceptAnswers.length
167
+ ? customConceptAnswers
168
+ : isLoadingConceptAnswers
169
+ ? []
170
+ : conceptAnswers.map((answer) => ({ ...answer, label: answer.display })),
171
+ [customConceptAnswers, conceptAnswers, isLoadingConceptAnswers],
172
+ );
157
173
 
158
174
  return (
159
175
  <div className={classNames(styles.customField, styles.halfWidthInDesktopView)}>
160
176
  {!isLoadingConceptAnswers ? (
161
177
  <Field name={fieldName}>
162
178
  {({ field, form: { touched, errors }, meta }) => {
163
- if (fieldDefinition?.customConceptAnswers?.length) {
164
- return (
165
- <Layer>
166
- <Select
167
- id={fieldName}
168
- name={fieldName}
169
- required={required}
170
- labelText={label ?? concept?.display}
171
- invalid={errors[fieldName] && touched[fieldName]}
172
- {...field}>
173
- <SelectItem key={`no-answer-select-item-${fieldName}`} value={''} text="" />
174
- {fieldDefinition?.customConceptAnswers.map((answer) => (
175
- <SelectItem key={answer.uuid} value={answer.uuid} text={answer.label} />
176
- ))}
177
- </Select>
178
- </Layer>
179
- );
180
- }
181
179
  return (
182
180
  <Layer>
183
181
  <Select
@@ -187,9 +185,13 @@ function CodedObsField({ concept, answerConceptSetUuid, label, required }: Coded
187
185
  required={required}
188
186
  invalid={errors[fieldName] && touched[fieldName]}
189
187
  {...field}>
190
- <SelectItem key={`no-answer-select-item-${fieldName}`} value={''} text="" />
191
- {conceptAnswers.map((answer) => (
192
- <SelectItem key={answer.uuid} value={answer.uuid} text={answer.display} />
188
+ <SelectItem
189
+ key={`no-answer-select-item-${fieldName}`}
190
+ value={''}
191
+ text={t('selectAnOption', 'Select an option')}
192
+ />
193
+ {answers.map((answer) => (
194
+ <SelectItem key={answer.uuid} value={answer.uuid} text={answer.label} />
193
195
  ))}
194
196
  </Select>
195
197
  </Layer>