@kenyaemr/esm-patient-registration-app 4.5.0 → 4.5.2

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 (78) hide show
  1. package/dist/130.js +2 -0
  2. package/dist/{858.js.LICENSE.txt → 130.js.LICENSE.txt} +2 -0
  3. package/dist/130.js.map +1 -0
  4. package/dist/196.js +1 -0
  5. package/dist/196.js.map +1 -0
  6. package/dist/208.js +2 -0
  7. package/dist/208.js.map +1 -0
  8. package/dist/317.js +1 -1
  9. package/dist/317.js.map +1 -1
  10. package/dist/319.js +1 -0
  11. package/dist/537.js +1 -0
  12. package/dist/537.js.map +1 -0
  13. package/dist/591.js +1 -1
  14. package/dist/591.js.map +1 -1
  15. package/dist/62.js +1 -1
  16. package/dist/62.js.map +1 -1
  17. package/dist/635.js +1 -1
  18. package/dist/635.js.map +1 -1
  19. package/dist/68.js +1 -1
  20. package/dist/68.js.map +1 -1
  21. package/dist/735.js +1 -1
  22. package/dist/742.js +1 -0
  23. package/dist/742.js.map +1 -0
  24. package/dist/757.js +1 -1
  25. package/dist/784.js.map +1 -1
  26. package/dist/788.js +1 -0
  27. package/dist/807.js +1 -1
  28. package/dist/821.js +1 -1
  29. package/dist/821.js.map +1 -1
  30. package/dist/833.js +1 -1
  31. package/dist/857.js +1 -0
  32. package/dist/857.js.map +1 -0
  33. package/dist/9.js +1 -1
  34. package/dist/9.js.map +1 -1
  35. package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
  36. package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +133 -137
  37. package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -1
  38. package/dist/main.js +1 -1
  39. package/dist/main.js.map +1 -1
  40. package/dist/routes.json +1 -0
  41. package/package.json +2 -2
  42. package/src/add-patient-link.test.tsx +18 -0
  43. package/src/config-schema.ts +19 -5
  44. package/src/index.ts +33 -105
  45. package/src/nav-link.test.tsx +13 -0
  46. package/src/patient-registration/field/address/address-field.component.tsx +1 -1
  47. package/src/patient-registration/field/address/address-hierarchy.resource.tsx +2 -1
  48. package/src/patient-registration/field/field.test.tsx +291 -0
  49. package/src/patient-registration/field/name/name-field.component.tsx +45 -28
  50. package/src/patient-registration/form-manager.ts +1 -0
  51. package/src/patient-registration/input/combo-input/combo-input.component.tsx +28 -19
  52. package/src/patient-registration/input/input.scss +5 -0
  53. package/src/patient-registration/section/death-info/death-info-section.test.tsx +9 -6
  54. package/src/root.component.tsx +11 -11
  55. package/src/routes.json +46 -0
  56. package/translations/am.json +91 -0
  57. package/translations/es.json +91 -0
  58. package/translations/fr.json +8 -6
  59. package/translations/he.json +8 -6
  60. package/translations/km.json +8 -6
  61. package/dist/144.js +0 -2
  62. package/dist/144.js.map +0 -1
  63. package/dist/207.js +0 -1
  64. package/dist/207.js.map +0 -1
  65. package/dist/330.js +0 -1
  66. package/dist/330.js.map +0 -1
  67. package/dist/59.js +0 -1
  68. package/dist/59.js.map +0 -1
  69. package/dist/805.js +0 -1
  70. package/dist/805.js.map +0 -1
  71. package/dist/858.js +0 -2
  72. package/dist/858.js.map +0 -1
  73. package/dist/876.js +0 -1
  74. package/dist/876.js.map +0 -1
  75. package/dist/887.js +0 -1
  76. package/dist/887.js.map +0 -1
  77. package/dist/kenyaemr-esm-patient-registration-app.old +0 -1
  78. /package/dist/{144.js.LICENSE.txt → 208.js.LICENSE.txt} +0 -0
@@ -41,6 +41,7 @@ export interface RegistrationConfig {
41
41
  defaultUnknownGivenName: string;
42
42
  defaultUnknownFamilyName: string;
43
43
  displayCapturePhoto: boolean;
44
+ displayReverseFieldOrder: boolean;
44
45
  };
