@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.
- package/dist/144.js +1 -1
- package/dist/207.js +1 -1
- package/dist/207.js.map +1 -1
- package/dist/574.js +1 -1
- package/dist/59.js +1 -1
- package/dist/59.js.map +1 -1
- package/dist/68.js +1 -1
- package/dist/68.js.map +1 -1
- package/dist/821.js +1 -1
- package/dist/833.js +1 -0
- package/dist/876.js +1 -0
- package/dist/876.js.map +1 -0
- package/dist/{kenya-esm-patient-registration-app.js → kenyaemr-esm-patient-registration-app.js} +1 -1
- package/dist/{kenya-esm-patient-registration-app.js.buildmanifest.json → kenyaemr-esm-patient-registration-app.js.buildmanifest.json} +57 -35
- package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -0
- package/dist/{kenya-esm-patient-registration-app.old → kenyaemr-esm-patient-registration-app.old} +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/package.json +2 -2
- package/src/config-schema.ts +3 -13
- package/src/patient-registration/field/address/address-field.component.tsx +180 -20
- package/src/patient-registration/field/address/address-hierarchy-levels.component.tsx +68 -0
- package/src/patient-registration/field/address/address-hierarchy.resource.tsx +103 -0
- package/src/patient-registration/field/address/custom-address-field.component.tsx +30 -0
- package/src/patient-registration/field/address/tests/address-hierarchy.test.tsx +140 -0
- package/src/patient-registration/field/address/{address-hierarchy.test.tsx → tests/mocks.ts} +3 -80
- package/src/patient-registration/field/custom-field.component.tsx +1 -1
- package/src/patient-registration/field/field.component.tsx +2 -2
- package/src/patient-registration/field/name/name-field.component.tsx +1 -1
- package/src/patient-registration/input/combo-input/combo-input.component.tsx +106 -62
- package/src/patient-registration/input/combo-input/selection-tick.component.tsx +20 -0
- package/src/patient-registration/input/input.scss +6 -1
- package/src/patient-registration/patient-registration-context.ts +1 -0
- package/src/patient-registration/patient-registration-hooks.ts +1 -0
- package/src/patient-registration/patient-registration-utils.ts +3 -3
- package/src/patient-registration/patient-registration.component.tsx +1 -0
- package/src/patient-registration/patient-registration.scss +14 -0
- package/src/patient-registration/validation/patient-registration-validation.test.tsx +128 -114
- package/src/patient-verification/verification-modal/confirm-prompt.component.tsx +1 -0
- package/src/root.component.tsx +1 -1
- package/translations/en.json +3 -5
- package/translations/he.json +89 -0
- package/dist/822.js +0 -1
- package/dist/822.js.map +0 -1
- package/dist/kenya-esm-patient-registration-app.js.map +0 -1
- package/src/patient-registration/field/address/address-hierarchy.component.tsx +0 -143
- package/src/patient-registration/input/combo-input/combo-input.test.tsx +0 -43
- /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 {
|
|
3
|
+
import { ResourcesContext } from '../../../offline.resources';
|
|
4
|
+
import { SkeletonText, InlineNotification } from '@carbon/react';
|
|
4
5
|
import styles from '../field.scss';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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
|
-
|
|
9
|
-
|
|
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
|
|
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
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
+
});
|
package/src/patient-registration/field/address/{address-hierarchy.test.tsx → tests/mocks.ts}
RENAMED
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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 {
|
|
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 <
|
|
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
|
-
|
|
65
|
+
name="capture-patient-photo-slot"
|
|
66
66
|
state={{ onCapturePhoto, initialState: currentPhoto }}
|
|
67
67
|
/>
|
|
68
68
|
)}
|