@openmrs/esm-form-engine-lib 2.1.0-pre.1362

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 (272) hide show
  1. package/.editorconfig +12 -0
  2. package/.eslintignore +2 -0
  3. package/.eslintrc +58 -0
  4. package/.husky/pre-commit +6 -0
  5. package/.husky/pre-push +6 -0
  6. package/.prettierignore +4 -0
  7. package/LICENSE.txt +401 -0
  8. package/README.md +136 -0
  9. package/__mocks__/concepts.mock.json +140 -0
  10. package/__mocks__/forms/afe-forms/component_art.json +38 -0
  11. package/__mocks__/forms/afe-forms/component_preclinic-review.json +38 -0
  12. package/__mocks__/forms/afe-forms/demo_hts-form.json +62 -0
  13. package/__mocks__/forms/afe-forms/form-component.json +38 -0
  14. package/__mocks__/forms/afe-forms/mini-form.json +31 -0
  15. package/__mocks__/forms/afe-forms/nested-form1.json +38 -0
  16. package/__mocks__/forms/afe-forms/nested-form2.json +38 -0
  17. package/__mocks__/forms/afe-forms/test-orders.json +72 -0
  18. package/__mocks__/forms/afe-forms/test-schema-transformer-form.json +88 -0
  19. package/__mocks__/forms/rfe-forms/age-validation-form.json +58 -0
  20. package/__mocks__/forms/rfe-forms/bmi-test-form.json +69 -0
  21. package/__mocks__/forms/rfe-forms/bsa-test-form.json +69 -0
  22. package/__mocks__/forms/rfe-forms/component_art.json +1705 -0
  23. package/__mocks__/forms/rfe-forms/component_preclinic-review.json +480 -0
  24. package/__mocks__/forms/rfe-forms/conditional-answered-form.json +97 -0
  25. package/__mocks__/forms/rfe-forms/conditional-required-form.json +281 -0
  26. package/__mocks__/forms/rfe-forms/demo_hts-form.json +346 -0
  27. package/__mocks__/forms/rfe-forms/edd-test-form.json +88 -0
  28. package/__mocks__/forms/rfe-forms/external_data_source_form.json +43 -0
  29. package/__mocks__/forms/rfe-forms/filter-answer-options-test-form.json +87 -0
  30. package/__mocks__/forms/rfe-forms/form-component.json +43 -0
  31. package/__mocks__/forms/rfe-forms/forms-loader.test.schema.ts +209 -0
  32. package/__mocks__/forms/rfe-forms/historical-expressions-form.json +170 -0
  33. package/__mocks__/forms/rfe-forms/labour_and_delivery_test_form.json +374 -0
  34. package/__mocks__/forms/rfe-forms/mini-form.json +29 -0
  35. package/__mocks__/forms/rfe-forms/mockHistoricalvisitsEncounter.json +89 -0
  36. package/__mocks__/forms/rfe-forms/months-on-art-form.json +90 -0
  37. package/__mocks__/forms/rfe-forms/multi-select-form.json +86 -0
  38. package/__mocks__/forms/rfe-forms/nested-form1.json +43 -0
  39. package/__mocks__/forms/rfe-forms/nested-form2.json +43 -0
  40. package/__mocks__/forms/rfe-forms/next-visit-test-form.json +78 -0
  41. package/__mocks__/forms/rfe-forms/obs-group-test_form.json +137 -0
  42. package/__mocks__/forms/rfe-forms/obs-list-data.ts +37 -0
  43. package/__mocks__/forms/rfe-forms/post-submission-test-form.json +116 -0
  44. package/__mocks__/forms/rfe-forms/reference-by-mapping-form.json +54 -0
  45. package/__mocks__/forms/rfe-forms/required-form.json +50 -0
  46. package/__mocks__/forms/rfe-forms/sample_fields.json +36 -0
  47. package/__mocks__/forms/rfe-forms/test-enrolment-form.json +241 -0
  48. package/__mocks__/forms/rfe-forms/treatment-end-date-test-form.json +121 -0
  49. package/__mocks__/forms/rfe-forms/viral-load-status-form.json +75 -0
  50. package/__mocks__/forms/rfe-forms/zscore-bmi-for-age-form.json +79 -0
  51. package/__mocks__/forms/rfe-forms/zscore-height-for-age-form.json +79 -0
  52. package/__mocks__/forms/rfe-forms/zscore-weight-height-form.json +77 -0
  53. package/__mocks__/packages/hiv/forms/hts_poc/1.0.json +8 -0
  54. package/__mocks__/packages/hiv/forms/hts_poc/1.1.json +91 -0
  55. package/__mocks__/packages/test-forms-registry.ts +12 -0
  56. package/__mocks__/patient.mock.ts +173 -0
  57. package/__mocks__/react-i18next.js +49 -0
  58. package/__mocks__/react-markdown.tsx +5 -0
  59. package/__mocks__/session.mock.ts +117 -0
  60. package/__mocks__/single-spa-react.js +11 -0
  61. package/__mocks__/use-initial-values/encounter.mock.json +963 -0
  62. package/__mocks__/use-initial-values/patient.mock.json +73 -0
  63. package/__mocks__/visit.mock.ts +19 -0
  64. package/dist/openmrs-esm-form-engine-lib.js +1 -0
  65. package/jest.config.js +30 -0
  66. package/package.json +104 -0
  67. package/prettier.config.js +8 -0
  68. package/readme/form-engine.jpeg +0 -0
  69. package/src/adapters/control-adapter.ts +29 -0
  70. package/src/adapters/encounter-datetime-adapter.ts +38 -0
  71. package/src/adapters/encounter-location-adapter.ts +39 -0
  72. package/src/adapters/encounter-provider-adapter.ts +48 -0
  73. package/src/adapters/encounter-role-adapter.ts +54 -0
  74. package/src/adapters/inline-date-adapter.ts +58 -0
  75. package/src/adapters/obs-adapter.ts +280 -0
  76. package/src/adapters/obs-comment-adapter.ts +60 -0
  77. package/src/adapters/orders-adapter.ts +75 -0
  78. package/src/adapters/patient-identifier-adapter.ts +40 -0
  79. package/src/adapters/program-state-adapter.ts +52 -0
  80. package/src/api/index.ts +178 -0
  81. package/src/components/error/error-modal.component.tsx +37 -0
  82. package/src/components/error/error.scss +4 -0
  83. package/src/components/extension/extension-parcel.component.tsx +32 -0
  84. package/src/components/field-label/field-label.component.tsx +32 -0
  85. package/src/components/field-label/field-label.scss +11 -0
  86. package/src/components/group/obs-group.component.tsx +29 -0
  87. package/src/components/group/obs-group.scss +12 -0
  88. package/src/components/inputs/content-switcher/content-switcher.component.tsx +71 -0
  89. package/src/components/inputs/content-switcher/content-switcher.scss +55 -0
  90. package/src/components/inputs/date/date.component.tsx +149 -0
  91. package/src/components/inputs/date/date.scss +36 -0
  92. package/src/components/inputs/file/camera/camera.component.tsx +34 -0
  93. package/src/components/inputs/file/camera/camera.scss +3 -0
  94. package/src/components/inputs/file/file.component.tsx +159 -0
  95. package/src/components/inputs/file/file.scss +101 -0
  96. package/src/components/inputs/fixed-value/fixed-value.component.tsx +19 -0
  97. package/src/components/inputs/markdown/markdown-wrapper.component.tsx +14 -0
  98. package/src/components/inputs/markdown/markdown.component.tsx +8 -0
  99. package/src/components/inputs/multi-select/multi-select.component.tsx +151 -0
  100. package/src/components/inputs/multi-select/multi-select.scss +25 -0
  101. package/src/components/inputs/multi-select/multi-select.test.tsx +90 -0
  102. package/src/components/inputs/number/number.component.tsx +69 -0
  103. package/src/components/inputs/number/number.scss +15 -0
  104. package/src/components/inputs/radio/radio.component.tsx +79 -0
  105. package/src/components/inputs/radio/radio.scss +36 -0
  106. package/src/components/inputs/select/dropdown.component.tsx +73 -0
  107. package/src/components/inputs/select/dropdown.scss +11 -0
  108. package/src/components/inputs/select/dropdown.test.tsx +120 -0
  109. package/src/components/inputs/text/text.component.tsx +65 -0
  110. package/src/components/inputs/text/text.scss +15 -0
  111. package/src/components/inputs/text/text.test.tsx +104 -0
  112. package/src/components/inputs/text-area/text-area.component.tsx +63 -0
  113. package/src/components/inputs/text-area/text-area.scss +11 -0
  114. package/src/components/inputs/toggle/toggle.component.tsx +66 -0
  115. package/src/components/inputs/toggle/toggle.scss +12 -0
  116. package/src/components/inputs/tooltip/tooltip.component.tsx +23 -0
  117. package/src/components/inputs/tooltip/tooltip.scss +8 -0
  118. package/src/components/inputs/ui-select-extended/ui-select-extended.component.tsx +187 -0
  119. package/src/components/inputs/ui-select-extended/ui-select-extended.scss +15 -0
  120. package/src/components/inputs/ui-select-extended/ui-select-extended.test.tsx +211 -0
  121. package/src/components/inputs/unspecified/unspecified.component.tsx +74 -0
  122. package/src/components/inputs/unspecified/unspecified.scss +7 -0
  123. package/src/components/inputs/unspecified/unspecified.test.tsx +95 -0
  124. package/src/components/inputs/workspace-launcher/workspace-launcher.component.tsx +35 -0
  125. package/src/components/inputs/workspace-launcher/workspace-launcher.scss +15 -0
  126. package/src/components/label/label.component.tsx +20 -0
  127. package/src/components/label/label.scss +11 -0
  128. package/src/components/loaders/loader.component.tsx +16 -0
  129. package/src/components/loaders/loader.scss +20 -0
  130. package/src/components/patient-banner/patient-banner.component.tsx +20 -0
  131. package/src/components/patient-banner/patient-banner.scss +12 -0
  132. package/src/components/previous-value-review/previous-value-review.component.tsx +49 -0
  133. package/src/components/previous-value-review/previous-value-review.scss +36 -0
  134. package/src/components/processor-factory/form-processor-factory.component.tsx +127 -0
  135. package/src/components/renderer/custom-hooks-renderer.component.tsx +30 -0
  136. package/src/components/renderer/field/fieldLogic.ts +214 -0
  137. package/src/components/renderer/field/form-field-renderer.component.tsx +281 -0
  138. package/src/components/renderer/field/form-field-renderer.scss +5 -0
  139. package/src/components/renderer/form/form-renderer.component.tsx +89 -0
  140. package/src/components/renderer/form/state.ts +54 -0
  141. package/src/components/renderer/page/page.renderer.component.tsx +50 -0
  142. package/src/components/renderer/page/page.renderer.scss +36 -0
  143. package/src/components/renderer/section/section-renderer.component.tsx +21 -0
  144. package/src/components/renderer/section/section-renderer.scss +19 -0
  145. package/src/components/repeat/helpers.test.ts +29 -0
  146. package/src/components/repeat/helpers.ts +68 -0
  147. package/src/components/repeat/repeat-controls.component.tsx +38 -0
  148. package/src/components/repeat/repeat-controls.scss +7 -0
  149. package/src/components/repeat/repeat.component.tsx +201 -0
  150. package/src/components/repeat/repeat.scss +30 -0
  151. package/src/components/repeat/repeat.test.ts +29 -0
  152. package/src/components/sidebar/sidebar.component.tsx +134 -0
  153. package/src/components/sidebar/sidebar.scss +121 -0
  154. package/src/components/value/value.component.tsx +27 -0
  155. package/src/components/value/value.scss +17 -0
  156. package/src/components/value/view/field-value-view.component.tsx +33 -0
  157. package/src/components/value/view/field-value-view.scss +31 -0
  158. package/src/constants.ts +12 -0
  159. package/src/datasources/concept-data-source.ts +42 -0
  160. package/src/datasources/data-source.ts +23 -0
  161. package/src/datasources/encounter-role-datasource.ts +15 -0
  162. package/src/datasources/historical-data-source.ts +11 -0
  163. package/src/datasources/location-data-source.ts +27 -0
  164. package/src/datasources/provider-datasource.ts +15 -0
  165. package/src/datasources/select-concept-answers-datasource.ts +15 -0
  166. package/src/declarations.d.ts +4 -0
  167. package/src/external-function-context.tsx +8 -0
  168. package/src/form-context.tsx +42 -0
  169. package/src/form-engine.component.tsx +178 -0
  170. package/src/form-engine.scss +140 -0
  171. package/src/form-engine.test.tsx +817 -0
  172. package/src/globals.ts +1 -0
  173. package/src/hooks/useClobData.tsx +21 -0
  174. package/src/hooks/useConcepts.tsx +55 -0
  175. package/src/hooks/useDatasourceDependentValue.ts +16 -0
  176. package/src/hooks/useEncounter.tsx +32 -0
  177. package/src/hooks/useEncounterRole.tsx +15 -0
  178. package/src/hooks/useEvaluateFormFieldExpressions.ts +138 -0
  179. package/src/hooks/useFieldValidationResults.ts +18 -0
  180. package/src/hooks/useFormCollapse.tsx +36 -0
  181. package/src/hooks/useFormFieldValidators.ts +22 -0
  182. package/src/hooks/useFormFieldValueAdapters.ts +24 -0
  183. package/src/hooks/useFormFields.ts +37 -0
  184. package/src/hooks/useFormFieldsMeta.ts +48 -0
  185. package/src/hooks/useFormJson.test.tsx +173 -0
  186. package/src/hooks/useFormJson.tsx +237 -0
  187. package/src/hooks/useFormStateHelpers.ts +50 -0
  188. package/src/hooks/useFormsConfig.tsx +27 -0
  189. package/src/hooks/useInitialValues.ts +38 -0
  190. package/src/hooks/usePatientData.tsx +32 -0
  191. package/src/hooks/usePatientPrograms.ts +32 -0
  192. package/src/hooks/usePostSubmissionActions.test.tsx +42 -0
  193. package/src/hooks/usePostSubmissionActions.ts +31 -0
  194. package/src/hooks/useProcessorDependencies.ts +30 -0
  195. package/src/hooks/useRestMaxResultsCount.ts +5 -0
  196. package/src/hooks/useSystemSetting.ts +36 -0
  197. package/src/hooks/useWorkspaceLayout.ts +29 -0
  198. package/src/index.ts +12 -0
  199. package/src/lifecycle.ts +33 -0
  200. package/src/post-submission-actions/program-enrollment-action.ts +138 -0
  201. package/src/processors/encounter/encounter-form-processor.ts +337 -0
  202. package/src/processors/encounter/encounter-processor-helper.ts +320 -0
  203. package/src/processors/form-processor.ts +41 -0
  204. package/src/provider/form-factory-helper.ts +100 -0
  205. package/src/provider/form-factory-provider.tsx +169 -0
  206. package/src/provider/form-provider.tsx +37 -0
  207. package/src/registry/inbuilt-components/InbuiltPostSubmissionActions.ts +9 -0
  208. package/src/registry/inbuilt-components/control-templates.ts +57 -0
  209. package/src/registry/inbuilt-components/inbuiltControls.ts +99 -0
  210. package/src/registry/inbuilt-components/inbuiltDataSources.ts +41 -0
  211. package/src/registry/inbuilt-components/inbuiltFieldValueAdapters.ts +64 -0
  212. package/src/registry/inbuilt-components/inbuiltTransformers.ts +10 -0
  213. package/src/registry/inbuilt-components/inbuiltValidators.ts +33 -0
  214. package/src/registry/inbuilt-components/template-component-map.ts +28 -0
  215. package/src/registry/registry.test.ts +20 -0
  216. package/src/registry/registry.ts +261 -0
  217. package/src/routes.json +1 -0
  218. package/src/setupI18n.ts +16 -0
  219. package/src/setupTests.ts +5 -0
  220. package/src/transformers/default-schema-transformer.test.ts +155 -0
  221. package/src/transformers/default-schema-transformer.ts +239 -0
  222. package/src/types/domain.ts +129 -0
  223. package/src/types/index.ts +130 -0
  224. package/src/types/schema.ts +238 -0
  225. package/src/typings.d.ts +9 -0
  226. package/src/utils/boolean-utils.ts +25 -0
  227. package/src/utils/common-expression-helpers.ts +503 -0
  228. package/src/utils/common-utils.test.ts +136 -0
  229. package/src/utils/common-utils.ts +55 -0
  230. package/src/utils/error-utils.ts +37 -0
  231. package/src/utils/expression-parser.test.ts +308 -0
  232. package/src/utils/expression-parser.ts +158 -0
  233. package/src/utils/expression-runner.test.ts +387 -0
  234. package/src/utils/expression-runner.ts +219 -0
  235. package/src/utils/form-helper.test.ts +482 -0
  236. package/src/utils/form-helper.ts +210 -0
  237. package/src/utils/form-page-utils.ts +13 -0
  238. package/src/utils/forms-loader.test.ts +323 -0
  239. package/src/utils/forms-loader.ts +306 -0
  240. package/src/utils/post-submission-action-helper.ts +71 -0
  241. package/src/utils/test-utils.ts +54 -0
  242. package/src/utils/zscore-service.ts +59 -0
  243. package/src/validators/conditional-answered-validator.test.ts +61 -0
  244. package/src/validators/conditional-answered-validator.ts +17 -0
  245. package/src/validators/date-validator.test.ts +46 -0
  246. package/src/validators/date-validator.ts +19 -0
  247. package/src/validators/default-value-validator.test.ts +90 -0
  248. package/src/validators/default-value-validator.ts +36 -0
  249. package/src/validators/form-validator.test.ts +188 -0
  250. package/src/validators/form-validator.ts +95 -0
  251. package/src/validators/js-expression-validator.test.ts +118 -0
  252. package/src/validators/js-expression-validator.ts +44 -0
  253. package/src/validators/schema.ts +34 -0
  254. package/src/zscore/bfa_boys_5_above.json +2522 -0
  255. package/src/zscore/bfa_girls_5_above.json +2522 -0
  256. package/src/zscore/hfa_boys_5_above.json +2186 -0
  257. package/src/zscore/hfa_boys_below5.json +22286 -0
  258. package/src/zscore/hfa_girls_5_above.json +2186 -0
  259. package/src/zscore/hfa_girls_below5.json +22286 -0
  260. package/src/zscore/wfl_boys_below5.json +7814 -0
  261. package/src/zscore/wfl_girls_below5.json +7814 -0
  262. package/src/zscore-tests/bmi-age.test.tsx +88 -0
  263. package/src/zscore-tests/height-age.test.tsx +96 -0
  264. package/src/zscore-tests/weight-height.test.tsx +87 -0
  265. package/tools/i18next-parser.config.js +93 -0
  266. package/translations/en.json +47 -0
  267. package/translations/es.json +38 -0
  268. package/translations/fr.json +38 -0
  269. package/translations/km.json +38 -0
  270. package/tsconfig.json +19 -0
  271. package/turbo.json +15 -0
  272. package/webpack.config.js +1 -0