45
46
  gender: Array<Gender>;
46
47
  address: {
@@ -78,12 +79,12 @@ export const builtInSections: Array<SectionDefinition> = [
78
79
  name: 'Basic Info',
79
80
  fields: ['name', 'gender', 'dob', 'id'],
80
81
  },
81
- { id: 'contact', name: 'Contact Details', fields: ['address'] },
82
+ { id: 'contact', name: 'Contact Details', fields: ['address', 'phone'] },
82
83
  { id: 'death', name: 'Death Info', fields: [] },
83
84
  { id: 'relationships', name: 'Relationships', fields: [] },
84
85
  ];
85
86
 
86
- export const builtInFields = ['name', 'gender', 'dob', 'address', 'id'] as const;
87
+ export const builtInFields = ['name', 'gender', 'dob', 'address', 'id', 'phone & email'] as const;
87
88
 
88
89
  export const esmPatientRegistrationSchema = {
89
90
  sections: {
@@ -184,8 +185,15 @@ export const esmPatientRegistrationSchema = {
184
185
  },
185
186
  },
186
187
  _default: [
187
-
188
-
188
+ {
189
+ id: 'phone',
190
+ type: 'person attribute',
191
+ uuid: '14d4f066-15f5-102d-96e4-000c29c2a5d7',
192
+ showHeading: false,
193
+ validation: {
194
+ matches: '',
195
+ },
196
+ },
189
197
  ],
190
198
  _description:
191
199
  'Definitions for custom fields that can be used in sectionDefinitions. Can also be used to override built-in fields.',
@@ -213,6 +221,11 @@ export const esmPatientRegistrationSchema = {
213
221
  _default: true,
214
222
  _description: 'Whether to display capture patient photo slot on name field',
215
223
  },
224
+ displayReverseFieldOrder: {
225
+ _type: Type.Boolean,
226
+ _default: false,
227
+ _description: "Whether to display the name fields in the order 'Family name' -> 'Middle name' -> 'First name'",
228
+ },
216
229
  },
