@kenyaemr/esm-patient-registration-app 4.5.3 → 4.5.5

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 (101) hide show
  1. package/dist/117.js +2 -0
  2. package/dist/117.js.map +1 -0
  3. package/dist/130.js +1 -1
  4. package/dist/130.js.map +1 -1
  5. package/dist/208.js +1 -1
  6. package/dist/218.js +1 -0
  7. package/dist/218.js.map +1 -0
  8. package/dist/275.js +1 -0
  9. package/dist/275.js.map +1 -0
  10. package/dist/319.js +1 -1
  11. package/dist/{821.js → 348.js} +1 -1
  12. package/dist/{821.js.map → 348.js.map} +1 -1
  13. package/dist/574.js +1 -1
  14. package/dist/68.js +1 -1
  15. package/dist/68.js.map +1 -1
  16. package/dist/693.js +1 -0
  17. package/dist/693.js.map +1 -0
  18. package/dist/757.js +1 -1
  19. package/dist/788.js +1 -1
  20. package/dist/807.js +1 -1
  21. package/dist/833.js +1 -1
  22. package/dist/879.js +1 -0
  23. package/dist/879.js.map +1 -0
  24. package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
  25. package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +171 -122
  26. package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -1
  27. package/dist/main.js +1 -1
  28. package/dist/main.js.map +1 -1
  29. package/dist/routes.json +1 -1
  30. package/jest.config.js +3 -0
  31. package/package.json +5 -2
  32. package/src/config-schema.ts +11 -5
  33. package/src/index.ts +9 -2
  34. package/src/offline.resources.ts +8 -4
  35. package/src/offline.ts +1 -1
  36. package/src/patient-registration/field/__mocks__/field.resource.ts +1 -1
  37. package/src/patient-registration/field/address/address-field.component.tsx +25 -70
  38. package/src/patient-registration/field/address/address-hierarchy-levels.component.tsx +11 -9
  39. package/src/patient-registration/field/address/address-hierarchy.resource.tsx +57 -3
  40. package/src/patient-registration/field/address/address-search.component.tsx +1 -1
  41. package/src/patient-registration/field/address/tests/address-hierarchy.test.tsx +137 -63
  42. package/src/patient-registration/field/address/tests/address-search-component.test.tsx +128 -0
  43. package/src/patient-registration/field/address/tests/mocks.ts +93 -99
  44. package/src/patient-registration/field/dob/dob.component.tsx +48 -44
  45. package/src/patient-registration/field/dob/dob.test.tsx +6 -1
  46. package/src/patient-registration/field/field.resource.ts +1 -1
  47. package/src/patient-registration/field/id/id-field.component.tsx +1 -1
  48. package/src/patient-registration/field/id/identifier-selection-overlay.tsx +1 -1
  49. package/src/patient-registration/field/name/name-field.component.tsx +38 -22
  50. package/src/patient-registration/field/obs/obs-field.component.tsx +14 -13
  51. package/src/patient-registration/field/person-attributes/coded-attributes.component.tsx +1 -1
  52. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +1 -1
  53. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +103 -0
  54. package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +187 -0
  55. package/src/patient-registration/field/person-attributes/person-attributes.resource.tsx +1 -1
  56. package/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx +3 -3
  57. package/src/patient-registration/field/person-attributes/text-person-attribute-field.test.tsx +88 -0
  58. package/src/patient-registration/form-manager.test.ts +1 -2
  59. package/src/patient-registration/form-manager.ts +1 -9
  60. package/src/patient-registration/input/basic-input/input/input.test.tsx +0 -135
  61. package/src/patient-registration/input/basic-input/select/select-input.test.tsx +8 -4
  62. package/src/patient-registration/input/combo-input/combo-input.component.tsx +8 -6
  63. package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +1 -1
  64. package/src/patient-registration/input/custom-input/identifier/utils.test.ts +81 -0
  65. package/src/patient-registration/input/custom-input/identifier/utils.ts +1 -1
  66. package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +1 -2
  67. package/src/patient-registration/patient-registration-context.ts +1 -1
  68. package/src/patient-registration/patient-registration-hooks.ts +14 -2
  69. package/src/patient-registration/patient-registration-utils.ts +1 -4
  70. package/src/patient-registration/patient-registration.component.tsx +1 -12
  71. package/src/patient-registration/patient-registration.resource.tsx +1 -72
  72. package/src/patient-registration/patient-registration.test.tsx +250 -247
  73. package/src/patient-registration/{patient-registration-types.tsx → patient-registration.types.tsx} +45 -1
  74. package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +83 -79
  75. package/src/patient-registration/section/patient-relationships/relationships-section.test.tsx +88 -0
  76. package/src/patient-registration/section/patient-relationships/relationships.resource.tsx +1 -1
  77. package/src/patient-registration/validation/patient-registration-validation.tsx +1 -1
  78. package/src/patient-verification/patient-verification-hook.tsx +12 -1
  79. package/src/patient-verification/patient-verification-utils.ts +1 -1
  80. package/src/patient-verification/patient-verification.component.tsx +1 -1
  81. package/src/patient-verification/verification-modal/confirm-prompt.component.tsx +11 -0
  82. package/src/root.component.tsx +1 -1
  83. package/src/routes.json +62 -51
  84. package/src/widgets/cancel-patient-edit.test.tsx +24 -0
  85. package/src/widgets/delete-identifier-confirmation-modal.test.tsx +31 -0
  86. package/src/widgets/display-photo.test.tsx +37 -0
  87. package/src/widgets/edit-patient-details-button.test.tsx +36 -0
  88. package/translations/am.json +0 -7
  89. package/translations/en.json +2 -5
  90. package/translations/es.json +0 -7
  91. package/translations/fr.json +0 -7
  92. package/translations/he.json +0 -7
  93. package/translations/km.json +0 -7
  94. package/__mocks__/react-i18next.js +0 -49
  95. package/dist/196.js +0 -1
  96. package/dist/196.js.map +0 -1
  97. package/dist/59.js +0 -1
  98. package/dist/59.js.map +0 -1
  99. package/dist/9.js +0 -2
  100. package/dist/9.js.map +0 -1
  101. /package/dist/{9.js.LICENSE.txt → 117.js.LICENSE.txt} +0 -0
