@kenyaemr/esm-patient-registration-app 5.2.2 → 6.0.0

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 (122) hide show
  1. package/dist/130.js +1 -1
  2. package/dist/130.js.map +1 -1
  3. package/dist/271.js +1 -0
  4. package/dist/319.js +1 -1
  5. package/dist/330.js +1 -1
  6. package/dist/460.js +1 -1
  7. package/dist/537.js +1 -1
  8. package/dist/574.js +1 -1
  9. package/dist/59.js +1 -1
  10. package/dist/59.js.map +1 -1
  11. package/dist/619.js +1 -0
  12. package/dist/619.js.map +1 -0
  13. package/dist/644.js +1 -0
  14. package/dist/735.js +1 -1
  15. package/dist/757.js +1 -1
  16. package/dist/784.js +1 -1
  17. package/dist/788.js +1 -1
  18. package/dist/807.js +1 -1
  19. package/dist/833.js +1 -1
  20. package/dist/895.js +2 -0
  21. package/dist/{388.js.LICENSE.txt → 895.js.LICENSE.txt} +4 -2
  22. package/dist/895.js.map +1 -0
  23. package/dist/{openmrs-esm-patient-registration-app.js → kenyaemr-esm-patient-registration-app.js} +1 -1
  24. package/dist/{openmrs-esm-patient-registration-app.js.buildmanifest.json → kenyaemr-esm-patient-registration-app.js.buildmanifest.json} +117 -73
  25. package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -0
  26. package/dist/main.js +1 -1
  27. package/dist/main.js.LICENSE.txt +4 -2
  28. package/dist/main.js.map +1 -1
  29. package/dist/routes.json +1 -1
  30. package/package.json +6 -5
  31. package/src/add-patient-link.test.tsx +9 -7
  32. package/src/config-schema.ts +31 -38
  33. package/src/constants.ts +1 -1
  34. package/src/offline.resources.ts +13 -18
  35. package/src/offline.ts +8 -6
  36. package/src/patient-registration/before-save-prompt.tsx +2 -1
  37. package/src/patient-registration/field/__mocks__/field.resource.ts +1 -1
  38. package/src/patient-registration/field/address/address-field.component.tsx +5 -4
  39. package/src/patient-registration/field/address/address-hierarchy.resource.tsx +2 -2
  40. package/src/patient-registration/field/address/address-search.component.tsx +1 -14
  41. package/src/patient-registration/field/address/custom-address-field.component.tsx +1 -1
  42. package/src/patient-registration/field/address/tests/address-hierarchy.test.tsx +3 -3
  43. package/src/patient-registration/field/address/tests/address-search-component.test.tsx +14 -7
  44. package/src/patient-registration/field/custom-field.component.tsx +1 -1
  45. package/src/patient-registration/field/dob/dob.component.tsx +2 -2
  46. package/src/patient-registration/field/dob/dob.test.tsx +0 -3
  47. package/src/patient-registration/field/field.component.tsx +4 -1
  48. package/src/patient-registration/field/field.resource.ts +4 -4
  49. package/src/patient-registration/field/field.test.tsx +98 -95
  50. package/src/patient-registration/field/gender/gender-field.component.tsx +4 -4
  51. package/src/patient-registration/field/gender/gender-field.test.tsx +7 -14
  52. package/src/patient-registration/field/id/id-field.component.tsx +6 -6
  53. package/src/patient-registration/field/id/id-field.test.tsx +9 -7
  54. package/src/patient-registration/field/id/identifier-selection-overlay.component.tsx +1 -1
  55. package/src/patient-registration/field/name/name-field.component.tsx +1 -1
  56. package/src/patient-registration/field/obs/obs-field.component.tsx +35 -33
  57. package/src/patient-registration/field/obs/obs-field.test.tsx +106 -28
  58. package/src/patient-registration/field/person-attributes/coded-attributes.component.tsx +1 -1
  59. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +70 -24
  60. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +54 -30
  61. package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +4 -3
  62. package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +1 -1
  63. package/src/patient-registration/field/person-attributes/{person-attributes.resource.tsx → person-attributes.resource.ts} +3 -3
  64. package/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx +1 -1
  65. package/src/patient-registration/field/phone/phone-field.component.tsx +16 -0
  66. package/src/patient-registration/form-manager.test.ts +1 -1
  67. package/src/patient-registration/form-manager.ts +22 -24
  68. package/src/patient-registration/input/basic-input/select/select-input.test.tsx +3 -3
  69. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.component.tsx +5 -5
  70. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +70 -58
  71. package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +2 -2
  72. package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +2 -5
  73. package/src/patient-registration/input/custom-input/identifier/utils.ts +1 -1
  74. package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +1 -1
  75. package/src/patient-registration/input/dummy-data/dummy-data-input.test.tsx +6 -6
  76. package/src/patient-registration/patient-registration-context.ts +3 -4
  77. package/src/patient-registration/patient-registration-hooks.ts +25 -20
  78. package/src/patient-registration/patient-registration-utils.ts +7 -7
  79. package/src/patient-registration/patient-registration.component.tsx +20 -10
  80. package/src/patient-registration/patient-registration.resource.test.tsx +3 -3
  81. package/src/patient-registration/{patient-registration.resource.tsx → patient-registration.resource.ts} +15 -15
  82. package/src/patient-registration/patient-registration.test.tsx +270 -251
  83. package/src/patient-registration/{patient-registration.types.tsx → patient-registration.types.ts} +12 -3
  84. package/src/patient-registration/section/death-info/death-info-section.test.tsx +33 -45
  85. package/src/patient-registration/section/demographics/demographics-section.test.tsx +1 -2
  86. package/src/patient-registration/section/generic-section.component.tsx +1 -1
  87. package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +3 -3
  88. package/src/patient-registration/section/patient-relationships/relationships-section.test.tsx +17 -5
  89. package/src/patient-registration/section/patient-relationships/relationships.resource.tsx +4 -4
  90. package/src/patient-registration/section/section-wrapper.component.tsx +1 -1
  91. package/src/patient-registration/section/section.component.tsx +1 -1
  92. package/src/patient-registration/validation/patient-registration-validation.test.tsx +140 -126
  93. package/src/patient-registration/validation/patient-registration-validation.tsx +54 -46
  94. package/src/patient-verification/patient-verification-hook.tsx +13 -4
  95. package/src/patient-verification/patient-verification-utils.ts +20 -12
  96. package/src/patient-verification/patient-verification.component.tsx +13 -6
  97. package/src/patient-verification/patient-verification.scss +0 -1
  98. package/src/patient-verification/verification-modal/confirm-prompt.component.tsx +2 -11
  99. package/src/routes.json +1 -0
  100. package/src/widgets/cancel-patient-edit.test.tsx +7 -4
  101. package/src/widgets/delete-identifier-confirmation-modal.test.tsx +7 -4
  102. package/src/widgets/display-photo.test.tsx +1 -1
  103. package/src/widgets/edit-patient-details-button.test.tsx +12 -7
  104. package/translations/am.json +30 -14
  105. package/translations/ar.json +30 -14
  106. package/translations/en.json +11 -11
  107. package/translations/es.json +34 -22
  108. package/translations/fr.json +48 -40
  109. package/translations/he.json +22 -2
  110. package/translations/km.json +22 -2
  111. package/translations/zh.json +97 -0
  112. package/translations/zh_CN.json +97 -0
  113. package/tsconfig.json +1 -1
  114. package/__mocks__/autogenerationoptions.mock.ts +0 -34
  115. package/dist/388.js +0 -2
  116. package/dist/388.js.map +0 -1
  117. package/dist/598.js +0 -1
  118. package/dist/598.js.map +0 -1
  119. package/dist/openmrs-esm-patient-registration-app.js.map +0 -1
  120. package/src/patient-registration/field/__mocks__/identifier-types.mock.ts +0 -76
  121. package/src/patient-registration/field/__mocks__/identifiers.mock.ts +0 -27
  122. package/src/patient-registration/field/address/tests/mocks.ts +0 -98
