@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
@@ -0,0 +1,100 @@
1
+ import React from 'react';
2
+ import { vi, describe, it, expect, beforeEach, type Mock } from 'vitest';
3
+ import { Formik, Form } from 'formik';
4
+ import { render, screen } from '@testing-library/react';
5
+ import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
6
+ import { type RegistrationConfig, esmPatientRegistrationSchema } from '../../../config-schema';
7
+ import { PhoneField } from './phone-field.component';
8
+ import { PatientRegistrationContextProvider } from '../../patient-registration-context';
9
+ import { initialFormValues } from '../../patient-registration.component';
10
+ import { type FormValues } from '../../patient-registration.types';
11
+
12
+ const mockUseConfig = vi.mocked(useConfig<RegistrationConfig>);
13
+
14
+ // Mock PersonAttributeField since PhoneField is a wrapper around it
15
+ vi.mock('../person-attributes/person-attribute-field.component', () => ({
16
+ PersonAttributeField: ({ fieldDefinition }) => (
17
+ <div data-testid="phone-field">
18
+ <label htmlFor="phone-input">Phone</label>
19
+ <input id="phone-input" name="phone" data-field-uuid={fieldDefinition.uuid} />
20
+ </div>
21
+ ),
22
+ }));
23
+
24
+ /**
25
+ * Helper to render PhoneField.
26
+ */
27
+ const renderPhoneField = () => {
28
+ return render(
29
+ <Formik initialValues={{}} onSubmit={() => {}}>
30
+ <Form>
31
+ <PatientRegistrationContextProvider
32
+ value={{
33
+ identifierTypes: [],
34
+ values: initialFormValues,
35
+ validationSchema: null,
36
+ inEditMode: false,
37
+ setFieldValue: vi.fn().mockResolvedValue(undefined),
38
+ setCapturePhotoProps: vi.fn(),
39
+ setFieldTouched: vi.fn().mockResolvedValue(undefined),
40
+ currentPhoto: '',
41
+ isOffline: false,
42
+ initialFormValues: initialFormValues,
43
+ }}>
44
+ <PhoneField />
45
+ </PatientRegistrationContextProvider>
46
+ </Form>
47
+ </Formik>,
48
+ );
49
+ };
50
+
51
+ describe('PhoneField', () => {
52
+ beforeEach(() => {
53
+ mockUseConfig.mockReturnValue({
54
+ ...getDefaultsFromConfigSchema(esmPatientRegistrationSchema),
55
+ fieldConfigurations: {
56
+ phone: {
57
+ personAttributeUuid: 'phone-uuid-123',
58
+ validation: {
59
+ required: false,
60
+ matches: null,
61
+ },
62
+ },
63
+ } as RegistrationConfig['fieldConfigurations'],
64
+ });
65
+ });
66
+
67
+ it('renders the phone field', () => {
68
+ renderPhoneField();
69
+
70
+ expect(screen.getByTestId('phone-field')).toBeInTheDocument();
71
+ expect(screen.getByLabelText(/phone/i)).toBeInTheDocument();
72
+ });
73
+
74
+ it('passes correct field definition to PersonAttributeField', () => {
75
+ renderPhoneField();
76
+
77
+ const phoneInput = screen.getByLabelText(/phone/i);
78
+ expect(phoneInput).toHaveAttribute('data-field-uuid', 'phone-uuid-123');
79
+ });
80
+
81
+ it('uses configured person attribute UUID', () => {
82
+ mockUseConfig.mockReturnValue({
83
+ ...getDefaultsFromConfigSchema(esmPatientRegistrationSchema),
84
+ fieldConfigurations: {
85
+ phone: {
86
+ personAttributeUuid: 'custom-phone-uuid',
87
+ validation: {
88
+ required: false,
89
+ matches: null,
90
+ },
91
+ },
92
+ } as RegistrationConfig['fieldConfigurations'],
93
+ });
94
+
95
+ renderPhoneField();
96
+
97
+ const phoneInput = screen.getByLabelText(/phone/i);
98
+ expect(phoneInput).toHaveAttribute('data-field-uuid', 'custom-phone-uuid');
99
+ });
100
+ });
@@ -1,13 +1,14 @@
1
1
  import { FormManager } from './form-manager';
