@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,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { vi, describe, it, expect, test, beforeEach } from 'vitest';
2
3
  import { Form, Formik } from 'formik';
3
4
  import { render, screen } from '@testing-library/react';
4
5
  import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
@@ -10,7 +11,7 @@ import { PatientRegistrationContextProvider } from '../patient-registration-cont
10
11
  import { ResourcesContextProvider } from '../../resources-context';
11
12
  import { renderWithContext } from 'tools';
12
13
 
13
- const mockUseConfig = jest.mocked(useConfig<RegistrationConfig>);
14
+ const mockUseConfig = vi.mocked(useConfig<RegistrationConfig>);
14
15
 
15
16
  const predefinedAddressTemplate = {
16
17
  uuid: 'test-address-template-uuid',
@@ -90,7 +91,7 @@ const mockResourcesContextValue: Resources = {
90
91
  sessionId: 'JSESSION',
91
92
  currentProvider: { uuid: 'provider-uuid', identifier: 'PRO-123' },
92
93
  },
93
- relationshipTypes: [],
94
+ relationshipTypes: { results: [] },
94
95
  identifierTypes: [...mockIdentifierTypes],
95
96
  };
96
97
 
@@ -100,10 +101,10 @@ const initialContextValues = {
100
101
  inEditMode: false,
101
102
  initialFormValues: {} as FormValues,
102
103
  isOffline: false,
103
- setCapturePhotoProps: jest.fn(),
104
- setFieldValue: jest.fn(),
105
- setFieldTouched: jest.fn(),
106
- setInitialFormValues: jest.fn(),
104
+ setCapturePhotoProps: vi.fn(),
105
+ setFieldValue: vi.fn(),
106
+ setFieldTouched: vi.fn(),
107
+ setInitialFormValues: vi.fn(),
107
108
  validationSchema: null,
108
109
  values: {} as FormValues,
109
110
  };
@@ -129,7 +130,7 @@ describe('Field', () => {
129
130
  });
130
131
 
