@olaboot/esm-patient-registration-app 9.2.1 → 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 (342) 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 +507 -507
  137. package/src/constants.ts +1 -1
  138. package/src/index.ts +2 -24
  139. package/src/nav-link.test.tsx +1 -0
  140. package/src/offline.resources.ts +97 -31
  141. package/src/patient-registration/before-save-prompt.test.tsx +199 -0
  142. package/src/patient-registration/field/__mocks__/field.resource.ts +8 -7
  143. package/src/patient-registration/field/address/address-field.component.tsx +183 -186
  144. package/src/patient-registration/field/address/address-hierarchy-levels.component.tsx +6 -1
  145. package/src/patient-registration/field/address/address-hierarchy.test.tsx +289 -296
  146. package/src/patient-registration/field/address/address-search.component.tsx +20 -8
  147. package/src/patient-registration/field/address/address-search.scss +19 -2
  148. package/src/patient-registration/field/address/address-search.test.tsx +333 -141
  149. package/src/patient-registration/field/address/custom-address-field.component.tsx +1 -1
  150. package/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx +1 -1
  151. package/src/patient-registration/field/cause-of-death/cause-of-death.test.tsx +251 -0
  152. package/src/patient-registration/field/custom-field.component.tsx +1 -1
  153. package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +1 -1
  154. package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.test.tsx +144 -0
  155. package/src/patient-registration/field/dob/dob.component.tsx +2 -2
  156. package/src/patient-registration/field/dob/dob.test.tsx +370 -54
  157. package/src/patient-registration/field/field.component.tsx +1 -1
  158. package/src/patient-registration/field/field.resource.ts +2 -2
  159. package/src/patient-registration/field/field.test.tsx +333 -330
  160. package/src/patient-registration/field/gender/gender-field.test.tsx +240 -54
  161. package/src/patient-registration/field/id/id-field.component.tsx +146 -144
  162. package/src/patient-registration/field/id/id-field.test.tsx +177 -121
  163. package/src/patient-registration/field/id/identifier-selection-overlay.component.tsx +200 -200
  164. package/src/patient-registration/field/id/identifier-selection-overlay.test.tsx +346 -0
  165. package/src/patient-registration/field/name/name-field.component.tsx +2 -2
  166. package/src/patient-registration/field/name/name-field.test.tsx +282 -0
  167. package/src/patient-registration/field/obs/obs-field.test.tsx +294 -118
  168. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +172 -108
  169. package/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx +3 -3
  170. package/src/patient-registration/field/person-attributes/location-person-attribute-field.resource.tsx +2 -5
  171. package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +2 -2
  172. package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +249 -131
  173. package/src/patient-registration/field/person-attributes/person-attributes.resource.ts +1 -1
  174. package/src/patient-registration/field/person-attributes/text-person-attribute-field.test.tsx +98 -70
  175. package/src/patient-registration/field/phone/phone-field.test.tsx +100 -0
  176. package/src/patient-registration/form-manager.test.ts +6 -5
  177. package/src/patient-registration/form-manager.ts +5 -2
  178. package/src/patient-registration/input/basic-input/input/input.component.tsx +3 -121
  179. package/src/patient-registration/input/basic-input/input/input.test.tsx +151 -51
  180. package/src/patient-registration/input/basic-input/select/select-input.test.tsx +113 -33
  181. package/src/patient-registration/input/combo-input/combo-input.component.tsx +60 -24
  182. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.component.tsx +10 -101
  183. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +144 -108
  184. package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +241 -177
  185. package/src/patient-registration/input/custom-input/identifier/utils.test.ts +47 -8
  186. package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +12 -12
  187. package/src/patient-registration/input/dummy-data/dummy-data-input.test.tsx +52 -20
  188. package/src/patient-registration/input/input.scss +1 -2
  189. package/src/patient-registration/patient-registration-context.ts +5 -3
  190. package/src/patient-registration/patient-registration-hooks.ts +4 -12
  191. package/src/patient-registration/patient-registration-utils.test.ts +2 -1
  192. package/src/patient-registration/patient-registration-utils.ts +2 -98
  193. package/src/patient-registration/patient-registration.component.tsx +50 -46
  194. package/src/patient-registration/patient-registration.resource.test.tsx +4 -7
  195. package/src/patient-registration/patient-registration.resource.ts +1 -4
  196. package/src/patient-registration/patient-registration.scss +16 -3
  197. package/src/patient-registration/patient-registration.test.tsx +614 -580
  198. package/src/patient-registration/patient-registration.types.ts +17 -28
  199. package/src/patient-registration/section/death-info/death-info-section.test.tsx +130 -34
  200. package/src/patient-registration/section/demographics/demographics-section.test.tsx +122 -68
  201. package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +15 -15
  202. package/src/patient-registration/section/patient-relationships/relationships-section.test.tsx +278 -84
  203. package/src/patient-registration/section/section-wrapper.component.tsx +1 -1
  204. package/src/patient-registration/ui-components/overlay/overlay.test.tsx +104 -0
  205. package/src/patient-registration/validation/patient-registration-validation.test.ts +2 -1
  206. package/src/patient-registration/validation/patient-registration-validation.ts +9 -3
  207. package/src/root.component.tsx +2 -5
  208. package/src/widgets/cancel-patient-edit.test.tsx +48 -11
  209. package/src/widgets/delete-identifier-confirmation.test.tsx +77 -24
  210. package/src/widgets/edit-patient-details-button.component.tsx +14 -18
  211. package/src/widgets/edit-patient-details-button.scss +2 -2
  212. package/src/widgets/edit-patient-details-button.test.tsx +11 -13
  213. package/translations/am.json +9 -4
  214. package/translations/ar.json +9 -4
  215. package/translations/ar_SY.json +9 -4
  216. package/translations/bn.json +9 -4
  217. package/translations/cs.json +9 -4
  218. package/translations/de.json +120 -115
  219. package/translations/en.json +10 -4
  220. package/translations/en_US.json +9 -4
  221. package/translations/es.json +9 -4
  222. package/translations/es_MX.json +9 -4
  223. package/translations/fr.json +9 -4
  224. package/translations/he.json +9 -4
  225. package/translations/hi.json +9 -4
  226. package/translations/hi_IN.json +9 -4
  227. package/translations/id.json +9 -4
  228. package/translations/it.json +9 -4
  229. package/translations/ka.json +9 -4
  230. package/translations/km.json +9 -4
  231. package/translations/ku.json +9 -4
  232. package/translations/ky.json +9 -4
  233. package/translations/lg.json +9 -4
  234. package/translations/ne.json +9 -4
  235. package/translations/pl.json +9 -4
  236. package/translations/pt.json +9 -4
  237. package/translations/pt_BR.json +10 -5
  238. package/translations/qu.json +9 -4
  239. package/translations/ro_RO.json +9 -4
  240. package/translations/ru_RU.json +9 -4
  241. package/translations/si.json +9 -4
  242. package/translations/sq.json +9 -4
  243. package/translations/sw.json +9 -4
  244. package/translations/sw_KE.json +9 -4
  245. package/translations/tr.json +9 -4
  246. package/translations/tr_TR.json +9 -4
  247. package/translations/uk.json +9 -4
  248. package/translations/uz.json +9 -4
  249. package/translations/uz@Latn.json +9 -4
  250. package/translations/uz_UZ.json +9 -4
  251. package/translations/vi.json +9 -4
  252. package/translations/zh.json +50 -45
  253. package/translations/zh_CN.json +9 -4
  254. package/translations/zh_TW.json +9 -4
  255. package/vitest.config.ts +4 -0
  256. package/dist/126.js +0 -1
  257. package/dist/15.js +0 -1
  258. package/dist/1564.js +0 -1
  259. package/dist/1567.js +0 -1
  260. package/dist/1845.js +0 -1
  261. package/dist/1953.js +0 -1
  262. package/dist/200.js +0 -1
  263. package/dist/200.js.map +0 -1
  264. package/dist/215.js +0 -1
  265. package/dist/2178.js +0 -1
  266. package/dist/250.js +0 -1
  267. package/dist/250.js.map +0 -1
  268. package/dist/2523.js +0 -1
  269. package/dist/2523.js.map +0 -1
  270. package/dist/2566.js +0 -1
  271. package/dist/2586.js +0 -1
  272. package/dist/2586.js.map +0 -1
  273. package/dist/2716.js +0 -1
  274. package/dist/2716.js.map +0 -1
  275. package/dist/2759.js +0 -1
  276. package/dist/2821.js +0 -6
  277. package/dist/2821.js.map +0 -1
  278. package/dist/3089.js +0 -1
  279. package/dist/3089.js.map +0 -1
  280. package/dist/3230.js +0 -1
  281. package/dist/3441.js +0 -1
  282. package/dist/3565.js +0 -1
  283. package/dist/3571.js +0 -1
  284. package/dist/3571.js.map +0 -1
  285. package/dist/3746.js +0 -1
  286. package/dist/3925.js +0 -1
  287. package/dist/3946.js +0 -1
  288. package/dist/4024.js +0 -1
  289. package/dist/4744.js +0 -1
  290. package/dist/4744.js.map +0 -1
  291. package/dist/4809.js +0 -1
  292. package/dist/4894.js +0 -1
  293. package/dist/4970.js +0 -1
  294. package/dist/4970.js.map +0 -1
  295. package/dist/5130.js +0 -1
  296. package/dist/5187.js +0 -1
  297. package/dist/5491.js +0 -1
  298. package/dist/5491.js.map +0 -1
  299. package/dist/5595.js +0 -1
  300. package/dist/5961.js +0 -1
  301. package/dist/6133.js +0 -1
  302. package/dist/634.js +0 -1
  303. package/dist/634.js.map +0 -1
  304. package/dist/6456.js +0 -1
  305. package/dist/6466.js +0 -1
  306. package/dist/6613.js +0 -1
  307. package/dist/6783.js +0 -1
  308. package/dist/7073.js +0 -38
  309. package/dist/7073.js.map +0 -1
  310. package/dist/7154.js +0 -1
  311. package/dist/7154.js.map +0 -1
  312. package/dist/7348.js +0 -1
  313. package/dist/7439.js +0 -1
  314. package/dist/7439.js.map +0 -1
  315. package/dist/7543.js +0 -1
  316. package/dist/7607.js +0 -1
  317. package/dist/772.js +0 -1
  318. package/dist/7984.js +0 -1
  319. package/dist/7984.js.map +0 -1
  320. package/dist/8538.js +0 -1
  321. package/dist/8538.js.map +0 -1
  322. package/dist/8599.js +0 -1
  323. package/dist/8727.js +0 -1
  324. package/dist/8847.js +0 -1
  325. package/dist/9015.js +0 -1
  326. package/dist/906.js +0 -1
  327. package/dist/9065.js +0 -1
  328. package/dist/9182.js +0 -1
  329. package/dist/9339.js +0 -1
  330. package/dist/9453.js +0 -1
  331. package/dist/9833.js +0 -1
  332. package/dist/9833.js.map +0 -1
  333. package/dist/9856.js +0 -1
  334. package/dist/9856.js.map +0 -1
  335. package/dist/9920.js +0 -1
  336. package/dist/9938.js +0 -1
  337. package/dist/9943.js +0 -1
  338. package/dist/9943.js.map +0 -1
  339. package/dist/olaboot-esm-patient-registration-app.js +0 -5
  340. package/dist/olaboot-esm-patient-registration-app.js.map +0 -1
  341. package/jest.config.js +0 -3
  342. package/src/resource.ts +0 -12
