@kenyaemr/esm-patient-registration-app 4.4.4 → 4.5.1

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 (48) hide show
  1. package/dist/144.js +1 -1
  2. package/dist/207.js +1 -1
  3. package/dist/207.js.map +1 -1
  4. package/dist/574.js +1 -1
  5. package/dist/59.js +1 -1
  6. package/dist/59.js.map +1 -1
  7. package/dist/68.js +1 -1
  8. package/dist/68.js.map +1 -1
  9. package/dist/821.js +1 -1
  10. package/dist/833.js +1 -0
  11. package/dist/876.js +1 -0
  12. package/dist/876.js.map +1 -0
  13. package/dist/{kenya-esm-patient-registration-app.js → kenyaemr-esm-patient-registration-app.js} +1 -1
  14. package/dist/{kenya-esm-patient-registration-app.js.buildmanifest.json → kenyaemr-esm-patient-registration-app.js.buildmanifest.json} +57 -35
  15. package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -0
  16. package/dist/{kenya-esm-patient-registration-app.old → kenyaemr-esm-patient-registration-app.old} +1 -1
  17. package/dist/main.js +1 -1
  18. package/dist/main.js.map +1 -1
  19. package/package.json +2 -2
  20. package/src/config-schema.ts +3 -13
  21. package/src/patient-registration/field/address/address-field.component.tsx +180 -20
  22. package/src/patient-registration/field/address/address-hierarchy-levels.component.tsx +68 -0
  23. package/src/patient-registration/field/address/address-hierarchy.resource.tsx +103 -0
  24. package/src/patient-registration/field/address/custom-address-field.component.tsx +30 -0
  25. package/src/patient-registration/field/address/tests/address-hierarchy.test.tsx +140 -0
  26. package/src/patient-registration/field/address/{address-hierarchy.test.tsx → tests/mocks.ts} +3 -80
  27. package/src/patient-registration/field/custom-field.component.tsx +1 -1
  28. package/src/patient-registration/field/field.component.tsx +2 -2
  29. package/src/patient-registration/field/name/name-field.component.tsx +1 -1
  30. package/src/patient-registration/input/combo-input/combo-input.component.tsx +106 -62
  31. package/src/patient-registration/input/combo-input/selection-tick.component.tsx +20 -0
  32. package/src/patient-registration/input/input.scss +6 -1
  33. package/src/patient-registration/patient-registration-context.ts +1 -0
  34. package/src/patient-registration/patient-registration-hooks.ts +1 -0
  35. package/src/patient-registration/patient-registration-utils.ts +3 -3
  36. package/src/patient-registration/patient-registration.component.tsx +1 -0
  37. package/src/patient-registration/patient-registration.scss +14 -0
  38. package/src/patient-registration/validation/patient-registration-validation.test.tsx +128 -114
  39. package/src/patient-verification/verification-modal/confirm-prompt.component.tsx +1 -0
  40. package/src/root.component.tsx +1 -1
  41. package/translations/en.json +3 -5
  42. package/translations/he.json +89 -0
  43. package/dist/822.js +0 -1
  44. package/dist/822.js.map +0 -1
  45. package/dist/kenya-esm-patient-registration-app.js.map +0 -1
  46. package/src/patient-registration/field/address/address-hierarchy.component.tsx +0 -143
  47. package/src/patient-registration/input/combo-input/combo-input.test.tsx +0 -43
  48. /package/src/{declarations.d.tsx → declarations.d.ts} +0 -0
@@ -1,31 +1,191 @@
1
- import React from 'react';
1
+ import React, { useEffect, useState, useContext, useMemo } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
- import { Input } from '../../input/basic-input/input/input.component';
3
+ import { ResourcesContext } from '../../../offline.resources';
4
+ import { SkeletonText, InlineNotification } from '@carbon/react';
4
5
  import styles from '../field.scss';
