@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.
- package/.editorconfig +12 -0
- package/.eslintignore +2 -0
- package/.eslintrc +58 -0
- package/.husky/pre-commit +6 -0
- package/.husky/pre-push +6 -0
- package/.prettierignore +4 -0
- package/LICENSE.txt +401 -0
- package/README.md +136 -0
- package/__mocks__/concepts.mock.json +140 -0
- package/__mocks__/forms/afe-forms/component_art.json +38 -0
- package/__mocks__/forms/afe-forms/component_preclinic-review.json +38 -0
- package/__mocks__/forms/afe-forms/demo_hts-form.json +62 -0
- package/__mocks__/forms/afe-forms/form-component.json +38 -0
- package/__mocks__/forms/afe-forms/mini-form.json +31 -0
- package/__mocks__/forms/afe-forms/nested-form1.json +38 -0
- package/__mocks__/forms/afe-forms/nested-form2.json +38 -0
- package/__mocks__/forms/afe-forms/test-orders.json +72 -0
- package/__mocks__/forms/afe-forms/test-schema-transformer-form.json +88 -0
- package/__mocks__/forms/rfe-forms/age-validation-form.json +58 -0
- package/__mocks__/forms/rfe-forms/bmi-test-form.json +69 -0
- package/__mocks__/forms/rfe-forms/bsa-test-form.json +69 -0
- package/__mocks__/forms/rfe-forms/component_art.json +1705 -0
- package/__mocks__/forms/rfe-forms/component_preclinic-review.json +480 -0
- package/__mocks__/forms/rfe-forms/conditional-answered-form.json +97 -0
- package/__mocks__/forms/rfe-forms/conditional-required-form.json +281 -0
- package/__mocks__/forms/rfe-forms/demo_hts-form.json +346 -0
- package/__mocks__/forms/rfe-forms/edd-test-form.json +88 -0
- package/__mocks__/forms/rfe-forms/external_data_source_form.json +43 -0
- package/__mocks__/forms/rfe-forms/filter-answer-options-test-form.json +87 -0
- package/__mocks__/forms/rfe-forms/form-component.json +43 -0
- package/__mocks__/forms/rfe-forms/forms-loader.test.schema.ts +209 -0
- package/__mocks__/forms/rfe-forms/historical-expressions-form.json +170 -0
- package/__mocks__/forms/rfe-forms/labour_and_delivery_test_form.json +374 -0
- package/__mocks__/forms/rfe-forms/mini-form.json +29 -0
- package/__mocks__/forms/rfe-forms/mockHistoricalvisitsEncounter.json +89 -0
- package/__mocks__/forms/rfe-forms/months-on-art-form.json +90 -0
- package/__mocks__/forms/rfe-forms/multi-select-form.json +86 -0
- package/__mocks__/forms/rfe-forms/nested-form1.json +43 -0
- package/__mocks__/forms/rfe-forms/nested-form2.json +43 -0
- package/__mocks__/forms/rfe-forms/next-visit-test-form.json +78 -0
- package/__mocks__/forms/rfe-forms/obs-group-test_form.json +137 -0
- package/__mocks__/forms/rfe-forms/obs-list-data.ts +37 -0
- package/__mocks__/forms/rfe-forms/post-submission-test-form.json +116 -0
- package/__mocks__/forms/rfe-forms/reference-by-mapping-form.json +54 -0
- package/__mocks__/forms/rfe-forms/required-form.json +50 -0
- package/__mocks__/forms/rfe-forms/sample_fields.json +36 -0
- package/__mocks__/forms/rfe-forms/test-enrolment-form.json +241 -0
- package/__mocks__/forms/rfe-forms/treatment-end-date-test-form.json +121 -0
- package/__mocks__/forms/rfe-forms/viral-load-status-form.json +75 -0
- package/__mocks__/forms/rfe-forms/zscore-bmi-for-age-form.json +79 -0
- package/__mocks__/forms/rfe-forms/zscore-height-for-age-form.json +79 -0
- package/__mocks__/forms/rfe-forms/zscore-weight-height-form.json +77 -0
- package/__mocks__/packages/hiv/forms/hts_poc/1.0.json +8 -0
- package/__mocks__/packages/hiv/forms/hts_poc/1.1.json +91 -0
- package/__mocks__/packages/test-forms-registry.ts +12 -0
- package/__mocks__/patient.mock.ts +173 -0
- package/__mocks__/react-i18next.js +49 -0
- package/__mocks__/react-markdown.tsx +5 -0
- package/__mocks__/session.mock.ts +117 -0
- package/__mocks__/single-spa-react.js +11 -0
- package/__mocks__/use-initial-values/encounter.mock.json +963 -0
- package/__mocks__/use-initial-values/patient.mock.json +73 -0
- package/__mocks__/visit.mock.ts +19 -0
- package/dist/openmrs-esm-form-engine-lib.js +1 -0
- package/jest.config.js +30 -0
- package/package.json +104 -0
- package/prettier.config.js +8 -0
- package/readme/form-engine.jpeg +0 -0
- package/src/adapters/control-adapter.ts +29 -0
- package/src/adapters/encounter-datetime-adapter.ts +38 -0
- package/src/adapters/encounter-location-adapter.ts +39 -0
- package/src/adapters/encounter-provider-adapter.ts +48 -0
- package/src/adapters/encounter-role-adapter.ts +54 -0
- package/src/adapters/inline-date-adapter.ts +58 -0
- package/src/adapters/obs-adapter.ts +280 -0
- package/src/adapters/obs-comment-adapter.ts +60 -0
- package/src/adapters/orders-adapter.ts +75 -0
- package/src/adapters/patient-identifier-adapter.ts +40 -0
- package/src/adapters/program-state-adapter.ts +52 -0
- package/src/api/index.ts +178 -0
- package/src/components/error/error-modal.component.tsx +37 -0
- package/src/components/error/error.scss +4 -0
- package/src/components/extension/extension-parcel.component.tsx +32 -0
- package/src/components/field-label/field-label.component.tsx +32 -0
- package/src/components/field-label/field-label.scss +11 -0
- package/src/components/group/obs-group.component.tsx +29 -0
- package/src/components/group/obs-group.scss +12 -0
- package/src/components/inputs/content-switcher/content-switcher.component.tsx +71 -0
- package/src/components/inputs/content-switcher/content-switcher.scss +55 -0
- package/src/components/inputs/date/date.component.tsx +149 -0
- package/src/components/inputs/date/date.scss +36 -0
- package/src/components/inputs/file/camera/camera.component.tsx +34 -0
- package/src/components/inputs/file/camera/camera.scss +3 -0
- package/src/components/inputs/file/file.component.tsx +159 -0
- package/src/components/inputs/file/file.scss +101 -0
- package/src/components/inputs/fixed-value/fixed-value.component.tsx +19 -0
- package/src/components/inputs/markdown/markdown-wrapper.component.tsx +14 -0
- package/src/components/inputs/markdown/markdown.component.tsx +8 -0
- package/src/components/inputs/multi-select/multi-select.component.tsx +151 -0
- package/src/components/inputs/multi-select/multi-select.scss +25 -0
- package/src/components/inputs/multi-select/multi-select.test.tsx +90 -0
- package/src/components/inputs/number/number.component.tsx +69 -0
- package/src/components/inputs/number/number.scss +15 -0
- package/src/components/inputs/radio/radio.component.tsx +79 -0
- package/src/components/inputs/radio/radio.scss +36 -0
- package/src/components/inputs/select/dropdown.component.tsx +73 -0
- package/src/components/inputs/select/dropdown.scss +11 -0
- package/src/components/inputs/select/dropdown.test.tsx +120 -0
- package/src/components/inputs/text/text.component.tsx +65 -0
- package/src/components/inputs/text/text.scss +15 -0
- package/src/components/inputs/text/text.test.tsx +104 -0
- package/src/components/inputs/text-area/text-area.component.tsx +63 -0
- package/src/components/inputs/text-area/text-area.scss +11 -0
- package/src/components/inputs/toggle/toggle.component.tsx +66 -0
- package/src/components/inputs/toggle/toggle.scss +12 -0
- package/src/components/inputs/tooltip/tooltip.component.tsx +23 -0
- package/src/components/inputs/tooltip/tooltip.scss +8 -0
- package/src/components/inputs/ui-select-extended/ui-select-extended.component.tsx +187 -0
- package/src/components/inputs/ui-select-extended/ui-select-extended.scss +15 -0
- package/src/components/inputs/ui-select-extended/ui-select-extended.test.tsx +211 -0
- package/src/components/inputs/unspecified/unspecified.component.tsx +74 -0
- package/src/components/inputs/unspecified/unspecified.scss +7 -0
- package/src/components/inputs/unspecified/unspecified.test.tsx +95 -0
- package/src/components/inputs/workspace-launcher/workspace-launcher.component.tsx +35 -0
- package/src/components/inputs/workspace-launcher/workspace-launcher.scss +15 -0
- package/src/components/label/label.component.tsx +20 -0
- package/src/components/label/label.scss +11 -0
- package/src/components/loaders/loader.component.tsx +16 -0
- package/src/components/loaders/loader.scss +20 -0
- package/src/components/patient-banner/patient-banner.component.tsx +20 -0
- package/src/components/patient-banner/patient-banner.scss +12 -0
- package/src/components/previous-value-review/previous-value-review.component.tsx +49 -0
- package/src/components/previous-value-review/previous-value-review.scss +36 -0
- package/src/components/processor-factory/form-processor-factory.component.tsx +127 -0
- package/src/components/renderer/custom-hooks-renderer.component.tsx +30 -0
- package/src/components/renderer/field/fieldLogic.ts +214 -0
- package/src/components/renderer/field/form-field-renderer.component.tsx +281 -0
- package/src/components/renderer/field/form-field-renderer.scss +5 -0
- package/src/components/renderer/form/form-renderer.component.tsx +89 -0
- package/src/components/renderer/form/state.ts +54 -0
- package/src/components/renderer/page/page.renderer.component.tsx +50 -0
- package/src/components/renderer/page/page.renderer.scss +36 -0
- package/src/components/renderer/section/section-renderer.component.tsx +21 -0
- package/src/components/renderer/section/section-renderer.scss +19 -0
- package/src/components/repeat/helpers.test.ts +29 -0
- package/src/components/repeat/helpers.ts +68 -0
- package/src/components/repeat/repeat-controls.component.tsx +38 -0
- package/src/components/repeat/repeat-controls.scss +7 -0
- package/src/components/repeat/repeat.component.tsx +201 -0
- package/src/components/repeat/repeat.scss +30 -0
- package/src/components/repeat/repeat.test.ts +29 -0
- package/src/components/sidebar/sidebar.component.tsx +134 -0
- package/src/components/sidebar/sidebar.scss +121 -0
- package/src/components/value/value.component.tsx +27 -0
- package/src/components/value/value.scss +17 -0
- package/src/components/value/view/field-value-view.component.tsx +33 -0
- package/src/components/value/view/field-value-view.scss +31 -0
- package/src/constants.ts +12 -0
- package/src/datasources/concept-data-source.ts +42 -0
- package/src/datasources/data-source.ts +23 -0
- package/src/datasources/encounter-role-datasource.ts +15 -0
- package/src/datasources/historical-data-source.ts +11 -0
- package/src/datasources/location-data-source.ts +27 -0
- package/src/datasources/provider-datasource.ts +15 -0
- package/src/datasources/select-concept-answers-datasource.ts +15 -0
- package/src/declarations.d.ts +4 -0
- package/src/external-function-context.tsx +8 -0
- package/src/form-context.tsx +42 -0
- package/src/form-engine.component.tsx +178 -0
- package/src/form-engine.scss +140 -0
- package/src/form-engine.test.tsx +817 -0
- package/src/globals.ts +1 -0
- package/src/hooks/useClobData.tsx +21 -0
- package/src/hooks/useConcepts.tsx +55 -0
- package/src/hooks/useDatasourceDependentValue.ts +16 -0
- package/src/hooks/useEncounter.tsx +32 -0
- package/src/hooks/useEncounterRole.tsx +15 -0
- package/src/hooks/useEvaluateFormFieldExpressions.ts +138 -0
- package/src/hooks/useFieldValidationResults.ts +18 -0
- package/src/hooks/useFormCollapse.tsx +36 -0
- package/src/hooks/useFormFieldValidators.ts +22 -0
- package/src/hooks/useFormFieldValueAdapters.ts +24 -0
- package/src/hooks/useFormFields.ts +37 -0
- package/src/hooks/useFormFieldsMeta.ts +48 -0
- package/src/hooks/useFormJson.test.tsx +173 -0
- package/src/hooks/useFormJson.tsx +237 -0
- package/src/hooks/useFormStateHelpers.ts +50 -0
- package/src/hooks/useFormsConfig.tsx +27 -0
- package/src/hooks/useInitialValues.ts +38 -0
- package/src/hooks/usePatientData.tsx +32 -0
- package/src/hooks/usePatientPrograms.ts +32 -0
- package/src/hooks/usePostSubmissionActions.test.tsx +42 -0
- package/src/hooks/usePostSubmissionActions.ts +31 -0
- package/src/hooks/useProcessorDependencies.ts +30 -0
- package/src/hooks/useRestMaxResultsCount.ts +5 -0
- package/src/hooks/useSystemSetting.ts +36 -0
- package/src/hooks/useWorkspaceLayout.ts +29 -0
- package/src/index.ts +12 -0
- package/src/lifecycle.ts +33 -0
- package/src/post-submission-actions/program-enrollment-action.ts +138 -0
- package/src/processors/encounter/encounter-form-processor.ts +337 -0
- package/src/processors/encounter/encounter-processor-helper.ts +320 -0
- package/src/processors/form-processor.ts +41 -0
- package/src/provider/form-factory-helper.ts +100 -0
- package/src/provider/form-factory-provider.tsx +169 -0
- package/src/provider/form-provider.tsx +37 -0
- package/src/registry/inbuilt-components/InbuiltPostSubmissionActions.ts +9 -0
- package/src/registry/inbuilt-components/control-templates.ts +57 -0
- package/src/registry/inbuilt-components/inbuiltControls.ts +99 -0
- package/src/registry/inbuilt-components/inbuiltDataSources.ts +41 -0
- package/src/registry/inbuilt-components/inbuiltFieldValueAdapters.ts +64 -0
- package/src/registry/inbuilt-components/inbuiltTransformers.ts +10 -0
- package/src/registry/inbuilt-components/inbuiltValidators.ts +33 -0
- package/src/registry/inbuilt-components/template-component-map.ts +28 -0
- package/src/registry/registry.test.ts +20 -0
- package/src/registry/registry.ts +261 -0
- package/src/routes.json +1 -0
- package/src/setupI18n.ts +16 -0
- package/src/setupTests.ts +5 -0
- package/src/transformers/default-schema-transformer.test.ts +155 -0
- package/src/transformers/default-schema-transformer.ts +239 -0
- package/src/types/domain.ts +129 -0
- package/src/types/index.ts +130 -0
- package/src/types/schema.ts +238 -0
- package/src/typings.d.ts +9 -0
- package/src/utils/boolean-utils.ts +25 -0
- package/src/utils/common-expression-helpers.ts +503 -0
- package/src/utils/common-utils.test.ts +136 -0
- package/src/utils/common-utils.ts +55 -0
- package/src/utils/error-utils.ts +37 -0
- package/src/utils/expression-parser.test.ts +308 -0
- package/src/utils/expression-parser.ts +158 -0
- package/src/utils/expression-runner.test.ts +387 -0
- package/src/utils/expression-runner.ts +219 -0
- package/src/utils/form-helper.test.ts +482 -0
- package/src/utils/form-helper.ts +210 -0
- package/src/utils/form-page-utils.ts +13 -0
- package/src/utils/forms-loader.test.ts +323 -0
- package/src/utils/forms-loader.ts +306 -0
- package/src/utils/post-submission-action-helper.ts +71 -0
- package/src/utils/test-utils.ts +54 -0
- package/src/utils/zscore-service.ts +59 -0
- package/src/validators/conditional-answered-validator.test.ts +61 -0
- package/src/validators/conditional-answered-validator.ts +17 -0
- package/src/validators/date-validator.test.ts +46 -0
- package/src/validators/date-validator.ts +19 -0
- package/src/validators/default-value-validator.test.ts +90 -0
- package/src/validators/default-value-validator.ts +36 -0
- package/src/validators/form-validator.test.ts +188 -0
- package/src/validators/form-validator.ts +95 -0
- package/src/validators/js-expression-validator.test.ts +118 -0
- package/src/validators/js-expression-validator.ts +44 -0
- package/src/validators/schema.ts +34 -0
- package/src/zscore/bfa_boys_5_above.json +2522 -0
- package/src/zscore/bfa_girls_5_above.json +2522 -0
- package/src/zscore/hfa_boys_5_above.json +2186 -0
- package/src/zscore/hfa_boys_below5.json +22286 -0
- package/src/zscore/hfa_girls_5_above.json +2186 -0
- package/src/zscore/hfa_girls_below5.json +22286 -0
- package/src/zscore/wfl_boys_below5.json +7814 -0
- package/src/zscore/wfl_girls_below5.json +7814 -0
- package/src/zscore-tests/bmi-age.test.tsx +88 -0
- package/src/zscore-tests/height-age.test.tsx +96 -0
- package/src/zscore-tests/weight-height.test.tsx +87 -0
- package/tools/i18next-parser.config.js +93 -0
- package/translations/en.json +47 -0
- package/translations/es.json +38 -0
- package/translations/fr.json +38 -0
- package/translations/km.json +38 -0
- package/tsconfig.json +19 -0
- package/turbo.json +15 -0
- 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
|
+
}
|