@ampath/esm-patient-registration-app 6.0.1-pre.96 → 9.2.0-next.12

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 (288) hide show
  1. package/dist/1119.js +1 -0
  2. package/dist/1197.js +1 -0
  3. package/dist/21.js +1 -0
  4. package/dist/21.js.map +1 -0
  5. package/dist/2146.js +1 -0
  6. package/dist/2372.js +1 -0
  7. package/dist/2372.js.map +1 -0
  8. package/dist/2470.js +1 -0
  9. package/dist/2470.js.map +1 -0
  10. package/dist/2690.js +1 -0
  11. package/dist/2913.js +2 -0
  12. package/dist/{913.js.LICENSE.txt → 2913.js.LICENSE.txt} +3 -23
  13. package/dist/2913.js.map +1 -0
  14. package/dist/3093.js +1 -0
  15. package/dist/3093.js.map +1 -0
  16. package/dist/3099.js +1 -0
  17. package/dist/3144.js +2 -0
  18. package/dist/3144.js.LICENSE.txt +19 -0
  19. package/dist/3144.js.map +1 -0
  20. package/dist/320.js +2 -0
  21. package/dist/{876.js.LICENSE.txt → 320.js.LICENSE.txt} +2 -3
  22. package/dist/320.js.map +1 -0
  23. package/dist/3464.js +1 -0
  24. package/dist/3464.js.map +1 -0
  25. package/dist/3474.js +2 -0
  26. package/dist/3474.js.LICENSE.txt +8 -0
  27. package/dist/3474.js.map +1 -0
  28. package/dist/3584.js +1 -0
  29. package/dist/4041.js +2 -0
  30. package/dist/4041.js.map +1 -0
  31. package/dist/4055.js +1 -0
  32. package/dist/4132.js +1 -0
  33. package/dist/4300.js +1 -0
  34. package/dist/4335.js +1 -0
  35. package/dist/4463.js +1 -0
  36. package/dist/4463.js.map +1 -0
  37. package/dist/4618.js +1 -0
  38. package/dist/4652.js +1 -0
  39. package/dist/4944.js +1 -0
  40. package/dist/5173.js +1 -0
  41. package/dist/5220.js +2 -0
  42. package/dist/5220.js.LICENSE.txt +29 -0
  43. package/dist/5220.js.map +1 -0
  44. package/dist/5241.js +1 -0
  45. package/dist/5442.js +1 -0
  46. package/dist/5661.js +1 -0
  47. package/dist/6022.js +1 -0
  48. package/dist/6078.js +2 -0
  49. package/dist/6078.js.LICENSE.txt +9 -0
  50. package/dist/6078.js.map +1 -0
  51. package/dist/627.js +1 -0
  52. package/dist/627.js.map +1 -0
  53. package/dist/6276.js +1 -0
  54. package/dist/6276.js.map +1 -0
  55. package/dist/6468.js +1 -0
  56. package/dist/6679.js +1 -0
  57. package/dist/6737.js +2 -0
  58. package/dist/6737.js.LICENSE.txt +9 -0
  59. package/dist/6737.js.map +1 -0
  60. package/dist/6840.js +1 -0
  61. package/dist/6859.js +1 -0
  62. package/dist/7092.js +1 -0
  63. package/dist/7092.js.map +1 -0
  64. package/dist/7097.js +1 -0
  65. package/dist/7159.js +1 -0
  66. package/dist/723.js +1 -0
  67. package/dist/7495.js +2 -0
  68. package/dist/7495.js.LICENSE.txt +9 -0
  69. package/dist/7495.js.map +1 -0
  70. package/dist/7617.js +1 -0
  71. package/dist/795.js +1 -0
  72. package/dist/8163.js +1 -0
  73. package/dist/8349.js +1 -0
  74. package/dist/8404.js +2 -0
  75. package/dist/{629.js.LICENSE.txt → 8404.js.LICENSE.txt} +9 -3
  76. package/dist/8404.js.map +1 -0
  77. package/dist/8434.js +1 -0
  78. package/dist/8434.js.map +1 -0
  79. package/dist/8618.js +1 -0
  80. package/dist/89.js +2 -0
  81. package/dist/89.js.LICENSE.txt +9 -0
  82. package/dist/89.js.map +1 -0
  83. package/dist/890.js +1 -0
  84. package/dist/9214.js +1 -0
  85. package/dist/9538.js +1 -0
  86. package/dist/9569.js +1 -0
  87. package/dist/986.js +1 -0
  88. package/dist/9876.js +1 -0
  89. package/dist/9876.js.map +1 -0
  90. package/dist/9879.js +1 -0
  91. package/dist/9895.js +1 -0
  92. package/dist/9900.js +1 -0
  93. package/dist/9913.js +1 -0
  94. package/dist/main.js +1 -1
  95. package/dist/main.js.LICENSE.txt +36 -1
  96. package/dist/main.js.map +1 -1
  97. package/dist/openmrs-esm-patient-registration-app.js +1 -0
  98. package/dist/openmrs-esm-patient-registration-app.js.buildmanifest.json +1576 -0
  99. package/dist/openmrs-esm-patient-registration-app.js.map +1 -0
  100. package/dist/routes.json +1 -1
  101. package/package.json +16 -15
  102. package/src/{add-patient-link.tsx → add-patient-link.extension.tsx} +4 -2
  103. package/src/add-patient-link.test.tsx +6 -10
  104. package/src/config-schema.ts +109 -55
  105. package/src/constants.ts +1 -1
  106. package/src/declarations.d.ts +5 -4
  107. package/src/index.ts +10 -29
  108. package/src/nav-link.test.tsx +3 -3
  109. package/src/offline.resources.ts +26 -18
  110. package/src/patient-photo.extension.tsx +3 -1
  111. package/src/patient-registration/field/address/address-field.component.tsx +58 -37
  112. package/src/patient-registration/field/address/address-hierarchy-levels.component.tsx +16 -18
  113. package/src/patient-registration/field/address/address-hierarchy.resource.tsx +3 -3
  114. package/src/patient-registration/field/address/address-hierarchy.test.tsx +290 -0
  115. package/src/patient-registration/field/address/address-search.component.tsx +7 -5
  116. package/src/patient-registration/field/address/address-search.scss +5 -5
  117. package/src/patient-registration/field/address/address-search.test.tsx +140 -0
  118. package/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx +98 -0
  119. package/src/patient-registration/field/custom-field.component.tsx +3 -9
  120. package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +84 -0
  121. package/src/patient-registration/field/dob/dob.component.tsx +55 -50
  122. package/src/patient-registration/field/dob/dob.test.tsx +90 -0
  123. package/src/patient-registration/field/field.component.tsx +12 -6
  124. package/src/patient-registration/field/field.resource.ts +11 -4
  125. package/src/patient-registration/field/field.scss +69 -25
  126. package/src/patient-registration/field/field.test.tsx +329 -0
  127. package/src/patient-registration/field/gender/gender-field.component.tsx +14 -9
  128. package/src/patient-registration/field/gender/gender-field.test.tsx +73 -33
  129. package/src/patient-registration/field/id/id-field.component.tsx +24 -23
  130. package/src/patient-registration/field/id/id-field.test.tsx +147 -0
  131. package/src/patient-registration/field/id/identifier-selection-overlay.component.tsx +12 -10
  132. package/src/patient-registration/field/id/identifier-selection.scss +12 -8
  133. package/src/patient-registration/field/name/name-field.component.tsx +10 -5
  134. package/src/patient-registration/field/obs/obs-field.component.tsx +59 -2
  135. package/src/patient-registration/field/obs/obs-field.test.tsx +133 -39
  136. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +3 -3
  137. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +141 -0
  138. package/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx +105 -0
  139. package/src/patient-registration/field/person-attributes/location-person-attribute-field.resource.tsx +48 -0
  140. package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +19 -22
  141. package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +193 -0
  142. package/src/patient-registration/field/person-attributes/text-person-attribute-field.test.tsx +90 -0
  143. package/src/patient-registration/form-manager.test.ts +91 -0
  144. package/src/patient-registration/form-manager.ts +49 -23
  145. package/src/patient-registration/input/basic-input/input/input.component.tsx +6 -2
  146. package/src/patient-registration/input/basic-input/select/select-input.test.tsx +49 -0
  147. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.scss +5 -5
  148. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +164 -0
  149. package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +73 -36
  150. package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +335 -0
  151. package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +3 -0
  152. package/src/patient-registration/input/dummy-data/dummy-data-input.test.tsx +2 -11
  153. package/src/patient-registration/input/input.scss +17 -13
  154. package/src/patient-registration/patient-registration-context.ts +22 -11
  155. package/src/patient-registration/patient-registration-hooks.ts +158 -193
  156. package/src/patient-registration/patient-registration-utils.test.ts +33 -0
  157. package/src/patient-registration/patient-registration-utils.ts +11 -13
  158. package/src/patient-registration/patient-registration.component.tsx +87 -103
  159. package/src/patient-registration/{patient-registration.resource.testt.tsx → patient-registration.resource.test.tsx} +0 -4
  160. package/src/patient-registration/patient-registration.resource.ts +27 -3
  161. package/src/patient-registration/patient-registration.scss +27 -38
  162. package/src/patient-registration/patient-registration.test.tsx +579 -0
  163. package/src/patient-registration/patient-registration.types.ts +23 -25
  164. package/src/patient-registration/section/death-info/death-info-section.component.tsx +22 -17
  165. package/src/patient-registration/section/death-info/death-info-section.test.tsx +47 -0
  166. package/src/patient-registration/section/demographics/demographics-section.component.tsx +5 -5
  167. package/src/patient-registration/section/demographics/demographics-section.test.tsx +98 -0
  168. package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +8 -7
  169. package/src/patient-registration/section/patient-relationships/relationships-section.test.tsx +113 -0
  170. package/src/patient-registration/section/patient-relationships/relationships.resource.tsx +28 -28
  171. package/src/patient-registration/section/patient-relationships/relationships.scss +4 -4
  172. package/src/patient-registration/section/section-wrapper.component.tsx +1 -1
  173. package/src/patient-registration/section/section.component.tsx +1 -1
  174. package/src/patient-registration/section/section.scss +21 -1
  175. package/src/patient-registration/ui-components/overlay/overlay.scss +8 -8
  176. package/src/patient-registration/validation/{patient-registration-validation.test.tsx → patient-registration-validation.test.ts} +71 -23
  177. package/src/patient-registration/validation/patient-registration-validation.ts +123 -0
  178. package/src/resources-context.ts +14 -0
  179. package/src/root.component.tsx +3 -3
  180. package/src/routes.json +10 -24
  181. package/src/widgets/cancel-patient-edit.modal.tsx +33 -0
  182. package/src/widgets/cancel-patient-edit.test.tsx +22 -0
  183. package/src/widgets/delete-identifier-confirmation.modal.tsx +48 -0
  184. package/src/widgets/{delete-identifier-confirmation-modal.testt.tsx → delete-identifier-confirmation.test.tsx} +5 -7
  185. package/src/widgets/edit-patient-details-button.component.tsx +0 -1
  186. package/src/widgets/edit-patient-details-button.test.tsx +35 -0
  187. package/translations/am.json +43 -35
  188. package/translations/ar.json +41 -33
  189. package/translations/ar_SY.json +119 -0
  190. package/translations/bn.json +119 -0
  191. package/translations/de.json +119 -0
  192. package/translations/en.json +44 -42
  193. package/translations/en_US.json +119 -0
  194. package/translations/es.json +69 -57
  195. package/translations/es_MX.json +119 -0
  196. package/translations/fr.json +74 -58
  197. package/translations/he.json +44 -40
  198. package/translations/hi.json +119 -0
  199. package/translations/hi_IN.json +119 -0
  200. package/translations/id.json +119 -0
  201. package/translations/it.json +119 -0
  202. package/translations/ka.json +119 -0
  203. package/translations/km.json +44 -40
  204. package/translations/ku.json +119 -0
  205. package/translations/ky.json +119 -0
  206. package/translations/lg.json +119 -0
  207. package/translations/ne.json +119 -0
  208. package/translations/pl.json +119 -0
  209. package/translations/pt.json +119 -0
  210. package/translations/pt_BR.json +119 -0
  211. package/translations/qu.json +119 -0
  212. package/translations/ro_RO.json +119 -0
  213. package/translations/ru_RU.json +119 -0
  214. package/translations/si.json +119 -0
  215. package/translations/sw.json +119 -0
  216. package/translations/sw_KE.json +119 -0
  217. package/translations/tr.json +119 -0
  218. package/translations/tr_TR.json +119 -0
  219. package/translations/uk.json +119 -0
  220. package/translations/uz.json +119 -0
  221. package/translations/uz@Latn.json +119 -0
  222. package/translations/uz_UZ.json +119 -0
  223. package/translations/vi.json +119 -0
  224. package/translations/zh.json +45 -23
  225. package/translations/zh_CN.json +39 -17
  226. package/.turbo/turbo-build.log +0 -40
  227. package/dist/132.js +0 -1
  228. package/dist/197.js +0 -1
  229. package/dist/236.js +0 -1
  230. package/dist/236.js.map +0 -1
  231. package/dist/300.js +0 -1
  232. package/dist/335.js +0 -1
  233. package/dist/372.js +0 -1
  234. package/dist/372.js.map +0 -1
  235. package/dist/41.js +0 -2
  236. package/dist/41.js.map +0 -1
  237. package/dist/449.js +0 -1
  238. package/dist/449.js.map +0 -1
  239. package/dist/464.js +0 -1
  240. package/dist/464.js.map +0 -1
  241. package/dist/495.js +0 -1
  242. package/dist/495.js.map +0 -1
  243. package/dist/55.js +0 -1
  244. package/dist/56.js +0 -1
  245. package/dist/56.js.map +0 -1
  246. package/dist/621.js +0 -1
  247. package/dist/621.js.map +0 -1
  248. package/dist/629.js +0 -2
  249. package/dist/629.js.map +0 -1
  250. package/dist/652.js +0 -1
  251. package/dist/661.js +0 -1
  252. package/dist/757.js +0 -1
  253. package/dist/757.js.map +0 -1
  254. package/dist/828.js +0 -1
  255. package/dist/828.js.map +0 -1
  256. package/dist/830.js +0 -1
  257. package/dist/830.js.map +0 -1
  258. package/dist/831.js +0 -2
  259. package/dist/831.js.LICENSE.txt +0 -3
  260. package/dist/831.js.map +0 -1
  261. package/dist/876.js +0 -2
  262. package/dist/876.js.map +0 -1
  263. package/dist/879.js +0 -1
  264. package/dist/913.js +0 -2
  265. package/dist/913.js.map +0 -1
  266. package/dist/927.js +0 -1
  267. package/dist/927.js.map +0 -1
  268. package/dist/99.js +0 -1
  269. package/dist/ampath-esm-patient-registration-app.js +0 -1
  270. package/dist/ampath-esm-patient-registration-app.js.buildmanifest.json +0 -694
  271. package/dist/ampath-esm-patient-registration-app.js.map +0 -1
  272. package/src/patient-registration/date-util.ts +0 -52
  273. package/src/patient-registration/field/person-attributes/custom-person-attribute-field.component.tsx +0 -56
  274. package/src/patient-registration/validation/patient-registration-validation.tsx +0 -60
  275. package/src/patient-verification/assets/counties.json +0 -236
  276. package/src/patient-verification/assets/verification-assets.ts +0 -11
  277. package/src/patient-verification/patient-verification-hook.tsx +0 -176
  278. package/src/patient-verification/patient-verification-utils.ts +0 -179
  279. package/src/patient-verification/patient-verification.component.tsx +0 -124
  280. package/src/patient-verification/patient-verification.scss +0 -25
  281. package/src/patient-verification/verification-modal/confirm-prompt.component.tsx +0 -72
  282. package/src/patient-verification/verification-modal/empty-prompt.component.tsx +0 -35
  283. package/src/patient-verification/verification-types.ts +0 -50
  284. package/src/widgets/cancel-patient-edit.component.tsx +0 -37
  285. package/src/widgets/delete-identifier-confirmation-modal.tsx +0 -41
  286. package/src/widgets/delete-identifier-modal.scss +0 -34
  287. /package/dist/{41.js.LICENSE.txt → 4041.js.LICENSE.txt} +0 -0
  288. /package/src/patient-registration/input/custom-input/identifier/{utils.testt.ts → utils.test.ts} +0 -0
