@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,21 +1,21 @@
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
- import { render, screen } from '@testing-library/react';
4
+ import { Formik, Form } from 'formik';
5
+ import { render, screen, waitFor, fireEvent } from '@testing-library/react';
4
6
  import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
5
7
  import { esmPatientRegistrationSchema, type FieldDefinition, type RegistrationConfig } from '../../../config-schema';
6
8
  import { useConcept, useConceptAnswers } from '../field.resource';
7
9
  import { ObsField } from './obs-field.component';
8
- import {
9
- PatientRegistrationContextProvider,
10
- type PatientRegistrationContextProps,
11
- } from '../../patient-registration-context';
12
- import { mockOpenmrsId, mockPatient } from '__mocks__';
10
+ import { PatientRegistrationContextProvider } from '../../patient-registration-context';
11
+ import { initialFormValues } from '../../patient-registration.component';
12
+ import { type FormValues } from '../../patient-registration.types';
13
13
 
14
- const mockUseConcept = jest.mocked(useConcept);
15
- const mockUseConceptAnswers = jest.mocked(useConceptAnswers);
16
- const mockUseConfig = jest.mocked(useConfig<RegistrationConfig>);
14
+ const mockUseConcept = vi.mocked(useConcept);
15
+ const mockUseConceptAnswers = vi.mocked(useConceptAnswers);
16
+ const mockUseConfig = vi.mocked(useConfig<RegistrationConfig>);
17
17
 
18
- jest.mock('../field.resource');
18
+ vi.mock('../field.resource');
19
19
 
20
20
  const useConceptMockImpl = (uuid: string) => {
21
21
  let data;
@@ -93,18 +93,6 @@ const useConceptAnswersMockImpl = (uuid: string) => {
93
93
  }
94
94
  };
95
95
 