2
+ import { vi, describe, it, expect } from 'vitest';
2
3
  import { type FormValues } from './patient-registration.types';
3
4
  import { generateIdentifier } from './patient-registration.resource';
4
5
 
5
- jest.mock('./patient-registration.resource', () => ({
6
- ...jest.requireActual('./patient-registration.resource'),
7
- generateIdentifier: jest.fn(),
6
+ vi.mock('./patient-registration.resource', async () => ({
7
+ ...((await vi.importActual('./patient-registration.resource')) as object),
8
+ generateIdentifier: vi.fn(),
8
9
  }));
9
10
 
10
- const mockGenerateIdentifier = generateIdentifier as jest.Mock;
11
+ const mockGenerateIdentifier = vi.mocked(generateIdentifier);
11
12
 
12
13
  const formValues: FormValues = {
13
14
  patientUuid: '',
@@ -76,7 +77,7 @@ describe('FormManager', () => {
76
77
  it('should generate identifier if it has autoGeneration and manual entry disabled', async () => {
77
78
  formValues.identifiers.foo.autoGeneration = true;
78
79
  formValues.identifiers.foo.selectedSource.autoGenerationOption.manualEntryEnabled = false;
79
- mockGenerateIdentifier.mockResolvedValue({ data: { identifier: '10001V' } });
80
+ mockGenerateIdentifier.mockResolvedValue({ data: { identifier: '10001V' } } as any);
80
81
  await FormManager.savePatientIdentifiers(true, undefined, formValues.identifiers, {}, 'Nyc');
81
82
  expect(mockGenerateIdentifier.mock.calls).toHaveLength(1);
82
83
  });
@@ -10,6 +10,7 @@ import {
10
10
  } from '@openmrs/esm-framework';
11
11
  import { patientRegistration } from '../constants';
12
12
  import {
13
+ type AddressProperties,
13
14
  type AttributeValue,
14
15
  type CapturePhotoProps,
15
16
  type Encounter,
@@ -36,11 +37,13 @@ import {
36
37
  } from './patient-registration.resource';
37
38
  import { type RegistrationConfig } from '../config-schema';
38
39
 
40
+ type AddressFieldValues = Partial<Record<AddressProperties, string>>;
41
+
39
42
  export type SavePatientForm = (
40
43
  isNewPatient: boolean,
41
44
  values: FormValues,
42
45
  patientUuidMap: PatientUuidMapType,
43
- initialAddressFieldValues: Record<string, any>,
46
+ initialAddressFieldValues: AddressFieldValues,
44
47
  capturePhotoProps: CapturePhotoProps,
45
48
  currentLocation: string,
46
49
  initialIdentifierValues: FormValues['identifiers'],
@@ -303,7 +306,7 @@ export class FormManager {
303
306
  isNewPatient: boolean,
304
307
  values: FormValues,
305
308
  patientUuidMap: PatientUuidMapType,
306
- initialAddressFieldValues: Record<string, any>,
309
+ initialAddressFieldValues: AddressFieldValues,
307
310
  identifiers: Array<PatientIdentifier>,
308
311
  config?: RegistrationConfig,
309
312
  ): Patient {
@@ -3,125 +3,7 @@ import { useTranslation } from 'react-i18next';
3
3
  import { Layer, TextInput } from '@carbon/react';
4
4
  import { useField } from 'formik';
5
5
 
6
- // FIXME Temporarily imported here
7
- export interface TextInputProps
8
- extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'defaultValue' | 'id' | 'size' | 'value'> {
9
- /**
10
- * Specify an optional className to be applied to the `<input>` node
11
- */
12
- className?: string;
13
-
14
- /**
15
- * Optionally provide the default value of the `<input>`
16
- */
17
- defaultValue?: string | number;
18
-
19
- /**
20
- * Specify whether the `<input>` should be disabled
21
- */
22
- disabled?: boolean;
23
-
24
- /**
25
- * Specify whether to display the character counter
26
- */
27
- enableCounter?: boolean;
28
-
29
- /**
30
- * Provide text that is used alongside the control label for additional help
31
- */
32
- helperText?: React.ReactNode;
33
-
34
- /**
35
- * Specify whether you want the underlying label to be visually hidden
36
- */
37
- hideLabel?: boolean;
38
-
39
- /**
40
- * Specify a custom `id` for the `<input>`
41
- */
42
- id: string;
43
-
44
- /**
45
- * `true` to use the inline version.
46
- */
47
- inline?: boolean;
48
-
49
- /**
50
- * Specify whether the control is currently invalid
51
- */
52
- invalid?: boolean;
53
-
54
- /**
55
- * Provide the text that is displayed when the control is in an invalid state
56
- */
57
- invalidText?: React.ReactNode;
58
-
59
- /**
60
- * Provide the text that will be read by a screen reader when visiting this
61
- * control
62
- */
63
- labelText: React.ReactNode;
64
-
65
- /**
66
- * `true` to use the light version. For use on $ui-01 backgrounds only.
67
- * Don't use this to make tile background color same as container background color.
68
- * 'The `light` prop for `TextInput` has ' +
69
- 'been deprecated in favor of the new `Layer` component. It will be removed in the next major release.'
70
- */
71
- light?: boolean;
72
-
73
- /**
74
- * Max character count allowed for the input. This is needed in order for enableCounter to display
75
- */
76
- maxCount?: number;
77
-
78
- /**
79
- * Optionally provide an `onChange` handler that is called whenever `<input>`
80
- * is updated
81
- */
82
- onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
83
-
84
- /**
85
- * Optionally provide an `onClick` handler that is called whenever the
86
- * `<input>` is clicked
87
- */
88
- onClick?: (event: React.MouseEvent<HTMLElement>) => void;
89
-
90
- /**
91
- * Specify the placeholder attribute for the `<input>`
92
- */
93
- placeholder?: string;
94
-
95
- /**
96
- * Whether the input should be read-only
97
- */
98
- readOnly?: boolean;
99
-
100
- /**
101
- * Specify the size of the Text Input. Currently supports the following:
102
- */
103
- size?: 'sm' | 'md' | 'lg' | 'xl';
104
-
105
- /**
106
- * Specify the type of the `<input>`
107
- */
108
- type?: string;
109
-
110
- /**
111
- * Specify the value of the `<input>`
112
- */
113
- value?: string | number | undefined;
114
-
115
- /**
116
- * Specify whether the control is currently in warning state
117
- */
118
- warn?: boolean;
119
-
120
- /**
121
- * Provide the text that is displayed when the control is in warning state
122
- */
123
- warnText?: React.ReactNode;
124
- }
6
+ export type TextInputProps = React.ComponentProps<typeof TextInput>;
125
7
 
126
8
  interface InputProps extends TextInputProps {
127
9
  checkWarning?(value: string): string;
@@ -146,9 +28,9 @@ export const Input: React.FC<InputProps> = ({ checkWarning, ...props }) => {
146
28
  t('numberInNameDubious')
147
29
  t('yearsEstimateRequired')
148
30
  t('deathdayIsRequired', 'Death date is required when the patient is marked as deceased.')
149
- t('deathdayInvalidDate', 'Date of death is invalid')
31
+ t('deathdayBeforeBirthday', 'Death date and time cannot be before the birthday')
150
32
  t('deathCauseRequired', 'Cause of death is required')
151
- t('nonCodedCauseOfDeathRequired', 'Non-coded cause of death is required')
33
+ t('nonCodedCauseOfDeathRequiredWhenSelected', 'Non-coded cause of death is required')
152
34
  */
153
35
 
154
36
  const value = field.value || '';
@@ -1,72 +1,172 @@
1
1
  import React from 'react';
2
- import { render, screen } from '@testing-library/react';
2
+ import { vi, describe, it, expect, test } from 'vitest';
3
+ import { render, screen, waitFor } from '@testing-library/react';
3
4
  import userEvent from '@testing-library/user-event';
4
5
  import { Formik, Form } from 'formik';
5
6
  import { Input } from './input.component';
6
7
 
7
- describe('text input', () => {
8
- const setupInput = async () => {
9
- render(
10
- <Formik initialValues={{ text: '' }} onSubmit={() => {}}>
11
- <Form>
12
- <Input
13
- id="text"
14
- labelText="Text"
15
- name="text"
16
- placeholder="Enter text"
17
- required
18
- checkWarning={(value) => {
19
- if (value.length > 5) {
20
- return 'name should be of 5 char';
21
- }
22
- }}
23
- />
24
- </Form>
25
- </Formik>,
26
- );
27
- return screen.getByLabelText('Text') as HTMLInputElement;
8
+ /**
9
+ * Helper to render Input component with Formik.
10
+ */
11
+ function renderInput(
12
+ props: {
13
+ name?: string;
14
+ labelText?: string;
15
+ required?: boolean;
16
+ checkWarning?: (value: string) => string | undefined;
17
+ placeholder?: string;
18
+ } = {},
19
+ initialValues: Record<string, string> = {},
20
+ ) {
21
+ const defaultProps = {
22
+ id: 'text',
23
+ name: 'text',
24
+ labelText: 'Text',
25
+ placeholder: 'Enter text',
26
+ required: false,
27
+ ...props,
28
28
  };
29
29
 
30
- it('exists', async () => {
31
- const input = await setupInput();
32
- expect(input.type).toEqual('text');
33
- });
30
+ return render(
31
+ <Formik initialValues={{ text: '', ...initialValues }} onSubmit={() => {}}>
32
+ <Form>
33
+ <Input {...defaultProps} />
34
+ </Form>
35
+ </Formik>,
36
+ );
37
+ }
38
+
39
+ describe('Input component', () => {
40
+ describe('Rendering', () => {
41
+ it('renders the input field', () => {
42
+ renderInput();
34
43
 
35
- it('can input valid data without warning', async () => {
36
- const user = userEvent.setup();
44
+ const input = screen.getByLabelText('Text (optional)') as HTMLInputElement;
45
+ expect(input).toBeInTheDocument();
46
+ expect(input.type).toBe('text');
47
+ });
37
48
 
38
- const input = await setupInput();
39
- const userInput = 'text';
49
+ it('shows required label when field is required', () => {
50
+ renderInput({ required: true });
40
51
 
41
- await user.type(input, userInput);
42
- await user.tab();
52
+ expect(screen.getByLabelText('Text')).toBeInTheDocument();
53
+ expect(screen.queryByLabelText('Text (optional)')).not.toBeInTheDocument();
54
+ });
43
55
 
44
- expect(input.value).toEqual(userInput);
45
- expect(screen.queryByText('name should be of 5 char')).not.toBeInTheDocument();
56
+ it('shows optional label when field is not required', () => {
57
+ renderInput({ required: false });
58
+
59
+ expect(screen.getByLabelText('Text (optional)')).toBeInTheDocument();
60
+ });
46
61
  });
47
62
 
48
- it('should show a warning when the invalid input is entered', async () => {
49
- const user = userEvent.setup();
63
+ describe('User interaction', () => {
64
+ it('allows user to type and updates the input value', async () => {
65
+ const user = userEvent.setup();
66
+ renderInput({ required: true });
67
+
68
+ const input = screen.getByLabelText('Text') as HTMLInputElement;
69
+ await user.type(input, 'test value');
50
70
 
51
- const input = await setupInput();
52
- const userInput = 'Hello World';
71
+ expect(input.value).toBe('test value');
72
+ });
53
73
 
54
- await userEvent.clear(input);
74
+ it('updates form values when user types', async () => {
75
+ const user = userEvent.setup();
76
+ let formValues: Record<string, string> = {};
55
77
 
56
- await user.type(input, userInput);
57
- await user.tab();
78
+ render(
79
+ <Formik initialValues={{ text: '' }} onSubmit={() => {}}>
80
+ {({ values }) => {
81
+ formValues = values as Record<string, string>;
82
+ return (
83
+ <Form>
84
+ <Input id="text" name="text" labelText="Text" required />
85
+ </Form>
86
+ );
87
+ }}
88
+ </Formik>,
89
+ );
58
90
 
59
- expect(screen.getByText('name should be of 5 char')).toBeInTheDocument();
91
+ const input = screen.getByLabelText('Text') as HTMLInputElement;
92
+ await user.type(input, 'typed value');
93
+
94
+ await waitFor(() => {
95
+ expect(formValues.text).toBe('typed value');
96
+ });
97
+ });
60
98
  });
61
99
 
62
- it('should show the correct label text if the field is not required', () => {
63
- render(
64
- <Formik initialValues={{ text: '' }} onSubmit={() => {}}>
65
- <Form>
66
- <Input id="text" labelText="Text" name="text" placeholder="Enter text" />
67
- </Form>
68
- </Formik>,
69
- );
70
- expect(screen.getByLabelText('Text (optional)')).toBeInTheDocument();
100
+ describe('Warning validation', () => {
101
+ it('shows warning when checkWarning returns a warning message', async () => {
102
+ const user = userEvent.setup();
103
+ renderInput({
104
+ required: true,
105
+ checkWarning: (value) => {
106
+ if (value.length > 5) {
107
+ return 'name should be of 5 char';
108
+ }
109
+ },
110
+ });
111
+
112
+ const input = screen.getByLabelText('Text') as HTMLInputElement;
113
+ await user.type(input, 'Hello World');
114
+ await user.tab();
115
+
116
+ await waitFor(() => {
117
+ expect(screen.getByText('name should be of 5 char')).toBeInTheDocument();
118
+ });
119
+ });
120
+
121
+ it('does not show warning when input is valid', async () => {
122
+ const user = userEvent.setup();
123
+ renderInput({
124
+ required: true,
125
+ checkWarning: (value) => {
126
+ if (value.length > 5) {
127
+ return 'name should be of 5 char';
128
+ }
129
+ },
130
+ });
131
+
132
+ const input = screen.getByLabelText('Text') as HTMLInputElement;
133
+ await user.type(input, 'text');
134
+ await user.tab();
135
+
136
+ await waitFor(() => {
137
+ expect(screen.queryByText('name should be of 5 char')).not.toBeInTheDocument();
138
+ });
139
+ });
140
+
141
+ it('clears warning when user corrects input', async () => {
142
+ const user = userEvent.setup();
143
+ renderInput({
144
+ required: true,
145
+ checkWarning: (value) => {
146
+ if (value.length > 5) {
147
+ return 'name should be of 5 char';
148
+ }
149
+ },
150
+ });
151
+
152
+ const input = screen.getByLabelText('Text') as HTMLInputElement;
153
+
154
+ // Type invalid value
155
+ await user.type(input, 'Hello World');
156
+ await user.tab();
157
+
158
+ await waitFor(() => {
159
+ expect(screen.getByText('name should be of 5 char')).toBeInTheDocument();
160
+ });
161
+
162
+ // Clear and type valid value
163
+ await user.clear(input);
164
+ await user.type(input, 'text');
165
+ await user.tab();
166
+
167
+ await waitFor(() => {
168
+ expect(screen.queryByText('name should be of 5 char')).not.toBeInTheDocument();
169
+ });
170
+ });
71
171
  });
72
172
  });