@@ -1,15 +1,18 @@
1
1
  import React from 'react';
2
+ import { vi, describe, it, expect, test } from 'vitest';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { screen, waitFor } from '@testing-library/react';
2
5
  import { Form, Formik } from 'formik';
3
- import { screen } from '@testing-library/react';
4
6
  import { type Resources } from '../../../offline.resources';
5
7
  import { type FormValues } from '../../patient-registration.types';
6
8
  import { PatientRegistrationContextProvider } from '../../patient-registration-context';
7
9
  import { RelationshipsSection } from './relationships-section.component';
8
10
  import { ResourcesContextProvider } from '../../../resources-context';
9
11
  import { renderWithContext } from 'tools';
12
+ import { initialFormValues } from '../../patient-registration.component';
10
13
 
11
- jest.mock('../../patient-registration.resource', () => ({
12
- fetchPerson: jest.fn().mockResolvedValue({
14
+ vi.mock('../../patient-registration.resource', () => ({
15
+ fetchPerson: vi.fn().mockResolvedValue({
13
16
  data: {
14
17
  results: [
15
18
  { uuid: '42ae5ce0-d64b-11ea-9064-5adc43bbdd24', display: 'Person 1' },
@@ -19,95 +22,286 @@ jest.mock('../../patient-registration.resource', () => ({
19
22
  }),
20
23
  }));
21
24
 
22
- let mockResourcesContextValue = {
23
- addressTemplate: null,
24
- currentSession: {
25
- authenticated: true,
26
- sessionId: 'JSESSION',
27
- currentProvider: { uuid: '45ce6c2e-dd5a-11e6-9d9c-0242ac150002', identifier: 'PRO-123' },
28
- },
29
- identifierTypes: [],
30
- relationshipTypes: null,
31
- } as Resources;
32
-
33
- const initialContextValues = {
34
- currentPhoto: 'data:image/png;base64,1234567890',
35
- identifierTypes: [],
36
- inEditMode: false,
37
- initialFormValues: {} as FormValues,
38
- isOffline: false,
39
- setCapturePhotoProps: jest.fn(),
40
- setFieldValue: jest.fn(),
41
- setFieldTouched: jest.fn(),
42
- setInitialFormValues: jest.fn(),
43
- validationSchema: null,
44
- values: {} as FormValues,
25
+ const mockRelationshipTypes = {
26
+ results: [
27
+ {
28
+ displayAIsToB: 'Mother',
29
+ aIsToB: 'Mother',
30
+ bIsToA: 'Child',
31
+ displayBIsToA: 'Child',
32
+ uuid: '42ae5ce0-d64b-11ea-9064-5adc43bbdd34',
33
+ },
34
+ {
35
+ displayAIsToB: 'Father',
36
+ aIsToB: 'Father',
37
+ bIsToA: 'Child',
38
+ displayBIsToA: 'Child',
39
+ uuid: '52ae5ce0-d64b-11ea-9064-5adc43bbdd24',
40
+ },
41
+ ],
45
42
  };
46
43
 
44
+ /**
45
+ * Helper to render RelationshipsSection with Formik for state-dependent tests.
46
+ */
47
+ function renderRelationshipsSectionWithFormik(
48
+ initialValues: Partial<FormValues> = {},
49
+ resourcesContextValue: Resources,
50
+ ) {
51
+ const defaultValues = {
52
+ ...initialFormValues,
53
+ relationships: [],
54
+ ...initialValues,
55
+ };
56
+
57
+ let formValuesRef: FormValues = { ...initialFormValues, ...defaultValues } as FormValues;
58
+
59
+ const utils = renderWithContext(
60
+ <Formik initialValues={defaultValues} onSubmit={() => {}}>
61
+ {({ setFieldValue, values }) => {
62
+ formValuesRef = { ...initialFormValues, ...values } as FormValues;
63
+ return (
64
+ <Form>
65
+ <PatientRegistrationContextProvider
66
+ value={{
67
+ identifierTypes: [],
68
+ values: formValuesRef,
69
+ validationSchema: null,
70
+ inEditMode: false,
71
+ setFieldValue: setFieldValue as any,
72
+ setCapturePhotoProps: vi.fn(),
73
+ setFieldTouched: vi.fn().mockResolvedValue(undefined),
74
+ currentPhoto: '',
75
+ isOffline: false,
76
+ initialFormValues: formValuesRef,
77
+ }}>
78
+ <RelationshipsSection />
79
+ </PatientRegistrationContextProvider>
80
+ </Form>
81
+ );
82
+ }}
83
+ </Formik>,
84
+ ResourcesContextProvider,
85
+ resourcesContextValue,
86
+ );
87
+
88
+ return {
89
+ ...utils,
90
+ getFormValues: () => formValuesRef,
91
+ };
92
+ }
93
+
47
94
  describe('RelationshipsSection', () => {
48
- it('renders a loader when relationshipTypes are not available', () => {
49
- renderWithContext(
50
- <Formik initialValues={{}} onSubmit={null}>
51
- <Form>
52
- <RelationshipsSection />
53
- </Form>
54
- </Formik>,
55
- ResourcesContextProvider,
56
- mockResourcesContextValue,
57
- );
58
-
59
- expect(screen.getByLabelText(/loading relationships section/i)).toBeInTheDocument();
60
- expect(screen.getByRole('progressbar')).toBeInTheDocument();
61
- expect(screen.queryByText(/add relationship/i)).not.toBeInTheDocument();
95
+ describe('Loading state', () => {
96
+ it('renders a loader when relationshipTypes are not available', () => {
97
+ const mockResourcesContextValue = {
98
+ addressTemplate: null,
99
+ currentSession: {
100
+ authenticated: true,
101
+ sessionId: 'JSESSION',
102
+ currentProvider: { uuid: '45ce6c2e-dd5a-11e6-9d9c-0242ac150002', identifier: 'PRO-123' },
103
+ },
104
+ identifierTypes: [],
105
+ relationshipTypes: null,
106
+ } as Resources;
107
+
108
+ renderRelationshipsSectionWithFormik({}, mockResourcesContextValue);
109
+
110
+ expect(screen.getByLabelText(/loading relationships section/i)).toBeInTheDocument();
111
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
112
+ expect(screen.queryByText(/add relationship/i)).not.toBeInTheDocument();
113
+ });
62
114
  });
63
115
 
64
- it('renders relationships when relationshipTypes are available', () => {
65
- const relationshipTypes = {
66
- results: [
116
+ describe('Rendering', () => {
117
+ it('renders the relationships section when relationshipTypes are available', () => {
118
+ const mockResourcesContextValue = {
119
+ addressTemplate: null,
120
+ currentSession: {
121
+ authenticated: true,
122
+ sessionId: 'JSESSION',
123
+ currentProvider: { uuid: '45ce6c2e-dd5a-11e6-9d9c-0242ac150002', identifier: 'PRO-123' },
124
+ },
125
+ identifierTypes: [],
126
+ relationshipTypes: mockRelationshipTypes,
127
+ } as Resources;
128
+
129
+ renderRelationshipsSectionWithFormik({}, mockResourcesContextValue);
130
+
131
+ expect(screen.getByLabelText(/relationships section/i)).toBeInTheDocument();
132
+ expect(screen.getByRole('button', { name: /add relationship/i })).toBeInTheDocument();
133
+ });
134
+
135
+ it('renders existing relationships', () => {
136
+ const mockResourcesContextValue = {
137
+ addressTemplate: null,
138
+ currentSession: {
139
+ authenticated: true,
140
+ sessionId: 'JSESSION',
141
+ currentProvider: { uuid: '45ce6c2e-dd5a-11e6-9d9c-0242ac150002', identifier: 'PRO-123' },
142
+ },
143
+ identifierTypes: [],
144
+ relationshipTypes: mockRelationshipTypes,
145
+ } as Resources;
146
+
147
+ renderRelationshipsSectionWithFormik(
67
148
  {
68
- displayAIsToB: 'Mother',
69
- aIsToB: 'Mother',
70
- bIsToA: 'Child',
71
- displayBIsToA: 'Child',
72
- uuid: '42ae5ce0-d64b-11ea-9064-5adc43bbdd34',
149
+ relationships: [
150
+ {
151
+ action: 'ADD',
152
+ relatedPersonUuid: '11524ae7-3ef6-4ab6-aff6-804ffc58704a',
153
+ relatedPersonName: 'John Doe',
154
+ relationshipType: '',
155
+ },
156
+ ],
157
+ },
158
+ mockResourcesContextValue,
159
+ );
160
+
161
+ expect(screen.getByLabelText(/relationships section/i)).toBeInTheDocument();
162
+ expect(screen.getByRole('heading', { name: /relationship/i })).toBeInTheDocument();
163
+ expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument();
164
+ expect(screen.getByRole('searchbox', { name: /full name/i })).toBeInTheDocument();
165
+ });
166
+
167
+ it('renders relationship type options', () => {
168
+ const mockResourcesContextValue = {
169
+ addressTemplate: null,
170
+ currentSession: {
171
+ authenticated: true,
172
+ sessionId: 'JSESSION',
173
+ currentProvider: { uuid: '45ce6c2e-dd5a-11e6-9d9c-0242ac150002', identifier: 'PRO-123' },
73
174
  },
175
+ identifierTypes: [],
176
+ relationshipTypes: mockRelationshipTypes,
177
+ } as Resources;
178
+
179
+ renderRelationshipsSectionWithFormik(
74
180
  {
75
- displayAIsToB: 'Father',
76
- aIsToB: 'Father',
77
- bIsToA: 'Child',
78
- displayBIsToA: 'Child',
79
- uuid: '52ae5ce0-d64b-11ea-9064-5adc43bbdd24',
181
+ relationships: [{ action: 'ADD', relatedPersonUuid: '', relationshipType: '' }],
80
182
  },
81
- ],
82
- };
83
- mockResourcesContextValue = {
84
- ...mockResourcesContextValue,
85
- relationshipTypes: relationshipTypes,
86
- };
87
-
88
- renderWithContext(
89
- <Formik
90
- initialValues={{
91
- relationships: [{ action: 'ADD', relatedPersonUuid: '11524ae7-3ef6-4ab6-aff6-804ffc58704a' }],
92
- }}
93
- onSubmit={null}>
94
- <Form>
95
- <PatientRegistrationContextProvider value={initialContextValues}>
96
- <RelationshipsSection />
97
- </PatientRegistrationContextProvider>
98
- </Form>
99
- </Formik>,
100
- ResourcesContextProvider,
101
- mockResourcesContextValue,
102
- );
103
-
104
- expect(screen.getByLabelText(/relationships section/i)).toBeInTheDocument();
105
- expect(screen.getByRole('heading', { name: /relationship/i })).toBeInTheDocument();
106
- expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument();
107
- expect(screen.getByRole('button', { name: /add relationship/i })).toBeInTheDocument();
108
- expect(screen.getByRole('searchbox', { name: /full name/i })).toBeInTheDocument();
109
- expect(screen.getByRole('option', { name: /mother/i })).toBeInTheDocument();
110
- expect(screen.getByRole('option', { name: /father/i })).toBeInTheDocument();
111
- expect(screen.getAllByRole('option', { name: /child/i }).length).toEqual(2);
183
+ mockResourcesContextValue,
184
+ );
185
+
186
+ expect(screen.getByRole('option', { name: /mother/i })).toBeInTheDocument();
187
+ expect(screen.getByRole('option', { name: /father/i })).toBeInTheDocument();
188
+ expect(screen.getAllByRole('option', { name: /child/i }).length).toBeGreaterThan(0);
189
+ });
190
+ });
191
+
192
+ describe('User interaction', () => {
193
+ it('adds a new relationship when user clicks add relationship button', async () => {
194
+ const user = userEvent.setup();
195
+ const mockResourcesContextValue = {
196
+ addressTemplate: null,
197
+ currentSession: {
198
+ authenticated: true,
199
+ sessionId: 'JSESSION',
200
+ currentProvider: { uuid: '45ce6c2e-dd5a-11e6-9d9c-0242ac150002', identifier: 'PRO-123' },
201
+ },
202
+ identifierTypes: [],
203
+ relationshipTypes: mockRelationshipTypes,
204
+ } as Resources;
205
+
206
+ const { getFormValues } = renderRelationshipsSectionWithFormik({}, mockResourcesContextValue);
207
+
208
+ const addButton = screen.getByRole('button', { name: /add relationship/i });
209
+ expect(addButton).toBeInTheDocument();
210
+
211
+ await user.click(addButton);
212
+
213
+ await waitFor(() => {
214
+ const formValues = getFormValues();
215
+ expect(formValues.relationships.length).toBe(1);
216
+ });
217
+
218
+ await waitFor(() => {
219
+ const formValues = getFormValues();
220
+ expect(formValues.relationships[0]?.action).toBe('ADD');
221
+ });
222
+
223
+ // New relationship form should be visible
224
+ expect(screen.getByRole('searchbox', { name: /full name/i })).toBeInTheDocument();
225
+ });
226
+
227
+ it('removes a new relationship from the array when user clicks delete button', async () => {
228
+ const user = userEvent.setup();
229
+ const mockResourcesContextValue = {
230
+ addressTemplate: null,
231
+ currentSession: {
232
+ authenticated: true,
233
+ sessionId: 'JSESSION',
234
+ currentProvider: { uuid: '45ce6c2e-dd5a-11e6-9d9c-0242ac150002', identifier: 'PRO-123' },
235
+ },
236
+ identifierTypes: [],
237
+ relationshipTypes: mockRelationshipTypes,
238
+ } as Resources;
239
+
240
+ const { getFormValues } = renderRelationshipsSectionWithFormik(
241
+ {
242
+ relationships: [
243
+ { action: 'ADD', relatedPersonUuid: 'test-uuid', relatedPersonName: 'Test Person', relationshipType: '' },
244
+ ],
245
+ },
246
+ mockResourcesContextValue,
247
+ );
248
+
249
+ expect(getFormValues().relationships.length).toBe(1);
250
+
251
+ const deleteButton = screen.getByRole('button', { name: /delete/i });
252
+ expect(deleteButton).toBeInTheDocument();
253
+
254
+ await user.click(deleteButton);
255
+
256
+ // For new relationships (action: 'ADD'), clicking delete removes them from the array
257
+ await waitFor(() => {
258
+ const formValues = getFormValues();
259
+ expect(formValues.relationships.length).toBe(0);
260
+ });
261
+ });
262
+
263
+ it('marks an existing relationship for deletion when user clicks delete button', async () => {
264
+ const user = userEvent.setup();
265
+ const mockResourcesContextValue = {
266
+ addressTemplate: null,
267
+ currentSession: {
268
+ authenticated: true,
269
+ sessionId: 'JSESSION',
270
+ currentProvider: { uuid: '45ce6c2e-dd5a-11e6-9d9c-0242ac150002', identifier: 'PRO-123' },
271
+ },
272
+ identifierTypes: [],
273
+ relationshipTypes: mockRelationshipTypes,
274
+ } as Resources;
275
+
276
+ const { getFormValues } = renderRelationshipsSectionWithFormik(
277
+ {
278
+ relationships: [
279
+ {
280
+ uuid: 'existing-relationship-uuid',
281
+ relatedPersonUuid: 'test-uuid',
282
+ relatedPersonName: 'Test Person',
283
+ relationshipType: '42ae5ce0-d64b-11ea-9064-5adc43bbdd34/aIsToB',
284
+ },
285
+ ],
286
+ },
287
+ mockResourcesContextValue,
288
+ );
289
+
290
+ const deleteButton = screen.getByRole('button', { name: /delete/i });
291
+ expect(deleteButton).toBeInTheDocument();
292
+
293
+ await user.click(deleteButton);
294
+
295
+ // For existing relationships (with uuid, no action), clicking delete sets action to 'DELETE'
296
+ await waitFor(() => {
297
+ const formValues = getFormValues();
298
+ expect(formValues.relationships[0]?.action).toBe('DELETE');
299
+ });
300
+
301
+ // Should show restore notification
302
+ await waitFor(() => {
303
+ expect(screen.getByText(/relationship removed/i)).toBeInTheDocument();
304
+ });
305
+ });
112
306
  });
113
307
  });
@@ -23,7 +23,7 @@ export const SectionWrapper = ({ sectionDefinition, index }: SectionWrapperProps
23
23
  * t('relationshipsSection', 'Relationships')
24
24
  */
25
25
  return (
26
- <div id={sectionDefinition.id} style={{ scrollMarginTop: '4rem' }}>
26
+ <div id={sectionDefinition.id} style={{ scrollMarginTop: '1rem' }}>
27
27
  <h3 className={styles.productiveHeading02} style={{ color: '#161616' }}>
28
28
  {index + 1}. {t(`${sectionDefinition.id}Section`, sectionDefinition.name)}
29
29
  </h3>
@@ -0,0 +1,104 @@
1
+ import React from 'react';
2
+ import { vi, describe, it, expect, beforeEach } from 'vitest';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { render, screen } from '@testing-library/react';
5
+ import { useLayoutType, isDesktop } from '@openmrs/esm-framework';
6
+ import Overlay from './overlay.component';
7
+
8
+ const mockUseLayoutType = vi.mocked(useLayoutType);
9
+ const mockIsDesktop = vi.mocked(isDesktop);
10
+ /**
11
+ * Helper to render Overlay component.
12
+ */
13
+ function renderOverlay(
14
+ close = vi.fn(),
15
+ header = 'Test Header',
16
+ buttonsGroup?: React.ReactElement,
17
+ children?: React.ReactNode,
18
+ ) {
19
+ return render(<Overlay close={close} header={header} buttonsGroup={buttonsGroup} children={children} />);
20
+ }
21
+
22
+ describe('Overlay component', () => {
23
+ const mockClose = vi.fn();
24
+
25
+ beforeEach(() => {
26
+ mockIsDesktop.mockReturnValue(true);
27
+ mockUseLayoutType.mockReturnValue('desktop' as any);
28
+ });
29
+
30
+ describe('Desktop layout', () => {
31
+ it('renders header text', () => {
32
+ renderOverlay(mockClose, 'Test Header');
33
+
34
+ expect(screen.getByText('Test Header')).toBeInTheDocument();
35
+ });
36
+
37
+ it('renders close button with icon', () => {
38
+ renderOverlay(mockClose, 'Test Header');
39
+
40
+ const closeButton = screen.getByRole('button', { name: /close overlay/i });
41
+ expect(closeButton).toBeInTheDocument();
42
+ });
43
+
44
+ it('calls close when close button is clicked', async () => {
45
+ const user = userEvent.setup();
46
+ renderOverlay(mockClose, 'Test Header');
47
+
48
+ const closeButton = screen.getByRole('button', { name: /close overlay/i });
49
+ await user.click(closeButton);
50
+
51
+ expect(mockClose).toHaveBeenCalledTimes(1);
52
+ });
53
+
54
+ it('renders children content', () => {
55
+ renderOverlay(mockClose, 'Test Header', undefined, <div>Test Content</div>);
56
+
57
+ expect(screen.getByText('Test Content')).toBeInTheDocument();
58
+ });
59
+
60
+ it('renders buttonsGroup when provided', () => {
61
+ const buttonsGroup = (
62
+ <div>
63
+ <button>Save</button>
64
+ <button>Cancel</button>
65
+ </div>
66
+ );
67
+ renderOverlay(mockClose, 'Test Header', buttonsGroup);
68
+
69
+ expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument();
70
+ expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
71
+ });
72
+ });
73
+
74
+ describe('Tablet layout', () => {
75
+ beforeEach(() => {
76
+ mockIsDesktop.mockReturnValue(false);
77
+ mockUseLayoutType.mockReturnValue('tablet' as any);
78
+ });
79
+
80
+ it('renders header with back arrow button', () => {
81
+ renderOverlay(mockClose, 'Test Header');
82
+
83
+ const backButton = screen.getByRole('button', { name: /close overlay/i });
84
+ expect(backButton).toBeInTheDocument();
85
+ expect(screen.getByText('Test Header')).toBeInTheDocument();
86
+ });
87
+
88
+ it('calls close when back button is clicked', async () => {
89
+ const user = userEvent.setup();
90
+ renderOverlay(mockClose, 'Test Header');
91
+
92
+ const backButton = screen.getByRole('button', { name: /close overlay/i });
93
+ await user.click(backButton);
94
+
95
+ expect(mockClose).toHaveBeenCalledTimes(1);
96
+ });
97
+
98
+ it('renders children content in tablet layout', () => {
99
+ renderOverlay(mockClose, 'Test Header', undefined, <div>Tablet Content</div>);
100
+
101
+ expect(screen.getByText('Tablet Content')).toBeInTheDocument();
102
+ });
103
+ });
104
+ });
@@ -1,9 +1,10 @@
1
1
  import dayjs from 'dayjs';
2
+ import { vi, describe, it, expect, beforeEach } from 'vitest';
2
3
  import { getConfig } from '@openmrs/esm-framework';
3
4
  import { type RegistrationConfig } from '../../config-schema';
4
5
  import { getValidationSchema } from './patient-registration-validation';
5
6
 
6
- const mockGetConfig = jest.mocked(getConfig);
7
+ const mockGetConfig = vi.mocked(getConfig);
7
8
 
8
9
  describe('Patient registration validation', () => {
9
10
  beforeEach(() => {
@@ -1,11 +1,15 @@
1
1
  import dayjs from 'dayjs';
2
2
  import * as Yup from 'yup';
3
+ import type { ObjectSchema } from 'yup';
3
4
  import mapValues from 'lodash/mapValues';
4
5
  import { type RegistrationConfig } from '../../config-schema';
5
6
  import { type FormValues } from '../patient-registration.types';
6
7
  import { getDatetime } from '../patient-registration.resource';
7
8
 
8
- export function getValidationSchema(config: RegistrationConfig, t: (key: string, defaultValue: string) => string) {
9
+ export function getValidationSchema(
10
+ config: RegistrationConfig,
11
+ t: (key: string, defaultValue: string) => string,
12
+ ): ObjectSchema<any> {
9
13
  return Yup.object({
10
14
  givenName: Yup.string().required(t('givenNameRequired', 'Given name is required')),
11
15
  familyName: Yup.string().required(t('familyNameRequired', 'Family name is required')),
@@ -56,7 +60,7 @@ export function getValidationSchema(config: RegistrationConfig, t: (key: string,
56
60
  .max(new Date(), t('deathDateInFuture', 'Death date cannot be in future'))
57
61
  .test(
58
62
  'deathDate-after-birthdate',
59
- t('deathdayInvalidDate', 'Death date and time cannot be before the birthday'),
63
+ t('deathdayBeforeBirthday', 'Death date and time cannot be before the birthday'),
60
64
  function (value) {
61
65
  const { birthdate } = this.parent;
62
66
  if (birthdate && value) {
@@ -95,7 +99,9 @@ export function getValidationSchema(config: RegistrationConfig, t: (key: string,
95
99
  }),
96
100
  nonCodedCauseOfDeath: Yup.string().when(['isDead', 'deathCause'], {
97
101
  is: (isDead, deathCause) => isDead && deathCause === config.freeTextFieldConceptUuid,
98
- then: Yup.string().required(t('nonCodedCauseOfDeathRequired', 'Cause of death is required')),
102
+ then: Yup.string().required(
103
+ t('nonCodedCauseOfDeathRequiredWhenSelected', 'Non-coded cause of death is required'),
104
+ ),
99
105
  otherwise: Yup.string().nullable(),
100
106
  }),
101
107
  email: Yup.string().optional().email(t('invalidEmail', 'Invalid email')),
@@ -2,8 +2,8 @@ import React, { useMemo } from 'react';
2
2
  import classNames from 'classnames';
3
3
  import useSWRImmutable from 'swr/immutable';
4
4
  import { BrowserRouter, Route, Routes } from 'react-router-dom';
5
- import { Grid, Row } from '@carbon/react';
6
- import { ExtensionSlot, useConnectivity, useSession } from '@openmrs/esm-framework';
5
+ import { Grid } from '@carbon/react';
6
+ import { useConnectivity, useSession } from '@openmrs/esm-framework';
7
7
  import {
8
8
  fetchAddressTemplate,
9
9
  fetchAllRelationshipTypes,
@@ -34,9 +34,6 @@ export default function Root() {
34
34
  return (
35
35
  <main className={classNames('omrs-main-content', styles.root)}>
36
36
  <Grid className={styles.grid}>
37
- <Row>
38
- <ExtensionSlot name="breadcrumbs-slot" />
39
- </Row>
40
37
  <ResourcesContextProvider
41
38
  value={{
42
39
  addressTemplate,
@@ -1,22 +1,59 @@
1
1
  import React from 'react';
2
+ import { vi, describe, it, expect } from 'vitest';
2
3
  import userEvent from '@testing-library/user-event';
3
4
  import { screen, render } from '@testing-library/react';
4
5
  import CancelPatientEdit from './cancel-patient-edit.modal';
5
6
 
7
+ /**
8
+ * Helper to render CancelPatientEdit modal.
9
+ */
10
+ function renderCancelPatientEditModal(close = vi.fn(), onConfirm = vi.fn()) {
11
+ return render(<CancelPatientEdit close={close} onConfirm={onConfirm} />);
12
+ }
13
+
6
14
  describe('CancelPatientEdit modal', () => {
7
- const mockClose = jest.fn();
8
- const mockOnConfirm = jest.fn();
15
+ const mockClose = vi.fn();
16
+ const mockOnConfirm = vi.fn();
17
+
18
+ describe('Rendering', () => {
19
+ it('renders modal with title and message', () => {
20
+ renderCancelPatientEditModal(mockClose, mockOnConfirm);
21
+
22
+ expect(screen.getByText(/are you sure you want to discard these changes\?/i)).toBeInTheDocument();
23
+ expect(
24
+ screen.getByText(/your unsaved changes will be lost if you proceed to discard the form/i),
25
+ ).toBeInTheDocument();
26
+ });
27
+
28
+ it('renders Cancel and Discard buttons', () => {
29
+ renderCancelPatientEditModal(mockClose, mockOnConfirm);
30
+
31
+ expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
32
+ expect(screen.getByRole('button', { name: /discard/i })).toBeInTheDocument();
33
+ });
34
+ });
35
+
36
+ describe('User interaction', () => {
37
+ it('calls close when Cancel button is clicked', async () => {
38
+ const user = userEvent.setup();
39
+ renderCancelPatientEditModal(mockClose, mockOnConfirm);
40
+
41
+ const cancelButton = screen.getByRole('button', { name: /cancel/i });
42
+ await user.click(cancelButton);
43
+
44
+ expect(mockClose).toHaveBeenCalledTimes(1);
45
+ expect(mockOnConfirm).not.toHaveBeenCalled();
46
+ });
9
47
 
10
- it('renders the modal and triggers close and onConfirm functions', async () => {
11
- const user = userEvent.setup();
12
- render(<CancelPatientEdit close={mockClose} onConfirm={mockOnConfirm} />);
48
+ it('calls onConfirm when Discard button is clicked', async () => {
49
+ const user = userEvent.setup();
50
+ renderCancelPatientEditModal(mockClose, mockOnConfirm);
13
51
 
14
- const cancelButton = screen.getByRole('button', { name: /Cancel/i });
15
- await user.click(cancelButton);
16
- expect(mockClose).toHaveBeenCalledTimes(1);
52
+ const discardButton = screen.getByRole('button', { name: /discard/i });
53
+ await user.click(discardButton);
17
54
 
18
- const discardButton = screen.getByRole('button', { name: /discard/i });
19
- await user.click(discardButton);
20
- expect(mockOnConfirm).toHaveBeenCalledTimes(1);
55
+ expect(mockOnConfirm).toHaveBeenCalledTimes(1);
56
+ expect(mockClose).not.toHaveBeenCalled();
57
+ });
21
58
  });
22
59
  });