96
- type FieldProps = {
97
- children: ({ field, form: { touched, errors }, meta }) => React.ReactNode;
98
- };
99
-
100
- jest.mock('formik', () => ({
101
- ...(jest.requireActual('formik') as object),
102
- Field: jest.fn(({ children }: FieldProps) => (
103
- <>{children({ field: {}, form: { touched: {}, errors: {} }, meta: { error: undefined } })}</>
104
- )),
105
- useField: jest.fn(() => [{ value: null }, {}]),
106
- }));
107
-
108
96
  const textFieldDef: FieldDefinition = {
109
97
  id: 'chief-complaint',
110
98
  type: 'obs',
@@ -165,44 +153,52 @@ const codedFieldDef: FieldDefinition = {
165
153
  customConceptAnswers: [],
166
154
  };
167
155
 
168
- const mockInitialFormValues = {
169
- additionalFamilyName: '',
170
- additionalGivenName: '',
171
- additionalMiddleName: '',
172
- addNameInLocalLanguage: false,
173
- address: {},
174
- birthdate: null,
175
- birthdateEstimated: false,
176
- deathCause: '',
177
- deathDate: '',
178
- familyName: 'Doe',
179
- gender: 'male',
180
- givenName: 'John',
181
- identifiers: mockOpenmrsId,
182
- isDead: false,
183
- middleName: 'Test',
184
- monthsEstimated: 0,
185
- patientUuid: mockPatient.uuid,
186
- relationships: [],
187
- telephoneNumber: '',
188
- yearsEstimated: 0,
189
- deathTime: '',
190
- deathTimeFormat: 'AM' as const,
191
- nonCodedCauseOfDeath: '',
192
- };
156
+ /**
157
+ * Helper to render ObsField with Formik render props for state-dependent tests.
158
+ */
159
+ const renderObsFieldWithFormik = (
160
+ fieldDefinition: FieldDefinition,
161
+ initialValues: Partial<FormValues> = {},
162
+ options?: { enableReinitialize?: boolean },
163
+ ) => {
164
+ const defaultValues = {
165
+ obs: {},
166
+ ...initialValues,
167
+ };
168
+
169
+ let formValuesRef: FormValues = { ...initialFormValues, ...defaultValues } as FormValues;
193
170
 
194
- const initialContextValues: PatientRegistrationContextProps = {
195
- currentPhoto: null,
196
- inEditMode: false,
197
- identifierTypes: [],
198
- initialFormValues: mockInitialFormValues,
199
- isOffline: false,
200
- setCapturePhotoProps: jest.fn(),
201
- setFieldValue: jest.fn(),
202
- setInitialFormValues: jest.fn(),
203
- validationSchema: null,
204
- values: mockInitialFormValues,
205
- setFieldTouched: jest.fn(),
171
+ const utils = render(
172
+ <Formik initialValues={defaultValues} onSubmit={() => {}} enableReinitialize={options?.enableReinitialize}>
173
+ {({ setFieldValue, values, setFieldTouched }) => {
174
+ formValuesRef = { ...initialFormValues, ...values } as FormValues;
175
+ return (
176
+ <Form>
177
+ <PatientRegistrationContextProvider
178
+ value={{
179
+ identifierTypes: [],
180
+ values: formValuesRef,
181
+ validationSchema: null,
182
+ inEditMode: false,
183
+ setFieldValue: setFieldValue as any,
184
+ setCapturePhotoProps: vi.fn(),
185
+ setFieldTouched: setFieldTouched as any,
186
+ currentPhoto: '',
187
+ isOffline: false,
188
+ initialFormValues: formValuesRef,
189
+ }}>
190
+ <ObsField fieldDefinition={fieldDefinition} />
191
+ </PatientRegistrationContextProvider>
192
+ </Form>
193
+ );
194
+ }}
195
+ </Formik>,
196
+ );
197
+
198
+ return {
199
+ ...utils,
200
+ getFormValues: () => formValuesRef,
201
+ };
206
202
  };
207
203
 
208
204
  describe('ObsField', () => {
@@ -215,85 +211,265 @@ describe('ObsField', () => {
215
211
  mockUseConceptAnswers.mockImplementation(useConceptAnswersMockImpl);
216
212
  });
217
213
 
218
- it("logs an error and doesn't render if no registration encounter type is provided", () => {
214
+ it('does not render if no registration encounter type is provided', () => {
215
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
219
216
  mockUseConfig.mockReturnValue({
220
217
  ...getDefaultsFromConfigSchema(esmPatientRegistrationSchema),
221
218
  registrationObs: { encounterTypeUuid: null },
222
219
  } as RegistrationConfig);
223
220
 
224
- console.error = jest.fn();
225
- render(<ObsField fieldDefinition={textFieldDef} />);
226
- expect(console.error).toHaveBeenCalledWith(
227
- expect.stringMatching(/no registration encounter type has been configured/i),
228
- );
229
- expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
221
+ const { container } = render(<ObsField fieldDefinition={textFieldDef} />);
222
+ expect(container).toBeEmptyDOMElement();
223
+ consoleSpy.mockRestore();
230
224
  });
231
225
 
232
- it('renders a text box for text concept', () => {
233
- render(<ObsField fieldDefinition={textFieldDef} />);
226
+ it('does not render while concept is loading', () => {
227
+ mockUseConcept.mockReturnValue({
228
+ data: null,
229
+ isLoading: true,
230
+ });
234
231
 
235
- expect(screen.getByRole('textbox', { name: 'Chief complaint (optional)' })).toBeInTheDocument();
232
+ const { container } = render(<ObsField fieldDefinition={textFieldDef} />);
233
+ expect(container).toBeEmptyDOMElement();
236
234
  });
237
235
 
238
- it('renders a number box for number concept', () => {
239
- render(<ObsField fieldDefinition={numberFieldDef} />);
236
+ describe('Text obs field', () => {
237
+ it('renders a text input for text concept', () => {
238
+ renderObsFieldWithFormik(textFieldDef);
239
+
240
+ expect(screen.getByRole('textbox', { name: /chief complaint/i })).toBeInTheDocument();
241
+ });
242
+
243
+ it('allows user to enter text', async () => {
244
+ const user = userEvent.setup();
245
+ const { getFormValues } = renderObsFieldWithFormik(textFieldDef);
246
+
247
+ const textInput = screen.getByRole('textbox', { name: /chief complaint/i }) as HTMLInputElement;
248
+ await user.type(textInput, 'Patient has fever');
240
249
 
241
- expect(screen.getByRole('spinbutton', { name: 'Weight (optional)' })).toBeInTheDocument();
250
+ await waitFor(() => {
251
+ expect(getFormValues().obs['chief-complaint-uuid']).toBe('Patient has fever');
252
+ });
253
+ });
254
+
255
+ it('validates input against regex pattern', async () => {
256
+ const user = userEvent.setup();
257
+ renderObsFieldWithFormik({
258
+ ...textFieldDef,
259
+ validation: {
260
+ required: false,
261
+ matches: '^[A-Z]+$', // Only uppercase letters
262
+ },
263
+ });
264
+
265
+ const textInput = screen.getByRole('textbox', { name: /chief complaint/i }) as HTMLInputElement;
266
+ await user.type(textInput, 'lowercase');
267
+ await user.tab();
268
+
269
+ await waitFor(() => {
270
+ expect(screen.getByText(/invalid input/i)).toBeInTheDocument();
271
+ });
272
+ });
273
+
274
+ it('does not show error for valid input matching regex', async () => {
275
+ const user = userEvent.setup();
276
+ renderObsFieldWithFormik({
277
+ ...textFieldDef,
278
+ validation: {
279
+ required: false,
280
+ matches: '^[A-Z]+$', // Only uppercase letters
281
+ },
282
+ });
283
+
284
+ const textInput = screen.getByRole('textbox', { name: /chief complaint/i }) as HTMLInputElement;
285
+ await user.type(textInput, 'FEVER');
286
+ await user.tab();
287
+
288
+ await waitFor(() => {
289
+ expect(screen.queryByText(/invalid input/i)).not.toBeInTheDocument();
290
+ });
291
+ });
292
+
293
+ it('renders as required when configured', () => {
294
+ renderObsFieldWithFormik({
295
+ ...textFieldDef,
296
+ validation: {
297
+ required: true,
298
+ matches: null,
299
+ },
300
+ });
301
+
302
+ const textInput = screen.getByRole('textbox', { name: /chief complaint/i });
303
+ expect(textInput).toBeRequired();
304
+ });
305
+
306
+ it('renders as optional when not required', () => {
307
+ renderObsFieldWithFormik(textFieldDef);
308
+
309
+ const textInput = screen.getByRole('textbox', { name: /chief complaint/i });
310
+ expect(textInput).not.toBeRequired();
311
+ });
242
312
  });
243
313
 
244
- it('renders a datepicker for date concept', async () => {
245
- const user = userEvent.setup();
246
- render(
247
- <PatientRegistrationContextProvider value={initialContextValues}>
248
- <ObsField fieldDefinition={dateFieldDef} />
249
- </PatientRegistrationContextProvider>,
250
- );
251
-
252
- expect(screen.getByText(/vaccination date/i)).toBeInTheDocument();
253
-
254
- const dateInput = screen.getByLabelText(/vaccination date/i);
255
- expect(dateInput).toBeInTheDocument();
256
- await user.clear(dateInput);
257
- await user.type(dateInput, '28/05/2024');
258
- // FIXME: Make the date input work
259
- // expect(dateInput).toHaveValue('28/05/2024');
314
+ describe('Numeric obs field', () => {
315
+ it('renders a number input for numeric concept', () => {
316
+ renderObsFieldWithFormik(numberFieldDef);
317
+
318
+ expect(screen.getByRole('spinbutton', { name: /weight/i })).toBeInTheDocument();
319
+ });
320
+
321
+ it('allows user to enter numeric value', async () => {
322
+ const user = userEvent.setup();
323
+ const { getFormValues } = renderObsFieldWithFormik(numberFieldDef);
324
+
325
+ const numberInput = screen.getByRole('spinbutton', { name: /weight/i }) as HTMLInputElement;
326
+ await user.type(numberInput, '75');
327
+
328
+ await waitFor(() => {
329
+ // Numeric inputs can store values as numbers or strings depending on Formik behavior
330
+ const value = getFormValues().obs['weight-uuid'];
331
+ const stringValue = String(value);
332
+ expect(stringValue === '75' || Number(value) === 75).toBe(true);
333
+ });
334
+ });
335
+
336
+ it('renders as required when configured', () => {
337
+ renderObsFieldWithFormik({
338
+ ...numberFieldDef,
339
+ validation: {
340
+ required: true,
341
+ matches: null,
342
+ },
343
+ });
344
+
345
+ const numberInput = screen.getByRole('spinbutton', { name: /weight/i });
346
+ expect(numberInput).toBeRequired();
347
+ });
260
348
  });
261
349
 
262
- it('renders a select for a coded concept', () => {
263
- render(<ObsField fieldDefinition={codedFieldDef} />);
350
+ describe('Date obs field', () => {
351
+ it('renders a date picker for date concept', () => {
352
+ renderObsFieldWithFormik(dateFieldDef);
353
+
354
+ expect(screen.getByLabelText(/vaccination date/i)).toBeInTheDocument();
355
+ });
356
+
357
+ it('allows user to enter date', async () => {
358
+ const { getFormValues } = renderObsFieldWithFormik(dateFieldDef);
359
+
360
+ const dateInput = screen.getByLabelText(/vaccination date/i) as HTMLInputElement;
361
+ const dateString = '2020-01-15';
362
+
363
+ fireEvent.change(dateInput, { target: { value: dateString } });
364
+ fireEvent.blur(dateInput);
365
+
366
+ await waitFor(() => {
367
+ expect(getFormValues().obs['vaccination-date-uuid']).toBeTruthy();
368
+ });
369
+ });
370
+
371
+ it('renders as required when configured', () => {
372
+ renderObsFieldWithFormik({
373
+ ...dateFieldDef,
374
+ validation: {
375
+ required: true,
376
+ matches: null,
377
+ },
378
+ });
264
379
 
265
- expect(screen.getByRole('combobox', { name: 'Nationality' })).toBeInTheDocument();
266
- expect(screen.getByRole('option', { name: 'USA' })).toBeInTheDocument();
267
- expect(screen.getByRole('option', { name: 'Mexico' })).toBeInTheDocument();
380
+ const dateInput = screen.getByLabelText(/vaccination date/i);
381
+ // OpenmrsDatePicker uses isRequired prop, not HTML required attribute
382
+ expect(dateInput).toBeInTheDocument();
383
+ });
268
384
  });
269
385
 
270
- it('select uses answerConcept for answers when it is provided', async () => {
271
- render(<ObsField fieldDefinition={{ ...codedFieldDef, answerConceptSetUuid: 'other-countries-uuid' }} />);
386
+ describe('Coded obs field', () => {
387
+ it('renders a select for a coded concept', () => {
388
+ renderObsFieldWithFormik(codedFieldDef);
272
389
 
273
- expect(screen.getByRole('combobox', { name: 'Nationality' })).toBeInTheDocument();
274
- expect(screen.getByRole('option', { name: 'Kenya' })).toBeInTheDocument();
275
- expect(screen.getByRole('option', { name: 'Uganda' })).toBeInTheDocument();
276
- expect(screen.queryByRole('option', { name: 'USA' })).not.toBeInTheDocument();
277
- expect(screen.queryByRole('option', { name: 'Mexico' })).not.toBeInTheDocument();
390
+ expect(screen.getByRole('combobox', { name: 'Nationality' })).toBeInTheDocument();
391
+ expect(screen.getByRole('option', { name: 'USA' })).toBeInTheDocument();
392
+ expect(screen.getByRole('option', { name: 'Mexico' })).toBeInTheDocument();
393
+ });
394
+
395
+ it('allows user to select an option', async () => {
396
+ const user = userEvent.setup();
397
+ const { getFormValues } = renderObsFieldWithFormik(codedFieldDef);
398
+
399
+ const select = screen.getByRole('combobox', { name: 'Nationality' }) as HTMLSelectElement;
400
+ await user.selectOptions(select, 'usa');
401
+
402
+ await waitFor(() => {
403
+ expect(getFormValues().obs['nationality-uuid']).toBe('usa');
404
+ });
405
+ expect(select.value).toBe('usa');
406
+ });
407
+
408
+ it('uses answerConceptSetUuid for answers when provided', () => {
409
+ renderObsFieldWithFormik({
410
+ ...codedFieldDef,
411
+ answerConceptSetUuid: 'other-countries-uuid',
412
+ });
413
+
414
+ expect(screen.getByRole('combobox', { name: 'Nationality' })).toBeInTheDocument();
415
+ expect(screen.getByRole('option', { name: 'Kenya' })).toBeInTheDocument();
416
+ expect(screen.getByRole('option', { name: 'Uganda' })).toBeInTheDocument();
417
+ expect(screen.queryByRole('option', { name: 'USA' })).not.toBeInTheDocument();
418
+ expect(screen.queryByRole('option', { name: 'Mexico' })).not.toBeInTheDocument();
419
+ });
420
+
421
+ it('uses customConceptAnswers when provided', () => {
422
+ renderObsFieldWithFormik({
423
+ ...codedFieldDef,
424
+ customConceptAnswers: [
425
+ {
426
+ uuid: 'mozambique-uuid',
427
+ label: 'Mozambique',
428
+ },
429
+ ],
430
+ });
431
+
432
+ expect(screen.getByRole('combobox', { name: 'Nationality' })).toBeInTheDocument();
433
+ expect(screen.getByRole('option', { name: 'Mozambique' })).toBeInTheDocument();
434
+ expect(screen.queryByRole('option', { name: 'Uganda' })).not.toBeInTheDocument();
435
+ });
436
+
437
+ it('renders as required when configured', () => {
438
+ renderObsFieldWithFormik({
439
+ ...codedFieldDef,
440
+ validation: {
441
+ required: true,
442
+ matches: null,
443
+ },
444
+ });
445
+
446
+ const select = screen.getByRole('combobox', { name: 'Nationality' });
447
+ expect(select).toBeRequired();
448
+ });
278
449
  });
279
450
 
280
- it('select uses customConceptAnswers for answers when provided', async () => {
281
- render(
282
- <ObsField
283
- fieldDefinition={{
284
- ...codedFieldDef,
285
- customConceptAnswers: [
286
- {
287
- uuid: 'mozambique-uuid',
288
- label: 'Mozambique',
289
- },
290
- ],
291
- }}
292
- />,
293
- );
294
-
295
- expect(screen.getByRole('combobox', { name: 'Nationality' })).toBeInTheDocument();
296
- expect(screen.getByRole('option', { name: 'Mozambique' })).toBeInTheDocument();
297
- expect(screen.queryByRole('option', { name: 'Uganda' })).not.toBeInTheDocument();
451
+ describe('Error handling', () => {
452
+ it('displays error for unknown datatype', () => {
453
+ mockUseConcept.mockReturnValue({
454
+ data: {
455
+ uuid: 'unknown-uuid',
456
+ display: 'Unknown Field',
457
+ datatype: { display: 'UnknownType', uuid: 'unknown' },
458
+ answers: [],
459
+ setMembers: [],
460
+ },
461
+ isLoading: false,
462
+ });
463
+
464
+ renderObsFieldWithFormik({
465
+ ...textFieldDef,
466
+ uuid: 'unknown-uuid',
467
+ });
468
+
469
+ // InlineNotification renders with kind="error" which shows an error message
470
+ // Check for the specific error message text
471
+ expect(screen.getByText(/unknown datatype/i)).toBeInTheDocument();
472
+ expect(screen.getByText(/UnknownType/i)).toBeInTheDocument();
473
+ });
298
474
  });
299
475
  });