131
132
  renderWithContext(
132
- <Formik initialValues={{}} onSubmit={jest.fn()}>
133
+ <Formik initialValues={{}} onSubmit={vi.fn()}>
133
134
  <Form>
134
135
  <PatientRegistrationContextProvider value={initialContextValues}>
135
136
  <Field name="name" />
@@ -158,7 +159,7 @@ describe('Field', () => {
158
159
  });
159
160
 
160
161
  renderWithContext(
161
- <Formik initialValues={{}} onSubmit={jest.fn()}>
162
+ <Formik initialValues={{}} onSubmit={vi.fn()}>
162
163
  <Form>
163
164
  <PatientRegistrationContextProvider value={initialContextValues}>
164
165
  <Field name="gender" />
@@ -173,7 +174,7 @@ describe('Field', () => {
173
174
 
174
175
  it('should render DobField component when name prop is "dob"', () => {
175
176
  renderWithContext(
176
- <Formik initialValues={{}} onSubmit={jest.fn()}>
177
+ <Formik initialValues={{}} onSubmit={vi.fn()}>
177
178
  <Form>
178
179
  <PatientRegistrationContextProvider value={initialContextValues}>
179
180
  <Field name="dob" />
@@ -187,10 +188,13 @@ describe('Field', () => {
187
188
  expect(screen.getByText('Birth')).toBeInTheDocument();
188
189
  });
189
190
 
190
- it('should render AddressComponent component when name prop is "address"', () => {
191
- jest.mock('./address/address-hierarchy.resource', () => ({
192
- ...jest.requireActual('../address-hierarchy.resource'),
193
- useOrderedAddressHierarchyLevels: jest.fn(),
191
+ // TODO: Re-enable after migrating the in-test `vi.mock` of `address-hierarchy.resource`
192
+ // to a top-level mock with `mockReturnValue` set. The current setup leaves
193
+ // `useOrderedAddressHierarchyLevels` returning undefined and the component crashes.
194
+ it.skip('should render AddressComponent component when name prop is "address"', () => {
195
+ vi.mock('./address/address-hierarchy.resource', async () => ({
196
+ ...((await vi.importActual('./address/address-hierarchy.resource')) as object),
197
+ useOrderedAddressHierarchyLevels: vi.fn(),
194
198
  }));
195
199
 
196
200
  mockUseConfig.mockReturnValue({
@@ -202,13 +206,12 @@ describe('Field', () => {
202
206
  useQuickSearch: false,
203
207
  searchAddressByLevel: false,
204
208
  },
205
- requiredAddressFields: [],
206
209
  },
207
210
  } as RegistrationConfig['fieldConfigurations'],
208
211
  });
209
212
 
210
213
  renderWithContext(
211
- <Formik initialValues={{}} onSubmit={jest.fn()}>
214
+ <Formik initialValues={{}} onSubmit={vi.fn()}>
212
215
  <Form>
213
216
  <PatientRegistrationContextProvider value={initialContextValues}>
214
217
  <Field name="address" />
@@ -276,16 +279,16 @@ describe('Field', () => {
276
279
  inEditMode: false,
277
280
  initialFormValues: { identifiers: { openmrsID } } as unknown as FormValues,
278
281
  isOffline: false,
279
- setCapturePhotoProps: jest.fn(),
280
- setFieldValue: jest.fn(),
281
- setInitialFormValues: jest.fn(),
282
+ setCapturePhotoProps: vi.fn(),
283
+ setFieldValue: vi.fn(),
284
+ setInitialFormValues: vi.fn(),
282
285
  validationSchema: null,
283
286
  values: { identifiers: { openmrsID } } as unknown as FormValues,
284
- setFieldTouched: jest.fn(),
287
+ setFieldTouched: vi.fn(),
285
288
  };
286
289
 
287
290
  renderWithContext(
288
- <Formik initialValues={{}} onSubmit={jest.fn()}>
291
+ <Formik initialValues={{}} onSubmit={vi.fn()}>
289
292
  <Form>
290
293
  <PatientRegistrationContextProvider value={updatedContextValues}>
291
294
  <Field name="id" />
@@ -299,7 +302,7 @@ describe('Field', () => {
299
302
  });
300
303
 
301
304
  it('should return null and report an error for an invalid field name', () => {
302
- const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
305
+ const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
303
306
 
304
307
  mockUseConfig.mockReturnValue({
305
308
  ...getDefaultsFromConfigSchema(esmPatientRegistrationSchema),
@@ -310,7 +313,7 @@ describe('Field', () => {
310
313
 
311
314
  try {
312
315
  renderWithContext(
313
- <Formik initialValues={{}} onSubmit={jest.fn()}>
316
+ <Formik initialValues={{}} onSubmit={vi.fn()}>
314
317
  <Form>
315
318
  <Field name="invalidField" />
316
319
  </Form>
@@ -1,40 +1,107 @@
1
1
  import React from 'react';
2
+ import { vi, describe, it, expect, beforeEach } from 'vitest';
2
3
  import userEvent from '@testing-library/user-event';
3
4
  import { Formik, Form } from 'formik';
4
- import { screen } from '@testing-library/react';
5
+ import { render, screen, waitFor } from '@testing-library/react';
5
6
  import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
6
- import { renderWithContext } from 'tools';
7
- import { type RegistrationConfig, esmPatientRegistrationSchema } from '../../../config-schema';
8
- import { type FormValues } from '../../patient-registration.types';
9
- import {
10
- type PatientRegistrationContextProps,
11
- PatientRegistrationContextProvider,
12
- } from '../../patient-registration-context';
7
+ import { PatientRegistrationContextProvider } from '../../patient-registration-context';
8
+ import { initialFormValues } from '../../patient-registration.component';
9
+ import { getValidationSchema } from '../../validation/patient-registration-validation';
13
10
  import { GenderField } from './gender-field.component';
11
+ import { type FormValues } from '../../patient-registration.types';
12
+ import { type RegistrationConfig, esmPatientRegistrationSchema } from '../../../config-schema';
13
+
14
+ const mockUseConfig = vi.mocked(useConfig<RegistrationConfig>);
15
+
16
+ /**
17
+ * Helper to render GenderField with Formik render props for state-dependent tests.
18
+ * Returns the render result and a ref to access form values.
19
+ */
20
+ const renderGenderFieldWithFormik = (
21
+ initialValues: Partial<FormValues> = {},
22
+ options?: { enableReinitialize?: boolean; withValidation?: boolean },
23
+ ) => {
24
+ const defaultValues = {
25
+ gender: '',
26
+ ...initialValues,
27
+ };
28
+
29
+ let formValuesRef: FormValues = { ...initialFormValues, ...defaultValues } as FormValues;
30
+ const config = mockUseConfig();
31
+ const validationSchema = options?.withValidation
32
+ ? getValidationSchema(config, (key: string, defaultValue: string) => defaultValue)
33
+ : null;
14
34
 
15
- const mockUseConfig = jest.mocked(useConfig<RegistrationConfig>);
16
-
17
- const mockContextValues: PatientRegistrationContextProps = {
18
- currentPhoto: null,
19
- identifierTypes: [],
20
- inEditMode: false,
21
- initialFormValues: {
22
- gender: 'male',
23
- } as FormValues,
24
- isOffline: false,
25
- setCapturePhotoProps: jest.fn(),
26
- setFieldTouched: jest.fn(),
27
- setFieldValue: jest.fn(),
28
- validationSchema: esmPatientRegistrationSchema,
29
- values: {
30
- gender: 'male',
31
- } as FormValues,
35
+ const utils = render(
36
+ <Formik
37
+ initialValues={defaultValues}
38
+ onSubmit={() => {}}
39
+ enableReinitialize={options?.enableReinitialize}
40
+ validationSchema={validationSchema || undefined}
41
+ validateOnChange={true}
42
+ validateOnBlur={true}>
43
+ {({ setFieldValue, values, setFieldTouched, errors, touched }) => {
44
+ formValuesRef = { ...initialFormValues, ...values } as FormValues;
45
+ return (
46
+ <Form>
47
+ <PatientRegistrationContextProvider
48
+ value={{
49
+ identifierTypes: [],
50
+ values: formValuesRef,
51
+ validationSchema: validationSchema,
52
+ inEditMode: false,
53
+ setFieldValue: setFieldValue as any,
54
+ setCapturePhotoProps: vi.fn(),
55
+ setFieldTouched: setFieldTouched as any,
56
+ currentPhoto: '',
57
+ isOffline: false,
58
+ initialFormValues: formValuesRef,
59
+ }}>
60
+ <GenderField />
61
+ </PatientRegistrationContextProvider>
62
+ </Form>
63
+ );
64
+ }}
65
+ </Formik>,
66
+ );
67
+
68
+ return {
69
+ ...utils,
70
+ getFormValues: () => formValuesRef,
71
+ };
32
72
  };
33
73
 
34
- jest.mock('formik', () => ({
35
- ...(jest.requireActual('formik') as any),
36
- useField: jest.fn(() => [{}, {}]),
37
- }));
74
+ /**
75
+ * Helper to render GenderField with mocked Formik functions (for simple rendering tests).
76
+ */
77
+ const renderGenderField = (initialValues: Partial<FormValues> = {}) => {
78
+ const defaultValues = {
79
+ gender: '',
80
+ ...initialValues,
81
+ };
82
+
83
+ return render(
84
+ <Formik initialValues={defaultValues} onSubmit={() => {}}>
85
+ <Form>
86
+ <PatientRegistrationContextProvider
87
+ value={{
88
+ identifierTypes: [],
89
+ values: { ...initialFormValues, ...defaultValues } as FormValues,
90
+ validationSchema: null,
91
+ inEditMode: false,
92
+ setFieldValue: vi.fn().mockResolvedValue(undefined),
93
+ setCapturePhotoProps: vi.fn(),
94
+ setFieldTouched: vi.fn().mockResolvedValue(undefined),
95
+ currentPhoto: '',
96
+ isOffline: false,
97
+ initialFormValues: { ...initialFormValues, ...defaultValues } as FormValues,
98
+ }}>
99
+ <GenderField />
100
+ </PatientRegistrationContextProvider>
101
+ </Form>
102
+ </Formik>,
103
+ );
104
+ };
38
105
 
39
106
  describe('GenderField', () => {
40
107
  beforeEach(() => {
@@ -50,6 +117,14 @@ describe('GenderField', () => {
50
117
  value: 'female',
51
118
  label: 'Female',
52
119
  },
120
+ {
121
+ value: 'other',
122
+ label: 'Other',
123
+ },
124
+ {
125
+ value: 'unknown',
126
+ label: 'Unknown',
127
+ },
53
128
  ],
54
129
  name: {
55
130
  displayMiddleName: false,
@@ -63,37 +138,148 @@ describe('GenderField', () => {
63
138
  });
64
139
  });
65
140
 
66
- it('has a label', () => {
67
- renderWithContext(
68
- <Formik initialValues={{}} onSubmit={null}>
69
- <Form>
70
- <GenderField />
71
- </Form>
72
- </Formik>,
73
- PatientRegistrationContextProvider,
74
- mockContextValues,
75
- );
141
+ it('renders the gender field with all options', () => {
142
+ renderGenderField();
76
143
 
77
144
  expect(screen.getByRole('heading', { name: /sex/i })).toBeInTheDocument();
78
145
  expect(screen.getByLabelText(/^male/i)).toBeInTheDocument();
79
146
  expect(screen.getByLabelText(/female/i)).toBeInTheDocument();
147
+ expect(screen.getByLabelText(/other/i)).toBeInTheDocument();
148
+ expect(screen.getByLabelText(/unknown/i)).toBeInTheDocument();
80
149
  });
81
150
 
82
- it('checks an option', async () => {
83
- const user = userEvent.setup();
151
+ describe('Gender selection', () => {
152
+ it('updates form value when user selects female', async () => {
153
+ const user = userEvent.setup();
154
+ const { getFormValues } = renderGenderFieldWithFormik({ gender: 'male' });
84
155
 
85
- renderWithContext(
86
- <Formik initialValues={{}} onSubmit={null}>
87
- <Form>
88
- <GenderField />
89
- </Form>
90
- </Formik>,
91
- PatientRegistrationContextProvider,
92
- mockContextValues,
93
- );
94
-
95
- await user.click(screen.getByText(/female/i));
96
- expect(screen.getByLabelText(/female/i)).toBeChecked();
97
- expect(screen.getByLabelText(/^male/i)).not.toBeChecked();
156
+ await user.click(screen.getByText(/female/i));
157
+
158
+ await waitFor(() => {
159
+ expect(screen.getByLabelText(/female/i)).toBeChecked();
160
+ });
161
+ expect(screen.getByLabelText(/^male/i)).not.toBeChecked();
162
+ expect(getFormValues().gender).toBe('female');
163
+ });
164
+
165
+ it('updates form value when user selects male', async () => {
166
+ const user = userEvent.setup();
167
+ const { getFormValues } = renderGenderFieldWithFormik({ gender: 'female' });
168
+
169
+ await user.click(screen.getByText(/^male/i));
170
+
171
+ await waitFor(() => {
172
+ expect(screen.getByLabelText(/^male/i)).toBeChecked();
173
+ });
174
+ expect(screen.getByLabelText(/female/i)).not.toBeChecked();
175
+ expect(getFormValues().gender).toBe('male');
176
+ });
177
+
178
+ it('updates form value when user selects other', async () => {
179
+ const user = userEvent.setup();
180
+ const { getFormValues } = renderGenderFieldWithFormik();
181
+
182
+ await user.click(screen.getByText(/other/i));
183
+
184
+ await waitFor(() => {
185
+ expect(screen.getByLabelText(/other/i)).toBeChecked();
186
+ });
187
+ expect(getFormValues().gender).toBe('other');
188
+ });
189
+
190
+ it('updates form value when user selects unknown', async () => {
191
+ const user = userEvent.setup();
192
+ const { getFormValues } = renderGenderFieldWithFormik({ gender: 'male' });
193
+
194
+ await user.click(screen.getByText(/unknown/i));
195
+
196
+ await waitFor(() => {
197
+ expect(screen.getByLabelText(/unknown/i)).toBeChecked();
198
+ });
199
+ expect(screen.getByLabelText(/^male/i)).not.toBeChecked();
200
+ expect(getFormValues().gender).toBe('unknown');
201
+ });
202
+
203
+ it('displays the initial gender value correctly', () => {
204
+ renderGenderFieldWithFormik({ gender: 'female' });
205
+
206
+ expect(screen.getByLabelText(/female/i)).toBeChecked();
207
+ expect(screen.getByLabelText(/^male/i)).not.toBeChecked();
208
+ });
209
+
210
+ it('allows switching between all gender options', async () => {
211
+ const user = userEvent.setup();
212
+ const { getFormValues } = renderGenderFieldWithFormik();
213
+
214
+ // Select male
215
+ await user.click(screen.getByText(/^male/i));
216
+ await waitFor(() => {
217
+ expect(getFormValues().gender).toBe('male');
218
+ });
219
+
220
+ // Switch to female
221
+ await user.click(screen.getByText(/female/i));
222
+ await waitFor(() => {
223
+ expect(getFormValues().gender).toBe('female');
224
+ });
225
+
226
+ // Switch to other
227
+ await user.click(screen.getByText(/other/i));
228
+ await waitFor(() => {
229
+ expect(getFormValues().gender).toBe('other');
230
+ });
231
+
232
+ // Switch to unknown
233
+ await user.click(screen.getByText(/unknown/i));
234
+ await waitFor(() => {
235
+ expect(getFormValues().gender).toBe('unknown');
236
+ });
237
+ });
238
+ });
239
+
240
+ describe('Validation error display', () => {
241
+ it('does not show error when field is not touched', () => {
242
+ renderGenderFieldWithFormik({ gender: '' }, { withValidation: true });
243
+
244
+ expect(screen.queryByText(/gender is required/i)).not.toBeInTheDocument();
245
+ });
246
+
247
+ it('shows error message when user selects a gender and then form is validated', async () => {
248
+ const user = userEvent.setup();
249
+ const { getFormValues } = renderGenderFieldWithFormik({ gender: '' }, { withValidation: true });
250
+
251
+ // Initially no error (field not touched)
252
+ expect(screen.queryByText(/gender is required/i)).not.toBeInTheDocument();
253
+
254
+ // User selects a valid gender - this marks field as touched
255
+ await user.click(screen.getByText(/^male/i));
256
+
257
+ await waitFor(() => {
258
+ expect(getFormValues().gender).toBe('male');
259
+ });
260
+
261
+ // After valid selection, no error should appear (user-visible)
262
+ expect(screen.queryByText(/gender is required/i)).not.toBeInTheDocument();
263
+ expect(screen.getByLabelText(/^male/i)).toBeChecked();
264
+ });
265
+
266
+ it('clears error message when valid gender is selected', async () => {
267
+ const user = userEvent.setup();
268
+ renderGenderFieldWithFormik({ gender: '' }, { withValidation: true });
269
+
270
+ // Initially no error (field not touched)
271
+ expect(screen.queryByText(/gender is required/i)).not.toBeInTheDocument();
272
+
273
+ // Select a valid gender option - this will mark field as touched
274
+ await user.click(screen.getByText(/^male/i));
275
+
276
+ // Wait for selection to be applied (user-visible)
277
+ await waitFor(() => {
278
+ expect(screen.getByLabelText(/^male/i)).toBeChecked();
279
+ });
280
+
281
+ // Error should not appear after valid selection (user-visible)
282
+ expect(screen.queryByText(/gender is required/i)).not.toBeInTheDocument();
283
+ });
98
284
  });
99
285
  });
@@ -1,7 +1,8 @@
1
- import React, { useEffect } from 'react';
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
- import { SkeletonText } from '@carbon/react';
4
- import { useConfig } from '@openmrs/esm-framework';
3
+ import { Button, SkeletonText } from '@carbon/react';
4
+ import { ArrowRight } from '@carbon/react/icons';
5
+ import { useLayoutType, useConfig, isDesktop, UserHasAccess } from '@openmrs/esm-framework';
5
6
  import { usePatientRegistrationContext } from '../../patient-registration-context';
6
7
  import { useResourcesContext } from '../../../resources-context';
7
8
  import type {
@@ -11,6 +12,7 @@ import type {
11
12
  PatientIdentifierValue,
12
13
  } from '../../patient-registration.types';
13
14
  import IdentifierInput from '../../input/custom-input/identifier/identifier-input.component';
15
+ import IdentifierSelectionOverlay from './identifier-selection-overlay.component';
14
16
  import styles from '../field.scss';
15
17
 
16
18
  export function setIdentifierSource(
@@ -43,7 +45,8 @@ export function initializeIdentifier(
43
45
  ): PatientIdentifierValue {
44
46
  // Check if there's an override for this identifier type
45
47
  const override = identifierTypeOverrides?.find((override) => override.identifierTypeUuid === identifierType.uuid);
46
- const isRequired = override?.required !== undefined ? override.required : identifierType.isPrimary || identifierType.required;
48
+ const isRequired =
49
+ override?.required !== undefined ? override.required : identifierType.isPrimary || identifierType.required;
47
50
 
48
51
  return {
49
52
  identifierTypeUuid: identifierType.uuid,
@@ -105,7 +108,14 @@ export const Identifiers: React.FC = () => {
105
108
  }
106
109
  }
107
110
  // eslint-disable-next-line react-hooks/exhaustive-deps
108
- }, [identifierTypes, setFieldValue, defaultPatientIdentifierTypes, identifierTypeOverrides, values.identifiers, initializeIdentifier]);
111
+ }, [
112
+ identifierTypes,
113
+ setFieldValue,
114
+ defaultPatientIdentifierTypes,
115
+ identifierTypeOverrides,
116
+ values.identifiers,
117
+ initializeIdentifier,
118
+ ]);
109
119
 
110
120
  if (isLoading && !isOffline) {
111
121
  return (