5
- import { FieldDefinition } from '../../../config-schema';
6
- import { Field } from 'formik';
6
+ import { Input } from '../../input/basic-input/input/input.component';
7
+ import { useConfig } from '@openmrs/esm-framework';
8
+ import AddressSearchComponent from './address-search.component';
9
+ import { PatientRegistrationContext } from '../../patient-registration-context';
10
+ import { useOrderedAddressHierarchyLevels } from './address-hierarchy.resource';
11
+ import AddressHierarchyLevels from './address-hierarchy-levels.component';
7
12
 
8
- export interface AddressFieldProps {
9
- fieldDefinition: FieldDefinition;
13
+ function parseString(xmlDockAsString: string) {
14
+ const parser = new DOMParser();
15
+ return parser.parseFromString(xmlDockAsString, 'text/xml');
16
+ }
17
+ function getTagAsDocument(tagName: string, template: XMLDocument) {
18
+ const tmp = template.getElementsByTagName(tagName)[0];
19
+ return tmp ? parseString(tmp.outerHTML) : parseString('');
10
20
  }
11
21
 
12
- export const AddressField: React.FC<AddressFieldProps> = ({ fieldDefinition }) => {
22
+ export const AddressComponent: React.FC = () => {
23
+ 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
+ >([]);
13
32
  const { t } = useTranslation();
33
+ const { addressTemplate } = useContext(ResourcesContext);
34
+ const addressTemplateXml = addressTemplate?.results[0].value;
35
+ const setSelectedValue = (value: string) => {
36
+ setSelected(value);
37
+ };
38
+ const config = useConfig();
39
+ const {
40
+ fieldConfigurations: {
41
+ address: {
42
+ useAddressHierarchy: { enabled, useQuickSearch, searchAddressByLevel },
43
+ },
44
+ },
45
+ } = config;
46
+
47
+ const { setFieldValue, values, setInitialFormValues } = useContext(PatientRegistrationContext);
48
+ const { orderedFields, isLoadingFieldOrder, errorFetchingFieldOrder } = useOrderedAddressHierarchyLevels();
49
+
50
+ 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]);
98
+
99
+ const orderedAddressFields = useMemo(() => {
100
+ if (isLoadingFieldOrder || errorFetchingFieldOrder) {
101
+ return [];
102
+ }
103
+
104
+ const orderMap = Object.fromEntries(orderedFields.map((field, indx) => [field, indx]));
105
+
106
+ return [...addressLayout].sort(
107
+ (existingField1, existingField2) => orderMap[existingField1.name] - orderMap[existingField2.name],
108
+ );
109
+ }, [isLoadingFieldOrder, errorFetchingFieldOrder, orderedFields, addressLayout]);
14
110
 
111
+ if (!addressTemplate) {
112
+ return (
113
+ <AddressComponentContainer>
114
+ <SkeletonText />
115
+ </AddressComponentContainer>
116
+ );
117
+ }
118
+
119
+ if (!enabled) {
120
+ return (
121
+ <AddressComponentContainer>
122
+ {addressLayout.map((attributes, index) => (
123
+ <Input
124
+ key={`combo_input_${index}`}
125
+ name={`address.${attributes.name}`}
126
+ labelText={t(attributes.label)}
127
+ id={attributes.name}
128
+ setSelectedValue={setSelectedValue}
129
+ selected={selected}
130
+ />
131
+ ))}
132
+ </AddressComponentContainer>
133
+ );
134
+ }
135
+
136
+ if (isLoadingFieldOrder) {
137
+ return (
138
+ <AddressComponentContainer>
139
+ <SkeletonText />
140
+ </AddressComponentContainer>
141
+ );
142
+ }
143
+
144
+ if (errorFetchingFieldOrder) {
145
+ return (
146
+ <AddressComponentContainer>
147
+ <InlineNotification
148
+ style={{ margin: '0', minWidth: '100%' }}
149
+ kind="error"
150
+ lowContrast={true}
151
+ title={t('errorFetchingOrderedFields', 'Error occured fetching ordered fields for address hierarchy')}
152
+ />
153
+ </AddressComponentContainer>
154
+ );
155
+ }
156
+
157
+ return (
158
+ <AddressComponentContainer>
159
+ {useQuickSearch && <AddressSearchComponent addressLayout={orderedAddressFields} />}
160
+ {searchAddressByLevel ? (
161
+ <AddressHierarchyLevels orderedAddressFields={orderedAddressFields} />
162
+ ) : (
163
+ orderedAddressFields.map((attributes, index) => (
164
+ <Input
165
+ key={`combo_input_${index}`}
166
+ name={`address.${attributes.name}`}
167
+ labelText={t(attributes.label)}
168
+ id={attributes.name}
169
+ setSelectedValue={setSelectedValue}
170
+ selected={selected}
171
+ />
172
+ ))
173
+ )}
174
+ </AddressComponentContainer>
175
+ );
176
+ };
177
+
178
+ const AddressComponentContainer = ({ children }) => {
179
+ const { t } = useTranslation();
15
180
  return (
16
- <div className={`${styles.customField} ${styles.halfWidthInDesktopView}`}>
17
- <Field name={fieldDefinition.id}>
18
- {({ field, form: { touched, errors }, meta }) => {
19
- return (
20
- <Input
21
- id={fieldDefinition.id}
22
- labelText={t(`${fieldDefinition.label}`, `${fieldDefinition.label}`)}
23
- {...field}
24
- required={fieldDefinition.validation.required}
25
- />
26
- );
27
- }}
28
- </Field>
181
+ <div>
182
+ <h4 className={styles.productiveHeading02Light}>{t('addressHeader', 'Address')}</h4>
183
+ <div
184
+ style={{
185
+ paddingBottom: '5%',
186
+ }}>
187
+ {children}
188
+ </div>
29
189
  </div>
30
190
  );
31
191
  };