@@ -1,21 +1,48 @@
1
- import { defineConfigSchema, getConfig } from '@openmrs/esm-framework';
1
+ import dayjs from 'dayjs';
2
+ import { getConfig } from '@openmrs/esm-framework';
3
+ import { type RegistrationConfig } from '../../config-schema';
2
4
  import { getValidationSchema } from './patient-registration-validation';
3
- import { type RegistrationConfig, esmPatientRegistrationSchema } from '../../config-schema';
4
- describe('Patient Registration Validation', () => {
5
- beforeAll(() => {
6
- defineConfigSchema('@openmrs/esm-patient-registration-app', esmPatientRegistrationSchema);
5
+
6
+ const mockGetConfig = jest.mocked(getConfig);
7
+
8
+ describe('Patient registration validation', () => {
9
+ beforeEach(() => {
10
+ mockGetConfig.mockResolvedValue({
11
+ fieldConfigurations: {
12
+ gender: [
13
+ {
14
+ label: 'M',
15
+ value: 'male',
16
+ },
17
+ {
18
+ label: 'F',
19
+ value: 'female',
20
+ },
21
+ {
22
+ label: 'O',
23
+ value: 'other',
24
+ },
25
+ {
26
+ label: 'U',
27
+ value: 'unknown',
28
+ },
29
+ ],
30
+ },
31
+ });
7
32
  });
8
33
 
9
34
  const validFormValues = {
10
- givenName: 'John',
11
- familyName: 'Doe',
12
- additionalGivenName: '',
13
35
  additionalFamilyName: '',
14
- gender: 'male',
36
+ additionalGivenName: '',
15
37
  birthdate: new Date('1990-01-01'),
16
38
  birthdateEstimated: false,
39
+ isDead: false,
40
+ causeOfDeath: null,
17
41
  deathDate: null,
18
42
  email: 'john.doe@example.com',
43
+ familyName: 'Doe',
44
+ gender: 'male',
45
+ givenName: 'John',
19
46
  identifiers: {
20
47
  nationalId: {
21
48
  required: true,
@@ -29,8 +56,10 @@ describe('Patient Registration Validation', () => {
29
56
  };
30
57
 
31
58
  const validateFormValues = async (formValues) => {
32
- const config = (await getConfig('@openmrs/esm-patient-registration-app')) as any as RegistrationConfig;
33
- const validationSchema = getValidationSchema(config);
59
+ const config = (await getConfig('@openmrs/esm-patient-registration-app')) as unknown as RegistrationConfig;
60
+ const mockT = (key: string, defaultValue: string) => defaultValue;
61
+
62
+ const validationSchema = getValidationSchema(config, mockT);
34
63
  try {
35
64
  await validationSchema.validate(formValues, { abortEarly: false });
36
65
  } catch (err) {
@@ -49,7 +78,7 @@ describe('Patient Registration Validation', () => {
49
78
  givenName: '',
50
79
  };
51
80
  const validationError = await validateFormValues(invalidFormValues);
52
- expect(validationError.errors).toContain('givenNameRequired');
81
+ expect(validationError.errors).toContain('Given name is required');
53
82
  });
54
83
 
55
84
  it('should require familyName', async () => {
@@ -58,7 +87,7 @@ describe('Patient Registration Validation', () => {
58
87
  familyName: '',
59
88
  };
60
89
  const validationError = await validateFormValues(invalidFormValues);
61
- expect(validationError.errors).toContain('familyNameRequired');
90
+ expect(validationError.errors).toContain('Family name is required');
62
91
  });
63
92
 
64
93
  it('should require additionalGivenName when addNameInLocalLanguage is true', async () => {
@@ -68,7 +97,7 @@ describe('Patient Registration Validation', () => {
68
97
  additionalGivenName: '',
69
98
  };
70
99
  const validationError = await validateFormValues(invalidFormValues);
71
- expect(validationError.errors).toContain('givenNameRequired');
100
+ expect(validationError.errors).toContain('Given name is required');
72
101
  });
73
102
 
74
103
  it('should require additionalFamilyName when addNameInLocalLanguage is true', async () => {
@@ -78,7 +107,7 @@ describe('Patient Registration Validation', () => {
78
107
  additionalFamilyName: '',
79
108
  };
80
109
  const validationError = await validateFormValues(invalidFormValues);
81
- expect(validationError.errors).toContain('familyNameRequired');
110
+ expect(validationError.errors).toContain('Family name is required');
82
111
  });
83
112
 
84
113
  it('should require gender', async () => {
@@ -87,7 +116,7 @@ describe('Patient Registration Validation', () => {
87
116
  gender: '',
88
117
  };
89
118
  const validationError = await validateFormValues(invalidFormValues);
90
- expect(validationError.errors).toContain('genderUnspecified');
119
+ expect(validationError.errors).toContain('Gender unspecified');
91
120
  });
92
121
 
93
122
  it('should allow female as a valid gender', async () => {
@@ -117,13 +146,22 @@ describe('Patient Registration Validation', () => {
117
146
  expect(validationError).toBeFalsy();
118
147
  });
119
148
 
120
- it('should throw error when date of birth is a future date', async () => {
149
+ it('should throw an error when date of birth is a future date', async () => {
121
150
  const invalidFormValues = {
122
151
  ...validFormValues,
123
152
  birthdate: new Date('2100-01-01'),
124
153
  };
125
154
  const validationError = await validateFormValues(invalidFormValues);
126
- expect(validationError.errors).toContain('birthdayNotInTheFuture');
155
+ expect(validationError.errors).toContain('Birthday cannot be in future');
156
+ });
157
+
158
+ it('should throw an error when date of birth is more than 140 years ago', async () => {
159
+ const invalidFormValues = {
160
+ ...validFormValues,
161
+ birthdate: dayjs().subtract(141, 'years').toDate(),
162
+ };
163
+ const validationError = await validateFormValues(invalidFormValues);
164
+ expect(validationError.errors).toContain('Birthday cannot be more than 140 years ago');
127
165
  });
128
166
 
129
167
  it('should require yearsEstimated when birthdateEstimated is true', async () => {
@@ -132,10 +170,10 @@ describe('Patient Registration Validation', () => {
132
170
  birthdateEstimated: true,
133
171
  };
134
172
  const validationError = await validateFormValues(invalidFormValues);
135
- expect(validationError.errors).toContain('yearsEstimateRequired');
173
+ expect(validationError.errors).toContain('Estimated years required');
136
174
  });
137
175
 
138
- it('should throw error when monthEstimated is negative', async () => {
176
+ it('should throw an error when monthEstimated is negative', async () => {
139
177
  const invalidFormValues = {
140
178
  ...validFormValues,
141
179
  birthdateEstimated: true,
@@ -143,15 +181,25 @@ describe('Patient Registration Validation', () => {
143
181
  monthsEstimated: -1,
144
182
  };
145
183
  const validationError = await validateFormValues(invalidFormValues);
146
- expect(validationError.errors).toContain('negativeMonths');
184
+ expect(validationError.errors).toContain('Estimated months cannot be negative');
185
+ });
186
+
187
+ it('should throw an error when yearsEstimated is more than 140', async () => {
188
+ const invalidFormValues = {
189
+ ...validFormValues,
190
+ birthdateEstimated: true,
191
+ yearsEstimated: 141,
192
+ };
193
+ const validationError = await validateFormValues(invalidFormValues);
194
+ expect(validationError.errors).toContain('Estimated years cannot be more than 140');
147
195
  });
148
196
 
149
- it('should throw error when deathDate is in future', async () => {
197
+ it('should throw an error when deathDate is in future', async () => {
150
198
  const invalidFormValues = {
151
199
  ...validFormValues,
152
200
  deathDate: new Date('2100-01-01'),
153
201
  };
154
202
  const validationError = await validateFormValues(invalidFormValues);
155
- expect(validationError.errors).toContain('deathdayNotInTheFuture');
203
+ expect(validationError.errors).toContain('Death date cannot be in future');
156
204
  });
157
205
  });
@@ -0,0 +1,123 @@
1
+ import dayjs from 'dayjs';
2
+ import * as Yup from 'yup';
3
+ import mapValues from 'lodash/mapValues';
4
+ import { type RegistrationConfig } from '../../config-schema';
5
+ import { type FormValues } from '../patient-registration.types';
6
+ import { getDatetime } from '../patient-registration.resource';
7
+
8
+ export function getValidationSchema(config: RegistrationConfig, t: (key: string, defaultValue: string) => string) {
9
+ return Yup.object({
10
+ givenName: Yup.string().required(t('givenNameRequired', 'Given name is required')),
11
+ familyName: Yup.string().required(t('familyNameRequired', 'Family name is required')),
12
+ additionalGivenName: Yup.string().when('addNameInLocalLanguage', {
13
+ is: true,
14
+ then: Yup.string().required(t('givenNameRequired', 'Given name is required')),
15
+ otherwise: Yup.string().notRequired(),
16
+ }),
17
+ additionalFamilyName: Yup.string().when('addNameInLocalLanguage', {
18
+ is: true,
19
+ then: Yup.string().required(t('familyNameRequired', 'Family name is required')),
20
+ otherwise: Yup.string().notRequired(),
21
+ }),
22
+ gender: Yup.string()
23
+ .oneOf(
24
+ config.fieldConfigurations.gender.map((g) => g.value),
25
+ t('genderUnspecified', 'Gender unspecified'),
26
+ )
27
+ .required(t('genderRequired', 'Gender is required')),
28
+ birthdate: Yup.date().when('birthdateEstimated', {
29
+ is: false,
30
+ then: Yup.date()
31
+ .required(t('birthdayRequired', 'Birthday is required'))
32
+ .max(Date(), t('birthdayNotInTheFuture', 'Birthday cannot be in future'))
33
+ .min(
34
+ dayjs().subtract(140, 'years').toDate(),
35
+ t('birthdayNotOver140YearsAgo', 'Birthday cannot be more than 140 years ago'),
36
+ )
37
+ .nullable(),
38
+ otherwise: Yup.date().nullable(),
39
+ }),
40
+ yearsEstimated: Yup.number().when('birthdateEstimated', {
41
+ is: true,
42
+ then: Yup.number()
43
+ .required(t('yearsEstimateRequired', 'Estimated years required'))
44
+ .min(0, t('negativeYears', 'Estimated years cannot be negative'))
45
+ .max(140, t('nonsensicalYears', 'Estimated years cannot be more than 140')),
46
+ otherwise: Yup.number().nullable(),
47
+ }),
48
+ monthsEstimated: Yup.number().min(0, t('negativeMonths', 'Estimated months cannot be negative')),
49
+ isDead: Yup.boolean(),
50
+ deathDate: Yup.date()
51
+ .when('isDead', {
52
+ is: true,
53
+ then: Yup.date().required(t('deathDateRequired', 'Death date is required')),
54
+ otherwise: Yup.date().nullable(),
55
+ })
56
+ .max(new Date(), t('deathDateInFuture', 'Death date cannot be in future'))
57
+ .test(
58
+ 'deathDate-after-birthdate',
59
+ t('deathdayInvalidDate', 'Death date and time cannot be before the birthday'),
60
+ function (value) {
61
+ const { birthdate } = this.parent;
62
+ if (birthdate && value) {
63
+ return dayjs(value).isAfter(birthdate);
64
+ }
65
+ return true;
66
+ },
67
+ )
68
+ .test('deathDate-before-today', t('deathDateInFuture', 'Death date cannot be in future'), function (value) {
69
+ const { deathTime, deathTimeFormat } = this.parent;
70
+ if (value && deathTime && deathTimeFormat && /^(1[0-2]|0?[1-9]):([0-5]?[0-9])$/.test(deathTime)) {
71
+ return dayjs(getDatetime(value, deathTime, deathTimeFormat)).isBefore(dayjs());
72
+ }
73
+ return true;
74
+ }),
75
+ deathTime: Yup.string()
76
+ .when('isDead', {
77
+ is: true,
78
+ then: Yup.string().required(t('deathTimeRequired', 'Death time is required')),
79
+ otherwise: Yup.string().nullable(),
80
+ })
81
+ .matches(/^(1[0-2]|0?[1-9]):([0-5]?[0-9])$/, t('deathTimeInvalid', "Time doesn't match the format 'hh:mm'")),
82
+
83
+ deathTimeFormat: Yup.string()
84
+ .when('isDead', {
85
+ is: true,
86
+ then: Yup.string().required(t('deathTimeFormatRequired', 'Time format is required')),
87
+ otherwise: Yup.string().nullable(),
88
+ })
89
+ .oneOf(['AM', 'PM'], t('deathTimeFormatInvalid', 'Time format is invalid')),
90
+
91
+ deathCause: Yup.string().when('isDead', {
92
+ is: true,
93
+ then: Yup.string().required(t('deathCauseRequired', 'Cause of death is required')),
94
+ otherwise: Yup.string().nullable(),
95
+ }),
96
+ nonCodedCauseOfDeath: Yup.string().when(['isDead', 'deathCause'], {
97
+ is: (isDead, deathCause) => isDead && deathCause === config.freeTextFieldConceptUuid,
98
+ then: Yup.string().required(t('nonCodedCauseOfDeathRequired', 'Cause of death is required')),
99
+ otherwise: Yup.string().nullable(),
100
+ }),
101
+ email: Yup.string().optional().email(t('invalidEmail', 'Invalid email')),
102
+ identifiers: Yup.lazy((obj: FormValues['identifiers']) =>
103
+ Yup.object(
104
+ mapValues(obj, () =>
105
+ Yup.object({
106
+ required: Yup.bool(),
107
+ identifierValue: Yup.string().when('required', {
108
+ is: true,
109
+ then: Yup.string().required(t('identifierValueRequired', 'Identifier value is required')),
110
+ otherwise: Yup.string().notRequired(),
111
+ }),
112
+ }),
113
+ ),
114
+ ),
115
+ ),
116
+ relationships: Yup.array().of(
117
+ Yup.object().shape({
118
+ relatedPersonUuid: Yup.string().required(),
119
+ relationshipType: Yup.string().required(),
120
+ }),
121
+ ),
122
+ });
123
+ }
@@ -0,0 +1,14 @@
1
+ import { createContext, useContext } from 'react';
2
+ import { type Resources } from './offline.resources';
3
+
4
+ export const ResourcesContext = createContext<Resources>(null);
5
+
6
+ export const ResourcesContextProvider = ResourcesContext.Provider;
7
+
8
+ export const useResourcesContext = () => {
9
+ const context = useContext(ResourcesContext);
10
+ if (!context) {
11
+ throw new Error('useResourcesContext must be used within a ResourcesContextProvider');
12
+ }
13
+ return context;
14
+ };
@@ -5,11 +5,11 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom';
5
5
  import { Grid, Row } from '@carbon/react';
6
6
  import { ExtensionSlot, useConnectivity, useSession } from '@openmrs/esm-framework';
7
7
  import {
8
- ResourcesContext,
9
8
  fetchAddressTemplate,
10
9
  fetchAllRelationshipTypes,
11
10
  fetchPatientIdentifierTypesWithSources,
12
11
  } from './offline.resources';
12
+ import { ResourcesContextProvider } from './resources-context';
13
13
  import { FormManager } from './patient-registration/form-manager';
14
14
  import { PatientRegistration } from './patient-registration/patient-registration.component';
15
15
  import styles from './root.scss';
@@ -37,7 +37,7 @@ export default function Root() {
37
37
  <Row>
38
38
  <ExtensionSlot name="breadcrumbs-slot" />
39
39
  </Row>
40
- <ResourcesContext.Provider
40
+ <ResourcesContextProvider
41
41
  value={{
42
42
  addressTemplate,
43
43
  relationshipTypes,
@@ -56,7 +56,7 @@ export default function Root() {
56
56
  />
57
57
  </Routes>
58
58
  </BrowserRouter>
59
- </ResourcesContext.Provider>
59
+ </ResourcesContextProvider>
60
60
  </Grid>
61
61
  </main>
62
62
  );
package/src/routes.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.openmrs.org/routes.schema.json",
3
3
  "backendDependencies": {
4
- "webservices.rest": "^2.24.0"
4
+ "webservices.rest": ">=2.2.0"
5
5
  },
6
6
  "pages": [
7
7
  {
@@ -23,13 +23,8 @@
23
23
  "name": "add-patient-action",
24
24
  "slot": "top-nav-actions-slot",
25
25
  "online": true,
26
- "offline": true
27
- },
28
- {
29
- "component": "cancelPatientEditModal",
30
- "name": "cancel-patient-edit-modal",
31
- "online": true,
32
- "offline": true
26
+ "offline": true,
27
+ "order": 30
33
28
  },
34
29
  {
35
30
  "component": "patientPhotoExtension",
@@ -51,25 +46,16 @@
51
46
  "slot": "patient-search-actions-slot",
52
47
  "online": true,
53
48
  "offline": true
54
- },
55
- {
56
- "component": "deleteIdentifierConfirmationModal",
57
- "name": "delete-identifier-confirmation-modal",
58
- "online": true,
59
- "offline": true
60
- },
49
+ }
50
+ ],
51
+ "modals": [
61
52
  {
62
- "component": "emptyClientRegistryModal",
63
- "name": "empty-client-registry-modal",
64
- "online": true,
65
- "offline": true
53
+ "name": "cancel-patient-edit-modal",
54
+ "component": "cancelPatientEditModal"
66
55
  },
67
56
  {
68
- "component": "confirmClientRegistryModal",
69
- "name": "confirm-client-registry-modal",
70
- "online": true,
71
- "offline": true
57
+ "name": "delete-identifier-confirmation-modal",
58
+ "component": "deleteIdentifierConfirmationModal"
72
59
  }
73
60
  ]
74
61
  }
75
-
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
4
+
5
+ interface CancelPatientEditPropsModal {
6
+ close(): void;
7
+ onConfirm(): void;
8
+ }
9
+
10
+ const CancelPatientEditModal: React.FC<CancelPatientEditPropsModal> = ({ close, onConfirm }) => {
11
+ const { t } = useTranslation();
12
+ return (
13
+ <>
14
+ <ModalHeader
15
+ closeModal={close}
16
+ title={t('confirmDiscardChangesTitle', 'Are you sure you want to discard these changes?')}
17
+ />
18
+ <ModalBody>
19
+ <p>{t('confirmDiscardChangesBody', 'Your unsaved changes will be lost if you proceed to discard the form')}.</p>
20
+ </ModalBody>
21
+ <ModalFooter>
22
+ <Button kind="secondary" onClick={close}>
23
+ {t('cancel', 'Cancel')}
24
+ </Button>
25
+ <Button kind="danger" onClick={onConfirm}>
26
+ {t('discard', 'Discard')}
27
+ </Button>
28
+ </ModalFooter>
29
+ </>
30
+ );
31
+ };
32
+
33
+ export default CancelPatientEditModal;
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { screen, render } from '@testing-library/react';
4
+ import CancelPatientEdit from './cancel-patient-edit.modal';
5
+
6
+ describe('CancelPatientEdit modal', () => {
7
+ const mockClose = jest.fn();
8
+ const mockOnConfirm = jest.fn();
9
+
10
+ it('renders the modal and triggers close and onConfirm functions', async () => {
11
+ const user = userEvent.setup();
12
+ render(<CancelPatientEdit close={mockClose} onConfirm={mockOnConfirm} />);
13
+
14
+ const cancelButton = screen.getByRole('button', { name: /Cancel/i });
15
+ await user.click(cancelButton);
16
+ expect(mockClose).toHaveBeenCalledTimes(1);
17
+
18
+ const discardButton = screen.getByRole('button', { name: /discard/i });
19
+ await user.click(discardButton);
20
+ expect(mockOnConfirm).toHaveBeenCalledTimes(1);
21
+ });
22
+ });
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button, ModalBody, ModalHeader, ModalFooter } from '@carbon/react';
4
+
5
+ interface DeleteIdentifierConfirmationModalProps {
6
+ closeModal: () => void;
7
+ deleteIdentifier: (x: boolean) => void;
8
+ identifierName: string;
9
+ identifierValue: string;
10
+ }
11
+
12
+ const DeleteIdentifierConfirmationModal: React.FC<DeleteIdentifierConfirmationModalProps> = ({
13
+ closeModal,
14
+ deleteIdentifier,
15
+ identifierName,
16
+ identifierValue,
17
+ }) => {
18
+ const { t } = useTranslation();
19
+
20
+ return (
21
+ <>
22
+ <ModalHeader
23
+ closeModal={closeModal}
24
+ title={t('deleteIdentifierModalHeading', 'Delete identifier?')}></ModalHeader>
25
+ <ModalBody>
26
+ <p>
27
+ {identifierName && identifierValue && (
28
+ <span>
29
+ <strong>{identifierName}</strong>
30
+ {t('deleteIdentifierModalText', ' has a value of ')} <strong>{identifierValue}</strong>.{' '}
31
+ </span>
32
+ )}
33
+ {t('confirmIdentifierDeletionText', 'Are you sure you want to delete this identifier?')}
34
+ </p>
35
+ </ModalBody>
36
+ <ModalFooter>
37
+ <Button kind="secondary" size="lg" onClick={closeModal}>
38
+ {t('cancel', 'Cancel')}
39
+ </Button>
40
+ <Button kind="danger" size="lg" onClick={() => deleteIdentifier(true)}>
41
+ {t('removeIdentifierButton', 'Remove identifier')}
42
+ </Button>
43
+ </ModalFooter>
44
+ </>
45
+ );
46
+ };
47
+
48
+ export default DeleteIdentifierConfirmationModal;
@@ -1,22 +1,20 @@
1
1
  import React from 'react';
2
2
  import userEvent from '@testing-library/user-event';
3
3
  import { render, screen } from '@testing-library/react';
4
- import DeleteIdentifierConfirmationModal from './delete-identifier-confirmation-modal';
4
+ import DeleteIdentifierConfirmationModal from './delete-identifier-confirmation.modal';
5
5
 
6
- describe('DeleteIdentifierConfirmationModal component', () => {
6
+ describe('DeleteIdentifierConfirmationModal', () => {
7
7
  const mockDeleteIdentifier = jest.fn();
8
+ const closeModal = jest.fn();
8
9
  const mockIdentifierName = 'Identifier Name';
9
10
  const mockIdentifierValue = 'Identifier Value';
10
11
 
11
- beforeEach(() => {
12
- jest.clearAllMocks();
13
- });
14
-
15
12
  it('renders the modal and triggers deleteIdentifier function', async () => {
16
13
  const user = userEvent.setup();
17
14
 
18
15
  render(
19
16
  <DeleteIdentifierConfirmationModal
17
+ closeModal={closeModal}
20
18
  deleteIdentifier={mockDeleteIdentifier}
21
19
  identifierName={mockIdentifierName}
22
20
  identifierValue={mockIdentifierValue}
@@ -25,7 +23,7 @@ describe('DeleteIdentifierConfirmationModal component', () => {
25
23
 
26
24
  const cancelButton = screen.getByRole('button', { name: /cancel/i });
27
25
  await user.click(cancelButton);
28
- expect(mockDeleteIdentifier).toHaveBeenCalledWith(false);
26
+ expect(closeModal).toHaveBeenCalledTimes(1);
29
27
 
30
28
  const removeButton = screen.getByRole('button', { name: /remove identifier/i });
31
29
  await user.click(removeButton);
@@ -20,7 +20,6 @@ const EditPatientDetailsButton: React.FC<EditPatientDetailsButtonProps> = ({ pat
20
20
  <button
21
21
  className="cds--overflow-menu-options__btn"
22
22
  role="menuitem"
23
- title={t('editPatientDetails', 'Edit patient details')}
24
23
  data-floating-menu-primary-focus
25
24
  onClick={handleClick}>
26
25
  <span className="cds--overflow-menu-options__option-content">
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { render, screen } from '@testing-library/react';
4
+ import { navigate } from '@openmrs/esm-framework';
5
+ import { mockPatient } from '__mocks__';
6
+ import EditPatientDetailsButton from './edit-patient-details-button.component';
7
+
8
+ const mockNavigate = jest.mocked(navigate);
9
+
10
+ describe('EditPatientDetailsButton', () => {
11
+ const patientUuid = mockPatient.uuid;
12
+
13
+ it('should navigate to the edit page when clicked', async () => {
14
+ const user = userEvent.setup();
15
+
16
+ render(<EditPatientDetailsButton patientUuid={patientUuid} />);
17
+
18
+ const button = screen.getByRole('menuitem');
19
+ await user.click(button);
20
+
21
+ expect(mockNavigate).toHaveBeenCalledWith({ to: expect.stringContaining(`/patient/${patientUuid}/edit`) });
22
+ });
23
+
24
+ it('should call the onTransition function when provided', async () => {
25
+ const user = userEvent.setup();
26
+
27
+ const onTransitionMock = jest.fn();
28
+ render(<EditPatientDetailsButton patientUuid={patientUuid} onTransition={onTransitionMock} />);
29
+
30
+ const button = screen.getByRole('menuitem');
31
+ await user.click(button);
32
+
33
+ expect(onTransitionMock).toHaveBeenCalled();
34
+ });
35
+ });