@olaboot/esm-patient-registration-app 9.2.0 → 10.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/1339.js +1 -0
- package/dist/1339.js.map +1 -0
- package/dist/1480.js +1 -0
- package/dist/1480.js.map +1 -0
- package/dist/1646.js +1 -0
- package/dist/1646.js.map +1 -0
- package/dist/1789.js +1 -0
- package/dist/1789.js.map +1 -0
- package/dist/1869.js +1 -0
- package/dist/1869.js.map +1 -0
- package/dist/1877.js +1 -0
- package/dist/1877.js.map +1 -0
- package/dist/2317.js +1 -0
- package/dist/2317.js.map +1 -0
- package/dist/2416.js +1 -0
- package/dist/2416.js.map +1 -0
- package/dist/2747.js +1 -0
- package/dist/2747.js.map +1 -0
- package/dist/282.js +1 -0
- package/dist/282.js.map +1 -0
- package/dist/2881.js +1 -0
- package/dist/2881.js.map +1 -0
- package/dist/3378.js +1 -0
- package/dist/3378.js.map +1 -0
- package/dist/3720.js +1 -0
- package/dist/3720.js.map +1 -0
- package/dist/3906.js +1 -0
- package/dist/3906.js.map +1 -0
- package/dist/3963.js +1 -0
- package/dist/3963.js.map +1 -0
- package/dist/3989.js +1 -0
- package/dist/3989.js.map +1 -0
- package/dist/4106.js +1 -0
- package/dist/4106.js.map +1 -0
- package/dist/4111.js +1 -0
- package/dist/4111.js.map +1 -0
- package/dist/434.js +1 -0
- package/dist/434.js.map +1 -0
- package/dist/4348.js +1 -0
- package/dist/4348.js.map +1 -0
- package/dist/4383.js +1 -0
- package/dist/4383.js.map +1 -0
- package/dist/4658.js +1 -0
- package/dist/4658.js.map +1 -0
- package/dist/466.js +1 -0
- package/dist/466.js.map +1 -0
- package/dist/4928.js +1 -0
- package/dist/4928.js.map +1 -0
- package/dist/5117.js +1 -0
- package/dist/5117.js.map +1 -0
- package/dist/5132.js +1 -0
- package/dist/5132.js.map +1 -0
- package/dist/5145.js +1 -0
- package/dist/5145.js.map +1 -0
- package/dist/5208.js +43 -0
- package/dist/5208.js.map +1 -0
- package/dist/527.js +1 -0
- package/dist/527.js.map +1 -0
- package/dist/5280.js +1 -0
- package/dist/5280.js.map +1 -0
- package/dist/5338.js +6 -0
- package/dist/5338.js.map +1 -0
- package/dist/5503.js +1 -0
- package/dist/5503.js.map +1 -0
- package/dist/555.js +1 -0
- package/dist/555.js.map +1 -0
- package/dist/556.js +1 -0
- package/dist/556.js.map +1 -0
- package/dist/5644.js +1 -0
- package/dist/5644.js.map +1 -0
- package/dist/5697.js +1 -0
- package/dist/{4024.js.map → 5697.js.map} +1 -1
- package/dist/5940.js +1 -0
- package/dist/5940.js.map +1 -0
- package/dist/6047.js +1 -0
- package/dist/6047.js.map +1 -0
- package/dist/6371.js +1 -0
- package/dist/6371.js.map +1 -0
- package/dist/6377.js +1 -0
- package/dist/6377.js.map +1 -0
- package/dist/6388.js +1 -0
- package/dist/6388.js.map +1 -0
- package/dist/6444.js +1 -0
- package/dist/6444.js.map +1 -0
- package/dist/6508.js +1 -0
- package/dist/6508.js.map +1 -0
- package/dist/6724.js +1 -0
- package/dist/6724.js.map +1 -0
- package/dist/689.js +1 -0
- package/dist/689.js.map +1 -0
- package/dist/6904.js +1 -0
- package/dist/6904.js.map +1 -0
- package/dist/7045.js +1 -0
- package/dist/7045.js.map +1 -0
- package/dist/7175.js +1 -0
- package/dist/7175.js.map +1 -0
- package/dist/7182.js +1 -0
- package/dist/7182.js.map +1 -0
- package/dist/7649.js +1 -0
- package/dist/7649.js.map +1 -0
- package/dist/7742.js +1 -0
- package/dist/7742.js.map +1 -0
- package/dist/7912.js +1 -0
- package/dist/7912.js.map +1 -0
- package/dist/8358.js +1 -0
- package/dist/8358.js.map +1 -0
- package/dist/8359.js +1 -0
- package/dist/8359.js.map +1 -0
- package/dist/8695.js +1 -0
- package/dist/8695.js.map +1 -0
- package/dist/903.js +1 -0
- package/dist/903.js.map +1 -0
- package/dist/9061.js +1 -0
- package/dist/9061.js.map +1 -0
- package/dist/9072.js +1 -0
- package/dist/9072.js.map +1 -0
- package/dist/9397.js +1 -0
- package/dist/9397.js.map +1 -0
- package/dist/9712.js +1 -0
- package/dist/9712.js.map +1 -0
- package/dist/9771.js +1 -0
- package/dist/9771.js.map +1 -0
- package/dist/9806.js +1 -0
- package/dist/9806.js.map +1 -0
- package/dist/9816.js +1 -0
- package/dist/9816.js.map +1 -0
- package/dist/main.js +7 -6
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-registration-app.js +6 -0
- package/dist/{olaboot-esm-patient-registration-app.js.buildmanifest.json → openmrs-esm-patient-registration-app.js.buildmanifest.json} +540 -455
- package/dist/openmrs-esm-patient-registration-app.js.map +1 -0
- package/dist/routes.json +1 -1
- package/package.json +8 -9
- package/src/add-patient-link.extension.tsx +3 -2
- package/src/add-patient-link.test.tsx +2 -1
- package/src/config-schema.ts +1 -1
- package/src/index.ts +2 -24
- package/src/nav-link.test.tsx +1 -0
- package/src/offline.resources.ts +97 -31
- package/src/patient-registration/before-save-prompt.test.tsx +199 -0
- package/src/patient-registration/field/__mocks__/field.resource.ts +8 -7
- package/src/patient-registration/field/address/address-field.component.tsx +10 -13
- package/src/patient-registration/field/address/address-hierarchy-levels.component.tsx +6 -1
- package/src/patient-registration/field/address/address-hierarchy.test.tsx +191 -198
- package/src/patient-registration/field/address/address-search.component.tsx +20 -8
- package/src/patient-registration/field/address/address-search.scss +19 -2
- package/src/patient-registration/field/address/address-search.test.tsx +249 -57
- package/src/patient-registration/field/address/custom-address-field.component.tsx +1 -1
- package/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx +1 -1
- package/src/patient-registration/field/cause-of-death/cause-of-death.test.tsx +251 -0
- package/src/patient-registration/field/custom-field.component.tsx +1 -1
- package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +1 -1
- package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.test.tsx +144 -0
- package/src/patient-registration/field/dob/dob.component.tsx +2 -2
- package/src/patient-registration/field/dob/dob.test.tsx +370 -54
- package/src/patient-registration/field/field.component.tsx +1 -1
- package/src/patient-registration/field/field.resource.ts +2 -2
- package/src/patient-registration/field/field.test.tsx +25 -22
- package/src/patient-registration/field/gender/gender-field.test.tsx +240 -54
- package/src/patient-registration/field/id/id-field.component.tsx +15 -5
- package/src/patient-registration/field/id/id-field.test.tsx +103 -47
- package/src/patient-registration/field/id/identifier-selection-overlay.test.tsx +346 -0
- package/src/patient-registration/field/name/name-field.component.tsx +2 -2
- package/src/patient-registration/field/name/name-field.test.tsx +282 -0
- package/src/patient-registration/field/obs/obs-field.test.tsx +294 -118
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +172 -108
- package/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx +3 -3
- package/src/patient-registration/field/person-attributes/location-person-attribute-field.resource.tsx +2 -5
- package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +2 -2
- package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +249 -131
- package/src/patient-registration/field/person-attributes/person-attributes.resource.ts +1 -1
- package/src/patient-registration/field/person-attributes/text-person-attribute-field.test.tsx +98 -70
- package/src/patient-registration/field/phone/phone-field.test.tsx +100 -0
- package/src/patient-registration/form-manager.test.ts +6 -5
- package/src/patient-registration/form-manager.ts +5 -2
- package/src/patient-registration/input/basic-input/input/input.component.tsx +3 -121
- package/src/patient-registration/input/basic-input/input/input.test.tsx +151 -51
- package/src/patient-registration/input/basic-input/select/select-input.test.tsx +113 -33
- package/src/patient-registration/input/combo-input/combo-input.component.tsx +60 -24
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.component.tsx +10 -101
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +144 -108
- package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +241 -177
- package/src/patient-registration/input/custom-input/identifier/utils.test.ts +47 -8
- package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +12 -12
- package/src/patient-registration/input/dummy-data/dummy-data-input.test.tsx +52 -20
- package/src/patient-registration/input/input.scss +1 -2
- package/src/patient-registration/patient-registration-context.ts +5 -3
- package/src/patient-registration/patient-registration-hooks.ts +4 -12
- package/src/patient-registration/patient-registration-utils.test.ts +2 -1
- package/src/patient-registration/patient-registration-utils.ts +2 -98
- package/src/patient-registration/patient-registration.component.tsx +50 -46
- package/src/patient-registration/patient-registration.resource.test.tsx +4 -7
- package/src/patient-registration/patient-registration.resource.ts +1 -4
- package/src/patient-registration/patient-registration.scss +16 -3
- package/src/patient-registration/patient-registration.test.tsx +99 -65
- package/src/patient-registration/patient-registration.types.ts +17 -28
- package/src/patient-registration/section/death-info/death-info-section.test.tsx +130 -34
- package/src/patient-registration/section/demographics/demographics-section.test.tsx +122 -68
- package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +15 -15
- package/src/patient-registration/section/patient-relationships/relationships-section.test.tsx +278 -84
- package/src/patient-registration/section/section-wrapper.component.tsx +1 -1
- package/src/patient-registration/ui-components/overlay/overlay.test.tsx +104 -0
- package/src/patient-registration/validation/patient-registration-validation.test.ts +2 -1
- package/src/patient-registration/validation/patient-registration-validation.ts +9 -3
- package/src/root.component.tsx +2 -5
- package/src/widgets/cancel-patient-edit.test.tsx +48 -11
- package/src/widgets/delete-identifier-confirmation.test.tsx +77 -24
- package/src/widgets/edit-patient-details-button.component.tsx +14 -18
- package/src/widgets/edit-patient-details-button.scss +2 -2
- package/src/widgets/edit-patient-details-button.test.tsx +11 -13
- package/translations/am.json +9 -4
- package/translations/ar.json +9 -4
- package/translations/ar_SY.json +9 -4
- package/translations/bn.json +9 -4
- package/translations/cs.json +9 -4
- package/translations/de.json +120 -115
- package/translations/en.json +9 -4
- package/translations/en_US.json +9 -4
- package/translations/es.json +9 -4
- package/translations/es_MX.json +9 -4
- package/translations/fr.json +9 -4
- package/translations/he.json +9 -4
- package/translations/hi.json +9 -4
- package/translations/hi_IN.json +9 -4
- package/translations/id.json +9 -4
- package/translations/it.json +9 -4
- package/translations/ka.json +9 -4
- package/translations/km.json +9 -4
- package/translations/ku.json +9 -4
- package/translations/ky.json +9 -4
- package/translations/lg.json +9 -4
- package/translations/ne.json +9 -4
- package/translations/pl.json +9 -4
- package/translations/pt.json +9 -4
- package/translations/pt_BR.json +10 -5
- package/translations/qu.json +9 -4
- package/translations/ro_RO.json +9 -4
- package/translations/ru_RU.json +9 -4
- package/translations/si.json +9 -4
- package/translations/sq.json +9 -4
- package/translations/sw.json +9 -4
- package/translations/sw_KE.json +9 -4
- package/translations/tr.json +9 -4
- package/translations/tr_TR.json +9 -4
- package/translations/uk.json +9 -4
- package/translations/uz.json +9 -4
- package/translations/uz@Latn.json +9 -4
- package/translations/uz_UZ.json +9 -4
- package/translations/vi.json +9 -4
- package/translations/zh.json +50 -45
- package/translations/zh_CN.json +9 -4
- package/translations/zh_TW.json +9 -4
- package/vitest.config.ts +4 -0
- package/ADDRESS_CONFIGURATION.md +0 -152
- package/IDENTIFIER_CONFIGURATION.md +0 -142
- package/IMPLEMENTATION_SUMMARY.md +0 -111
- package/QUICK_START.md +0 -95
- package/address-required-fields-config.json +0 -26
- package/dist/126.js +0 -1
- package/dist/15.js +0 -1
- package/dist/1564.js +0 -1
- package/dist/1567.js +0 -1
- package/dist/1845.js +0 -1
- package/dist/1953.js +0 -1
- package/dist/200.js +0 -1
- package/dist/200.js.map +0 -1
- package/dist/215.js +0 -1
- package/dist/2178.js +0 -1
- package/dist/250.js +0 -1
- package/dist/250.js.map +0 -1
- package/dist/2523.js +0 -1
- package/dist/2523.js.map +0 -1
- package/dist/2566.js +0 -1
- package/dist/2586.js +0 -1
- package/dist/2586.js.map +0 -1
- package/dist/2716.js +0 -1
- package/dist/2716.js.map +0 -1
- package/dist/2759.js +0 -1
- package/dist/2821.js +0 -6
- package/dist/2821.js.map +0 -1
- package/dist/3089.js +0 -1
- package/dist/3089.js.map +0 -1
- package/dist/3230.js +0 -1
- package/dist/3441.js +0 -1
- package/dist/3565.js +0 -1
- package/dist/3571.js +0 -1
- package/dist/3571.js.map +0 -1
- package/dist/3746.js +0 -1
- package/dist/3925.js +0 -1
- package/dist/3946.js +0 -1
- package/dist/4024.js +0 -1
- package/dist/4744.js +0 -1
- package/dist/4744.js.map +0 -1
- package/dist/4809.js +0 -1
- package/dist/4894.js +0 -1
- package/dist/4970.js +0 -1
- package/dist/4970.js.map +0 -1
- package/dist/5130.js +0 -1
- package/dist/5187.js +0 -1
- package/dist/5491.js +0 -1
- package/dist/5491.js.map +0 -1
- package/dist/5595.js +0 -1
- package/dist/5961.js +0 -1
- package/dist/6133.js +0 -1
- package/dist/634.js +0 -1
- package/dist/634.js.map +0 -1
- package/dist/6456.js +0 -1
- package/dist/6466.js +0 -1
- package/dist/6613.js +0 -1
- package/dist/6783.js +0 -1
- package/dist/7073.js +0 -38
- package/dist/7073.js.map +0 -1
- package/dist/7154.js +0 -1
- package/dist/7154.js.map +0 -1
- package/dist/7348.js +0 -1
- package/dist/7439.js +0 -1
- package/dist/7439.js.map +0 -1
- package/dist/7543.js +0 -1
- package/dist/7607.js +0 -1
- package/dist/772.js +0 -1
- package/dist/7984.js +0 -1
- package/dist/7984.js.map +0 -1
- package/dist/8538.js +0 -1
- package/dist/8538.js.map +0 -1
- package/dist/8599.js +0 -1
- package/dist/8727.js +0 -1
- package/dist/8847.js +0 -1
- package/dist/9015.js +0 -1
- package/dist/906.js +0 -1
- package/dist/9065.js +0 -1
- package/dist/9182.js +0 -1
- package/dist/9339.js +0 -1
- package/dist/9453.js +0 -1
- package/dist/9833.js +0 -1
- package/dist/9833.js.map +0 -1
- package/dist/9856.js +0 -1
- package/dist/9856.js.map +0 -1
- package/dist/9920.js +0 -1
- package/dist/9938.js +0 -1
- package/dist/9943.js +0 -1
- package/dist/9943.js.map +0 -1
- package/dist/olaboot-esm-patient-registration-app.js +0 -5
- package/dist/olaboot-esm-patient-registration-app.js.map +0 -1
- package/example-config.json +0 -14
- package/jest.config.js +0 -3
- package/src/resource.ts +0 -12
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
2
4
|
import { Form, Formik } from 'formik';
|
|
3
|
-
import { render, screen } from '@testing-library/react';
|
|
5
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
4
6
|
import { type FieldDefinition } from '../../../config-schema';
|
|
5
7
|
import { usePersonAttributeType } from './person-attributes.resource';
|
|
6
8
|
import { useConceptAnswers } from '../field.resource';
|
|
7
9
|
import { PersonAttributeField } from './person-attribute-field.component';
|
|
10
|
+
import { initialFormValues } from '../../patient-registration.component';
|
|
11
|
+
import { type FormValues } from '../../patient-registration.types';
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
...
|
|
11
|
-
usePersonAttributeType:
|
|
13
|
+
vi.mock('./person-attributes.resource', async () => ({
|
|
14
|
+
...((await vi.importActual('./person-attributes.resource')) as object),
|
|
15
|
+
usePersonAttributeType: vi.fn(),
|
|
12
16
|
}));
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
...
|
|
16
|
-
useConceptAnswers:
|
|
18
|
+
vi.mock('../field.resource', async () => ({
|
|
19
|
+
...((await vi.importActual('../field.resource')) as object),
|
|
20
|
+
useConceptAnswers: vi.fn(),
|
|
17
21
|
}));
|
|
18
22
|
|
|
19
|
-
const mockUsePersonAttributeType =
|
|
20
|
-
const mockUseConceptAnswers =
|
|
23
|
+
const mockUsePersonAttributeType = vi.mocked(usePersonAttributeType);
|
|
24
|
+
const mockUseConceptAnswers = vi.mocked(useConceptAnswers);
|
|
21
25
|
|
|
22
26
|
const mockPersonAttributeType = {
|
|
23
27
|
format: 'java.lang.String',
|
|
@@ -27,17 +31,38 @@ const mockPersonAttributeType = {
|
|
|
27
31
|
description: 'The person who referred the patient',
|
|
28
32
|
};
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Helper to render PersonAttributeField with Formik render props for state-dependent tests.
|
|
36
|
+
*/
|
|
37
|
+
const renderPersonAttributeFieldWithFormik = (
|
|
38
|
+
fieldDefinition: FieldDefinition,
|
|
39
|
+
initialValues: Partial<FormValues> = {},
|
|
40
|
+
options?: { enableReinitialize?: boolean },
|
|
41
|
+
) => {
|
|
42
|
+
const defaultValues = {
|
|
43
|
+
attributes: {},
|
|
44
|
+
...initialValues,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
let formValuesRef: FormValues = { ...initialFormValues, ...defaultValues } as FormValues;
|
|
48
|
+
|
|
49
|
+
const utils = render(
|
|
50
|
+
<Formik initialValues={defaultValues} onSubmit={() => {}} enableReinitialize={options?.enableReinitialize}>
|
|
51
|
+
{({ setFieldValue, values, setFieldTouched }) => {
|
|
52
|
+
formValuesRef = { ...initialFormValues, ...values } as FormValues;
|
|
53
|
+
return (
|
|
54
|
+
<Form>
|
|
55
|
+
<PersonAttributeField fieldDefinition={fieldDefinition} />
|
|
56
|
+
</Form>
|
|
57
|
+
);
|
|
58
|
+
}}
|
|
59
|
+
</Formik>,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
...utils,
|
|
64
|
+
getFormValues: () => formValuesRef,
|
|
65
|
+
};
|
|
41
66
|
};
|
|
42
67
|
|
|
43
68
|
describe('PersonAttributeField', () => {
|
|
@@ -49,145 +74,238 @@ describe('PersonAttributeField', () => {
|
|
|
49
74
|
});
|
|
50
75
|
});
|
|
51
76
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
77
|
+
describe('Loading and error states', () => {
|
|
78
|
+
it('renders a skeleton if attribute type is loading', async () => {
|
|
79
|
+
mockUsePersonAttributeType.mockReturnValue({
|
|
80
|
+
data: null,
|
|
81
|
+
isLoading: true,
|
|
82
|
+
error: null,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const fieldDefinition: FieldDefinition = {
|
|
86
|
+
id: 'referredBy',
|
|
87
|
+
uuid: 'attribute-uuid',
|
|
88
|
+
label: 'Attribute',
|
|
89
|
+
showHeading: true,
|
|
90
|
+
type: 'person attribute',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
renderPersonAttributeFieldWithFormik(fieldDefinition);
|
|
94
|
+
|
|
95
|
+
await screen.findByRole('heading', { name: /attribute/i });
|
|
96
|
+
expect(screen.queryByLabelText(/referred by/i)).not.toBeInTheDocument();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('renders an error notification if unable to fetch attribute type', () => {
|
|
100
|
+
mockUsePersonAttributeType.mockReturnValue({
|
|
101
|
+
data: null,
|
|
102
|
+
isLoading: false,
|
|
103
|
+
error: new Error('Failed to fetch attribute type'),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const fieldDefinition: FieldDefinition = {
|
|
107
|
+
id: 'referredBy',
|
|
108
|
+
uuid: 'attribute-uuid',
|
|
109
|
+
label: 'Attribute',
|
|
110
|
+
showHeading: false,
|
|
111
|
+
type: 'person attribute',
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
renderPersonAttributeFieldWithFormik(fieldDefinition);
|
|
115
|
+
|
|
116
|
+
// Check for the specific error message text
|
|
117
|
+
expect(screen.getByText(/Unable to fetch person attribute type/i)).toBeInTheDocument();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('renders an error notification if attribute type has unknown format', () => {
|
|
121
|
+
mockUsePersonAttributeType.mockReturnValue({
|
|
122
|
+
data: { ...mockPersonAttributeType, format: 'unknown' },
|
|
123
|
+
isLoading: false,
|
|
124
|
+
error: null,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const fieldDefinition: FieldDefinition = {
|
|
128
|
+
id: 'referredby',
|
|
129
|
+
label: 'Referred by',
|
|
130
|
+
type: 'person attribute',
|
|
131
|
+
uuid: '4dd56a75-14ab-4148-8700-1f4f704dc5b0',
|
|
132
|
+
validation: {
|
|
133
|
+
matches: '',
|
|
134
|
+
required: true,
|
|
135
|
+
},
|
|
136
|
+
showHeading: true,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
renderPersonAttributeFieldWithFormik(fieldDefinition);
|
|
140
|
+
|
|
141
|
+
// Check for the specific error message text
|
|
142
|
+
expect(screen.getByText(/Patient attribute type has unknown format/i)).toBeInTheDocument();
|
|
143
|
+
expect(screen.getByText(/unknown/i)).toBeInTheDocument();
|
|
144
|
+
});
|
|
65
145
|
});
|
|
66
146
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
147
|
+
describe('Heading display', () => {
|
|
148
|
+
it('shows heading when showHeading is true', () => {
|
|
149
|
+
const fieldDefinition: FieldDefinition = {
|
|
150
|
+
id: 'referredby',
|
|
151
|
+
label: 'Referred by',
|
|
152
|
+
type: 'person attribute',
|
|
153
|
+
uuid: '4dd56a75-14ab-4148-8700-1f4f704dc5b0',
|
|
154
|
+
validation: {
|
|
155
|
+
matches: '',
|
|
156
|
+
required: true,
|
|
157
|
+
},
|
|
158
|
+
showHeading: true,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
renderPersonAttributeFieldWithFormik(fieldDefinition);
|
|
72
162
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
163
|
+
expect(screen.getByRole('heading', { name: /referred by/i })).toBeInTheDocument();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('does not show heading when showHeading is false', () => {
|
|
167
|
+
const fieldDefinition: FieldDefinition = {
|
|
168
|
+
id: 'referredby',
|
|
169
|
+
label: 'Referred by',
|
|
170
|
+
type: 'person attribute',
|
|
171
|
+
uuid: '4dd56a75-14ab-4148-8700-1f4f704dc5b0',
|
|
172
|
+
validation: {
|
|
173
|
+
matches: '',
|
|
174
|
+
required: true,
|
|
175
|
+
},
|
|
176
|
+
showHeading: false,
|
|
177
|
+
};
|
|
80
178
|
|
|
81
|
-
|
|
179
|
+
renderPersonAttributeFieldWithFormik(fieldDefinition);
|
|
180
|
+
|
|
181
|
+
expect(screen.queryByRole('heading')).not.toBeInTheDocument();
|
|
182
|
+
});
|
|
82
183
|
});
|
|
83
184
|
|
|
84
|
-
|
|
85
|
-
|
|
185
|
+
describe('String format (text input)', () => {
|
|
186
|
+
const textFieldDefinition: FieldDefinition = {
|
|
86
187
|
id: 'referredby',
|
|
87
|
-
...fieldDefinition,
|
|
88
188
|
label: 'Referred by',
|
|
189
|
+
type: 'person attribute',
|
|
190
|
+
uuid: '4dd56a75-14ab-4148-8700-1f4f704dc5b0',
|
|
191
|
+
validation: {
|
|
192
|
+
matches: '',
|
|
193
|
+
required: false,
|
|
194
|
+
},
|
|
195
|
+
showHeading: true,
|
|
89
196
|
};
|
|
90
197
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
isLoading: false,
|
|
94
|
-
error: null,
|
|
95
|
-
});
|
|
198
|
+
it('renders a text input field for String format', () => {
|
|
199
|
+
renderPersonAttributeFieldWithFormik(textFieldDefinition);
|
|
96
200
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
{ uuid: '1', display: 'Option 1' },
|
|
100
|
-
{ uuid: '2', display: 'Option 2' },
|
|
101
|
-
],
|
|
102
|
-
error: null,
|
|
103
|
-
isLoading: false,
|
|
201
|
+
const input = screen.getByRole('textbox', { name: /referred by/i });
|
|
202
|
+
expect(input).toBeInTheDocument();
|
|
104
203
|
});
|
|
105
204
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<PersonAttributeField fieldDefinition={fieldDefinition} />
|
|
110
|
-
</Form>
|
|
111
|
-
</Formik>,
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
const input = screen.getByLabelText(/Referred by/i) as HTMLInputElement;
|
|
115
|
-
expect(input).toBeInTheDocument();
|
|
116
|
-
expect(input.type).toBe('select-one');
|
|
117
|
-
expect(screen.getByText('Option 1')).toBeInTheDocument();
|
|
118
|
-
expect(screen.getByText('Option 2')).toBeInTheDocument();
|
|
119
|
-
});
|
|
205
|
+
it('allows user to enter text', async () => {
|
|
206
|
+
const user = userEvent.setup();
|
|
207
|
+
const { getFormValues } = renderPersonAttributeFieldWithFormik(textFieldDefinition);
|
|
120
208
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
209
|
+
const input = screen.getByRole('textbox', { name: /referred by/i }) as HTMLInputElement;
|
|
210
|
+
await user.type(input, 'Dr. Smith');
|
|
211
|
+
|
|
212
|
+
await waitFor(() => {
|
|
213
|
+
expect(getFormValues().attributes['4dd56a75-14ab-4148-8700-1f4f704dc5b0']).toBe('Dr. Smith');
|
|
214
|
+
});
|
|
126
215
|
});
|
|
127
216
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
217
|
+
it('renders as required when configured', () => {
|
|
218
|
+
renderPersonAttributeFieldWithFormik({
|
|
219
|
+
...textFieldDefinition,
|
|
220
|
+
validation: {
|
|
221
|
+
matches: '',
|
|
222
|
+
required: true,
|
|
223
|
+
},
|
|
224
|
+
});
|
|
135
225
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
226
|
+
const input = screen.getByRole('textbox', { name: /referred by/i });
|
|
227
|
+
expect(input).toBeRequired();
|
|
228
|
+
});
|
|
139
229
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
230
|
+
it('renders as optional when not required', () => {
|
|
231
|
+
renderPersonAttributeFieldWithFormik(textFieldDefinition);
|
|
232
|
+
|
|
233
|
+
const input = screen.getByRole('textbox', { name: /referred by/i });
|
|
234
|
+
expect(input).not.toBeRequired();
|
|
145
235
|
});
|
|
236
|
+
});
|
|
146
237
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
label: '
|
|
151
|
-
showHeading: false,
|
|
238
|
+
describe('Concept format (coded select)', () => {
|
|
239
|
+
const codedFieldDefinition: FieldDefinition = {
|
|
240
|
+
id: 'referredby',
|
|
241
|
+
label: 'Referred by',
|
|
152
242
|
type: 'person attribute',
|
|
243
|
+
uuid: '4dd56a75-14ab-4148-8700-1f4f704dc5b0',
|
|
244
|
+
answerConceptSetUuid: '6682d17f-0777-45e4-a39b-93f77eb3531c',
|
|
245
|
+
validation: {
|
|
246
|
+
matches: '',
|
|
247
|
+
required: false,
|
|
248
|
+
},
|
|
249
|
+
showHeading: true,
|
|
153
250
|
};
|
|
154
251
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
);
|
|
252
|
+
beforeEach(() => {
|
|
253
|
+
mockUsePersonAttributeType.mockReturnValue({
|
|
254
|
+
data: { ...mockPersonAttributeType, format: 'org.openmrs.Concept' },
|
|
255
|
+
isLoading: false,
|
|
256
|
+
error: null,
|
|
257
|
+
});
|
|
162
258
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
259
|
+
mockUseConceptAnswers.mockReturnValue({
|
|
260
|
+
data: [
|
|
261
|
+
{ uuid: '1', display: 'Option 1' },
|
|
262
|
+
{ uuid: '2', display: 'Option 2' },
|
|
263
|
+
],
|
|
264
|
+
error: null,
|
|
265
|
+
isLoading: false,
|
|
266
|
+
});
|
|
267
|
+
});
|
|
166
268
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
269
|
+
it('renders a select field for Concept format', () => {
|
|
270
|
+
renderPersonAttributeFieldWithFormik(codedFieldDefinition);
|
|
271
|
+
|
|
272
|
+
const select = screen.getByRole('combobox', { name: /referred by/i });
|
|
273
|
+
expect(select).toBeInTheDocument();
|
|
274
|
+
expect(screen.getByText('Option 1')).toBeInTheDocument();
|
|
275
|
+
expect(screen.getByText('Option 2')).toBeInTheDocument();
|
|
172
276
|
});
|
|
173
277
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
label: 'Attribute',
|
|
178
|
-
showHeading: true,
|
|
179
|
-
type: 'person attribute',
|
|
180
|
-
};
|
|
278
|
+
it('allows user to select an option', async () => {
|
|
279
|
+
const user = userEvent.setup();
|
|
280
|
+
const { getFormValues } = renderPersonAttributeFieldWithFormik(codedFieldDefinition);
|
|
181
281
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
<Form>
|
|
185
|
-
<PersonAttributeField fieldDefinition={fieldDefinition} />
|
|
186
|
-
</Form>
|
|
187
|
-
</Formik>,
|
|
188
|
-
);
|
|
282
|
+
const select = screen.getByRole('combobox', { name: /referred by/i }) as HTMLSelectElement;
|
|
283
|
+
await user.selectOptions(select, '1');
|
|
189
284
|
|
|
190
|
-
|
|
191
|
-
|
|
285
|
+
await waitFor(() => {
|
|
286
|
+
expect(getFormValues().attributes['4dd56a75-14ab-4148-8700-1f4f704dc5b0']).toBe('1');
|
|
287
|
+
});
|
|
288
|
+
expect(select.value).toBe('1');
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('renders as required when configured', () => {
|
|
292
|
+
renderPersonAttributeFieldWithFormik({
|
|
293
|
+
...codedFieldDefinition,
|
|
294
|
+
validation: {
|
|
295
|
+
matches: '',
|
|
296
|
+
required: true,
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const select = screen.getByRole('combobox', { name: /referred by/i });
|
|
301
|
+
expect(select).toBeRequired();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('renders as optional when not required', () => {
|
|
305
|
+
renderPersonAttributeFieldWithFormik(codedFieldDefinition);
|
|
306
|
+
|
|
307
|
+
const select = screen.getByRole('combobox', { name: /referred by/i });
|
|
308
|
+
expect(select).not.toBeRequired();
|
|
309
|
+
});
|
|
192
310
|
});
|
|
193
311
|
});
|
|
@@ -5,7 +5,7 @@ import { type PersonAttributeTypeResponse } from '../../patient-registration.typ
|
|
|
5
5
|
export function usePersonAttributeType(personAttributeTypeUuid: string): {
|
|
6
6
|
data: PersonAttributeTypeResponse;
|
|
7
7
|
isLoading: boolean;
|
|
8
|
-
error:
|
|
8
|
+
error: Error | undefined;
|
|
9
9
|
} {
|
|
10
10
|
const { data, error, isLoading } = useSWRImmutable<FetchResponse<PersonAttributeTypeResponse>>(
|
|
11
11
|
`${restBaseUrl}/personattributetype/${personAttributeTypeUuid}`,
|
package/src/patient-registration/field/person-attributes/text-person-attribute-field.test.tsx
CHANGED
|
@@ -1,90 +1,118 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { vi, describe, it, expect } from 'vitest';
|
|
3
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
4
|
import userEvent from '@testing-library/user-event';
|
|
4
5
|
import { Form, Formik } from 'formik';
|
|
5
6
|
import { TextPersonAttributeField } from './text-person-attribute-field.component';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
const mockPersonAttributeType = {
|
|
9
|
+
format: 'java.lang.String',
|
|
10
|
+
display: 'Referred by',
|
|
11
|
+
uuid: '4dd56a75-14ab-4148-8700-1f4f704dc5b0',
|
|
12
|
+
description: 'Referred by',
|
|
13
|
+
name: 'Referred by',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Helper to render TextPersonAttributeField with Formik for state-dependent tests.
|
|
18
|
+
*/
|
|
19
|
+
const renderTextPersonAttributeField = (
|
|
20
|
+
props: {
|
|
21
|
+
id?: string;
|
|
22
|
+
label?: string;
|
|
23
|
+
validationRegex?: string;
|
|
24
|
+
required?: boolean;
|
|
25
|
+
} = {},
|
|
26
|
+
) => {
|
|
27
|
+
const utils = render(
|
|
28
|
+
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
29
|
+
<Form>
|
|
30
|
+
<TextPersonAttributeField
|
|
31
|
+
id={props.id || 'attributeId'}
|
|
32
|
+
personAttributeType={mockPersonAttributeType}
|
|
33
|
+
label={props.label}
|
|
34
|
+
validationRegex={props.validationRegex}
|
|
35
|
+
required={props.required}
|
|
36
|
+
/>
|
|
37
|
+
</Form>
|
|
38
|
+
</Formik>,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
return utils;
|
|
42
|
+
};
|
|
15
43
|
|
|
44
|
+
describe('TextPersonAttributeField', () => {
|
|
16
45
|
it('renders the input field with a label', () => {
|
|
17
|
-
|
|
18
|
-
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
19
|
-
<Form>
|
|
20
|
-
<TextPersonAttributeField
|
|
21
|
-
id="attributeId"
|
|
22
|
-
personAttributeType={mockPersonAttributeType}
|
|
23
|
-
label="Custom Label"
|
|
24
|
-
/>
|
|
25
|
-
</Form>
|
|
26
|
-
</Formik>,
|
|
27
|
-
);
|
|
46
|
+
renderTextPersonAttributeField({ label: 'Custom Label' });
|
|
28
47
|
|
|
29
48
|
expect(screen.getByRole('textbox', { name: /custom label \(optional\)/i })).toBeInTheDocument();
|
|
30
49
|
});
|
|
31
50
|
|
|
32
51
|
it('renders the input field with the default label if label prop is not provided', () => {
|
|
33
|
-
|
|
34
|
-
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
35
|
-
<Form>
|
|
36
|
-
<TextPersonAttributeField id="attributeId" personAttributeType={mockPersonAttributeType} />
|
|
37
|
-
</Form>
|
|
38
|
-
</Formik>,
|
|
39
|
-
);
|
|
52
|
+
renderTextPersonAttributeField();
|
|
40
53
|
|
|
41
54
|
expect(screen.getByRole('textbox', { name: /referred by \(optional\)/i })).toBeInTheDocument();
|
|
42
55
|
});
|
|
43
56
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
57
|
+
describe('Input validation', () => {
|
|
58
|
+
it('validates the input with the provided validationRegex', async () => {
|
|
59
|
+
const user = userEvent.setup();
|
|
60
|
+
const validationRegex = '^[A-Z]+$'; // Accepts only uppercase letters
|
|
61
|
+
|
|
62
|
+
renderTextPersonAttributeField({ validationRegex });
|
|
63
|
+
|
|
64
|
+
const textbox = screen.getByRole('textbox', { name: /referred by \(optional\)/i });
|
|
65
|
+
expect(textbox).toBeInTheDocument();
|
|
66
|
+
|
|
67
|
+
// Valid input: "ABC"
|
|
68
|
+
await user.type(textbox, 'ABC');
|
|
69
|
+
await user.tab();
|
|
70
|
+
|
|
71
|
+
await waitFor(() => {
|
|
72
|
+
expect(screen.queryByText(/invalid input/i)).not.toBeInTheDocument();
|
|
73
|
+
});
|
|
74
|
+
await user.clear(textbox);
|
|
75
|
+
|
|
76
|
+
// Invalid input: "abc" (contains lowercase letters)
|
|
77
|
+
await user.type(textbox, 'abc');
|
|
78
|
+
await user.tab();
|
|
79
|
+
|
|
80
|
+
await waitFor(() => {
|
|
81
|
+
expect(screen.getByText(/invalid input/i)).toBeInTheDocument();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('does not show error for valid input matching regex', async () => {
|
|
86
|
+
const user = userEvent.setup();
|
|
87
|
+
const validationRegex = '^[A-Z]+$';
|
|
88
|
+
|
|
89
|
+
renderTextPersonAttributeField({ validationRegex });
|
|
90
|
+
|
|
91
|
+
const textbox = screen.getByRole('textbox', { name: /referred by \(optional\)/i });
|
|
92
|
+
await user.type(textbox, 'VALID');
|
|
93
|
+
await user.tab();
|
|
94
|
+
|
|
95
|
+
await waitFor(() => {
|
|
96
|
+
expect(screen.queryByText(/invalid input/i)).not.toBeInTheDocument();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
74
99
|
});
|
|
75
100
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
101
|
+
describe('Required field', () => {
|
|
102
|
+
it('renders the input field as required when required prop is true', () => {
|
|
103
|
+
renderTextPersonAttributeField({ required: true });
|
|
104
|
+
const textbox = screen.getByRole('textbox', { name: /referred by/i });
|
|
105
|
+
|
|
106
|
+
expect(textbox).toBeInTheDocument();
|
|
107
|
+
expect(textbox).toBeRequired();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('renders as optional when required prop is false', () => {
|
|
111
|
+
renderTextPersonAttributeField({ required: false });
|
|
112
|
+
const textbox = screen.getByRole('textbox', { name: /referred by \(optional\)/i });
|
|
113
|
+
|
|
114
|
+
expect(textbox).toBeInTheDocument();
|
|
115
|
+
expect(textbox).not.toBeRequired();
|
|
116
|
+
});
|
|
89
117
|
});
|
|
90
118
|
});
|