@@ -2,17 +2,86 @@ import React from 'react';
2
2
  import { render, screen } from '@testing-library/react';
3
3
  import userEvent from '@testing-library/user-event';
4
4
  import { useConfig } from '@openmrs/esm-framework';
5
- import { FieldDefinition } from '../../../config-schema';
5
+ import { type FieldDefinition } from '../../../config-schema';
6
+ import { useConcept, useConceptAnswers } from '../field.resource';
6
7
  import { ObsField } from './obs-field.component';
7
8
 
8
9
  const mockUseConfig = useConfig as jest.Mock;
9
10
 
10
- // The UUIDs in this test all refer to ones that are in `__mocks__/field.resource.ts`
11
- jest.mock('../field.resource');
11
+ jest.mock('../field.resource'); // Mock the useConceptAnswers hook
12
+
13
+ const mockedUseConcept = useConcept as jest.Mock;
14
+ const mockedUseConceptAnswers = useConceptAnswers as jest.Mock;
15
+
16
+ const useConceptMockImpl = (uuid: string) => {
17
+ let data;
18
+ if (uuid == 'weight-uuid') {
19
+ data = {
20
+ uuid: 'weight-uuid',
21
+ display: 'Weight (kg)',
22
+ datatype: { display: 'Numeric', uuid: 'num' },
23
+ answers: [],
24
+ setMembers: [],
25
+ };
26
+ } else if (uuid == 'chief-complaint-uuid') {
27
+ data = {
28
+ uuid: 'chief-complaint-uuid',
29
+ display: 'Chief Complaint',
30
+ datatype: { display: 'Text', uuid: 'txt' },
31
+ answers: [],
32
+ setMembers: [],
33
+ };
34
+ } else if (uuid == 'nationality-uuid') {
35
+ data = {
36
+ uuid: 'nationality-uuid',
37
+ display: 'Nationality',
38
+ datatype: { display: 'Coded', uuid: 'cdd' },
39
+ answers: [
40
+ { display: 'USA', uuid: 'usa' },
41
+ { display: 'Mexico', uuid: 'mex' },
42
+ ],
43
+ setMembers: [],
44
+ };
45
+ } else {
46
+ throw Error(`Programming error, you probably didn't mean to do this: unknown concept uuid '${uuid}'`);
47
+ }
48
+ return {
49
+ data: data ?? null,
50
+ isLoading: !data,
51
+ };
52
+ };
53
+
54
+ const useConceptAnswersMockImpl = (uuid: string) => {
55
+ if (uuid == 'nationality-uuid') {
56
+ return {
57
+ data: [
58
+ { display: 'USA', uuid: 'usa' },
59
+ { display: 'Mexico', uuid: 'mex' },
60
+ ],
61
+ isLoading: false,
62
+ };
63
+ } else if (uuid == 'other-countries-uuid') {
64
+ return {
65
+ data: [
66
+ { display: 'Kenya', uuid: 'ke' },
67
+ { display: 'Uganda', uuid: 'ug' },
68
+ ],
69
+ isLoading: false,
70
+ };
71
+ } else if (uuid == '') {
72
+ return {
73
+ data: [],
74
+ isLoading: false,
75
+ };
76
+ } else {
77
+ throw Error(`Programming error, you probably didn't mean to do this: unknown concept answer set uuid '${uuid}'`);
78
+ }
79
+ };
12
80
 