package/src/globals.ts ADDED
@@ -0,0 +1 @@
1
+ export const moduleName = '@openmrs/esm-form-engine-lib';
@@ -0,0 +1,21 @@
1
+ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import { useMemo } from 'react';
3
+ import { type FormSchema, type OpenmrsForm } from '../types';
4
+ import useSWRImmutable from 'swr/immutable';
5
+
6
+ export function useClobData(form: OpenmrsForm) {
7
+ const valueReferenceUuid = useMemo(
8
+ () => form?.resources?.find(({ name }) => name === 'JSON schema').valueReference,
9
+ [form],
10
+ );
11
+ const { data, error } = useSWRImmutable<{ data: FormSchema }, Error>(
12
+ valueReferenceUuid ? `${restBaseUrl}/clobdata/${valueReferenceUuid}` : null,
13
+ openmrsFetch,
14
+ );
15
+
16
+ return {
17
+ clobdata: data?.data,
18
+ clobdataError: error || null,
19
+ isLoadingClobData: (!data && !error) || false,
20
+ };
21
+ }
@@ -0,0 +1,55 @@
1
+ import { useMemo } from 'react';
2
+ import { type FetchResponse, type OpenmrsResource, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
3
+ import useSWRInfinite from 'swr/infinite';
4
+
5
+ type ConceptFetchResponse = FetchResponse<{ results: Array<OpenmrsResource> }>;
6
+
7
+ const conceptRepresentation =
8
+ 'custom:(uuid,display,conceptClass:(uuid,display),answers:(uuid,display),conceptMappings:(conceptReferenceTerm:(conceptSource:(name),code)))';
9
+
10
+ export function useConcepts(references: Set<string>): {
11
+ concepts: Array<OpenmrsResource> | undefined;
12
+ isLoading: boolean;
13
+ error: Error | undefined;
14
+ } {
15
+
16
+ const chunkSize = 100;
17
+ const totalCount = references.size;
18
+ const totalPages = Math.ceil(totalCount / chunkSize);
19
+
20
+ const getUrl = (index, prevPageData: ConceptFetchResponse) => {
21
+ if (index >= totalPages) {
22
+ return null;
23
+ }
24
+
25
+ if (!chunkSize) {
26
+ return null;
27
+ }
28
+
29
+ const start = index * chunkSize;
30
+ const end = start + chunkSize;
31
+ const referenceChunk = Array.from(references).slice(start, end);
32
+ return `${restBaseUrl}/concept?references=${referenceChunk.join(',')}&v=${conceptRepresentation}`;
33
+ };
34
+
35
+ const { data, error, isLoading } = useSWRInfinite<ConceptFetchResponse, Error>(getUrl, openmrsFetch, {
36
+ initialSize: totalPages,
37
+ revalidateIfStale: false,
38
+ revalidateOnFocus: false,
39
+ revalidateOnReconnect: false,
40
+ });
41
+
42
+ const results = useMemo(
43
+ () => ({
44
+ // data?.[0] check is added for tests, as response is undefined in tests
45
+ // hence the returned concepts are [undefined], which breaks the form-helper.ts
46
+ // As it cannot read `uuid` of `undefined`
47
+ concepts: data && data?.[0] ? [].concat(...data.map((res) => res?.data?.results)) : undefined,
48
+ error,
49
+ isLoading,
50
+ }),
51
+ [data, error, isLoading],
52
+ );
53
+
54
+ return results;
55
+ }
@@ -0,0 +1,16 @@
1
+ import { type FormField } from '../types';
2
+ import { useFormProviderContext } from '../provider/form-provider';
3
+ import { useWatch } from 'react-hook-form';
4
+
5
+ const useDataSourceDependentValue = (field: FormField) => {
6
+ const dependentField = field.questionOptions['config']?.referencedField;
7
+ const {
8
+ methods: { control },
9
+ } = useFormProviderContext();
10
+
11
+ const dependentValue = useWatch({ control, name: dependentField, exact: true, disabled: !dependentField });
12
+
13
+ return dependentValue;
14
+ };
15
+
16
+ export default useDataSourceDependentValue;
@@ -0,0 +1,32 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
3
+ import { type FormSchema, type OpenmrsEncounter } from '../types';
4
+ import { encounterRepresentation } from '../constants';
5
+ import { isEmpty } from '../validators/form-validator';
6
+ import isString from 'lodash-es/isString';
7
+
8
+ export function useEncounter(formJson: FormSchema) {
9
+ const [encounter, setEncounter] = useState<OpenmrsEncounter>(null);
10
+ const [isLoading, setIsLoading] = useState(true);
11
+ const [error, setError] = useState(null);
12
+
13
+ useEffect(() => {
14
+ if (!isEmpty(formJson.encounter) && isString(formJson.encounter)) {
15
+ openmrsFetch(`${restBaseUrl}/encounter/${formJson.encounter}?v=${encounterRepresentation}`)
16
+ .then((response) => {
17
+ setEncounter(response.data);
18
+ setIsLoading(false);
19
+ })
20
+ .catch((error) => {
21
+ setError(error);
22
+ });
23
+ } else if (!isEmpty(formJson.encounter)) {
24
+ setEncounter(formJson.encounter as OpenmrsEncounter);
25
+ setIsLoading(false);
26
+ } else {
27
+ setIsLoading(false);
28
+ }
29
+ }, [formJson.encounter]);
30
+
31
+ return { encounter: encounter, error, isLoading };
32
+ }
@@ -0,0 +1,15 @@
1
+ import { type OpenmrsResource, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import useSWRImmutable from 'swr/immutable';
3
+
4
+ export function useEncounterRole() {
5
+ const { data, error, isLoading } = useSWRImmutable<{ data: { results: Array<OpenmrsResource> } }, Error>(
6
+ `${restBaseUrl}/encounterrole?v=custom:(uuid,display,name)`,
7
+ openmrsFetch,
8
+ );
9
+ const clinicalEncounterRole = data?.data.results.find((encounterRole) => encounterRole.name === 'Clinician');
10
+
11
+ if (clinicalEncounterRole) {
12
+ return { encounterRole: clinicalEncounterRole, error, isLoading };
13
+ }
14
+ return { encounterRole: data?.data.results[0], error, isLoading };
15
+ }
@@ -0,0 +1,138 @@
1
+ import { useEffect, useMemo, useState } from 'react';
2
+ import { type FormProcessorContextProps } from '../types';
3
+ import { type FormNode, evaluateExpression } from '../utils/expression-runner';
4
+ import { evalConditionalRequired, evaluateConditionalAnswered, evaluateHide } from '../utils/form-helper';
5
+ import { isTrue } from '../utils/boolean-utils';
6
+ import { isEmpty } from '../validators/form-validator';
7
+ import { type QuestionAnswerOption } from '../types/schema';
8
+
9
+ export const useEvaluateFormFieldExpressions = (
10
+ formValues: Record<string, any>,
11
+ factoryContext: FormProcessorContextProps,
12
+ ) => {
13
+ const { formFields, patient, sessionMode } = factoryContext;
14
+ const [evaluatedFormJson, setEvaluatedFormJson] = useState(factoryContext.formJson);
15
+ const evaluatedFields = useMemo(() => {
16
+ return formFields?.map((field) => {
17
+ const fieldNode: FormNode = { value: field, type: 'field' };
18
+ const runnerContext = {
19
+ patient,
20
+ mode: sessionMode,
21
+ };
22
+ // evaluate hide
23
+ if (field.hide?.hideWhenExpression) {
24
+ const isHidden = evaluateExpression(
25
+ field.hide.hideWhenExpression,
26
+ fieldNode,
27
+ formFields,
28
+ formValues,
29
+ runnerContext,
30
+ );
31
+ field.isHidden = isHidden;
32
+ if (Array.isArray(field.questions)) {
33
+ field.questions.forEach((question) => {
34
+ question.isHidden = isHidden;
35
+ });
36
+ }
37
+ } else {
38
+ field.isHidden = false;
39
+ }
40
+ // evaluate required
41
+ if (typeof field.required === 'object' && field.required.type === 'conditionalRequired') {
42
+ field.isRequired = evalConditionalRequired(field, formFields, formValues);
43
+ } else {
44
+ field.isRequired = isTrue(field.required as string);
45
+ }
46
+ // evaluate disabled
47
+ if (typeof field.disabled === 'object' && field.disabled.disableWhenExpression) {
48
+ field.isDisabled = evaluateExpression(
49
+ field.disabled.disableWhenExpression,
50
+ fieldNode,
51
+ formFields,
52
+ formValues,
53
+ runnerContext,
54
+ );
55
+ } else {
56
+ field.isDisabled = isTrue(field.disabled as string);
57
+ }
58
+ // evaluate conditional answered
59
+ if (field.validators?.some((validator) => validator.type === 'conditionalAnswered')) {
60
+ evaluateConditionalAnswered(field, formFields);
61
+ }
62
+ // evaluate conditional hide for answers
63
+ field.questionOptions.answers
64
+ ?.filter((answer) => !isEmpty(answer.hide?.hideWhenExpression))
65
+ .forEach((answer) => {
66
+ answer.isHidden = evaluateExpression(
67
+ answer.hide.hideWhenExpression,
68
+ fieldNode,
69
+ formFields,
70
+ formValues,
71
+ runnerContext,
72
+ );
73
+ });
74
+ // evaluate conditional disable for answers
75
+ field.questionOptions.answers
76
+ ?.filter((answer: QuestionAnswerOption) => !isEmpty(answer.disable?.disableWhenExpression))
77
+ .forEach((answer: QuestionAnswerOption) => {
78
+ answer.disable.isDisabled = evaluateExpression(
79
+ answer.disable?.disableWhenExpression,
80
+ fieldNode,
81
+ formFields,
82
+ formValues,
83
+ runnerContext,
84
+ );
85
+ });
86
+ // evaluate readonly
87
+ if (typeof field.readonly == 'string' && isNotBooleanString(field.readonly)) {
88
+ field.meta.readonlyExpression = field.readonly;
89
+ field.readonly = evaluateExpression(field.readonly, fieldNode, formFields, formValues, runnerContext);
90
+ }
91
+ // evaluate repeat limit
92
+ const limitExpression = field.questionOptions.repeatOptions?.limitExpression;
93
+ if (field.questionOptions.rendering === 'repeating' && !isEmpty(limitExpression)) {
94
+ field.questionOptions.repeatOptions.limit = evaluateExpression(
95
+ limitExpression,
96
+ fieldNode,
97
+ formFields,
98
+ formValues,
99
+ runnerContext,
100
+ );
101
+ }
102
+ return field;
103
+ });
104
+ }, [formValues, formFields, patient, sessionMode]);
105
+
106
+ useEffect(() => {
107
+ factoryContext.formJson?.pages?.forEach((page) => {
108
+ if (page.hide) {
109
+ evaluateHide({ value: page, type: 'page' }, formFields, formValues, sessionMode, patient, evaluateExpression);
110
+ } else {
111
+ page.isHidden = false;
112
+ }
113
+ page?.sections?.forEach((section) => {
114
+ if (section.hide) {
115
+ evaluateHide(
116
+ { value: section, type: 'section' },
117
+ formFields,
118
+ formValues,
119
+ sessionMode,
120
+ patient,
121
+ evaluateExpression,
122
+ );
123
+ } else {
124
+ section.isHidden = false;
125
+ }
126
+ });
127
+ });
128
+ setEvaluatedFormJson(factoryContext.formJson);
129
+ }, [factoryContext.formJson, formFields]);
130
+
131
+ return { evaluatedFormJson, evaluatedFields };
132
+ };
133
+
134
+ // helpers
135
+
136
+ function isNotBooleanString(str: string) {
137
+ return str !== 'true' && str !== 'false';
138
+ }
@@ -0,0 +1,18 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { type FormField } from '../types';
3
+
4
+ export function useFieldValidationResults(field: FormField) {
5
+ const [errors, setErrors] = useState([]);
6
+ const [warnings, setWarnings] = useState([]);
7
+
8
+ useEffect(() => {
9
+ if (field.meta?.submission?.errors) {
10
+ setErrors(field.meta.submission.errors);
11
+ }
12
+ if (field.meta?.submission?.warnings) {
13
+ setWarnings(field.meta.submission.warnings);
14
+ }
15
+ }, [field.meta?.submission]);
16
+
17
+ return { errors, warnings, setErrors, setWarnings };
18
+ }
@@ -0,0 +1,36 @@
1
+ import type { FormExpanded, SessionMode } from '../types';
2
+ import { useCallback, useEffect, useState } from 'react';
3
+
4
+ export function useFormCollapse(sessionMode: SessionMode) {
5
+ const [isFormExpanded, setIsFormExpanded] = useState<FormExpanded>(undefined);
6
+
7
+ const hideFormCollapseToggle = useCallback(() => {
8
+ const HideFormCollapseToggle = new CustomEvent('openmrs:form-view-embedded', { detail: { value: false } });
9
+ window.dispatchEvent(HideFormCollapseToggle);
10
+ }, []);
11
+
12
+ const handleFormCollapseToggle = useCallback((event) => {
13
+ setIsFormExpanded(event.detail.value);
14
+ }, []);
15
+
16
+ useEffect(() => {
17
+ const FormCollapseToggleVisibleEvent = new CustomEvent('openmrs:form-view-embedded', {
18
+ detail: { value: sessionMode != 'embedded-view' },
19
+ });
20
+
21
+ window.dispatchEvent(FormCollapseToggleVisibleEvent);
22
+ }, [sessionMode]);
23
+
24
+ useEffect(() => {
25
+ window.addEventListener('openmrs:form-collapse-toggle', handleFormCollapseToggle);
26
+
27
+ return () => {
28
+ window.removeEventListener('openmrs:form-collapse-toggle', handleFormCollapseToggle);
29
+ };
30
+ }, []);
31
+
32
+ return {
33
+ isFormExpanded,
34
+ hideFormCollapseToggle,
35
+ };
36
+ }
@@ -0,0 +1,22 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { getRegisteredValidator } from '../registry/registry';
3
+ import { type FormField, type FormFieldValidator } from '../types';
4
+
5
+ export function useFormFieldValidators(fields: FormField[]) {
6
+ const [validators, setValidators] = useState<Record<string, FormFieldValidator>>();
7
+
8
+ useEffect(() => {
9
+ const supportedTypes = new Set<string>();
10
+ fields.forEach((field) => {
11
+ field.validators?.forEach((validator) => supportedTypes.add(validator.type));
12
+ });
13
+ const supportedTypesArray = Array.from(supportedTypes);
14
+ Promise.all(supportedTypesArray.map((type) => getRegisteredValidator(type))).then((validators) => {
15
+ setValidators(
16
+ Object.assign({}, ...validators.map((validator, index) => ({ [supportedTypesArray[index]]: validator }))),
17
+ );
18
+ });
19
+ }, [fields]);
20
+
21
+ return validators;
22
+ }
@@ -0,0 +1,24 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { type FormField } from '../types';
3
+ import { type FormFieldValueAdapter } from '../types';
4
+ import { getRegisteredFieldValueAdapter } from '../registry/registry';
5
+
6
+ export const useFormFieldValueAdapters = (fields: FormField[]) => {
7
+ const [adapters, setAdapters] = useState<Record<string, FormFieldValueAdapter>>({});
8
+
9
+ useEffect(() => {
10
+ const supportedTypes = new Set<string>();
11
+ fields.forEach((field) => {
12
+ supportedTypes.add(field.type);
13
+ });
14
+ const supportedTypesArray = Array.from(supportedTypes);
15
+ Promise.all(supportedTypesArray.map((type) => getRegisteredFieldValueAdapter(type))).then((adapters) => {
16
+ const adaptersByType = supportedTypesArray.map((type, index) => ({
17
+ [type]: adapters[index],
18
+ }));
19
+ setAdapters(Object.assign({}, ...adaptersByType));
20
+ });
21
+ }, [fields]);
22
+
23
+ return adapters;
24
+ };
@@ -0,0 +1,37 @@
1
+ import { useMemo } from 'react';
2
+ import { type FormSchema, type FormField } from '../types';
3
+
4
+ export function useFormFields(form: FormSchema): { formFields: FormField[]; conceptReferences: Set<string> } {
5
+ const [flattenedFields, conceptReferences] = useMemo(() => {
6
+ const flattenedFieldsTemp = [];
7
+ const conceptReferencesTemp = new Set<string>();
8
+ form.pages?.forEach((page) =>
9
+ page.sections?.forEach((section) => {
10
+ section.questions?.forEach((question) => {
11
+ flattenedFieldsTemp.push(question);
12
+ if (question.type == 'obsGroup') {
13
+ question.questions.forEach((groupedField) => {
14
+ groupedField.meta.groupId = question.id;
15
+ flattenedFieldsTemp.push(groupedField);
16
+ });
17
+ }
18
+ });
19
+ }),
20
+ );
21
+ flattenedFieldsTemp.forEach((field) => {
22
+ if (field.questionOptions?.concept) {
23
+ conceptReferencesTemp.add(field.questionOptions.concept);
24
+ }
25
+ if (field.questionOptions?.answers) {
26
+ field.questionOptions.answers.forEach((answer) => {
27
+ if (answer.concept) {
28
+ conceptReferencesTemp.add(answer.concept);
29
+ }
30
+ });
31
+ }
32
+ });
33
+ return [flattenedFieldsTemp, conceptReferencesTemp];
34
+ }, [form]);
35
+
36
+ return { formFields: flattenedFields, conceptReferences };
37
+ }
@@ -0,0 +1,48 @@
1
+ import { type FormField } from '../types';
2
+ import { useMemo } from 'react';
3
+ import { codedTypes } from '../constants';
4
+ import { findConceptByReference } from '../utils/form-helper';
5
+ import { type OpenmrsResource } from '@openmrs/esm-framework';
6
+
7
+ export function useFormFieldsMeta(rawFormFields: FormField[], concepts: OpenmrsResource[]) {
8
+ const formFields = useMemo(() => {
9
+ if (rawFormFields.length && concepts?.length) {
10
+ return rawFormFields.map((field) => {
11
+ const matchingConcept = findConceptByReference(field.questionOptions.concept, concepts);
12
+ field.questionOptions.concept = matchingConcept ? matchingConcept.uuid : field.questionOptions.concept;
13
+ field.label = field.label ? field.label : matchingConcept?.display;
14
+ if (
15
+ codedTypes.includes(field.questionOptions.rendering) &&
16
+ !field.questionOptions.answers?.length &&
17
+ matchingConcept?.conceptClass?.display === 'Question' &&
18
+ matchingConcept?.answers?.length
19
+ ) {
20
+ field.questionOptions.answers = matchingConcept.answers.map((answer) => {
21
+ return {
22
+ concept: answer?.uuid,
23
+ label: answer?.display,
24
+ };
25
+ });
26
+ }
27
+ field.meta = {
28
+ ...(field.meta || {}),
29
+ concept: matchingConcept,
30
+ };
31
+ if (field.questionOptions.answers) {
32
+ field.questionOptions.answers = field.questionOptions.answers.map((answer) => {
33
+ const matchingAnswerConcept = findConceptByReference(answer.concept, concepts);
34
+ return {
35
+ ...answer,
36
+ concept: matchingAnswerConcept ? matchingAnswerConcept.uuid : answer.concept,
37
+ label: answer.label ? answer.label : matchingAnswerConcept?.display,
38
+ };
39
+ });
40
+ }
41
+ return field;
42
+ });
43
+ }
44
+ return [];
45
+ }, [concepts, rawFormFields]);
46
+
47
+ return formFields;
48
+ }
@@ -0,0 +1,173 @@
1
+ import { act, renderHook } from '@testing-library/react';
2
+ import { openmrsFetch } from '@openmrs/esm-framework';
3
+ import { when } from 'jest-when';
4
+ import { useFormJson } from './useFormJson';
5
+ import artComponentBody from '__mocks__/forms/rfe-forms/component_art.json';
6
+ import artComponentSkeleton from '__mocks__/forms/afe-forms/component_art.json';
7
+ import formComponentBody from '__mocks__/forms/rfe-forms/form-component.json';
8
+ import formComponentSkeleton from '__mocks__/forms/afe-forms/form-component.json';
9
+ import miniFormBody from '__mocks__/forms/rfe-forms/mini-form.json';
10
+ import miniFormSkeleton from '__mocks__/forms/afe-forms/mini-form.json';
11
+ import nestedForm1Body from '__mocks__/forms/rfe-forms/nested-form1.json';
12
+ import nestedForm1Skeleton from '__mocks__/forms/afe-forms/nested-form1.json';
13
+ import nestedForm2Body from '__mocks__/forms/rfe-forms/nested-form2.json';
14
+ import nestedForm2Skeleton from '__mocks__/forms/afe-forms/nested-form2.json';
15
+ import preclinicReviewComponentBody from '__mocks__/forms/rfe-forms/component_preclinic-review.json';
16
+ import preclinicReviewComponentSkeleton from '__mocks__/forms/afe-forms/component_preclinic-review.json';
17
+
18
+ const MINI_FORM_NAME = 'Mini Form';
19
+ const MINI_FORM_UUID = '112d73b4-79e5-4be8-b9ae-d0840f00d4cf';
20
+ const MINI_FORM_SCHEMA_VALUE_REF = '6bd7ba90-d2d6-4f81-9f09-7d1f23346a1c';
21
+
22
+ const PARENT_FORM_NAME = 'Nested Form One';
23
+ const PARENT_FORM_UUID = 'af7c1fe6-d669-414e-b066-e9733f0de7a8';
24
+ const PARENT_FORM_SCHEMA_VALUE_REF = '1ad1fccc-d279-46a0-8980-1d91afd6ba67';
25
+
26
+ const SUB_FORM_NAME = 'Nested Form Two';
27
+ const SUB_FORM_UUID = '8304e5ff-6324-4863-ac51-8fcbc6812b13';
28
+ const SUB_FORM_SCHEMA_VALUE_REF = 'ca52a95c-8bb4-4a9f-a0cf-f0df437592da';
29
+
30
+ const COMPONENT_FORM_NAME = 'Form Component';
31
+ const COMPONENT_FORM_UUID = 'af7c1fe6-d669-414e-b066-e9733f0de7b8';
32
+ const COMPONENT_FORM_SCHEMA_VALUE_REF = '74d06044-850f-11ee-b9d1-0242ac120002';
33
+ const COMPONENT_ART = 'component_art';
34
+ const COMPONENT_ART_UUID = '2f063f32-7f8a-11ee-b962-0242ac120002';
35
+ const COMPONENT_ART_SCHEMA_VALUE_REF = '74d06044-850f-11ee-b9d1-0242ac120003';
36
+ const COMPONENT_PRECLINIC_REVIEW = 'component_preclinic-review';
37
+ const COMPONENT_PRECLINIC_REVIEW_UUID = '2f063f32-7f8a-11ee-b962-0242ac120004';
38
+ const COMPONENT_PRECLINIC_REVIEW_SCHEMA_VALUE_REF = '74d06044-850f-11ee-b9d1-0242ac120004';
39
+
40
+ // Base setup
41
+ const mockOpenmrsFetch = openmrsFetch as jest.Mock;
42
+ mockOpenmrsFetch.mockImplementation(jest.fn());
43
+
44
+ // parent form
45
+ when(mockOpenmrsFetch)
46
+ .calledWith(buildPath(PARENT_FORM_NAME))
47
+ .mockResolvedValue({ data: { results: [nestedForm1Skeleton] } });
48
+ when(mockOpenmrsFetch).calledWith(buildPath(PARENT_FORM_UUID)).mockResolvedValue({ data: nestedForm1Skeleton });
49
+ when(mockOpenmrsFetch).calledWith(buildPath(PARENT_FORM_SCHEMA_VALUE_REF)).mockResolvedValue({ data: nestedForm1Body });
50
+
51
+ // sub form
52
+ when(mockOpenmrsFetch)
53
+ .calledWith(buildPath(SUB_FORM_NAME))
54
+ .mockResolvedValue({ data: { results: [nestedForm2Skeleton] } });
55
+ when(mockOpenmrsFetch).calledWith(buildPath(SUB_FORM_UUID)).mockResolvedValue({ data: nestedForm2Skeleton });
56
+ when(mockOpenmrsFetch).calledWith(buildPath(SUB_FORM_SCHEMA_VALUE_REF)).mockResolvedValue({ data: nestedForm2Body });
57
+
58
+ // mini form
59
+ when(mockOpenmrsFetch)
60
+ .calledWith(buildPath(MINI_FORM_NAME))
61
+ .mockResolvedValue({ data: { results: [miniFormSkeleton] } });
62
+ when(mockOpenmrsFetch).calledWith(buildPath(MINI_FORM_UUID)).mockResolvedValue({ data: miniFormSkeleton });
63
+ when(mockOpenmrsFetch).calledWith(buildPath(MINI_FORM_SCHEMA_VALUE_REF)).mockResolvedValue({ data: miniFormBody });
64
+
65
+ // form components
66
+ when(mockOpenmrsFetch)
67
+ .calledWith(buildPath(COMPONENT_FORM_NAME))
68
+ .mockResolvedValue({ data: { results: [formComponentSkeleton] } });
69
+ when(mockOpenmrsFetch).calledWith(buildPath(COMPONENT_FORM_UUID)).mockResolvedValue({ data: formComponentSkeleton });
70
+ when(mockOpenmrsFetch)
71
+ .calledWith(buildPath(COMPONENT_FORM_SCHEMA_VALUE_REF))
72
+ .mockResolvedValue({ data: formComponentBody });
73
+
74
+ when(mockOpenmrsFetch)
75
+ .calledWith(buildPath(COMPONENT_ART))
76
+ .mockResolvedValue({ data: { results: [artComponentSkeleton] } });
77
+ when(mockOpenmrsFetch).calledWith(buildPath(COMPONENT_ART_UUID)).mockResolvedValue({ data: artComponentSkeleton });
78
+ when(mockOpenmrsFetch)
79
+ .calledWith(buildPath(COMPONENT_ART_SCHEMA_VALUE_REF))
80
+ .mockResolvedValue({ data: artComponentBody });
81
+
82
+ when(mockOpenmrsFetch)
83
+ .calledWith(buildPath(COMPONENT_PRECLINIC_REVIEW))
84
+ .mockResolvedValue({ data: { results: [preclinicReviewComponentSkeleton] } });
85
+ when(mockOpenmrsFetch)
86
+ .calledWith(buildPath(COMPONENT_PRECLINIC_REVIEW_UUID))
87
+ .mockResolvedValue({ data: preclinicReviewComponentSkeleton });
88
+ when(mockOpenmrsFetch)
89
+ .calledWith(buildPath(COMPONENT_PRECLINIC_REVIEW_SCHEMA_VALUE_REF))
90
+ .mockResolvedValue({ data: preclinicReviewComponentBody });
91
+
92
+ describe('useFormJson', () => {
93
+ it('should fetch basic form by name', async () => {
94
+ let hook = null;
95
+ await act(async () => {
96
+ hook = renderHook(() => useFormJson(MINI_FORM_NAME, null, null, null));
97
+ });
98
+
99
+ expect(hook.result.current.isLoading).toBe(false);
100
+ expect(hook.result.current.error).toBe(undefined);
101
+ expect(hook.result.current.formJson.name).toBe(MINI_FORM_NAME);
102
+ });
103
+
104
+ it('should fetch basic form by UUID', async () => {
105
+ let hook = null;
106
+ await act(async () => {
107
+ hook = renderHook(() => useFormJson(MINI_FORM_UUID, null, null, null));
108
+ });
109
+
110
+ expect(hook.result.current.isLoading).toBe(false);
111
+ expect(hook.result.current.error).toBe(undefined);
112
+ expect(hook.result.current.formJson.name).toBe(MINI_FORM_NAME);
113
+ });
114
+
115
+ it('should load form with nested subforms', async () => {
116
+ let hook = null;
117
+ await act(async () => {
118
+ hook = renderHook(() => useFormJson(PARENT_FORM_NAME, null, null, null));
119
+ });
120
+
121
+ expect(hook.result.current.isLoading).toBe(false);
122
+ expect(hook.result.current.error).toBe(undefined);
123
+ expect(hook.result.current.formJson.name).toBe(PARENT_FORM_NAME);
124
+
125
+ // verify subforms
126
+ verifyEmbeddedForms(hook.result.current.formJson);
127
+ });
128
+
129
+ it('should load subforms for raw form json', async () => {
130
+ let hook = null;
131
+ await act(async () => {
132
+ hook = renderHook(() => useFormJson(null, nestedForm1Body, null, null));
133
+ });
134
+
135
+ expect(hook.result.current.isLoading).toBe(false);
136
+ expect(hook.result.current.error).toBe(undefined);
137
+ expect(hook.result.current.formJson.name).toBe(PARENT_FORM_NAME);
138
+
139
+ // verify subforms
140
+ verifyEmbeddedForms(hook.result.current.formJson);
141
+ });
142
+
143
+ it('should load form components in combined raw form json', async () => {
144
+ let hook = null;
145
+ await act(async () => {
146
+ hook = renderHook(() => useFormJson(null, formComponentBody, null, null));
147
+ });
148
+ expect(hook.result.current.isLoading).toBe(false);
149
+ expect(hook.result.current.error).toBe(undefined);
150
+ expect(hook.result.current.formJson.name).toBe(COMPONENT_FORM_NAME);
151
+
152
+ // verify form components have been loaded
153
+ verifyFormComponents(hook.result.current.formJson);
154
+ });
155
+ });
156
+
157
+ function buildPath(path: string) {
158
+ return when((url: string) => url.includes(path));
159
+ }
160
+
161
+ function verifyEmbeddedForms(formJson) {
162
+ // assert that the nestedForm2's (level one subform) pages have been aligned with the parent because they share the same encounterType
163
+ expect(formJson.pages.length).toBe(3);
164
+ // the mini form (it's not flattened into the parent form because it has a different encounterType)
165
+ const nestedSubform = formJson.pages[2].subform.form;
166
+ expect(nestedSubform.name).toBe(MINI_FORM_NAME);
167
+ expect(nestedSubform.pages.length).toBe(1);
168
+ }
169
+
170
+ function verifyFormComponents(formJson) {
171
+ // assert that alias has been replaced with the actual component
172
+ expect(formJson.pages.length).toBe(2);
173
+ }