@@ -37,7 +37,7 @@ export interface RegistrationConfig {
37
37
  fieldConfigurations: {
38
38
  name: {
39
39
  displayMiddleName: boolean;
40
- unidentifiedPatient: boolean;
40
+ allowUnidentifiedPatients: boolean;
41
41
  defaultUnknownGivenName: string;
42
42
  defaultUnknownFamilyName: string;
43
43
  displayCapturePhoto: boolean;
@@ -52,6 +52,7 @@ export interface RegistrationConfig {
52
52
  };
53
53
  };
54
54
  dateOfBirth: {
55
+ allowEstimatedDateOfBirth: boolean;
55
56
  useEstimatedDateOfBirth: {
56
57
  enabled: boolean;
57
58
  dayOfMonth: number;
@@ -201,10 +202,10 @@ export const esmPatientRegistrationSchema = {
201
202
  fieldConfigurations: {
202
203
  name: {
203
204
  displayMiddleName: { _type: Type.Boolean, _default: true },
204
- unidentifiedPatient: {
205
+ allowUnidentifiedPatients: {
205
206
  _type: Type.Boolean,
206
207
  _default: true,
207
- _description: 'Whether to allow patients to be registered without names.',
208
+ _description: 'Whether to allow registering unidentified patients.',
208
209
  },
209
210
  defaultUnknownGivenName: {
210
211
  _type: Type.String,
@@ -296,10 +297,15 @@ export const esmPatientRegistrationSchema = {
296
297
  },
297
298
  },
298
299
  dateOfBirth: {
300
+ allowEstimatedDateOfBirth: {
301
+ _type: Type.Boolean,
302
+ _description: 'Whether to allow estimated date of birth for a patient during registration',
303
+ _default: true,
304
+ },
299
305
  useEstimatedDateOfBirth: {
300
306
  enabled: {
301
307
  _type: Type.Boolean,
302
- _description: 'Whether to use custom day and month for estimated date of birth',
308
+ _description: 'Whether to use a fixed day and month for estimated date of birth',
303
309
  _default: false,
304
310
  },
305
311
  dayOfMonth: {
@@ -309,7 +315,7 @@ export const esmPatientRegistrationSchema = {
309
315
  },
310
316
  month: {
311
317
  _type: Type.Number,
312
- _description: 'The custom month to use on the estimated date of birth i.e 0 = Jan 11 = Dec',
318
+ _description: 'The custom month to use on the estimated date of birth i.e 0 = Jan & 11 = Dec',
313
319
  _default: 0,
314
320
  },
315
321
  },
package/src/index.ts CHANGED
@@ -58,5 +58,12 @@ export const deleteIdentifierConfirmationModal = getAsyncLifecycle(
58
58
  options,
59
59
  );
60
60
 
61
- export const emptyVerificationModal = getAsyncLifecycle(() => import('./patient-verification/verification-modal/empty-prompt.component'), options);
62
- export const confirmationVerificationModal = getAsyncLifecycle(() => import('./patient-verification/verification-modal/confirm-prompt.component'), options);
61
+ export const confirmClientRegistryModal = getAsyncLifecycle(
62
+ () => import('./patient-verification/verification-modal/confirm-prompt.component'),
63
+ options,
64
+ );
65
+
66
+ export const emptyClientRegistryModal = getAsyncLifecycle(
67
+ () => import('./patient-verification/verification-modal/empty-prompt.component'),
68
+ options,
69
+ );
@@ -2,12 +2,16 @@ import React from 'react';
2
2
  import find from 'lodash-es/find';
3
3
  import camelCase from 'lodash-es/camelCase';
4
4
  import escapeRegExp from 'lodash-es/escapeRegExp';
5
- import { FetchResponse, messageOmrsServiceWorker, openmrsFetch, Session } from '@openmrs/esm-framework';
6
- import { PatientIdentifierType, FetchedPatientIdentifierType } from './patient-registration/patient-registration-types';
5
+ import { messageOmrsServiceWorker, openmrsFetch, Session } from '@openmrs/esm-framework';
6
+ import type {
7
+ PatientIdentifierType,
8
+ FetchedPatientIdentifierType,
9
+ AddressTemplate,
10
+ } from './patient-registration/patient-registration.types';
7
11
  import { cacheForOfflineHeaders } from './constants';
8
12
 
9
13
  export interface Resources {
10
- addressTemplate: any;
14
+ addressTemplate: AddressTemplate;
11
15
  currentSession: Session;
12
16
  relationshipTypes: any;
13
17
  identifierTypes: Array<PatientIdentifierType>;
@@ -21,7 +25,7 @@ export async function fetchCurrentSession(): Promise<Session> {
21
25
  }
22
26
 
23
27
  export async function fetchAddressTemplate() {
24
- const { data } = await cacheAndFetch('/ws/rest/v1/systemsetting?q=layout.address.format&v=custom:(value)');
28
+ const { data } = await cacheAndFetch<AddressTemplate>('/ws/rest/v1/addresstemplate');
25
29
  return data;
26
30
  }
27
31
 
package/src/offline.ts CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  fetchPatientIdentifierTypesWithSources,
16
16
  } from './offline.resources';
17
17
  import { FormManager } from './patient-registration/form-manager';
18
- import { PatientRegistration } from './patient-registration/patient-registration-types';
18
+ import { PatientRegistration } from './patient-registration/patient-registration.types';
19
19
 
20
20
  export function setupOffline() {
21
21
  setupOfflineSync(patientRegistration, [], syncPatientRegistration, {
@@ -1,4 +1,4 @@
1
- import { ConceptResponse } from '../../patient-registration-types';
1
+ import { ConceptResponse } from '../../patient-registration.types';
2
2
 
3
3
  export const useConcept = jest.fn(function mockUseConceptImplementation(uuid: string): {
4
4
  data: ConceptResponse;
@@ -14,87 +14,44 @@ function parseString(xmlDockAsString: string) {
14
14
  const parser = new DOMParser();
15
15
  return parser.parseFromString(xmlDockAsString, 'text/xml');
16
16
  }
17
- function getTagAsDocument(tagName: string, template: XMLDocument) {
18
- const tmp = template.getElementsByTagName(tagName)[0];
19
- return tmp ? parseString(tmp.outerHTML) : parseString('');
20
- }
21
17
 
22
18
  export const AddressComponent: React.FC = () => {
23
19
  const [selected, setSelected] = useState('');
24
- const [addressLayout, setAddressLayout] = useState<
25
- Array<{
26
- id: string;
27
- name: string;
28
- value: string;
29
- label: string;
30
- }>
31
- >([]);
32
- const { t } = useTranslation();
33
20
  const { addressTemplate } = useContext(ResourcesContext);
34
- const addressTemplateXml = addressTemplate?.results[0].value;
35
- const setSelectedValue = (value: string) => {
36
- setSelected(value);
37
- };
21
+ const addressLayout = useMemo(() => {
22
+ if (!addressTemplate?.lines) {
23
+ return [];
24
+ }
25
+ const allFields = addressTemplate?.lines?.flat();
26
+ const fields = allFields?.filter(({ isToken }) => isToken === 'IS_ADDR_TOKEN');
27
+
28
+ return fields.map(({ displayText, codeName }) => ({
29
+ id: codeName,
30
+ name: codeName,
31
+ label: displayText,
32
+ }));
33
+ }, [addressTemplate]);
34
+
35
+ const { t } = useTranslation();
38
36
  const config = useConfig();
39
37
  const {
40
38
  fieldConfigurations: {
41
39
  address: {
42
- useAddressHierarchy: { enabled, useQuickSearch, searchAddressByLevel },
40
+ useAddressHierarchy: { enabled: addressHierarchyEnabled, useQuickSearch, searchAddressByLevel },
43
41
  },
44
42
  },
45
43
  } = config;
46
44
 
47
- const { setFieldValue, values, setInitialFormValues } = useContext(PatientRegistrationContext);
45
+ const { setFieldValue } = useContext(PatientRegistrationContext);
48
46
  const { orderedFields, isLoadingFieldOrder, errorFetchingFieldOrder } = useOrderedAddressHierarchyLevels();
49
47
 
50
48
  useEffect(() => {
51
- const templateXmlDoc = parseString(addressTemplateXml);
52
- const elementDefaults = getTagAsDocument('elementDefaults', templateXmlDoc);
53
- const defaultValuesEntries = elementDefaults.getElementsByTagName('entry');
54
- const defaultValues = Object.fromEntries(
55
- Array.prototype.map.call(defaultValuesEntries, (entry: Element) => {
56
- const [name, value] = Array.from(entry.getElementsByTagName('string'));
57
- return [name.innerHTML, value.innerHTML];
58
- }),
59
- );
60
- const nameMappings = getTagAsDocument('nameMappings', templateXmlDoc);
61
- const properties =
62
- Array.from(nameMappings.getElementsByTagName('property')).length > 0
63
- ? nameMappings.getElementsByTagName('property')
64
- : nameMappings.getElementsByTagName('entry');
65
-
66
- const propertiesObj = Array.prototype.map.call(properties, (property: Element) => {
67
- const name = property.getAttribute('name') ?? property.getElementsByTagName('string')[0].innerHTML;
68
- const label = property.getAttribute('value') ?? property.getElementsByTagName('string')[1].innerHTML;
69
- /*
70
- DO NOT REMOVE THIS COMMENT UNLESS YOU UNDERSTAND WHY IT IS HERE
71
-
72
- t('postalCode', 'Postal code')
73
- t('address1', 'Address line 1')
74
- t('address2', 'Address line 2')
75
- t('countyDistrict', 'District')
76
- t('stateProvince', 'State')
77
- t('cityVillage', 'city')
78
- t('country', 'Country')
79
- t('countyDistrict', 'District')
80
- */
81
- const value = defaultValues[name];
82
- setInitialFormValues((initialFormValues) => ({
83
- ...initialFormValues,
84
- address: {
85
- ...(initialFormValues.address ?? {}),
86
- [name]: value,
87
- },
88
- }));
89
- return {
90
- id: name,
91
- name,
92
- value,
93
- label,
94
- };
95
- });
96
- setAddressLayout(propertiesObj);
97
- }, [t, addressTemplateXml, setFieldValue, values, setInitialFormValues]);
49
+ if (addressTemplate?.elementDefaults) {
50
+ Object.entries(addressTemplate.elementDefaults).forEach(([name, defaultValue]) => {
51
+ setFieldValue(`address.${name}`, defaultValue);
52
+ });
53
+ }
54
+ }, [addressTemplate, setFieldValue]);
98
55
 
99
56
  const orderedAddressFields = useMemo(() => {
100
57
  if (isLoadingFieldOrder || errorFetchingFieldOrder) {
@@ -116,7 +73,7 @@ export const AddressComponent: React.FC = () => {
116
73
  );
117
74
  }
118
75
 
119
- if (!enabled) {
76
+ if (!addressHierarchyEnabled) {
120
77
  return (
121
78
  <AddressComponentContainer>
122
79
  {addressLayout.map((attributes, index) => (
@@ -125,7 +82,6 @@ export const AddressComponent: React.FC = () => {
125
82
  name={`address.${attributes.name}`}
126
83
  labelText={t(attributes.label)}
127
84
  id={attributes.name}
128
- setSelectedValue={setSelectedValue}
129
85
  selected={selected}
130
86
  />
131
87
  ))}
@@ -166,7 +122,6 @@ export const AddressComponent: React.FC = () => {
166
122
  name={`address.${attributes.name}`}
167
123
  labelText={t(attributes.label)}
168
124
  id={attributes.name}
169
- setSelectedValue={setSelectedValue}
170
125
  selected={selected}
171
126
  />
172
127
  ))
@@ -188,4 +143,4 @@ const AddressComponentContainer = ({ children }) => {
188
143
  </div>
189
144
  </div>
190
145
  );
191
- };
146
+ };
@@ -1,9 +1,8 @@
1
- import React, { useCallback, useMemo } from 'react';
1
+ import React, { useCallback } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { useAddressEntries, useAddressEntryFetchConfig } from './address-hierarchy.resource';
4
4
  import { useField } from 'formik';
5
5
  import ComboInput from '../../input/combo-input/combo-input.component';
6
- import { InlineNotification } from '@carbon/react';
7
6
 
8
7
  interface AddressHierarchyLevelsProps {
9
8
  orderedAddressFields: Array<any>;
@@ -34,22 +33,25 @@ interface AddressComboBoxProps {
34
33
 
35
34
  const AddressComboBox: React.FC<AddressComboBoxProps> = ({ attribute }) => {
36
35
  const { t } = useTranslation();
37
- const [field, meta, helpers] = useField(`address.${attribute.name}`);
36
+ const [field, meta, { setValue }] = useField(`address.${attribute.name}`);
38
37
  const { fetchEntriesForField, searchString, updateChildElements } = useAddressEntryFetchConfig(attribute.name);
39
38
  const { entries } = useAddressEntries(fetchEntriesForField, searchString);
40
39
 
41
- const handleInputChange = useCallback((newValue) => {
42
- helpers.setValue(newValue);
43
- }, []);
40
+ const handleInputChange = useCallback(
41
+ (newValue) => {
42
+ setValue(newValue);
43
+ },
44
+ [setValue],
45
+ );
44
46
 
45
47
  const handleSelection = useCallback(
46
48
  (selectedItem) => {
47
49
  if (meta.value !== selectedItem) {
48
- helpers.setValue(selectedItem);
50
+ setValue(selectedItem);
49
51
  updateChildElements();
50
52
  }
51
53
  },
52
- [updateChildElements, helpers.setValue],
54
+ [updateChildElements, meta.value, setValue],
53
55
  );
54
56
 
55
57
  return (
@@ -60,7 +62,7 @@ const AddressComboBox: React.FC<AddressComboBoxProps> = ({ attribute }) => {
60
62
  fieldProps={{
61
63
  ...field,
62
64
  id: attribute.name,
63
- labelText: `${attribute.label} (${t('optional', 'optional')})`,
65
+ labelText: `${t(attribute.label)} (${t('optional', 'optional')})`,
64
66
  }}
65
67
  handleInputChange={handleInputChange}
66
68
  />
@@ -1,4 +1,4 @@
1
- import { FetchResponse, openmrsFetch } from '@openmrs/esm-framework';
1
+ import { type FetchResponse, openmrsFetch } from '@openmrs/esm-framework';
2
2
  import { useField } from 'formik';
3
3
  import { useCallback, useContext, useEffect, useMemo } from 'react';
4
4
  import useSWRImmutable from 'swr/immutable';
@@ -10,7 +10,7 @@ interface AddressFields {
10
10
 
11
11
  export function useOrderedAddressHierarchyLevels() {
12
12
  const url = '/module/addresshierarchy/ajax/getOrderedAddressHierarchyLevels.form';
13
- const { data, isLoading, error } = useSWRImmutable<FetchResponse<Array<AddressFields>>>(url, openmrsFetch);
13
+ const { data, isLoading, error } = useSWRImmutable<FetchResponse<Array<AddressFields>>, Error>(url, openmrsFetch);
14
14
 
15
15
  const results = useMemo(
16
16
  () => ({
@@ -56,7 +56,7 @@ export function useAddressEntries(fetchResults, searchString) {
56
56
  * This also returns the function to reset the lower ordered fields if the value of a field is changed.
57
57
  */
58
58
  export function useAddressEntryFetchConfig(addressField: string) {
59
- const { orderedFields, isLoadingFieldOrder, errorFetchingFieldOrder } = useOrderedAddressHierarchyLevels();
59
+ const { orderedFields, isLoadingFieldOrder } = useOrderedAddressHierarchyLevels();
60
60
  const { setFieldValue } = useContext(PatientRegistrationContext);
61
61
  const [, { value: addressValues }] = useField('address');
62
62
 
@@ -101,3 +101,57 @@ export function useAddressEntryFetchConfig(addressField: string) {
101
101
 
102
102
  return results;
103
103
  }
104
+
105
+ export function useAddressHierarchy(searchString: string, separator: string) {
106
+ const { data, error, isLoading } = useSWRImmutable<
107
+ FetchResponse<
108
+ Array<{
109
+ address: string;
110
+ }>
111
+ >,
112
+ Error
113
+ >(
114
+ searchString
115
+ ? `/module/addresshierarchy/ajax/getPossibleFullAddresses.form?separator=${separator}&searchString=${searchString}`
116
+ : null,
117
+ openmrsFetch,
118
+ );
119
+
120
+ const results = useMemo(
121
+ () => ({
122
+ addresses: data?.data?.map((address) => address.address) ?? [],
123
+ error,
124
+ isLoading,
125
+ }),
126
+ [data?.data, error, isLoading],
127
+ );
128
+ return results;
129
+ }
130
+
131
+ export function useAddressHierarchyWithParentSearch(addressField: string, parentid: string, query: string) {
132
+ const { data, error, isLoading } = useSWRImmutable<
133
+ FetchResponse<
134
+ Array<{
135
+ uuid: string;
136
+ name: string;
137
+ }>
138
+ >,
139
+ Error
140
+ >(
141
+ query
142
+ ? `/module/addresshierarchy/ajax/getPossibleAddressHierarchyEntriesWithParents.form?addressField=${addressField}&limit=20&searchString=${query}&parentUuid=${parentid}`
143
+ : null,
144
+ openmrsFetch,
145
+ );
146
+
147
+ const results = useMemo(
148
+ () => ({
149
+ error: error,
150
+ isLoading,
151
+ addresses: data?.data ?? [],
152
+ }),
153
+ [data?.data, error, isLoading],
154
+ );
155
+
156
+ return results;
157
+ }
@@ -1,5 +1,5 @@
1
1
  import React, { useState, useRef, useEffect, useMemo } from 'react';
2
- import { useAddressHierarchy } from '../../patient-registration.resource';
2
+ import { useAddressHierarchy } from './address-hierarchy.resource';
3
3
  import { Search } from '@carbon/react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { useFormikContext } from 'formik';
@@ -6,8 +6,18 @@ import { Resources, ResourcesContext } from '../../../../offline.resources';
6
6
  import { PatientRegistrationContext } from '../../../patient-registration-context';
7
7
  import { useConfig } from '@openmrs/esm-framework';
8
8
  import { useOrderedAddressHierarchyLevels } from '../address-hierarchy.resource';
9
- import { mockResponse1, mockResponse2, mockedOrderedFields } from './mocks';
10
- import AddressHierarchyLevels from '../address-hierarchy-levels.component';
9
+ import { mockedAddressTemplate, mockedOrderedFields } from './mocks';
10
+
11
+ // Mocking the AddressSearchComponent
12
+ jest.mock('../address-search.component', () => jest.fn(() => <div data-testid="address-search-bar" />));
13
+ // Mocking the AddressHierarchyLevels
14
+ jest.mock('../address-hierarchy-levels.component', () => jest.fn(() => <div data-testid="address-hierarchy-levels" />));
15
+ // Mocking the SkeletonText
16
+ jest.mock('@carbon/react', () => ({
17
+ ...jest.requireActual('@carbon/react'),
18
+ SkeletonText: jest.fn(() => <div data-testid="skeleton-text" />),
19
+ InlineNotification: jest.fn(() => <div data-testid="inline-notification" />),
20
+ }));
11
21
 
12
22
  jest.mock('@openmrs/esm-framework', () => ({
13
23
  ...jest.requireActual('@openmrs/esm-framework'),
@@ -19,63 +29,91 @@ jest.mock('../address-hierarchy.resource', () => ({
19
29
  useOrderedAddressHierarchyLevels: jest.fn(),
20
30
  }));
21
31
 
22
- async function testAddressHierarchy(mockResponse) {
32
+ async function renderAddressHierarchy(addressTemplate = mockedAddressTemplate) {
23
33
  await render(
24
- <ResourcesContext.Provider value={{ addressTemplate: mockResponse } as Resources}>
34
+ <ResourcesContext.Provider value={{ addressTemplate } as Resources}>
25
35
  <Formik initialValues={{}} onSubmit={null}>
26
36
  <Form>
27
- <PatientRegistrationContext.Provider
28
- value={{ setFieldValue: jest.fn(), setInitialFormValues: jest.fn(), values: { address: {} } }}>
37
+ <PatientRegistrationContext.Provider value={{ setFieldValue: jest.fn() } as any}>
29
38
  <AddressComponent />
30
39
  </PatientRegistrationContext.Provider>
31
40
  </Form>
32
41
  </Formik>
33
42
  </ResourcesContext.Provider>,
34
43
  );
35
-
36
- const countryInput = screen.getByLabelText('Country (optional)');
37
- expect(countryInput).toBeInTheDocument();
38
- expect(countryInput).toHaveAttribute('name', 'address.country');
39
- const stateInput = screen.getByLabelText('State (optional)');
40
- expect(stateInput).toBeInTheDocument();
41
- expect(stateInput).toHaveAttribute('name', 'address.stateProvince');
42
- const cityInput = screen.getByLabelText('City (optional)');
43
- expect(cityInput).toBeInTheDocument();
44
- expect(cityInput).toHaveAttribute('name', 'address.cityVillage');
45
- const address1Input = screen.getByLabelText('Address line 1 (optional)');
46
- expect(address1Input).toBeInTheDocument();
47
- expect(address1Input).toHaveAttribute('name', 'address.address1');
48
- const address2Input = screen.getByLabelText('Address line 2 (optional)');
49
- expect(address2Input).toBeInTheDocument();
50
- expect(address2Input).toHaveAttribute('name', 'address.address2');
51
- const postalCodeInput = screen.getByLabelText('Postcode (optional)');
52
- expect(postalCodeInput).toBeInTheDocument();
53
- expect(postalCodeInput).toHaveAttribute('name', 'address.postalCode');
54
44
  }
55
45
 
56
- function testInputFieldOrder() {
57
- // Fields must be in the order of the orderedFields
58
- const inputs = screen.getAllByRole('textbox');
59
- inputs.forEach((input, indx) => {
60
- const inputName = input.getAttribute('name');
61
- // Names are in the format of address.${name}
62
- const fieldName = inputName.split('.')?.[1];
63
- expect(fieldName).toBe(mockedOrderedFields[indx]);
64
- });
65
- }
46
+ describe('Testing address hierarchy', () => {
47
+ beforeEach(cleanup);
66
48
 
67
- describe('address hierarchy', () => {
68
- beforeAll(() => {
49
+ it('should render skeleton when address template is loading', () => {
50
+ (useConfig as jest.Mock).mockImplementation(() => ({
51
+ fieldConfigurations: {
52
+ address: {
53
+ useAddressHierarchy: {
54
+ enabled: false,
55
+ useQuickSearch: false,
56
+ searchAddressByLevel: false,
57
+ },
58
+ },
59
+ },
60
+ }));
69
61
  (useOrderedAddressHierarchyLevels as jest.Mock).mockImplementation(() => ({
70
- orderedFields: mockedOrderedFields,
62
+ orderedFields: [],
71
63
  isLoadingFieldOrder: false,
72
64
  errorFetchingFieldOrder: null,
73
65
  }));
66
+ // @ts-ignore
67
+ renderAddressHierarchy(null);
68
+ const skeletonText = screen.getByTestId('skeleton-text');
69
+ expect(skeletonText).toBeInTheDocument();
74
70
  });
75
71
 
76
- beforeEach(cleanup);
72
+ it('should render skeleton when address hierarchy is enabled and addresshierarchy order is loading', () => {
73
+ (useConfig as jest.Mock).mockImplementation(() => ({
74
+ fieldConfigurations: {
75
+ address: {
76
+ useAddressHierarchy: {
77
+ enabled: true,
78
+ useQuickSearch: false,
79
+ searchAddressByLevel: false,
80
+ },
81
+ },
82
+ },
83
+ }));
84
+ (useOrderedAddressHierarchyLevels as jest.Mock).mockImplementation(() => ({
85
+ orderedFields: [],
86
+ isLoadingFieldOrder: true,
87
+ errorFetchingFieldOrder: null,
88
+ }));
89
+ renderAddressHierarchy();
90
+ const skeletonText = screen.getByTestId('skeleton-text');
91
+ expect(skeletonText).toBeInTheDocument();
92
+ });
93
+
94
+ it('should render skeleton when address hierarchy is enabled and addresshierarchy order is loading', () => {
95
+ (useConfig as jest.Mock).mockImplementation(() => ({
96
+ fieldConfigurations: {
97
+ address: {
98
+ useAddressHierarchy: {
99
+ enabled: true,
100
+ useQuickSearch: false,
101
+ searchAddressByLevel: false,
102
+ },
103
+ },
104
+ },
105
+ }));
106
+ (useOrderedAddressHierarchyLevels as jest.Mock).mockImplementation(() => ({
107
+ orderedFields: [],
108
+ isLoadingFieldOrder: false,
109
+ errorFetchingFieldOrder: true,
110
+ }));
111
+ renderAddressHierarchy();
112
+ const inlineNotification = screen.getByTestId('inline-notification');
113
+ expect(inlineNotification).toBeInTheDocument();
114
+ });
77
115
 
78
- it('renders text input fields matching addressTemplate config', async () => {
116
+ it('should render the address component with address hierarchy disabled', () => {
79
117
  (useConfig as jest.Mock).mockImplementation(() => ({
80
118
  fieldConfigurations: {
81
119
  address: {
@@ -87,37 +125,72 @@ describe('address hierarchy', () => {
87
125
  },
88
126
  },
89
127
  }));
90
- testAddressHierarchy(mockResponse1);
91
- // For cleaning up the input fields generated in first render
92
- cleanup();
93
- testAddressHierarchy(mockResponse2);
128
+ (useOrderedAddressHierarchyLevels as jest.Mock).mockImplementation(() => ({
129
+ orderedFields: [],
130
+ isLoadingFieldOrder: false,
131
+ errorFetchingFieldOrder: null,
132
+ }));
133
+ renderAddressHierarchy();
134
+ const allFields = mockedAddressTemplate.lines.flat().filter(({ isToken }) => isToken === 'IS_ADDR_TOKEN');
135
+ allFields.forEach((field) => {
136
+ const textFieldInput = screen.getByLabelText(`${field.displayText} (optional)`);
137
+ expect(textFieldInput).toBeInTheDocument();
138
+ });
94
139
  });
95
140
 
96
- it('renders combo input fields matching addressTemplate config', async () => {
141
+ it('should render the fields in order if the address hierarcy is enabled', () => {
97
142
  (useConfig as jest.Mock).mockImplementation(() => ({
98
143
  fieldConfigurations: {
99
144
  address: {
100
145
  useAddressHierarchy: {
101
146
  enabled: true,
102
- useQuickSearch: true,
147
+ useQuickSearch: false,
103
148
  searchAddressByLevel: false,
104
149
  },
105
150
  },
106
151
  },
107
152
  }));
153
+ (useOrderedAddressHierarchyLevels as jest.Mock).mockImplementation(() => ({
154
+ orderedFields: [],
155
+ isLoadingFieldOrder: false,
156
+ errorFetchingFieldOrder: null,
157
+ }));
158
+ renderAddressHierarchy();
159
+ const allFields = mockedAddressTemplate.lines.flat().filter(({ isToken }) => isToken === 'IS_ADDR_TOKEN');
160
+ const orderMap = Object.fromEntries(mockedOrderedFields.map((field, indx) => [field, indx]));
161
+ allFields.sort(
162
+ (existingField1, existingField2) =>
163
+ orderMap[existingField1.codeName ?? 0] - orderMap[existingField2.codeName ?? 0],
164
+ );
165
+ allFields.forEach((field) => {
166
+ const textFieldInput = screen.getByLabelText(`${field.displayText} (optional)`);
167
+ expect(textFieldInput).toBeInTheDocument();
168
+ });
169
+ });
108
170
 
109
- testAddressHierarchy(mockResponse1);
110
- const searchBox = screen.getByRole('searchbox');
111
- expect(searchBox).toBeInTheDocument();
112
- expect(searchBox.getAttribute('placeholder')).toBe('Search address');
113
- testInputFieldOrder();
114
- // For cleaning up the input fields generated in first render
115
- cleanup();
116
- testAddressHierarchy(mockResponse2);
117
- testInputFieldOrder();
171
+ it('should render quick search bar on above the fields when address hierarchy is enabled and quicksearch is set to true', () => {
172
+ (useConfig as jest.Mock).mockImplementation(() => ({
173
+ fieldConfigurations: {
174
+ address: {
175
+ useAddressHierarchy: {
176
+ enabled: true,
177
+ useQuickSearch: true,
178
+ searchAddressByLevel: false,
179
+ },
180
+ },
181
+ },
182
+ }));
183
+ (useOrderedAddressHierarchyLevels as jest.Mock).mockImplementation(() => ({
184
+ orderedFields: [],
185
+ isLoadingFieldOrder: false,
186
+ errorFetchingFieldOrder: null,
187
+ }));
188
+ renderAddressHierarchy();
189
+ const addressSearchBar = screen.getByTestId('address-search-bar');
190
+ expect(addressSearchBar).toBeInTheDocument();
118
191
  });
119
192
 
120
- it('renders combo input fields matching addressTemplate config and ordered fields', async () => {
193
+ it('should render combo boxes fields when address hierarchy is enabled and searchAddressByLevel is set to true', () => {
121
194
  (useConfig as jest.Mock).mockImplementation(() => ({
122
195
  fieldConfigurations: {
123
196
  address: {
@@ -129,12 +202,13 @@ describe('address hierarchy', () => {
129
202
  },
130
203
  },
131
204
  }));
132
-
133
- testAddressHierarchy(mockResponse1);
134
- testInputFieldOrder();
135
- // For cleaning up the input fields generated in first render
136
- cleanup();
137
- testAddressHierarchy(mockResponse2);
138
- testInputFieldOrder();
205
+ (useOrderedAddressHierarchyLevels as jest.Mock).mockImplementation(() => ({
206
+ orderedFields: [],
207
+ isLoadingFieldOrder: false,
208
+ errorFetchingFieldOrder: null,
209
+ }));
210
+ renderAddressHierarchy();
211
+ const addressHierarchyLevels = screen.getByTestId('address-hierarchy-levels');
212
+ expect(addressHierarchyLevels).toBeInTheDocument();
139
213
  });
140
214
  });