@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.
Files changed (346) hide show
  1. package/dist/1339.js +1 -0
  2. package/dist/1339.js.map +1 -0
  3. package/dist/1480.js +1 -0
  4. package/dist/1480.js.map +1 -0
  5. package/dist/1646.js +1 -0
  6. package/dist/1646.js.map +1 -0
  7. package/dist/1789.js +1 -0
  8. package/dist/1789.js.map +1 -0
  9. package/dist/1869.js +1 -0
  10. package/dist/1869.js.map +1 -0
  11. package/dist/1877.js +1 -0
  12. package/dist/1877.js.map +1 -0
  13. package/dist/2317.js +1 -0
  14. package/dist/2317.js.map +1 -0
  15. package/dist/2416.js +1 -0
  16. package/dist/2416.js.map +1 -0
  17. package/dist/2747.js +1 -0
  18. package/dist/2747.js.map +1 -0
  19. package/dist/282.js +1 -0
  20. package/dist/282.js.map +1 -0
  21. package/dist/2881.js +1 -0
  22. package/dist/2881.js.map +1 -0
  23. package/dist/3378.js +1 -0
  24. package/dist/3378.js.map +1 -0
  25. package/dist/3720.js +1 -0
  26. package/dist/3720.js.map +1 -0
  27. package/dist/3906.js +1 -0
  28. package/dist/3906.js.map +1 -0
  29. package/dist/3963.js +1 -0
  30. package/dist/3963.js.map +1 -0
  31. package/dist/3989.js +1 -0
  32. package/dist/3989.js.map +1 -0
  33. package/dist/4106.js +1 -0
  34. package/dist/4106.js.map +1 -0
  35. package/dist/4111.js +1 -0
  36. package/dist/4111.js.map +1 -0
  37. package/dist/434.js +1 -0
  38. package/dist/434.js.map +1 -0
  39. package/dist/4348.js +1 -0
  40. package/dist/4348.js.map +1 -0
  41. package/dist/4383.js +1 -0
  42. package/dist/4383.js.map +1 -0
  43. package/dist/4658.js +1 -0
  44. package/dist/4658.js.map +1 -0
  45. package/dist/466.js +1 -0
  46. package/dist/466.js.map +1 -0
  47. package/dist/4928.js +1 -0
  48. package/dist/4928.js.map +1 -0
  49. package/dist/5117.js +1 -0
  50. package/dist/5117.js.map +1 -0
  51. package/dist/5132.js +1 -0
  52. package/dist/5132.js.map +1 -0
  53. package/dist/5145.js +1 -0
  54. package/dist/5145.js.map +1 -0
  55. package/dist/5208.js +43 -0
  56. package/dist/5208.js.map +1 -0
  57. package/dist/527.js +1 -0
  58. package/dist/527.js.map +1 -0
  59. package/dist/5280.js +1 -0
  60. package/dist/5280.js.map +1 -0
  61. package/dist/5338.js +6 -0
  62. package/dist/5338.js.map +1 -0
  63. package/dist/5503.js +1 -0
  64. package/dist/5503.js.map +1 -0
  65. package/dist/555.js +1 -0
  66. package/dist/555.js.map +1 -0
  67. package/dist/556.js +1 -0
  68. package/dist/556.js.map +1 -0
  69. package/dist/5644.js +1 -0
  70. package/dist/5644.js.map +1 -0
  71. package/dist/5697.js +1 -0
  72. package/dist/{4024.js.map → 5697.js.map} +1 -1
  73. package/dist/5940.js +1 -0
  74. package/dist/5940.js.map +1 -0
  75. package/dist/6047.js +1 -0
  76. package/dist/6047.js.map +1 -0
  77. package/dist/6371.js +1 -0
  78. package/dist/6371.js.map +1 -0
  79. package/dist/6377.js +1 -0
  80. package/dist/6377.js.map +1 -0
  81. package/dist/6388.js +1 -0
  82. package/dist/6388.js.map +1 -0
  83. package/dist/6444.js +1 -0
  84. package/dist/6444.js.map +1 -0
  85. package/dist/6508.js +1 -0
  86. package/dist/6508.js.map +1 -0
  87. package/dist/6724.js +1 -0
  88. package/dist/6724.js.map +1 -0
  89. package/dist/689.js +1 -0
  90. package/dist/689.js.map +1 -0
  91. package/dist/6904.js +1 -0
  92. package/dist/6904.js.map +1 -0
  93. package/dist/7045.js +1 -0
  94. package/dist/7045.js.map +1 -0
  95. package/dist/7175.js +1 -0
  96. package/dist/7175.js.map +1 -0
  97. package/dist/7182.js +1 -0
  98. package/dist/7182.js.map +1 -0
  99. package/dist/7649.js +1 -0
  100. package/dist/7649.js.map +1 -0
  101. package/dist/7742.js +1 -0
  102. package/dist/7742.js.map +1 -0
  103. package/dist/7912.js +1 -0
  104. package/dist/7912.js.map +1 -0
  105. package/dist/8358.js +1 -0
  106. package/dist/8358.js.map +1 -0
  107. package/dist/8359.js +1 -0
  108. package/dist/8359.js.map +1 -0
  109. package/dist/8695.js +1 -0
  110. package/dist/8695.js.map +1 -0
  111. package/dist/903.js +1 -0
  112. package/dist/903.js.map +1 -0
  113. package/dist/9061.js +1 -0
  114. package/dist/9061.js.map +1 -0
  115. package/dist/9072.js +1 -0
  116. package/dist/9072.js.map +1 -0
  117. package/dist/9397.js +1 -0
  118. package/dist/9397.js.map +1 -0
  119. package/dist/9712.js +1 -0
  120. package/dist/9712.js.map +1 -0
  121. package/dist/9771.js +1 -0
  122. package/dist/9771.js.map +1 -0
  123. package/dist/9806.js +1 -0
  124. package/dist/9806.js.map +1 -0
  125. package/dist/9816.js +1 -0
  126. package/dist/9816.js.map +1 -0
  127. package/dist/main.js +7 -6
  128. package/dist/main.js.map +1 -1
  129. package/dist/openmrs-esm-patient-registration-app.js +6 -0
  130. package/dist/{olaboot-esm-patient-registration-app.js.buildmanifest.json → openmrs-esm-patient-registration-app.js.buildmanifest.json} +540 -455
  131. package/dist/openmrs-esm-patient-registration-app.js.map +1 -0
  132. package/dist/routes.json +1 -1
  133. package/package.json +8 -9
  134. package/src/add-patient-link.extension.tsx +3 -2
  135. package/src/add-patient-link.test.tsx +2 -1
  136. package/src/config-schema.ts +1 -1
  137. package/src/index.ts +2 -24
  138. package/src/nav-link.test.tsx +1 -0
  139. package/src/offline.resources.ts +97 -31
  140. package/src/patient-registration/before-save-prompt.test.tsx +199 -0
  141. package/src/patient-registration/field/__mocks__/field.resource.ts +8 -7
  142. package/src/patient-registration/field/address/address-field.component.tsx +10 -13
  143. package/src/patient-registration/field/address/address-hierarchy-levels.component.tsx +6 -1
  144. package/src/patient-registration/field/address/address-hierarchy.test.tsx +191 -198
  145. package/src/patient-registration/field/address/address-search.component.tsx +20 -8
  146. package/src/patient-registration/field/address/address-search.scss +19 -2
  147. package/src/patient-registration/field/address/address-search.test.tsx +249 -57
  148. package/src/patient-registration/field/address/custom-address-field.component.tsx +1 -1
  149. package/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx +1 -1
  150. package/src/patient-registration/field/cause-of-death/cause-of-death.test.tsx +251 -0
  151. package/src/patient-registration/field/custom-field.component.tsx +1 -1
  152. package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +1 -1
  153. package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.test.tsx +144 -0
  154. package/src/patient-registration/field/dob/dob.component.tsx +2 -2
  155. package/src/patient-registration/field/dob/dob.test.tsx +370 -54
  156. package/src/patient-registration/field/field.component.tsx +1 -1
  157. package/src/patient-registration/field/field.resource.ts +2 -2
  158. package/src/patient-registration/field/field.test.tsx +25 -22
  159. package/src/patient-registration/field/gender/gender-field.test.tsx +240 -54
  160. package/src/patient-registration/field/id/id-field.component.tsx +15 -5
  161. package/src/patient-registration/field/id/id-field.test.tsx +103 -47
  162. package/src/patient-registration/field/id/identifier-selection-overlay.test.tsx +346 -0
  163. package/src/patient-registration/field/name/name-field.component.tsx +2 -2
  164. package/src/patient-registration/field/name/name-field.test.tsx +282 -0
  165. package/src/patient-registration/field/obs/obs-field.test.tsx +294 -118
  166. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +172 -108
  167. package/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx +3 -3
  168. package/src/patient-registration/field/person-attributes/location-person-attribute-field.resource.tsx +2 -5
  169. package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +2 -2
  170. package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +249 -131
  171. package/src/patient-registration/field/person-attributes/person-attributes.resource.ts +1 -1
  172. package/src/patient-registration/field/person-attributes/text-person-attribute-field.test.tsx +98 -70
  173. package/src/patient-registration/field/phone/phone-field.test.tsx +100 -0
  174. package/src/patient-registration/form-manager.test.ts +6 -5
  175. package/src/patient-registration/form-manager.ts +5 -2
  176. package/src/patient-registration/input/basic-input/input/input.component.tsx +3 -121
  177. package/src/patient-registration/input/basic-input/input/input.test.tsx +151 -51
  178. package/src/patient-registration/input/basic-input/select/select-input.test.tsx +113 -33
  179. package/src/patient-registration/input/combo-input/combo-input.component.tsx +60 -24
  180. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.component.tsx +10 -101
  181. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +144 -108
  182. package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +241 -177
  183. package/src/patient-registration/input/custom-input/identifier/utils.test.ts +47 -8
  184. package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +12 -12
  185. package/src/patient-registration/input/dummy-data/dummy-data-input.test.tsx +52 -20
  186. package/src/patient-registration/input/input.scss +1 -2
  187. package/src/patient-registration/patient-registration-context.ts +5 -3
  188. package/src/patient-registration/patient-registration-hooks.ts +4 -12
  189. package/src/patient-registration/patient-registration-utils.test.ts +2 -1
  190. package/src/patient-registration/patient-registration-utils.ts +2 -98
  191. package/src/patient-registration/patient-registration.component.tsx +50 -46
  192. package/src/patient-registration/patient-registration.resource.test.tsx +4 -7
  193. package/src/patient-registration/patient-registration.resource.ts +1 -4
  194. package/src/patient-registration/patient-registration.scss +16 -3
  195. package/src/patient-registration/patient-registration.test.tsx +99 -65
  196. package/src/patient-registration/patient-registration.types.ts +17 -28
  197. package/src/patient-registration/section/death-info/death-info-section.test.tsx +130 -34
  198. package/src/patient-registration/section/demographics/demographics-section.test.tsx +122 -68
  199. package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +15 -15
  200. package/src/patient-registration/section/patient-relationships/relationships-section.test.tsx +278 -84
  201. package/src/patient-registration/section/section-wrapper.component.tsx +1 -1
  202. package/src/patient-registration/ui-components/overlay/overlay.test.tsx +104 -0
  203. package/src/patient-registration/validation/patient-registration-validation.test.ts +2 -1
  204. package/src/patient-registration/validation/patient-registration-validation.ts +9 -3
  205. package/src/root.component.tsx +2 -5
  206. package/src/widgets/cancel-patient-edit.test.tsx +48 -11
  207. package/src/widgets/delete-identifier-confirmation.test.tsx +77 -24
  208. package/src/widgets/edit-patient-details-button.component.tsx +14 -18
  209. package/src/widgets/edit-patient-details-button.scss +2 -2
  210. package/src/widgets/edit-patient-details-button.test.tsx +11 -13
  211. package/translations/am.json +9 -4
  212. package/translations/ar.json +9 -4
  213. package/translations/ar_SY.json +9 -4
  214. package/translations/bn.json +9 -4
  215. package/translations/cs.json +9 -4
  216. package/translations/de.json +120 -115
  217. package/translations/en.json +9 -4
  218. package/translations/en_US.json +9 -4
  219. package/translations/es.json +9 -4
  220. package/translations/es_MX.json +9 -4
  221. package/translations/fr.json +9 -4
  222. package/translations/he.json +9 -4
  223. package/translations/hi.json +9 -4
  224. package/translations/hi_IN.json +9 -4
  225. package/translations/id.json +9 -4
  226. package/translations/it.json +9 -4
  227. package/translations/ka.json +9 -4
  228. package/translations/km.json +9 -4
  229. package/translations/ku.json +9 -4
  230. package/translations/ky.json +9 -4
  231. package/translations/lg.json +9 -4
  232. package/translations/ne.json +9 -4
  233. package/translations/pl.json +9 -4
  234. package/translations/pt.json +9 -4
  235. package/translations/pt_BR.json +10 -5
  236. package/translations/qu.json +9 -4
  237. package/translations/ro_RO.json +9 -4
  238. package/translations/ru_RU.json +9 -4
  239. package/translations/si.json +9 -4
  240. package/translations/sq.json +9 -4
  241. package/translations/sw.json +9 -4
  242. package/translations/sw_KE.json +9 -4
  243. package/translations/tr.json +9 -4
  244. package/translations/tr_TR.json +9 -4
  245. package/translations/uk.json +9 -4
  246. package/translations/uz.json +9 -4
  247. package/translations/uz@Latn.json +9 -4
  248. package/translations/uz_UZ.json +9 -4
  249. package/translations/vi.json +9 -4
  250. package/translations/zh.json +50 -45
  251. package/translations/zh_CN.json +9 -4
  252. package/translations/zh_TW.json +9 -4
  253. package/vitest.config.ts +4 -0
  254. package/ADDRESS_CONFIGURATION.md +0 -152
  255. package/IDENTIFIER_CONFIGURATION.md +0 -142
  256. package/IMPLEMENTATION_SUMMARY.md +0 -111
  257. package/QUICK_START.md +0 -95
  258. package/address-required-fields-config.json +0 -26
  259. package/dist/126.js +0 -1
  260. package/dist/15.js +0 -1
  261. package/dist/1564.js +0 -1
  262. package/dist/1567.js +0 -1
  263. package/dist/1845.js +0 -1
  264. package/dist/1953.js +0 -1
  265. package/dist/200.js +0 -1
  266. package/dist/200.js.map +0 -1
  267. package/dist/215.js +0 -1
  268. package/dist/2178.js +0 -1
  269. package/dist/250.js +0 -1
  270. package/dist/250.js.map +0 -1
  271. package/dist/2523.js +0 -1
  272. package/dist/2523.js.map +0 -1
  273. package/dist/2566.js +0 -1
  274. package/dist/2586.js +0 -1
  275. package/dist/2586.js.map +0 -1
  276. package/dist/2716.js +0 -1
  277. package/dist/2716.js.map +0 -1
  278. package/dist/2759.js +0 -1
  279. package/dist/2821.js +0 -6
  280. package/dist/2821.js.map +0 -1
  281. package/dist/3089.js +0 -1
  282. package/dist/3089.js.map +0 -1
  283. package/dist/3230.js +0 -1
  284. package/dist/3441.js +0 -1
  285. package/dist/3565.js +0 -1
  286. package/dist/3571.js +0 -1
  287. package/dist/3571.js.map +0 -1
  288. package/dist/3746.js +0 -1
  289. package/dist/3925.js +0 -1
  290. package/dist/3946.js +0 -1
  291. package/dist/4024.js +0 -1
  292. package/dist/4744.js +0 -1
  293. package/dist/4744.js.map +0 -1
  294. package/dist/4809.js +0 -1
  295. package/dist/4894.js +0 -1
  296. package/dist/4970.js +0 -1
  297. package/dist/4970.js.map +0 -1
  298. package/dist/5130.js +0 -1
  299. package/dist/5187.js +0 -1
  300. package/dist/5491.js +0 -1
  301. package/dist/5491.js.map +0 -1
  302. package/dist/5595.js +0 -1
  303. package/dist/5961.js +0 -1
  304. package/dist/6133.js +0 -1
  305. package/dist/634.js +0 -1
  306. package/dist/634.js.map +0 -1
  307. package/dist/6456.js +0 -1
  308. package/dist/6466.js +0 -1
  309. package/dist/6613.js +0 -1
  310. package/dist/6783.js +0 -1
  311. package/dist/7073.js +0 -38
  312. package/dist/7073.js.map +0 -1
  313. package/dist/7154.js +0 -1
  314. package/dist/7154.js.map +0 -1
  315. package/dist/7348.js +0 -1
  316. package/dist/7439.js +0 -1
  317. package/dist/7439.js.map +0 -1
  318. package/dist/7543.js +0 -1
  319. package/dist/7607.js +0 -1
  320. package/dist/772.js +0 -1
  321. package/dist/7984.js +0 -1
  322. package/dist/7984.js.map +0 -1
  323. package/dist/8538.js +0 -1
  324. package/dist/8538.js.map +0 -1
  325. package/dist/8599.js +0 -1
  326. package/dist/8727.js +0 -1
  327. package/dist/8847.js +0 -1
  328. package/dist/9015.js +0 -1
  329. package/dist/906.js +0 -1
  330. package/dist/9065.js +0 -1
  331. package/dist/9182.js +0 -1
  332. package/dist/9339.js +0 -1
  333. package/dist/9453.js +0 -1
  334. package/dist/9833.js +0 -1
  335. package/dist/9833.js.map +0 -1
  336. package/dist/9856.js +0 -1
  337. package/dist/9856.js.map +0 -1
  338. package/dist/9920.js +0 -1
  339. package/dist/9938.js +0 -1
  340. package/dist/9943.js +0 -1
  341. package/dist/9943.js.map +0 -1
  342. package/dist/olaboot-esm-patient-registration-app.js +0 -5
  343. package/dist/olaboot-esm-patient-registration-app.js.map +0 -1
  344. package/example-config.json +0 -14
  345. package/jest.config.js +0 -3
  346. package/src/resource.ts +0 -12