@@ -0,0 +1,68 @@
1
+ import React, { useCallback, useMemo } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { useAddressEntries, useAddressEntryFetchConfig } from './address-hierarchy.resource';
4
+ import { useField } from 'formik';
5
+ import ComboInput from '../../input/combo-input/combo-input.component';
6
+ import { InlineNotification } from '@carbon/react';
7
+
8
+ interface AddressHierarchyLevelsProps {
9
+ orderedAddressFields: Array<any>;
10
+ }
11
+
12
+ const AddressHierarchyLevels: React.FC<AddressHierarchyLevelsProps> = ({ orderedAddressFields }) => {
13
+ const { t } = useTranslation();
14
+
15
+ return (
16
+ <>
17
+ {orderedAddressFields.map((attribute) => (
18
+ <AddressComboBox key={attribute.id} attribute={attribute} />
19
+ ))}
20
+ </>
21
+ );
22
+ };
23
+
24
+ export default AddressHierarchyLevels;
25
+
26
+ interface AddressComboBoxProps {
27
+ attribute: {
28
+ id: string;
29
+ name: string;
30
+ value: string;
31
+ label: string;
32
+ };
33
+ }
34
+
35
+ const AddressComboBox: React.FC<AddressComboBoxProps> = ({ attribute }) => {
36
+ const { t } = useTranslation();
37
+ const [field, meta, helpers] = useField(`address.${attribute.name}`);
38
+ const { fetchEntriesForField, searchString, updateChildElements } = useAddressEntryFetchConfig(attribute.name);
39
+ const { entries } = useAddressEntries(fetchEntriesForField, searchString);
40
+ console.log(searchString);
41
+ const handleInputChange = useCallback((newValue) => {
42
+ helpers.setValue(newValue);
43
+ }, []);
44
+
45
+ const handleSelection = useCallback(
46
+ (selectedItem) => {
47
+ if (meta.value !== selectedItem) {
48
+ helpers.setValue(selectedItem);
49
+ updateChildElements();
50
+ }
51
+ },
52
+ [updateChildElements, helpers.setValue],
53
+ );
54
+
55
+ return (
56
+ <ComboInput
57
+ entries={entries}
58
+ handleSelection={handleSelection}
59
+ name={`address.${attribute.name}`}
60
+ fieldProps={{
61
+ ...field,
62
+ id: attribute.name,
63
+ labelText: `${attribute.label} (${t('optional', 'optional')})`,
64
+ }}
65
+ handleInputChange={handleInputChange}
66
+ />
67
+ );
68
+ };
@@ -0,0 +1,103 @@
1
+ import { FetchResponse, openmrsFetch } from '@openmrs/esm-framework';
2
+ import { useField } from 'formik';
3
+ import { useCallback, useContext, useEffect, useMemo } from 'react';
4
+ import useSWRImmutable from 'swr/immutable';
5
+ import { PatientRegistrationContext } from '../../patient-registration-context';
6
+
7
+ interface AddressFields {
8
+ addressField: string;
9
+ }
10
+
11
+ export function useOrderedAddressHierarchyLevels() {
12
+ const url = '/module/addresshierarchy/ajax/getOrderedAddressHierarchyLevels.form';
13
+ const { data, isLoading, error } = useSWRImmutable<FetchResponse<Array<AddressFields>>>(url, openmrsFetch);
14
+
15
+ const results = useMemo(
16
+ () => ({
17
+ orderedFields: data?.data?.map((field) => field.addressField),
18
+ isLoadingFieldOrder: isLoading,
19
+ errorFetchingFieldOrder: error,
20
+ }),
21
+ [data, isLoading, error],
22
+ );
23
+
24
+ return results;
25
+ }
26
+
27
+ export function useAddressEntries(fetchResults, searchString) {
28
+ const encodedSearchString = encodeURIComponent(searchString);
29
+ const { data, isLoading, error } = useSWRImmutable<FetchResponse<Array<{ name: string }>>>(
30
+ fetchResults
31
+ ? `module/addresshierarchy/ajax/getChildAddressHierarchyEntries.form?searchString=${encodedSearchString}`
32
+ : null,
33
+ openmrsFetch,
34
+ );
35
+
36
+ useEffect(() => {
37
+ if (error) {
38
+ console.error(error);
39
+ }
40
+ }, [error]);
41
+
42
+ const results = useMemo(
43
+ () => ({
44
+ entries: data?.data?.map((item) => item.name),
45
+ isLoadingAddressEntries: isLoading,
46
+ errorFetchingAddressEntries: error,
47
+ }),
48
+ [data, isLoading, error],
49
+ );
50
+ return results;
51
+ }
52
+
53
+ /**
54
+ * This hook is being used to fetch ordered address fields as configured in the address hierarchy
55
+ * This hook returns the valid search term for valid fields to get suitable entries for the field
56
+ * This also returns the function to reset the lower ordered fields if the value of a field is changed.
57
+ */
58
+ export function useAddressEntryFetchConfig(addressField: string) {
59
+ const { orderedFields, isLoadingFieldOrder, errorFetchingFieldOrder } = useOrderedAddressHierarchyLevels();
60
+ const { setFieldValue } = useContext(PatientRegistrationContext);
61
+ const [, { value: addressValues }] = useField('address');
62
+
63
+ const index = useMemo(
64
+ () => (!isLoadingFieldOrder ? orderedFields.findIndex((field) => field === addressField) : -1),
65
+ [orderedFields, addressField, isLoadingFieldOrder],
66
+ );
67
+
68
+ const addressFieldSearchConfig = useMemo(() => {
69
+ let fetchEntriesForField = true;
70
+ const previousSelectedFields = orderedFields?.slice(0, index) ?? [];
71
+ let previousSelectedValues = [];
72
+ for (const fieldName of previousSelectedFields) {
73
+ if (!addressValues[fieldName]) {
74
+ fetchEntriesForField = false;
75
+ break;
76
+ }
77
+ previousSelectedValues.push(addressValues[fieldName]);
78
+ }
79
+ return {
80
+ fetchEntriesForField,
81
+ searchString: previousSelectedValues.join('|'),
82
+ };
83
+ }, [orderedFields, index, addressValues]);
84
+
85
+ const updateChildElements = useCallback(() => {
86
+ if (isLoadingFieldOrder) {
87
+ return;
88
+ }
89
+ orderedFields.slice(index + 1).map((fieldName) => {
90
+ setFieldValue(`address.${fieldName}`, '');
91
+ });
92
+ }, [index, isLoadingFieldOrder, orderedFields, setFieldValue]);
93
+
94
+ const results = useMemo(
95
+ () => ({
96
+ ...addressFieldSearchConfig,
97
+ updateChildElements,
98
+ }),
99
+ [addressFieldSearchConfig, updateChildElements],
100
+ );
101
+
102
+ return results;
103
+ }
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Input } from '../../input/basic-input/input/input.component';
4
+ import styles from '../field.scss';
5
+ import { FieldDefinition } from '../../../config-schema';
6
+ import { Field } from 'formik';
7
+
8
+ export interface AddressFieldProps {
9
+ fieldDefinition: FieldDefinition;
10
+ }
11
+
12
+ export const AddressField: React.FC<AddressFieldProps> = ({ fieldDefinition }) => {
13
+ const { t } = useTranslation();
14
+
15
+ return (
16
+ <div className={`${styles.customField} ${styles.halfWidthInDesktopView}`}>
17
+ <Field name={fieldDefinition.id}>
18
+ {({ field, form: { touched, errors }, meta }) => {
19
+ return (
20
+ <Input
21
+ id={fieldDefinition.id}
22
+ labelText={t(`${fieldDefinition.label}`, `${fieldDefinition.label}`)}
23
+ {...field}
24
+ />
25
+ );
26
+ }}
27
+ </Field>
28
+ </div>
29
+ );
30
+ };
@@ -0,0 +1,140 @@
1
+ import React from 'react';
2
+ import { cleanup, render, screen } from '@testing-library/react';
3
+ import { AddressComponent } from '../address-field.component';
4
+ import { Formik, Form } from 'formik';
5
+ import { Resources, ResourcesContext } from '../../../../offline.resources';
6
+ import { PatientRegistrationContext } from '../../../patient-registration-context';
7
+ import { useConfig } from '@openmrs/esm-framework';
8
+ import { useOrderedAddressHierarchyLevels } from '../address-hierarchy.resource';
9
+ import { mockResponse1, mockResponse2, mockedOrderedFields } from './mocks';
10
+ import AddressHierarchyLevels from '../address-hierarchy-levels.component';
11
+
12
+ jest.mock('@openmrs/esm-framework', () => ({
13
+ ...jest.requireActual('@openmrs/esm-framework'),
14
+ useConfig: jest.fn(),
15
+ }));
16
+
17
+ jest.mock('../address-hierarchy.resource', () => ({
18
+ ...(jest.requireActual('../address-hierarchy.resource') as jest.Mock),
19
+ useOrderedAddressHierarchyLevels: jest.fn(),
20
+ }));
21
+
22
+ async function testAddressHierarchy(mockResponse) {
23
+ await render(
24
+ <ResourcesContext.Provider value={{ addressTemplate: mockResponse } as Resources}>
25
+ <Formik initialValues={{}} onSubmit={null}>
26
+ <Form>
27
+ <PatientRegistrationContext.Provider
28
+ value={{ setFieldValue: jest.fn(), setInitialFormValues: jest.fn(), values: { address: {} } }}>
29
+ <AddressComponent />
30
+ </PatientRegistrationContext.Provider>
31
+ </Form>
32
+ </Formik>
33
+ </ResourcesContext.Provider>,
34
+ );
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
+ }
55
+
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
+ }
66
+
67
+ describe('address hierarchy', () => {
68
+ beforeAll(() => {
69
+ (useOrderedAddressHierarchyLevels as jest.Mock).mockImplementation(() => ({
70
+ orderedFields: mockedOrderedFields,
71
+ isLoadingFieldOrder: false,
72
+ errorFetchingFieldOrder: null,
73
+ }));
74
+ });
75
+
76
+ beforeEach(cleanup);
77
+
78
+ it('renders text input fields matching addressTemplate config', async () => {
79
+ (useConfig as jest.Mock).mockImplementation(() => ({
80
+ fieldConfigurations: {
81
+ address: {
82
+ useAddressHierarchy: {
83
+ enabled: false,
84
+ useQuickSearch: false,
85
+ searchAddressByLevel: false,
86
+ },
87
+ },
88
+ },
89
+ }));
90
+ testAddressHierarchy(mockResponse1);
91
+ // For cleaning up the input fields generated in first render
92
+ cleanup();
93
+ testAddressHierarchy(mockResponse2);
94
+ });
95
+
96
+ it('renders combo input fields matching addressTemplate config', async () => {
97
+ (useConfig as jest.Mock).mockImplementation(() => ({
98
+ fieldConfigurations: {
99
+ address: {
100
+ useAddressHierarchy: {
101
+ enabled: true,
102
+ useQuickSearch: true,
103
+ searchAddressByLevel: false,
104
+ },
105
+ },
106
+ },
107
+ }));
108
+
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();
118
+ });
119
+
120
+ it('renders combo input fields matching addressTemplate config and ordered fields', async () => {
121
+ (useConfig as jest.Mock).mockImplementation(() => ({
122
+ fieldConfigurations: {
123
+ address: {
124
+ useAddressHierarchy: {
125
+ enabled: true,
126
+ useQuickSearch: false,
127
+ searchAddressByLevel: true,
128
+ },
129
+ },
130
+ },
131
+ }));
132
+
133
+ testAddressHierarchy(mockResponse1);
134
+ testInputFieldOrder();
135
+ // For cleaning up the input fields generated in first render
136
+ cleanup();
137
+ testAddressHierarchy(mockResponse2);
138
+ testInputFieldOrder();
139
+ });
140
+ });
@@ -1,17 +1,4 @@
1
- import React from 'react';
2
- import { render, screen } from '@testing-library/react';
3
- import { AddressHierarchy } from './address-hierarchy.component';
4
- import { Formik, Form } from 'formik';
5
- import { Resources, ResourcesContext } from '../../../offline.resources';
6
- import { PatientRegistrationContext } from '../../patient-registration-context';
7
- import { useConfig } from '@openmrs/esm-framework';
8
-
9
- jest.mock('@openmrs/esm-framework', () => ({
10
- ...jest.requireActual('@openmrs/esm-framework'),
11
- useConfig: jest.fn(),
12
- }));
13
-
14
- const mockResponse1 = {
1
+ export const mockResponse1 = {
15
2
  results: [
16
3
  {
17
4
  value:
@@ -44,7 +31,7 @@ const mockResponse1 = {
44
31
  ],
45
32
  };
46
33
 
47
- const mockResponse2 = {
34
+ export const mockResponse2 = {
48
35
  results: [
49
36
  {
50
37
  value:
@@ -114,68 +101,4 @@ const mockResponse2 = {
114
101
  ],
115
102
  };
116
103
 
117
- async function testAddressHierarchy(mockResponse) {
118
- await render(
119
- <ResourcesContext.Provider value={{ addressTemplate: mockResponse } as Resources}>
120
- <Formik initialValues={{}} onSubmit={null}>
121
- <Form>
122
- <PatientRegistrationContext.Provider value={{ setFieldValue: jest.fn() }}>
123
- <AddressHierarchy />
124
- </PatientRegistrationContext.Provider>
125
- </Form>
126
- </Formik>
127
- </ResourcesContext.Provider>,
128
- );
129
- const countryInput = screen.getByLabelText('Country (optional)');
130
- expect(countryInput).toBeInTheDocument();
131
- expect(countryInput).toHaveAttribute('name', 'address.country');
132
- const stateInput = screen.getByLabelText('State (optional)');
133
- expect(stateInput).toBeInTheDocument();
134
- expect(stateInput).toHaveAttribute('name', 'address.stateProvince');
135
- const cityInput = screen.getByLabelText('City (optional)');
136
- expect(cityInput).toBeInTheDocument();
137
- expect(cityInput).toHaveAttribute('name', 'address.cityVillage');
138
- const address1Input = screen.getByLabelText('Address line 1 (optional)');
139
- expect(address1Input).toBeInTheDocument();
140
- expect(address1Input).toHaveAttribute('name', 'address.address1');
141
- const address2Input = screen.getByLabelText('Address line 2 (optional)');
142
- expect(address2Input).toBeInTheDocument();
143
- expect(address2Input).toHaveAttribute('name', 'address.address2');
144
- const postalCodeInput = screen.getByLabelText('Postcode (optional)');
145
- expect(postalCodeInput).toBeInTheDocument();
146
- expect(postalCodeInput).toHaveAttribute('name', 'address.postalCode');
147
- }
148
-
149
- describe('address hierarchy', () => {
150
- it('renders text input fields matching addressTemplate config', async () => {
151
- (useConfig as jest.Mock).mockImplementation(() => ({
152
- fieldConfigurations: {
153
- address: {
154
- useAddressHierarchy: {
155
- enabled: false,
156
- useQuickSearch: false,
157
- searchAddressByLevel: false,
158
- },
159
- },
160
- },
161
- }));
162
- testAddressHierarchy(mockResponse1);
163
- testAddressHierarchy(mockResponse2);
164
- });
165
-
166
- it('renders combo input fields matching addressTemplate config', async () => {
167
- (useConfig as jest.Mock).mockImplementation(() => ({
168
- fieldConfigurations: {
169
- address: {
170
- useAddressHierarchy: {
171
- enabled: true,
172
- useQuickSearch: false,
173
- searchAddressByLevel: true,
174
- },
175
- },
176
- },
177
- }));
178
- testAddressHierarchy(mockResponse1);
179
- testAddressHierarchy(mockResponse2);
180
- });
181
- });
104
+ export const mockedOrderedFields = ['country', 'stateProvince', 'cityVillage', 'postalCode', 'address1', 'address2'];
@@ -1,7 +1,7 @@
1
1
  import { useConfig } from '@openmrs/esm-framework';
2
2
  import React from 'react';
3
3
  import { RegistrationConfig } from '../../config-schema';
4
- import { AddressField } from './address/address-field.component';
4
+ import { AddressField } from './address/custom-address-field.component';
5
5
  import { ObsField } from './obs/obs-field.component';
6
6
  import { PersonAttributeField } from './person-attributes/person-attribute-field.component';
7
7
 
@@ -6,7 +6,7 @@ import { DobField } from './dob/dob.component';
6
6
  import { reportError, useConfig } from '@openmrs/esm-framework';
7
7
  import { builtInFields, RegistrationConfig } from '../../config-schema';
8
8
  import { CustomField } from './custom-field.component';
9
- import { AddressHierarchy } from './address/address-hierarchy.component';
9
+ import { AddressComponent } from './address/address-field.component';
10
10
 
11
11
  export interface FieldProps {
12
12
  name: string;
@@ -35,7 +35,7 @@ export function Field({ name }: FieldProps) {
35
35
  case 'dob':
36
36
  return <DobField />;
37
37
  case 'address':
38
- return <AddressHierarchy />;
38
+ return <AddressComponent />;
39
39
  case 'id':
40
40
  return <Identifiers />;
41
41
  default:
@@ -62,7 +62,7 @@ export const NameField = () => {
62
62
  {displayCapturePhoto && (
63
63
  <ExtensionSlot
64
64
  className={styles.photoExtension}
65
- extensionSlotName="capture-patient-photo-slot"
65
+ name="capture-patient-photo-slot"
66
66
  state={{ onCapturePhoto, initialState: currentPhoto }}
67
67
  />
68
68
  )}