217
230
  gender: {
218
231
  _type: Type.Array,
@@ -379,7 +392,8 @@ export const esmPatientRegistrationSchema = {
379
392
  );
380
393
  const badField = badSection.fields.find((f) => !allowedFields.includes(f));
381
394
  return (
382
- `The section definition '${badSection.id
395
+ `The section definition '${
396
+ badSection.id
383
397
  }' contains an invalid field '${badField}'. 'fields' can only contain the built-in fields '${builtInFields.join(
384
398
  "', '",
385
399
  )}'` +
package/src/index.ts CHANGED
@@ -1,25 +1,16 @@
1
1
  import { registerBreadcrumbs, defineConfigSchema, getAsyncLifecycle } from '@openmrs/esm-framework';
2
- import { FormManager } from './patient-registration/form-manager';
3
2
  import { esmPatientRegistrationSchema } from './config-schema';
4
3
  import { moduleName, patientRegistration } from './constants';
5
4
  import { setupOffline } from './offline';
6
5
 
7
- declare var __VERSION__: string;
8
- // __VERSION__ is replaced by Webpack with the version from package.json
9
- const version = __VERSION__;
6
+ export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
10
7
 
11
- const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
12
-
13
- const backendDependencies = {
14
- 'webservices.rest': '^2.24.0',
8
+ const options = {
9
+ featureName: 'Patient Registration',
10
+ moduleName,
15
11
  };
16
12
 
17
- function setupOpenMRS() {
18
- const options = {
19
- featureName: 'Patient Registration',
20
- moduleName,
21
- };
22
-
13
+ export function startupApp() {
23
14
  defineConfigSchema(moduleName, esmPatientRegistrationSchema);
24
15
 
25
16
  registerBreadcrumbs([
@@ -36,96 +27,33 @@ function setupOpenMRS() {
36
27
  ]);
37
28
 
38
29
  setupOffline();
39
-
40
- return {
41
- pages: [
42
- {
43
- load: getAsyncLifecycle(() => import('./root.component'), options),
44
- route: 'patient-registration',
45
- online: {
46
- savePatientForm: FormManager.savePatientFormOnline,
47
- isOffline: false,
48
- },
49
- offline: {
50
- savePatientForm: FormManager.savePatientFormOffline,
51
- isOffline: true,
52
- },
53
- },
54
- {
55
- load: getAsyncLifecycle(() => import('./root.component'), {
56
- featureName: 'edit-patient-details-form',
57
- moduleName,
58
- }),
59
- route: /patient\/([a-zA-Z0-9\-]+)\/edit/,
60
- online: {
61
- savePatientForm: FormManager.savePatientFormOnline,
62
- },
63
- offline: {
64
- savePatientForm: FormManager.savePatientFormOffline,
65
- },
66
- },
67
- ],
68
- extensions: [
69
- {
70
- id: 'add-patient-action',
71
- slot: 'top-nav-actions-slot',
72
- load: getAsyncLifecycle(() => import('./add-patient-link'), options),
73
- online: true,
74
- offline: true,
75
- },
76
- {
77
- id: 'cancel-patient-edit-modal',
78
- load: getAsyncLifecycle(() => import('./widgets/cancel-patient-edit.component'), options),
79
- online: true,
80
- offline: true,
81
- },
82
- {
83
- id: 'patient-photo-widget',
84
- slot: 'patient-photo-slot',
85
- load: getAsyncLifecycle(() => import('./widgets/display-photo.component'), options),
86
- online: true,
87
- offline: true,
88
- },
89
- {
90
- id: 'edit-patient-details-button',
91
- slot: 'patient-actions-slot',
92
- load: getAsyncLifecycle(() => import('./widgets/edit-patient-details-button.component'), options),
93
- online: true,
94
- offline: true,
95
- },
96
- {
97
- id: 'edit-patient-details-button',
98
- slot: 'patient-search-actions-slot',
99
- load: getAsyncLifecycle(() => import('./widgets/edit-patient-details-button.component'), options),
100
- online: true,
101
- offline: true,
102
- },
103
- {
104
- id: 'delete-identifier-confirmation-modal',
105
- load: getAsyncLifecycle(() => import('./widgets/delete-identifier-confirmation-modal'), options),
106
- online: true,
107
- offline: true,
108
- },
109
- {
110
- id: 'empty-client-registry-modal',
111
- load: getAsyncLifecycle(
112
- () => import('./patient-verification/verification-modal/empty-prompt.component'),
113
- options,
114
- ),
115
- online: true,
116
- offline: true,
117
- },
118
- {
119
- id: 'confirm-client-registry-modal',
120
- load: getAsyncLifecycle(
121
- () => import('./patient-verification/verification-modal/confirm-prompt.component'),
122
- options,
123
- ),
124
- online: true,
125
- offline: true,
126
- },
127
- ],
128
- };
129
30
  }
130
31
 
131
- export { backendDependencies, importTranslation, setupOpenMRS, version };
32
+ export const root = getAsyncLifecycle(() => import('./root.component'), options);
33
+
34
+ export const editPatient = getAsyncLifecycle(() => import('./root.component'), {
35
+ featureName: 'edit-patient-details-form',
36
+ moduleName,
37
+ });
38
+
39
+ export const addPatientLink = getAsyncLifecycle(() => import('./add-patient-link'), options);
40
+
41
+ export const cancelPatientEditModal = getAsyncLifecycle(
42
+ () => import('./widgets/cancel-patient-edit.component'),
43
+ options,
44
+ );
45
+
46
+ export const patientPhoto = getAsyncLifecycle(() => import('./widgets/display-photo.component'), options);
47
+
48
+ export const editPatientDetailsButton = getAsyncLifecycle(
49
+ () => import('./widgets/edit-patient-details-button.component'),
50
+ {
51
+ featureName: 'edit-patient-details',
52
+ moduleName,
53
+ },
54
+ );
55
+
56
+ export const deleteIdentifierConfirmationModal = getAsyncLifecycle(
57
+ () => import('./widgets/delete-identifier-confirmation-modal'),
58
+ options,
59
+ );
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import Root from './nav-link';
4
+
5
+ describe('Nav link component', () => {
6
+ it('renders a link to the patient registration page', () => {
7
+ const { getByText } = render(<Root />);
8
+ const linkElement = getByText('Patient Registration');
9
+
10
+ expect(linkElement).toBeInTheDocument();
11
+ expect(linkElement).toHaveAttribute('href', '/openmrs/spa/patient-registration');
12
+ });
13
+ });
@@ -188,4 +188,4 @@ const AddressComponentContainer = ({ children }) => {
188
188
  </div>
189
189
  </div>
190
190
  );
191
- };
191
+ };
@@ -25,9 +25,10 @@ export function useOrderedAddressHierarchyLevels() {
25
25
  }
26
26
 
27
27
  export function useAddressEntries(fetchResults, searchString) {
28
+ const encodedSearchString = encodeURIComponent(searchString);
28
29
  const { data, isLoading, error } = useSWRImmutable<FetchResponse<Array<{ name: string }>>>(
29
30
  fetchResults
30
- ? `module/addresshierarchy/ajax/getChildAddressHierarchyEntries.form?searchString=${searchString}`
31
+ ? `module/addresshierarchy/ajax/getChildAddressHierarchyEntries.form?searchString=${encodedSearchString}`
31
32
  : null,
32
33
  openmrsFetch,
33
34
  );
@@ -0,0 +1,291 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { Field } from './field.component';
4
+ import { useConfig } from '@openmrs/esm-framework';
5
+ import { PatientRegistrationContext } from '../patient-registration-context';
6
+ import { Resources, ResourcesContext } from '../../offline.resources';
7
+ import { Form, Formik } from 'formik';
8
+
9
+ jest.mock('@openmrs/esm-framework', () => ({
10
+ ...jest.requireActual('@openmrs/esm-framework'),
11
+ useConfig: jest.fn(),
12
+ }));
13
+ 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
+ ],
20
+ };
21
+
22
+ const mockedIdentifierTypes = [
23
+ {
24
+ fieldName: 'openMrsId',
25
+ format: null,
26
+ identifierSources: [
27
+ {
28
+ uuid: '8549f706-7e85-4c1d-9424-217d50a2988b',
29
+ name: 'Generator for OpenMRS ID',
30
+ description: 'Generator for OpenMRS ID',
31
+ baseCharacterSet: '0123456789ACDEFGHJKLMNPRTUVWXY',
32
+ prefix: '',
33
+ },
34
+ ],
35
+ isPrimary: true,
36
+ name: 'OpenMRS ID',
37
+ required: true,
38
+ uniquenessBehavior: 'UNIQUE',
39
+ uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
40
+ },
41
+ {
42
+ fieldName: 'idCard',
43
+ format: null,
44
+ identifierSources: [],
45
+ isPrimary: false,
46
+ name: 'ID Card',
47
+ required: false,
48
+ uniquenessBehavior: 'UNIQUE',
49
+ uuid: 'b4143563-16cd-4439-b288-f83d61670fc8',
50
+ },
51
+ {
52
+ fieldName: 'legacyId',
53
+ format: null,
54
+ identifierSources: [],
55
+ isPrimary: false,
56
+ name: 'Legacy ID',
57
+ required: false,
58
+ uniquenessBehavior: null,
59
+ uuid: '22348099-3873-459e-a32e-d93b17eda533',
60
+ },
61
+ {
62
+ fieldName: 'oldIdentificationNumber',
63
+ format: '',
64
+ identifierSources: [],
65
+ isPrimary: false,
66
+ name: 'Old Identification Number',
67
+ required: false,
68
+ uniquenessBehavior: null,
69
+ uuid: '8d79403a-c2cc-11de-8d13-0010c6dffd0f',
70
+ },
71
+ {
72
+ fieldName: 'openMrsIdentificationNumber',
73
+ format: '',
74
+ identifierSources: [],
75
+ isPrimary: false,
76
+ name: 'OpenMRS Identification Number',
77
+ required: false,
78
+ uniquenessBehavior: null,
79
+ uuid: '8d793bee-c2cc-11de-8d13-0010c6dffd0f',
80
+ },
81
+ ];
82
+ const mockResourcesContextValue = {
83
+ addressTemplate: predefinedAddressTemplate,
84
+ currentSession: {
85
+ authenticated: true,
86
+ sessionId: 'JSESSION',
87
+ currentProvider: { uuid: 'provider-uuid', identifier: 'PRO-123' },
88
+ },
89
+ relationshipTypes: [],
90
+ identifierTypes: [...mockedIdentifierTypes],
91
+ } as Resources;
92
+
93
+ describe('Field', () => {
94
+ beforeEach(() => {
95
+ jest.clearAllMocks();
96
+ });
97
+
98
+ it('should render NameField component when name prop is "name"', () => {
99
+ (useConfig as jest.Mock).mockImplementation(() => ({
100
+ fieldConfigurations: {
101
+ name: {
102
+ displayMiddleName: true,
103
+ unidentifiedPatient: true,
104
+ defaultUnknownGivenName: 'UNKNOWN',
105
+ defaultUnknownFamilyName: 'UNKNOWN',
106
+ },
107
+ },
108
+ }));
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
+ );
125
+ expect(screen.getByText('Full Name')).toBeInTheDocument();
126
+ });
127
+
128
+ it('should render GenderField component when name prop is "gender"', () => {
129
+ (useConfig as jest.Mock).mockImplementation(() => ({
130
+ fieldConfigurations: {
131
+ gender: [
132
+ {
133
+ value: 'Male',
134
+ label: 'Male',
135
+ id: 'male',
136
+ },
137
+ ],
138
+ },
139
+ }));
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
+ );
156
+ expect(screen.getByLabelText('Male')).toBeInTheDocument();
157
+ });
158
+
159
+ it('should render DobField component when name prop is "dob"', () => {
160
+ (useConfig as jest.Mock).mockImplementation(() => ({
161
+ fieldConfigurations: {
162
+ dob: {
163
+ minAgeLimit: 0,
164
+ maxAgeLimit: 120,
165
+ },
166
+ },
167
+ }));
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
+ );
184
+ expect(screen.getByText('Birth')).toBeInTheDocument();
185
+ });
186
+
187
+ it('should render AddressComponent component when name prop is "address"', () => {
188
+ jest.mock('./address/address-hierarchy.resource', () => ({
189
+ ...(jest.requireActual('../address-hierarchy.resource') as jest.Mock),
190
+ useOrderedAddressHierarchyLevels: jest.fn(),
191
+ }));
192
+ (useConfig as jest.Mock).mockImplementation(() => ({
193
+ fieldConfigurations: {
194
+ address: {
195
+ useAddressHierarchy: {
196
+ enabled: false,
197
+ useQuickSearch: false,
198
+ searchAddressByLevel: false,
199
+ },
200
+ },
201
+ },
202
+ }));
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
+ );
219
+ expect(screen.getByText('Address')).toBeInTheDocument();
220
+ });
221
+
222
+ it('should render Identifiers component when name prop is "id"', () => {
223
+ (useConfig as jest.Mock).mockImplementation(() => ({
224
+ defaultPatientIdentifierTypes: ['OpenMRS ID'],
225
+ }));
226
+ // initial value for the identifiers field
227
+ const openmrsID = {
228
+ name: 'OpenMRS ID',
229
+ fieldName: 'openMrsId',
230
+ required: true,
231
+ uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
232
+ format: null,
233
+ isPrimary: true,
234
+ identifierSources: [
235
+ {
236
+ uuid: '691eed12-c0f1-11e2-94be-8c13b969e334',
237
+ name: 'Generator 1 for OpenMRS ID',
238
+ autoGenerationOption: {
239
+ manualEntryEnabled: false,
240
+ automaticGenerationEnabled: true,
241
+ },
242
+ },
243
+ {
244
+ uuid: '01af8526-cea4-4175-aa90-340acb411771',
245
+ name: 'Generator 2 for OpenMRS ID',
246
+ autoGenerationOption: {
247
+ manualEntryEnabled: true,
248
+ automaticGenerationEnabled: true,
249
+ },
250
+ },
251
+ ],
252
+ autoGenerationSource: null,
253
+ };
254
+ render(
255
+ <ResourcesContext.Provider value={mockResourcesContextValue}>
256
+ <Formik initialValues={{}} onSubmit={null}>
257
+ <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
+ }}>
267
+ <Field name="id" />
268
+ </PatientRegistrationContext.Provider>
269
+ </Form>
270
+ </Formik>
271
+ </ResourcesContext.Provider>,
272
+ );
273
+ expect(screen.getByText('Identifiers')).toBeInTheDocument();
274
+ });
275
+
276
+ it('should return null and report an error for an invalid field name', () => {
277
+ (useConfig as jest.Mock).mockImplementation(() => ({
278
+ fieldDefinitions: [{ id: 'weight' }],
279
+ }));
280
+ let error = null;
281
+ try {
282
+ render(<Field name="invalidField" />);
283
+ } catch (err) {
284
+ error = err;
285
+ }
286
+ expect(error).toBe(
287
+ "Invalid field name 'invalidField'. Valid options are 'weight', 'name', 'gender', 'dob', 'address', 'id', 'phone & email'.",
288
+ );
289
+ expect(screen.queryByTestId('invalid-field')).not.toBeInTheDocument();
290
+ });
291
+ });
@@ -21,7 +21,7 @@ function checkNumber(value: string) {
21
21
  export const NameField = () => {
22
22
  const {
23
23
  fieldConfigurations: {
24
- name: { displayCapturePhoto },
24
+ name: { displayCapturePhoto, displayReverseFieldOrder },
25
25
  },
26
26
  } = useConfig() as RegistrationConfig;
27
27
  const { t } = useTranslation();
@@ -55,6 +55,36 @@ export const NameField = () => {
55
55
  }
56
56
  };
57
57
 
58
+ const firstNameField = (
59
+ <Input
60
+ id="givenName"
61
+ name="givenName"
62
+ labelText={t('givenNameLabelText', 'First Name')}
63
+ checkWarning={checkNumber}
64
+ required
65
+ />
66
+ );
67
+
68
+ const middleNameField = fieldConfigs.displayMiddleName && (
69
+ <Input
70
+ id="middleName"
71
+ name="middleName"
72
+ labelText={t('middleNameLabelText', 'Middle Name')}
73
+ light
74
+ checkWarning={checkNumber}
75
+ />
76
+ );
77
+
78
+ const familyNameField = (
79
+ <Input
80
+ id="familyName"
81
+ name="familyName"
82
+ labelText={t('familyNameLabelText', 'Family Name')}
83
+ checkWarning={checkNumber}
84
+ required
85
+ />
86
+ );
87
+
58
88
  return (
59
89
  <div>
60
90
  <h4 className={styles.productiveHeading02Light}>{t('fullNameLabelText', 'Full Name')}</h4>
@@ -75,33 +105,20 @@ export const NameField = () => {
75
105
  <Switch name="known" text={t('yes', 'Yes')} />
76
106
  <Switch name="unknown" text={t('no', 'No')} />
77
107
  </ContentSwitcher>
78
- {nameKnown && (
79
- <>
80
- <Input
81
- id="givenName"
82
- name="givenName"
83
- labelText={t('givenNameLabelText', 'First Name')}
84
- checkWarning={checkNumber}
85
- required
86
- />
87
- {fieldConfigs.displayMiddleName && (
88
- <Input
89
- id="middleName"
90
- name="middleName"
91
- labelText={t('middleNameLabelText', 'Middle Name')}
92
- light
93
- checkWarning={checkNumber}
94
- />
95
- )}
96
- <Input
97
- id="familyName"
98
- name="familyName"
99
- labelText={t('familyNameLabelText', 'Family Name')}
100
- checkWarning={checkNumber}
101
- required
102
- />
103
- </>
104
- )}
108
+ {nameKnown &&
109
+ (!displayReverseFieldOrder ? (
110
+ <>
111
+ {firstNameField}
112
+ {middleNameField}
113
+ {familyNameField}
114
+ </>
115
+ ) : (
116
+ <>
117
+ {familyNameField}
118
+ {middleNameField}
119
+ {firstNameField}
120
+ </>
121
+ ))}
105
122
  </div>
106
123
  </div>
107
124
  </div>
@@ -39,6 +39,7 @@ export type SavePatientForm = (
39
39
  savePatientTransactionManager: SavePatientTransactionManager,
40
40
  abortController?: AbortController,
41
41
  ) => Promise<string | void>;
42
+
42
43
  export class FormManager {
43
44
  static savePatientFormOffline: SavePatientForm = async (
44
45
  isNewPatient,