13
81
  type FieldProps = {
14
82
  children: ({ field, form: { touched, errors } }) => React.ReactNode;
15
83
  };
84
+
16
85
  jest.mock('formik', () => ({
17
86
  ...(jest.requireActual('formik') as object),
18
87
  Field: jest.fn(({ children }: FieldProps) => <>{children({ field: {}, form: { touched: {}, errors: {} } })}</>),
@@ -24,18 +93,14 @@ const textFieldDef: FieldDefinition = {
24
93
  type: 'obs',
25
94
  label: '',
26
95
  placeholder: '',
96
+ showHeading: false,
27
97
  uuid: 'chief-complaint-uuid',
28
98
  validation: {
29
99
  required: false,
30
100
  matches: null,
31
101
  },
32
102
  answerConceptSetUuid: null,
33
- customConceptAnswers: [
34
- {
35
- uuid: 'concept-uuid',
36
- label: '',
37
- },
38
- ],
103
+ customConceptAnswers: [],
39
104
  };
40
105
 
41
106
  const numberFieldDef: FieldDefinition = {
@@ -43,18 +108,14 @@ const numberFieldDef: FieldDefinition = {
43
108
  type: 'obs',
44
109
  label: '',
45
110
  placeholder: '',
111
+ showHeading: false,
46
112
  uuid: 'weight-uuid',
47
113
  validation: {
48
114
  required: false,
49
115
  matches: null,
50
116
  },
51
117
  answerConceptSetUuid: null,
52
- customConceptAnswers: [
53
- {
54
- uuid: 'concept-uuid',
55
- label: '',
56
- },
57
- ],
118
+ customConceptAnswers: [],
58
119
  };
59
120
 
60
121
  const codedFieldDef: FieldDefinition = {
@@ -62,30 +123,30 @@ const codedFieldDef: FieldDefinition = {
62
123
  type: 'obs',
63
124
  label: '',
64
125
  placeholder: '',
126
+ showHeading: false,
65
127
  uuid: 'nationality-uuid',
66
128
  validation: {
67
129
  required: false,
68
130
  matches: null,
69
131
  },
70
132
  answerConceptSetUuid: null,
71
- customConceptAnswers: [
72
- {
73
- uuid: 'concept-uuid',
74
- label: 'Kenya',
75
- },
76
- ],
133
+ customConceptAnswers: [],
77
134
  };
78
135
 
79
136
  describe('ObsField', () => {
80
137
  beforeEach(() => {
81
138
  mockUseConfig.mockReturnValue({ registrationObs: { encounterTypeUuid: 'reg-enc-uuid' } });
139
+ mockedUseConcept.mockImplementation(useConceptMockImpl);
140
+ mockedUseConceptAnswers.mockImplementation(useConceptAnswersMockImpl);
82
141
  });
83
142
 
84
143
  it("logs an error and doesn't render if no registration encounter type is provided", () => {
85
144
  mockUseConfig.mockReturnValue({ registrationObs: { encounterTypeUuid: null } });
86
145
  console.error = jest.fn();
87
146
  render(<ObsField fieldDefinition={textFieldDef} />);
88
- expect(console.error).toHaveBeenCalledWith(expect.stringMatching(/no registration encounter type.*configure/i));
147
+ expect(console.error).toHaveBeenCalledWith(
148
+ expect.stringMatching(/no registration encounter type has been configured/i),
149
+ );
89
150
  expect(screen.queryByRole('textbox')).toBeNull();
90
151
  });
91
152
 
@@ -107,15 +168,10 @@ describe('ObsField', () => {
107
168
  // expect(screen.getByLabelText("Nationality")).toBeInTheDocument();
108
169
  const select = screen.getByRole('combobox');
109
170
  expect(select).toBeInTheDocument();
110
- expect(select).toHaveDisplayValue('');
171
+ expect(select).toHaveDisplayValue('Select an option');
111
172
  });
112
173
 
113
174
  it('select uses answerConcept for answers when it is provided', async () => {
114
- mockUseConfig.mockReturnValue({
115
- registrationObs: { encounterTypeUuid: 'reg-enc-uuid' },
116
- fieldDefinitions: [codedFieldDef],
117
- });
118
-
119
175
  const user = userEvent.setup();
120
176
 
121
177
  render(<ObsField fieldDefinition={{ ...codedFieldDef, answerConceptSetUuid: 'other-countries-uuid' }} />);
@@ -124,4 +180,26 @@ describe('ObsField', () => {
124
180
  expect(select).toBeInTheDocument();
125
181
  await user.selectOptions(select, 'Kenya');
126
182
  });
183
+
184
+ it('select uses customConceptAnswers for answers when provided', async () => {
185
+ const user = userEvent.setup();
186
+
187
+ render(
188
+ <ObsField
189
+ fieldDefinition={{
190
+ ...codedFieldDef,
191
+ customConceptAnswers: [
192
+ {
193
+ uuid: 'mozambique-uuid',
194
+ label: 'Mozambique',
195
+ },
196
+ ],
197
+ }}
198
+ />,
199
+ );
200
+ // expect(screen.getByLabelText("Nationality")).toBeInTheDocument();
201
+ const select = screen.getByRole('combobox');
202
+ expect(select).toBeInTheDocument();
203
+ await user.selectOptions(select, 'Mozambique');
204
+ });
127
205
  });
@@ -3,7 +3,7 @@ import classNames from 'classnames';
3
3
  import { Layer, Select, SelectItem } from '@carbon/react';
4
4
  import { useConfig } from '@openmrs/esm-framework';
5
5
  import { Input } from '../../input/basic-input/input/input.component';
6
- import { CodedPersonAttributeConfig } from '../../patient-registration.types';
6
+ import { type CodedPersonAttributeConfig } from '../../patient-registration.types';
7
7
  import { useConceptAnswers } from '../field.resource';
8
8
  import { usePersonAttributeType } from './person-attributes.resource';
9
9
  import styles from './../field.scss';
@@ -1,18 +1,19 @@
1
- import React from 'react';
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
2
  import classNames from 'classnames';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { Field } from 'formik';
5
5
  import { Layer, Select, SelectItem } from '@carbon/react';
6
- import { Input } from '../../input/basic-input/input/input.component';
7
- import { PersonAttributeTypeResponse } from '../../patient-registration.types';
6
+ import { type PersonAttributeTypeResponse } from '../../patient-registration.types';
8
7
  import { useConceptAnswers } from '../field.resource';
9
8
  import styles from './../field.scss';
9
+ import { reportError } from '@openmrs/esm-framework';
10
10
 
11
11
  export interface CodedPersonAttributeFieldProps {
12
12
  id: string;
13
13
  personAttributeType: PersonAttributeTypeResponse;
14
14
  answerConceptSetUuid: string;
15
15
  label?: string;
16
+ customConceptAnswers: Array<{ uuid: string; label?: string }>;
16
17
  }
17
18
 
18
19
  export function CodedPersonAttributeField({
@@ -20,14 +21,75 @@ export function CodedPersonAttributeField({
20
21
  personAttributeType,
21
22
  answerConceptSetUuid,
22
23
  label,
24
+ customConceptAnswers,
23
25
  }: CodedPersonAttributeFieldProps) {
24
- const { data: conceptAnswers, isLoading: isLoadingConceptAnswers } = useConceptAnswers(answerConceptSetUuid);
26
+ const { data: conceptAnswers, isLoading: isLoadingConceptAnswers } = useConceptAnswers(
27
+ customConceptAnswers.length ? '' : answerConceptSetUuid,
28
+ );
29
+
25
30
  const { t } = useTranslation();
26
31
  const fieldName = `attributes.${personAttributeType.uuid}`;
32
+ const [error, setError] = useState(false);
33
+
34
+ useEffect(() => {
35
+ if (!answerConceptSetUuid && !customConceptAnswers.length) {
36
+ reportError(
37
+ t(
38
+ 'codedPersonAttributeNoAnswerSet',
39
+ `The person attribute field '{{codedPersonAttributeFieldId}}' is of type 'coded' but has been defined without an answer concept set UUID. The 'answerConceptSetUuid' key is required.`,
40
+ { codedPersonAttributeFieldId: id },
41
+ ),
42
+ );
43
+ setError(true);
44
+ }
45
+ }, [answerConceptSetUuid, customConceptAnswers]);
46
+
47
+ useEffect(() => {
48
+ if (!isLoadingConceptAnswers && !customConceptAnswers.length) {
49
+ if (!conceptAnswers) {
50
+ reportError(
51
+ t(
52
+ 'codedPersonAttributeAnswerSetInvalid',
53
+ `The coded person attribute field '{{codedPersonAttributeFieldId}}' has been defined with an invalid answer concept set UUID '{{answerConceptSetUuid}}'.`,
54
+ { codedPersonAttributeFieldId: id, answerConceptSetUuid },
55
+ ),
56
+ );
57
+ setError(true);
58
+ }
59
+ if (conceptAnswers?.length == 0) {
60
+ reportError(
61
+ t(
62
+ 'codedPersonAttributeAnswerSetEmpty',
63
+ `The coded person attribute field '{{codedPersonAttributeFieldId}}' has been defined with an answer concept set UUID '{{answerConceptSetUuid}}' that does not have any concept answers.`,
64
+ {
65
+ codedPersonAttributeFieldId: id,
66
+ answerConceptSetUuid,
67
+ },
68
+ ),
69
+ );
70
+ setError(true);
71
+ }
72
+ }
73
+ }, [isLoadingConceptAnswers, conceptAnswers, customConceptAnswers]);
74
+
75
+ const answers = useMemo(() => {
76
+ if (customConceptAnswers.length) {
77
+ return customConceptAnswers;
78
+ }
79
+ return isLoadingConceptAnswers || !conceptAnswers
80
+ ? []
81
+ : conceptAnswers
82
+ .map((answer) => ({ ...answer, label: answer.display }))
83
+ .sort((a, b) => a.label.localeCompare(b.label));
84
+ }, [customConceptAnswers, conceptAnswers, isLoadingConceptAnswers]);
85
+
86
+ if (error) {
87
+ return null;
88
+ }
27
89
 
28
90
  return (
29
91
  <div className={classNames(styles.customField, styles.halfWidthInDesktopView)}>
30
- {!isLoadingConceptAnswers && conceptAnswers?.length ? (
92
+ {!isLoadingConceptAnswers ? (
31
93
  <Layer>
32
94
  <Field name={fieldName}>
33
95
  {({ field, form: { touched, errors }, meta }) => {
@@ -40,8 +102,8 @@ export function CodedPersonAttributeField({
40
102
  invalid={errors[fieldName] && touched[fieldName]}
41
103
  {...field}>
42
104
  <SelectItem value={''} text={t('selectAnOption', 'Select an option')} />
43
- {conceptAnswers.map((answer) => (
44
- <SelectItem key={answer.uuid} value={answer.uuid} text={answer.display} />
105
+ {answers.map((answer) => (
106
+ <SelectItem key={answer.uuid} value={answer.uuid} text={answer.label} />
45
107
  ))}
46
108
  </Select>
47
109
  </>
@@ -49,23 +111,7 @@ export function CodedPersonAttributeField({
49
111
  }}
50
112
  </Field>
51
113
  </Layer>
52
- ) : (
53
- <Layer>
54
- <Field name={fieldName}>
55
- {({ field, form: { touched, errors }, meta }) => {
56
- return (
57
- <Input
58
- id={id}
59
- name={`person-attribute-${personAttributeType.uuid}`}
60
- labelText={label ?? personAttributeType?.display}
61
- invalid={errors[fieldName] && touched[fieldName]}
62
- {...field}
63
- />
64
- );
65
- }}
66
- </Field>
67
- </Layer>
68
- )}
114
+ ) : null}
69
115
  </div>
70
116
  );
71
117
  }
@@ -21,6 +21,8 @@ describe('CodedPersonAttributeField', () => {
21
21
  format: 'org.openmrs.Concept',
22
22
  display: 'Referred by',
23
23
  uuid: '4dd56a75-14ab-4148-8700-1f4f704dc5b0',
24
+ name: '',
25
+ description: '',
24
26
  };
25
27
  const answerConceptSetUuid = '6682d17f-0777-45e4-a39b-93f77eb3531c';
26
28
 
@@ -32,31 +34,47 @@ describe('CodedPersonAttributeField', () => {
32
34
  });
33
35
  });
34
36
 
35
- it('renders the Select component when conceptAnswers are available', () => {
36
- render(
37
- <Formik initialValues={{}} onSubmit={() => {}}>
38
- <Form>
39
- <CodedPersonAttributeField
40
- id="attributeId"
41
- personAttributeType={personAttributeType}
42
- answerConceptSetUuid={answerConceptSetUuid}
43
- label={personAttributeType.display}
44
- />
45
- </Form>
46
- </Formik>,
47
- );
48
-
49
- expect(screen.getByLabelText(/Referred by/i)).toBeInTheDocument();
50
- expect(screen.getByText(/Option 1/i)).toBeInTheDocument();
51
- expect(screen.getByText(/Option 2/i)).toBeInTheDocument();
37
+ it('shows error if there is no concept answer set provided', () => {
38
+ expect(() => {
39
+ render(
40
+ <Formik initialValues={{}} onSubmit={() => {}}>
41
+ <Form>
42
+ <CodedPersonAttributeField
43
+ id="attributeId"
44
+ personAttributeType={personAttributeType}
45
+ answerConceptSetUuid={null}
46
+ label={personAttributeType.display}
47
+ customConceptAnswers={[]}
48
+ />
49
+ </Form>
50
+ </Formik>,
51
+ );
52
+ }).toThrow(expect.stringMatching(/has been defined without an answer concept set UUID/i));
52
53
  });
53
54
 
54
- it('renders the Input component when conceptAnswers are not available', () => {
55
- mockedUseConceptAnswers.mockReturnValueOnce({
56
- data: null,
55
+ it('shows error if the concept answer set does not have any concept answers', () => {
56
+ mockedUseConceptAnswers.mockReturnValue({
57
+ data: [],
57
58
  isLoading: false,
58
59
  });
60
+ expect(() => {
61
+ render(
62
+ <Formik initialValues={{}} onSubmit={() => {}}>
63
+ <Form>
64
+ <CodedPersonAttributeField
65
+ id="attributeId"
66
+ personAttributeType={personAttributeType}
67
+ answerConceptSetUuid={answerConceptSetUuid}
68
+ label={personAttributeType.display}
69
+ customConceptAnswers={[]}
70
+ />
71
+ </Form>
72
+ </Formik>,
73
+ );
74
+ }).toThrow(expect.stringMatching(/does not have any concept answers/i));
75
+ });
59
76
 
77
+ it('renders the conceptAnswers as select options', () => {
60
78
  render(
61
79
  <Formik initialValues={{}} onSubmit={() => {}}>
62
80
  <Form>
@@ -65,23 +83,18 @@ describe('CodedPersonAttributeField', () => {
65
83
  personAttributeType={personAttributeType}
66
84
  answerConceptSetUuid={answerConceptSetUuid}
67
85
  label={personAttributeType.display}
86
+ customConceptAnswers={[]}
68
87
  />
69
88
  </Form>
70
89
  </Formik>,
71
90
  );
72
91
 
73
92
  expect(screen.getByLabelText(/Referred by/i)).toBeInTheDocument();
74
- expect(screen.queryByText(/Option 1/i)).not.toBeInTheDocument();
75
- expect(screen.queryByText(/Option 2/i)).not.toBeInTheDocument();
76
- expect(screen.getByRole('textbox')).toBeInTheDocument();
93
+ expect(screen.getByText(/Option 1/i)).toBeInTheDocument();
94
+ expect(screen.getByText(/Option 2/i)).toBeInTheDocument();
77
95
  });
78
96
 
79
- it('renders the Input component when conceptAnswers are still loading', () => {
80
- mockedUseConceptAnswers.mockReturnValueOnce({
81
- data: null,
82
- isLoading: true,
83
- });
84
-
97
+ it('renders customConceptAnswers as select options when they are provided', () => {
85
98
  render(
86
99
  <Formik initialValues={{}} onSubmit={() => {}}>
87
100
  <Form>
@@ -90,14 +103,25 @@ describe('CodedPersonAttributeField', () => {
90
103
  personAttributeType={personAttributeType}
91
104
  answerConceptSetUuid={answerConceptSetUuid}
92
105
  label={personAttributeType.display}
106
+ customConceptAnswers={[
107
+ {
108
+ uuid: 'A',
109
+ label: 'Special Option A',
110
+ },
111
+ {
112
+ uuid: 'B',
113
+ label: 'Special Option B',
114
+ },
115
+ ]}
93
116
  />
94
117
  </Form>
95
118
  </Formik>,
96
119
  );
97
120
 
98
121
  expect(screen.getByLabelText(/Referred by/i)).toBeInTheDocument();
122
+ expect(screen.getByText(/Special Option A/i)).toBeInTheDocument();
123
+ expect(screen.getByText(/Special Option B/i)).toBeInTheDocument();
99
124
  expect(screen.queryByText(/Option 1/i)).not.toBeInTheDocument();
100
125
  expect(screen.queryByText(/Option 2/i)).not.toBeInTheDocument();
101
- expect(screen.getByRole('textbox')).toBeInTheDocument();
102
126
  });
103
127
  });
@@ -1,6 +1,6 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { InlineNotification, TextInputSkeleton, SkeletonText } from '@carbon/react';
3
- import { FieldDefinition } from '../../../config-schema';
3
+ import { type FieldDefinition } from '../../../config-schema';
4
4
  import { CodedPersonAttributeField } from './coded-person-attribute-field.component';
5
5
  import { usePersonAttributeType } from './person-attributes.resource';
6
6
  import { TextPersonAttributeField } from './text-person-attribute-field.component';
@@ -24,9 +24,9 @@ export function PersonAttributeField({ fieldDefinition }: PersonAttributeFieldPr
24
24
  return (
25
25
  <TextPersonAttributeField
26
26
  personAttributeType={personAttributeType}
27
- validationRegex={fieldDefinition.validation.matches}
27
+ validationRegex={fieldDefinition.validation?.matches ?? ''}
28
28
  label={fieldDefinition.label}
29
- required={fieldDefinition.validation.required}
29
+ required={fieldDefinition.validation?.required ?? false}
30
30
  id={fieldDefinition?.id}
31
31
  />
32
32
  );
@@ -37,6 +37,7 @@ export function PersonAttributeField({ fieldDefinition }: PersonAttributeFieldPr
37
37
  answerConceptSetUuid={fieldDefinition.answerConceptSetUuid}
38
38
  label={fieldDefinition.label}
39
39
  id={fieldDefinition?.id}
40
+ customConceptAnswers={fieldDefinition.customConceptAnswers ?? []}
40
41
  />
41
42
  );
42
43
  default:
@@ -4,7 +4,7 @@ import { usePersonAttributeType } from './person-attributes.resource';
4
4
  import { PersonAttributeField } from './person-attribute-field.component';
5
5
  import { useConceptAnswers } from '../field.resource';
6
6
  import { Form, Formik } from 'formik';
7
- import { FieldDefinition } from '../../../config-schema';
7
+ import { type FieldDefinition } from '../../../config-schema';
8
8
 
9
9
  jest.mock('./person-attributes.resource'); // Mock the usePersonAttributeType hook
10
10
  jest.mock('../field.resource'); // Mock the useConceptAnswers hook
@@ -1,6 +1,6 @@
1
- import { FetchResponse, openmrsFetch } from '@openmrs/esm-framework';
1
+ import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
2
  import useSWRImmutable from 'swr/immutable';
3
- import { PersonAttributeTypeResponse } from '../../patient-registration.types';
3
+ import { type PersonAttributeTypeResponse } from '../../patient-registration.types';
4
4
 
5
5
  export function usePersonAttributeType(personAttributeTypeUuid: string): {
6
6
  data: PersonAttributeTypeResponse;
@@ -8,7 +8,7 @@ export function usePersonAttributeType(personAttributeTypeUuid: string): {
8
8
  error: any;
9
9
  } {
10
10
  const { data, error, isLoading } = useSWRImmutable<FetchResponse<PersonAttributeTypeResponse>>(
11
- `/ws/rest/v1/personattributetype/${personAttributeTypeUuid}`,
11
+ `${restBaseUrl}/personattributetype/${personAttributeTypeUuid}`,
12
12
  openmrsFetch,
13
13
  );
14
14
 
@@ -3,7 +3,7 @@ import classNames from 'classnames';
3
3
  import { Field } from 'formik';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { Input } from '../../input/basic-input/input/input.component';
6
- import { PersonAttributeTypeResponse } from '../../patient-registration.types';
6
+ import { type PersonAttributeTypeResponse } from '../../patient-registration.types';
7
7
  import styles from './../field.scss';
8
8
 
9
9
  export interface TextPersonAttributeFieldProps {
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { PersonAttributeField } from '../person-attributes/person-attribute-field.component';
3
+ import { useConfig } from '@openmrs/esm-framework';
4
+ import { type RegistrationConfig } from '../../../config-schema';
5
+
6
+ export function PhoneField() {
7
+ const config = useConfig<RegistrationConfig>();
8
+
9
+ const fieldDefinition = {
10
+ id: 'phone',
11
+ type: 'person attribute',
12
+ uuid: config.fieldConfigurations.phone.personAttributeUuid,
13
+ showHeading: false,
14
+ };
15
+ return <PersonAttributeField fieldDefinition={fieldDefinition} />;
16
+ }
@@ -1,5 +1,5 @@
1
1
  import { FormManager } from './form-manager';
2
- import { FormValues } from './patient-registration.types';
2
+ import { type FormValues } from './patient-registration.types';
3
3
 
4
4
  jest.mock('./patient-registration.resource');
5
5