@@ -1,141 +1,205 @@
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 { useConceptAnswers } from '../field.resource';
5
7
  import { CodedPersonAttributeField } from './coded-person-attribute-field.component';
8
+ import { initialFormValues } from '../../patient-registration.component';
9
+ import { type FormValues } from '../../patient-registration.types';
6
10
 
7
- const mockUseConceptAnswers = jest.mocked(useConceptAnswers);
11
+ const mockUseConceptAnswers = vi.mocked(useConceptAnswers);
8
12
 
9
- jest.mock('../field.resource', () => ({
10
- ...jest.requireActual('../field.resource'),
11
- useConceptAnswers: jest.fn(),
13
+ vi.mock('../field.resource', async () => ({
14
+ ...((await vi.importActual('../field.resource')) as object),
15
+ useConceptAnswers: vi.fn(),
12
16
  }));
13
17
 
18
+ const personAttributeType = {
19
+ format: 'org.openmrs.Concept',
20
+ display: 'Referred by',
21
+ uuid: '4dd56a75-14ab-4148-8700-1f4f704dc5b0',
22
+ name: '',
23
+ description: '',
24
+ };
25
+
26
+ const answerConceptSetUuid = '6682d17f-0777-45e4-a39b-93f77eb3531c';
27
+
28
+ /**
29
+ * Helper to render CodedPersonAttributeField with Formik render props for state-dependent tests.
30
+ */
31
+ const renderCodedPersonAttributeFieldWithFormik = (
32
+ props: {
33
+ answerConceptSetUuid?: string | null;
34
+ customConceptAnswers?: Array<{ uuid: string; label?: string }>;
35
+ required?: boolean;
36
+ } = {},
37
+ initialValues: Partial<FormValues> = {},
38
+ options?: { enableReinitialize?: boolean },
39
+ ) => {
40
+ const defaultValues = {
41
+ attributes: {},
42
+ ...initialValues,
43
+ };
44
+
45
+ let formValuesRef: FormValues = { ...initialFormValues, ...defaultValues } as FormValues;
46
+
47
+ const utils = render(
48
+ <Formik initialValues={defaultValues} onSubmit={() => {}} enableReinitialize={options?.enableReinitialize}>
49
+ {({ setFieldValue, values, setFieldTouched }) => {
50
+ formValuesRef = { ...initialFormValues, ...values } as FormValues;
51
+ return (
52
+ <Form>
53
+ <CodedPersonAttributeField
54
+ id="attributeId"
55
+ personAttributeType={personAttributeType}
56
+ answerConceptSetUuid={props.answerConceptSetUuid ?? answerConceptSetUuid}
57
+ label={personAttributeType.display}
58
+ customConceptAnswers={props.customConceptAnswers ?? []}
59
+ required={props.required ?? false}
60
+ />
61
+ </Form>
62
+ );
63
+ }}
64
+ </Formik>,
65
+ );
66
+
67
+ return {
68
+ ...utils,
69
+ getFormValues: () => formValuesRef,
70
+ };
71
+ };
72
+
14
73
  describe('CodedPersonAttributeField', () => {
15
74
  const conceptAnswers = [
16
75
  { uuid: '1', display: 'Option 1' },
17
76
  { uuid: '2', display: 'Option 2' },
18
77
  ];
19
78
 
20
- const personAttributeType = {
21
- format: 'org.openmrs.Concept',
22
- display: 'Referred by',
23
- uuid: '4dd56a75-14ab-4148-8700-1f4f704dc5b0',
24
- name: '',
25
- description: '',
26
- };
27
-
28
- const answerConceptSetUuid = '6682d17f-0777-45e4-a39b-93f77eb3531c';
29
- let consoleSpy: jest.SpyInstance;
30
-
31
79
  beforeEach(() => {
32
80
  mockUseConceptAnswers.mockReturnValue({
33
81
  data: conceptAnswers,
34
82
  isLoading: false,
35
83
  error: null,
36
84
  });
37
-
38
- consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
39
85
  });
40
86
 
41
- afterEach(() => {
42
- consoleSpy.mockRestore();
43
- });
87
+ describe('Rendering', () => {
88
+ it('renders the conceptAnswers as select options', () => {
89
+ renderCodedPersonAttributeFieldWithFormik();
44
90
 
45
- it('renders an error if there is no concept answer set provided', () => {
46
- expect(() => {
47
- render(
48
- <Formik initialValues={{}} onSubmit={() => {}}>
49
- <Form>
50
- <CodedPersonAttributeField
51
- answerConceptSetUuid={null}
52
- customConceptAnswers={[]}
53
- id="attributeId"
54
- label={personAttributeType.display}
55
- personAttributeType={personAttributeType}
56
- required={false}
57
- />
58
- </Form>
59
- </Formik>,
60
- );
61
- }).toThrow(expect.stringMatching(/has been defined without an answer concept set UUID/i));
91
+ expect(screen.getByLabelText(/Referred by/i)).toBeInTheDocument();
92
+ expect(screen.getByText(/Option 1/i)).toBeInTheDocument();
93
+ expect(screen.getByText(/Option 2/i)).toBeInTheDocument();
94
+ });
95
+
96
+ it('renders customConceptAnswers as select options when they are provided', () => {
97
+ renderCodedPersonAttributeFieldWithFormik({
98
+ customConceptAnswers: [
99
+ {
100
+ uuid: 'A',
101
+ label: 'Special Option A',
102
+ },
103
+ {
104
+ uuid: 'B',
105
+ label: 'Special Option B',
106
+ },
107
+ ],
108
+ });
109
+
110
+ expect(screen.getByLabelText(/Referred by/i)).toBeInTheDocument();
111
+ expect(screen.getByText(/Special Option A/i)).toBeInTheDocument();
112
+ expect(screen.getByText(/Special Option B/i)).toBeInTheDocument();
113
+ expect(screen.queryByText(/Option 1/i)).not.toBeInTheDocument();
114
+ expect(screen.queryByText(/Option 2/i)).not.toBeInTheDocument();
115
+ });
62
116
  });
63
117
 
64
- it('renders an error if the concept answer set does not have any concept answers', () => {
65
- mockUseConceptAnswers.mockReturnValue({
66
- data: [],
67
- isLoading: false,
68
- error: null,
118
+ describe('User interaction', () => {
119
+ it('allows user to select an option', async () => {
120
+ const user = userEvent.setup();
121
+ const { getFormValues } = renderCodedPersonAttributeFieldWithFormik();
122
+
123
+ const select = screen.getByRole('combobox', { name: /Referred by/i }) as HTMLSelectElement;
124
+ await user.selectOptions(select, '1');
125
+
126
+ await waitFor(() => {
127
+ expect(getFormValues().attributes[personAttributeType.uuid]).toBe('1');
128
+ });
129
+ expect(select.value).toBe('1');
69
130
  });
70
131
 
71
- expect(() => {
72
- render(
73
- <Formik initialValues={{}} onSubmit={() => {}}>
74
- <Form>
75
- <CodedPersonAttributeField
76
- id="attributeId"
77
- personAttributeType={personAttributeType}
78
- answerConceptSetUuid={answerConceptSetUuid}
79
- label={personAttributeType.display}
80
- customConceptAnswers={[]}
81
- required={false}
82
- />
83
- </Form>
84
- </Formik>,
85
- );
86
- }).toThrow(expect.stringMatching(/does not have any concept answers/i));
132
+ it('allows user to switch between options', async () => {
133
+ const user = userEvent.setup();
134
+ const { getFormValues } = renderCodedPersonAttributeFieldWithFormik();
135
+
136
+ const select = screen.getByRole('combobox', { name: /Referred by/i }) as HTMLSelectElement;
137
+
138
+ // Select first option
139
+ await user.selectOptions(select, '1');
140
+ await waitFor(() => {
141
+ expect(getFormValues().attributes[personAttributeType.uuid]).toBe('1');
142
+ });
143
+
144
+ // Switch to second option
145
+ await user.selectOptions(select, '2');
146
+ await waitFor(() => {
147
+ expect(getFormValues().attributes[personAttributeType.uuid]).toBe('2');
148
+ });
149
+ });
150
+
151
+ it('allows user to select custom concept answers', async () => {
152
+ const user = userEvent.setup();
153
+ const { getFormValues } = renderCodedPersonAttributeFieldWithFormik({
154
+ customConceptAnswers: [
155
+ {
156
+ uuid: 'A',
157
+ label: 'Special Option A',
158
+ },
159
+ {
160
+ uuid: 'B',
161
+ label: 'Special Option B',
162
+ },
163
+ ],
164
+ });
165
+
166
+ const select = screen.getByRole('combobox', { name: /Referred by/i }) as HTMLSelectElement;
167
+ await user.selectOptions(select, 'A');
168
+
169
+ await waitFor(() => {
170
+ expect(getFormValues().attributes[personAttributeType.uuid]).toBe('A');
171
+ });
172
+ });
87
173
  });
88
174
 
89
- it('renders the conceptAnswers as select options', () => {
90
- render(
91
- <Formik initialValues={{}} onSubmit={() => {}}>
92
- <Form>
93
- <CodedPersonAttributeField
94
- id="attributeId"
95
- personAttributeType={personAttributeType}
96
- answerConceptSetUuid={answerConceptSetUuid}
97
- label={personAttributeType.display}
98
- customConceptAnswers={[]}
99
- required={false}
100
- />
101
- </Form>
102
- </Formik>,
103
- );
104
-
105
- expect(screen.getByLabelText(/Referred by/i)).toBeInTheDocument();
106
- expect(screen.getByText(/Option 1/i)).toBeInTheDocument();
107
- expect(screen.getByText(/Option 2/i)).toBeInTheDocument();
175
+ describe('Required field', () => {
176
+ it('renders as required when required prop is true', () => {
177
+ renderCodedPersonAttributeFieldWithFormik({ required: true });
178
+
179
+ const select = screen.getByRole('combobox', { name: /Referred by/i });
180
+ expect(select).toBeRequired();
181
+ });
182
+
183
+ it('renders as optional when required prop is false', () => {
184
+ renderCodedPersonAttributeFieldWithFormik({ required: false });
185
+
186
+ const select = screen.getByRole('combobox', { name: /Referred by/i });
187
+ expect(select).not.toBeRequired();
188
+ });
108
189
  });
109
190
 
110
- it('renders customConceptAnswers as select options when they are provided', () => {
111
- render(
112
- <Formik initialValues={{}} onSubmit={() => {}}>
113
- <Form>
114
- <CodedPersonAttributeField
115
- id="attributeId"
116
- personAttributeType={personAttributeType}
117
- answerConceptSetUuid={answerConceptSetUuid}
118
- label={personAttributeType.display}
119
- customConceptAnswers={[
120
- {
121
- uuid: 'A',
122
- label: 'Special Option A',
123
- },
124
- {
125
- uuid: 'B',
126
- label: 'Special Option B',
127
- },
128
- ]}
129
- required={false}
130
- />
131
- </Form>
132
- </Formik>,
133
- );
134
-
135
- expect(screen.getByLabelText(/Referred by/i)).toBeInTheDocument();
136
- expect(screen.getByText(/Special Option A/i)).toBeInTheDocument();
137
- expect(screen.getByText(/Special Option B/i)).toBeInTheDocument();
138
- expect(screen.queryByText(/Option 1/i)).not.toBeInTheDocument();
139
- expect(screen.queryByText(/Option 2/i)).not.toBeInTheDocument();
191
+ describe('Loading state', () => {
192
+ it('does not render select while concept answers are loading', () => {
193
+ mockUseConceptAnswers.mockReturnValue({
194
+ data: null,
195
+ isLoading: true,
196
+ error: null,
197
+ });
198
+
199
+ renderCodedPersonAttributeFieldWithFormik();
200
+
201
+ // Component returns null while loading, so select should not be in document
202
+ expect(screen.queryByRole('combobox', { name: /Referred by/i })).not.toBeInTheDocument();
203
+ });
140
204
  });
141
205
  });
@@ -1,11 +1,11 @@
1
1
  import React, { useCallback, useMemo, useRef, useState } from 'react';
2
2
  import classNames from 'classnames';
3
+ import { ComboBox, InlineLoading, Layer } from '@carbon/react';
4
+ import { useTranslation } from 'react-i18next';
3
5
  import { Field, useField } from 'formik';
4
6
  import { type PersonAttributeTypeResponse } from '../../patient-registration.types';
5
- import styles from './../field.scss';
6
7
  import { useLocations } from './location-person-attribute-field.resource';
7
- import { ComboBox, InlineLoading, Layer } from '@carbon/react';
8
- import { useTranslation } from 'react-i18next';
8
+ import styles from './../field.scss';
9
9
 
10
10
  export interface LocationPersonAttributeFieldProps {
11
11
  id: string;
@@ -1,7 +1,7 @@
1
1
  import { useMemo } from 'react';
2
+ import useSWR from 'swr';
2
3
  import { type FetchResponse, fhirBaseUrl, openmrsFetch, useDebounce } from '@openmrs/esm-framework';
3
4
  import { type LocationEntry, type LocationResponse } from '@openmrs/esm-service-queues-app/src/types';
4
- import useSWR from 'swr';
5
5
 
6
6
  interface UseLocationsResult {
7
7
  locations: Array<LocationEntry>;
@@ -32,10 +32,7 @@ export function useLocations(locationTag: string | null, searchQuery: string = '
32
32
  return url + urlSearchParameters.toString();
33
33
  }, [locationTag, debouncedSearchQuery]);
34
34
 
35
- const { data, error, isLoading, isValidating } = useSWR<FetchResponse<LocationResponse>, Error>(
36
- constructUrl,
37
- openmrsFetch,
38
- );
35
+ const { data, isLoading, isValidating } = useSWR<FetchResponse<LocationResponse>, Error>(constructUrl, openmrsFetch);
39
36
 
40
37
  return useMemo(
41
38
  () => ({
@@ -1,12 +1,12 @@
1
1
  import React, { useMemo } from 'react';
2
+ import { useTranslation } from 'react-i18next';
2
3
  import { InlineNotification, TextInputSkeleton } from '@carbon/react';
3
4
  import { type FieldDefinition } from '../../../config-schema';
4
5
  import { CodedPersonAttributeField } from './coded-person-attribute-field.component';
5
6
  import { usePersonAttributeType } from './person-attributes.resource';
6
7
  import { TextPersonAttributeField } from './text-person-attribute-field.component';
7
- import { useTranslation } from 'react-i18next';
8
- import styles from '../field.scss';
9
8
  import { LocationPersonAttributeField } from './location-person-attribute-field.component';
9
+ import styles from '../field.scss';
10
10
 
11
11
  export interface PersonAttributeFieldProps {
12
12
  fieldDefinition: FieldDefinition;