@olaboot/esm-form-engine-lib 4.1.0
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/LICENSE.txt +401 -0
- package/README.md +202 -0
- package/dist/adapters/control-adapter.d.ts +3 -0
- package/dist/adapters/control-adapter.d.ts.map +1 -0
- package/dist/adapters/control-adapter.js +17 -0
- package/dist/adapters/encounter-datetime-adapter.d.ts +3 -0
- package/dist/adapters/encounter-datetime-adapter.d.ts.map +1 -0
- package/dist/adapters/encounter-datetime-adapter.js +26 -0
- package/dist/adapters/encounter-diagnosis-adapter.d.ts +10 -0
- package/dist/adapters/encounter-diagnosis-adapter.d.ts.map +1 -0
- package/dist/adapters/encounter-diagnosis-adapter.js +89 -0
- package/dist/adapters/encounter-diagnosis-adapter.test.js +217 -0
- package/dist/adapters/encounter-location-adapter.d.ts +3 -0
- package/dist/adapters/encounter-location-adapter.d.ts.map +1 -0
- package/dist/adapters/encounter-location-adapter.js +25 -0
- package/dist/adapters/encounter-provider-adapter.d.ts +3 -0
- package/dist/adapters/encounter-provider-adapter.d.ts.map +1 -0
- package/dist/adapters/encounter-provider-adapter.js +34 -0
- package/dist/adapters/encounter-role-adapter.d.ts +3 -0
- package/dist/adapters/encounter-role-adapter.d.ts.map +1 -0
- package/dist/adapters/encounter-role-adapter.js +40 -0
- package/dist/adapters/inline-date-adapter.d.ts +3 -0
- package/dist/adapters/inline-date-adapter.d.ts.map +1 -0
- package/dist/adapters/inline-date-adapter.js +56 -0
- package/dist/adapters/obs-adapter.d.ts +29 -0
- package/dist/adapters/obs-adapter.d.ts.map +1 -0
- package/dist/adapters/obs-adapter.js +284 -0
- package/dist/adapters/obs-adapter.test.js +1443 -0
- package/dist/adapters/obs-comment-adapter.d.ts +4 -0
- package/dist/adapters/obs-comment-adapter.d.ts.map +1 -0
- package/dist/adapters/obs-comment-adapter.js +52 -0
- package/dist/adapters/orders-adapter.d.ts +4 -0
- package/dist/adapters/orders-adapter.d.ts.map +1 -0
- package/dist/adapters/orders-adapter.js +64 -0
- package/dist/adapters/patient-identifier-adapter.d.ts +3 -0
- package/dist/adapters/patient-identifier-adapter.d.ts.map +1 -0
- package/dist/adapters/patient-identifier-adapter.js +40 -0
- package/dist/adapters/person-attribute-adapter.d.ts +3 -0
- package/dist/adapters/person-attribute-adapter.d.ts.map +1 -0
- package/dist/adapters/person-attribute-adapter.js +39 -0
- package/dist/adapters/person-attribute-adapter.test.js +146 -0
- package/dist/adapters/program-state-adapter.d.ts +3 -0
- package/dist/adapters/program-state-adapter.d.ts.map +1 -0
- package/dist/adapters/program-state-adapter.js +41 -0
- package/dist/adapters/program-state-adapter.test.js +411 -0
- package/dist/api/index.d.ts +27 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +170 -0
- package/dist/components/error/error-modal.component.d.ts +7 -0
- package/dist/components/error/error-modal.component.d.ts.map +1 -0
- package/dist/components/error/error-modal.component.js +31 -0
- package/dist/components/error/error.scss +4 -0
- package/dist/components/extension/extension-parcel.component.d.ts +5 -0
- package/dist/components/extension/extension-parcel.component.d.ts.map +1 -0
- package/dist/components/extension/extension-parcel.component.js +35 -0
- package/dist/components/field-label/field-label.component.d.ts +12 -0
- package/dist/components/field-label/field-label.component.d.ts.map +1 -0
- package/dist/components/field-label/field-label.component.js +33 -0
- package/dist/components/field-label/field-label.scss +15 -0
- package/dist/components/group/obs-group.component.d.ts +5 -0
- package/dist/components/group/obs-group.component.d.ts.map +1 -0
- package/dist/components/group/obs-group.component.js +48 -0
- package/dist/components/group/obs-group.scss +16 -0
- package/dist/components/inputs/content-switcher/content-switcher.component.d.ts +5 -0
- package/dist/components/inputs/content-switcher/content-switcher.component.d.ts.map +1 -0
- package/dist/components/inputs/content-switcher/content-switcher.component.js +70 -0
- package/dist/components/inputs/content-switcher/content-switcher.scss +55 -0
- package/dist/components/inputs/date/date.component.d.ts +5 -0
- package/dist/components/inputs/date/date.component.d.ts.map +1 -0
- package/dist/components/inputs/date/date.component.js +125 -0
- package/dist/components/inputs/date/date.scss +37 -0
- package/dist/components/inputs/date/date.test.js +85 -0
- package/dist/components/inputs/file/file-thumbnail.component.d.ts +10 -0
- package/dist/components/inputs/file/file-thumbnail.component.d.ts.map +1 -0
- package/dist/components/inputs/file/file-thumbnail.component.js +51 -0
- package/dist/components/inputs/file/file-thumbnail.scss +42 -0
- package/dist/components/inputs/file/file.component.d.ts +5 -0
- package/dist/components/inputs/file/file.component.d.ts.map +1 -0
- package/dist/components/inputs/file/file.component.js +90 -0
- package/dist/components/inputs/file/file.scss +21 -0
- package/dist/components/inputs/fixed-value/fixed-value.component.d.ts +5 -0
- package/dist/components/inputs/fixed-value/fixed-value.component.d.ts.map +1 -0
- package/dist/components/inputs/fixed-value/fixed-value.component.js +14 -0
- package/dist/components/inputs/markdown/markdown-wrapper.component.d.ts +6 -0
- package/dist/components/inputs/markdown/markdown-wrapper.component.d.ts.map +1 -0
- package/dist/components/inputs/markdown/markdown-wrapper.component.js +20 -0
- package/dist/components/inputs/markdown/markdown.component.d.ts +5 -0
- package/dist/components/inputs/markdown/markdown.component.d.ts.map +1 -0
- package/dist/components/inputs/markdown/markdown.component.js +26 -0
- package/dist/components/inputs/multi-select/multi-select.component.d.ts +5 -0
- package/dist/components/inputs/multi-select/multi-select.component.d.ts.map +1 -0
- package/dist/components/inputs/multi-select/multi-select.component.js +134 -0
- package/dist/components/inputs/multi-select/multi-select.scss +25 -0
- package/dist/components/inputs/multi-select/multi-select.test.js +106 -0
- package/dist/components/inputs/number/number.component.d.ts +5 -0
- package/dist/components/inputs/number/number.component.d.ts.map +1 -0
- package/dist/components/inputs/number/number.component.js +77 -0
- package/dist/components/inputs/number/number.scss +15 -0
- package/dist/components/inputs/number/number.test.js +98 -0
- package/dist/components/inputs/radio/radio.component.d.ts +5 -0
- package/dist/components/inputs/radio/radio.component.d.ts.map +1 -0
- package/dist/components/inputs/radio/radio.component.js +69 -0
- package/dist/components/inputs/radio/radio.scss +36 -0
- package/dist/components/inputs/radio/radio.test.js +190 -0
- package/dist/components/inputs/select/dropdown.component.d.ts +5 -0
- package/dist/components/inputs/select/dropdown.component.d.ts.map +1 -0
- package/dist/components/inputs/select/dropdown.component.js +82 -0
- package/dist/components/inputs/select/dropdown.scss +11 -0
- package/dist/components/inputs/select/dropdown.test.js +157 -0
- package/dist/components/inputs/text/text.component.d.ts +5 -0
- package/dist/components/inputs/text/text.component.d.ts.map +1 -0
- package/dist/components/inputs/text/text.component.js +62 -0
- package/dist/components/inputs/text/text.scss +15 -0
- package/dist/components/inputs/text/text.test.js +184 -0
- package/dist/components/inputs/text-area/text-area.component.d.ts +5 -0
- package/dist/components/inputs/text-area/text-area.component.d.ts.map +1 -0
- package/dist/components/inputs/text-area/text-area.component.js +60 -0
- package/dist/components/inputs/text-area/text-area.scss +11 -0
- package/dist/components/inputs/toggle/toggle.component.d.ts +5 -0
- package/dist/components/inputs/toggle/toggle.component.d.ts.map +1 -0
- package/dist/components/inputs/toggle/toggle.component.js +57 -0
- package/dist/components/inputs/toggle/toggle.scss +12 -0
- package/dist/components/inputs/ui-select-extended/ui-select-extended.component.d.ts +5 -0
- package/dist/components/inputs/ui-select-extended/ui-select-extended.component.d.ts.map +1 -0
- package/dist/components/inputs/ui-select-extended/ui-select-extended.component.js +203 -0
- package/dist/components/inputs/ui-select-extended/ui-select-extended.scss +19 -0
- package/dist/components/inputs/ui-select-extended/ui-select-extended.test.js +250 -0
- package/dist/components/inputs/unspecified/unspecified.component.d.ts +11 -0
- package/dist/components/inputs/unspecified/unspecified.component.d.ts.map +1 -0
- package/dist/components/inputs/unspecified/unspecified.component.js +65 -0
- package/dist/components/inputs/unspecified/unspecified.scss +7 -0
- package/dist/components/inputs/unspecified/unspecified.test.js +152 -0
- package/dist/components/inputs/workspace-launcher/workspace-launcher.component.d.ts +5 -0
- package/dist/components/inputs/workspace-launcher/workspace-launcher.component.d.ts.map +1 -0
- package/dist/components/inputs/workspace-launcher/workspace-launcher.component.js +64 -0
- package/dist/components/inputs/workspace-launcher/workspace-launcher.scss +21 -0
- package/dist/components/inputs/workspace-launcher/workspace-launcher.test.js +174 -0
- package/dist/components/label/label.component.d.ts +8 -0
- package/dist/components/label/label.component.d.ts.map +1 -0
- package/dist/components/label/label.component.js +15 -0
- package/dist/components/label/label.scss +15 -0
- package/dist/components/loaders/loader.component.d.ts +4 -0
- package/dist/components/loaders/loader.component.d.ts.map +1 -0
- package/dist/components/loaders/loader.component.js +14 -0
- package/dist/components/loaders/loader.scss +20 -0
- package/dist/components/patient-banner/patient-banner.component.d.ts +7 -0
- package/dist/components/patient-banner/patient-banner.component.d.ts.map +1 -0
- package/dist/components/patient-banner/patient-banner.component.js +16 -0
- package/dist/components/patient-banner/patient-banner.scss +12 -0
- package/dist/components/previous-value-review/previous-value-review.component.d.ts +12 -0
- package/dist/components/previous-value-review/previous-value-review.component.d.ts.map +1 -0
- package/dist/components/previous-value-review/previous-value-review.component.js +29 -0
- package/dist/components/previous-value-review/previous-value-review.scss +36 -0
- package/dist/components/processor-factory/form-processor-factory.component.d.ts +10 -0
- package/dist/components/processor-factory/form-processor-factory.component.d.ts.map +1 -0
- package/dist/components/processor-factory/form-processor-factory.component.js +114 -0
- package/dist/components/renderer/custom-hooks-renderer.component.d.ts +13 -0
- package/dist/components/renderer/custom-hooks-renderer.component.d.ts.map +1 -0
- package/dist/components/renderer/custom-hooks-renderer.component.js +13 -0
- package/dist/components/renderer/field/fieldLogic.d.ts +16 -0
- package/dist/components/renderer/field/fieldLogic.d.ts.map +1 -0
- package/dist/components/renderer/field/fieldLogic.js +238 -0
- package/dist/components/renderer/field/fieldLogic.test.js +140 -0
- package/dist/components/renderer/field/fieldRenderUtils.d.ts +9 -0
- package/dist/components/renderer/field/fieldRenderUtils.d.ts.map +1 -0
- package/dist/components/renderer/field/fieldRenderUtils.js +8 -0
- package/dist/components/renderer/field/fieldRenderUtils.test.js +51 -0
- package/dist/components/renderer/field/form-field-renderer.component.d.ts +19 -0
- package/dist/components/renderer/field/form-field-renderer.component.d.ts.map +1 -0
- package/dist/components/renderer/field/form-field-renderer.component.js +214 -0
- package/dist/components/renderer/field/form-field-renderer.scss +5 -0
- package/dist/components/renderer/form/form-renderer.component.d.ts +10 -0
- package/dist/components/renderer/form/form-renderer.component.d.ts.map +1 -0
- package/dist/components/renderer/form/form-renderer.component.js +102 -0
- package/dist/components/renderer/form/state.d.ts +41 -0
- package/dist/components/renderer/form/state.d.ts.map +1 -0
- package/dist/components/renderer/form/state.js +69 -0
- package/dist/components/renderer/page/page.renderer.component.d.ts +9 -0
- package/dist/components/renderer/page/page.renderer.component.d.ts.map +1 -0
- package/dist/components/renderer/page/page.renderer.component.js +85 -0
- package/dist/components/renderer/page/page.renderer.scss +74 -0
- package/dist/components/renderer/section/section-renderer.component.d.ts +6 -0
- package/dist/components/renderer/section/section-renderer.component.d.ts.map +1 -0
- package/dist/components/renderer/section/section-renderer.component.js +20 -0
- package/dist/components/renderer/section/section-renderer.scss +19 -0
- package/dist/components/repeat/helpers.d.ts +6 -0
- package/dist/components/repeat/helpers.d.ts.map +1 -0
- package/dist/components/repeat/helpers.js +64 -0
- package/dist/components/repeat/helpers.test.js +25 -0
- package/dist/components/repeat/repeat-controls.component.d.ts +12 -0
- package/dist/components/repeat/repeat-controls.component.d.ts.map +1 -0
- package/dist/components/repeat/repeat-controls.component.js +22 -0
- package/dist/components/repeat/repeat-controls.scss +7 -0
- package/dist/components/repeat/repeat.component.d.ts +5 -0
- package/dist/components/repeat/repeat.component.d.ts.map +1 -0
- package/dist/components/repeat/repeat.component.js +185 -0
- package/dist/components/repeat/repeat.scss +30 -0
- package/dist/components/repeat/repeat.test.js +25 -0
- package/dist/components/sidebar/page-observer.d.ts +20 -0
- package/dist/components/sidebar/page-observer.d.ts.map +1 -0
- package/dist/components/sidebar/page-observer.js +60 -0
- package/dist/components/sidebar/sidebar.component.d.ts +14 -0
- package/dist/components/sidebar/sidebar.component.d.ts.map +1 -0
- package/dist/components/sidebar/sidebar.component.js +77 -0
- package/dist/components/sidebar/sidebar.scss +114 -0
- package/dist/components/sidebar/useCurrentActivePage.d.ts +34 -0
- package/dist/components/sidebar/useCurrentActivePage.d.ts.map +1 -0
- package/dist/components/sidebar/useCurrentActivePage.js +114 -0
- package/dist/components/sidebar/useCurrentActivePage.test.js +208 -0
- package/dist/components/sidebar/usePageObserver.d.ts +11 -0
- package/dist/components/sidebar/usePageObserver.d.ts.map +1 -0
- package/dist/components/sidebar/usePageObserver.js +42 -0
- package/dist/components/value/value.component.d.ts +6 -0
- package/dist/components/value/value.component.d.ts.map +1 -0
- package/dist/components/value/value.component.js +24 -0
- package/dist/components/value/value.scss +17 -0
- package/dist/components/value/view/field-value-view.component.d.ts +10 -0
- package/dist/components/value/view/field-value-view.component.d.ts.map +1 -0
- package/dist/components/value/view/field-value-view.component.js +29 -0
- package/dist/components/value/view/field-value-view.scss +31 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +13 -0
- package/dist/datasources/concept-data-source.d.ts +6 -0
- package/dist/datasources/concept-data-source.d.ts.map +1 -0
- package/dist/datasources/concept-data-source.js +35 -0
- package/dist/datasources/data-source.d.ts +10 -0
- package/dist/datasources/data-source.d.ts.map +1 -0
- package/dist/datasources/data-source.js +41 -0
- package/dist/datasources/encounter-role-datasource.d.ts +6 -0
- package/dist/datasources/encounter-role-datasource.d.ts.map +1 -0
- package/dist/datasources/encounter-role-datasource.js +13 -0
- package/dist/datasources/historical-data-source.d.ts +6 -0
- package/dist/datasources/historical-data-source.d.ts.map +1 -0
- package/dist/datasources/historical-data-source.js +24 -0
- package/dist/datasources/location-data-source.d.ts +6 -0
- package/dist/datasources/location-data-source.d.ts.map +1 -0
- package/dist/datasources/location-data-source.js +24 -0
- package/dist/datasources/provider-datasource.d.ts +6 -0
- package/dist/datasources/provider-datasource.d.ts.map +1 -0
- package/dist/datasources/provider-datasource.js +13 -0
- package/dist/datasources/select-concept-answers-datasource.d.ts +9 -0
- package/dist/datasources/select-concept-answers-datasource.d.ts.map +1 -0
- package/dist/datasources/select-concept-answers-datasource.js +19 -0
- package/dist/declarations.d.js +0 -0
- package/dist/external-function-context.d.ts +7 -0
- package/dist/external-function-context.d.ts.map +1 -0
- package/dist/external-function-context.js +2 -0
- package/dist/form-engine.component.d.ts +23 -0
- package/dist/form-engine.component.d.ts.map +1 -0
- package/dist/form-engine.component.js +174 -0
- package/dist/form-engine.scss +148 -0
- package/dist/form-engine.test.js +1253 -0
- package/dist/globals.d.ts +3 -0
- package/dist/globals.d.ts.map +1 -0
- package/dist/globals.js +2 -0
- package/dist/hooks/useConcepts.d.ts +23 -0
- package/dist/hooks/useConcepts.d.ts.map +1 -0
- package/dist/hooks/useConcepts.js +31 -0
- package/dist/hooks/useDataSourceDependentValue.d.ts +4 -0
- package/dist/hooks/useDataSourceDependentValue.d.ts.map +1 -0
- package/dist/hooks/useDataSourceDependentValue.js +14 -0
- package/dist/hooks/useEncounter.d.ts +7 -0
- package/dist/hooks/useEncounter.d.ts.map +1 -0
- package/dist/hooks/useEncounter.js +38 -0
- package/dist/hooks/useEncounterRole.d.ts +7 -0
- package/dist/hooks/useEncounterRole.d.ts.map +1 -0
- package/dist/hooks/useEncounterRole.js +18 -0
- package/dist/hooks/useEvaluateFormFieldExpressions.d.ts +7 -0
- package/dist/hooks/useEvaluateFormFieldExpressions.d.ts.map +1 -0
- package/dist/hooks/useEvaluateFormFieldExpressions.js +112 -0
- package/dist/hooks/useExternalFormAction.d.ts +43 -0
- package/dist/hooks/useExternalFormAction.d.ts.map +1 -0
- package/dist/hooks/useExternalFormAction.js +71 -0
- package/dist/hooks/useFormCollapse.d.ts +6 -0
- package/dist/hooks/useFormCollapse.d.ts.map +1 -0
- package/dist/hooks/useFormCollapse.js +35 -0
- package/dist/hooks/useFormFieldValidators.d.ts +3 -0
- package/dist/hooks/useFormFieldValidators.d.ts.map +1 -0
- package/dist/hooks/useFormFieldValidators.js +20 -0
- package/dist/hooks/useFormFieldValueAdapters.d.ts +3 -0
- package/dist/hooks/useFormFieldValueAdapters.d.ts.map +1 -0
- package/dist/hooks/useFormFieldValueAdapters.js +21 -0
- package/dist/hooks/useFormFields.d.ts +6 -0
- package/dist/hooks/useFormFields.d.ts.map +1 -0
- package/dist/hooks/useFormFields.js +62 -0
- package/dist/hooks/useFormFieldsMeta.d.ts +4 -0
- package/dist/hooks/useFormFieldsMeta.d.ts.map +1 -0
- package/dist/hooks/useFormFieldsMeta.js +42 -0
- package/dist/hooks/useFormJson.d.ts +19 -0
- package/dist/hooks/useFormJson.d.ts.map +1 -0
- package/dist/hooks/useFormJson.js +198 -0
- package/dist/hooks/useFormJson.test.js +194 -0
- package/dist/hooks/useFormStateHelpers.d.ts +15 -0
- package/dist/hooks/useFormStateHelpers.d.ts.map +1 -0
- package/dist/hooks/useFormStateHelpers.js +83 -0
- package/dist/hooks/useFormWorkspaceSize.d.ts +7 -0
- package/dist/hooks/useFormWorkspaceSize.d.ts.map +1 -0
- package/dist/hooks/useFormWorkspaceSize.js +46 -0
- package/dist/hooks/useFormWorkspaceSize.test.js +141 -0
- package/dist/hooks/useInitialValues.d.ts +9 -0
- package/dist/hooks/useInitialValues.d.ts.map +1 -0
- package/dist/hooks/useInitialValues.js +29 -0
- package/dist/hooks/useInitialValues.test.js +78 -0
- package/dist/hooks/usePatientData.d.ts +6 -0
- package/dist/hooks/usePatientData.d.ts.map +1 -0
- package/dist/hooks/usePatientData.js +30 -0
- package/dist/hooks/usePatientPrograms.d.ts +7 -0
- package/dist/hooks/usePatientPrograms.d.ts.map +1 -0
- package/dist/hooks/usePatientPrograms.js +22 -0
- package/dist/hooks/usePostSubmissionActions.d.ts +13 -0
- package/dist/hooks/usePostSubmissionActions.d.ts.map +1 -0
- package/dist/hooks/usePostSubmissionActions.js +23 -0
- package/dist/hooks/usePostSubmissionActions.test.js +55 -0
- package/dist/hooks/useProcessorDependencies.d.ts +8 -0
- package/dist/hooks/useProcessorDependencies.d.ts.map +1 -0
- package/dist/hooks/useProcessorDependencies.js +33 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/lifecycle.d.ts +11 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +25 -0
- package/dist/post-submission-actions/mark-patient-deceased-action.d.ts +4 -0
- package/dist/post-submission-actions/mark-patient-deceased-action.d.ts.map +1 -0
- package/dist/post-submission-actions/mark-patient-deceased-action.js +53 -0
- package/dist/post-submission-actions/program-enrollment-action.d.ts +4 -0
- package/dist/post-submission-actions/program-enrollment-action.d.ts.map +1 -0
- package/dist/post-submission-actions/program-enrollment-action.js +105 -0
- package/dist/processors/encounter/encounter-form-processor.d.ts +26 -0
- package/dist/processors/encounter/encounter-form-processor.d.ts.map +1 -0
- package/dist/processors/encounter/encounter-form-processor.js +376 -0
- package/dist/processors/encounter/encounter-form-processor.test.js +207 -0
- package/dist/processors/encounter/encounter-processor-helper.d.ts +19 -0
- package/dist/processors/encounter/encounter-processor-helper.d.ts.map +1 -0
- package/dist/processors/encounter/encounter-processor-helper.js +293 -0
- package/dist/processors/form-processor.d.ts +25 -0
- package/dist/processors/form-processor.d.ts.map +1 -0
- package/dist/processors/form-processor.js +26 -0
- package/dist/provider/form-factory-helper.d.ts +8 -0
- package/dist/provider/form-factory-helper.d.ts.map +1 -0
- package/dist/provider/form-factory-helper.js +73 -0
- package/dist/provider/form-factory-provider.d.ts +48 -0
- package/dist/provider/form-factory-provider.d.ts.map +1 -0
- package/dist/provider/form-factory-provider.js +136 -0
- package/dist/provider/form-provider.d.ts +26 -0
- package/dist/provider/form-provider.d.ts.map +1 -0
- package/dist/provider/form-provider.js +17 -0
- package/dist/registry/inbuilt-components/InbuiltPostSubmissionActions.d.ts +4 -0
- package/dist/registry/inbuilt-components/InbuiltPostSubmissionActions.d.ts.map +1 -0
- package/dist/registry/inbuilt-components/InbuiltPostSubmissionActions.js +10 -0
- package/dist/registry/inbuilt-components/control-templates.d.ts +4 -0
- package/dist/registry/inbuilt-components/control-templates.d.ts.map +1 -0
- package/dist/registry/inbuilt-components/control-templates.js +54 -0
- package/dist/registry/inbuilt-components/inbuiltControls.d.ts +7 -0
- package/dist/registry/inbuilt-components/inbuiltControls.d.ts.map +1 -0
- package/dist/registry/inbuilt-components/inbuiltControls.js +94 -0
- package/dist/registry/inbuilt-components/inbuiltDataSources.d.ts +8 -0
- package/dist/registry/inbuilt-components/inbuiltDataSources.d.ts.map +1 -0
- package/dist/registry/inbuilt-components/inbuiltDataSources.js +36 -0
- package/dist/registry/inbuilt-components/inbuiltFieldValueAdapters.d.ts +4 -0
- package/dist/registry/inbuilt-components/inbuiltFieldValueAdapters.d.ts.map +1 -0
- package/dist/registry/inbuilt-components/inbuiltFieldValueAdapters.js +71 -0
- package/dist/registry/inbuilt-components/inbuiltTransformers.d.ts +4 -0
- package/dist/registry/inbuilt-components/inbuiltTransformers.d.ts.map +1 -0
- package/dist/registry/inbuilt-components/inbuiltTransformers.js +7 -0
- package/dist/registry/inbuilt-components/inbuiltValidators.d.ts +7 -0
- package/dist/registry/inbuilt-components/inbuiltValidators.d.ts.map +1 -0
- package/dist/registry/inbuilt-components/inbuiltValidators.js +29 -0
- package/dist/registry/inbuilt-components/template-component-map.d.ts +5 -0
- package/dist/registry/inbuilt-components/template-component-map.d.ts.map +1 -0
- package/dist/registry/inbuilt-components/template-component-map.js +27 -0
- package/dist/registry/registry.d.ts +61 -0
- package/dist/registry/registry.d.ts.map +1 -0
- package/dist/registry/registry.js +184 -0
- package/dist/registry/registry.test.js +17 -0
- package/dist/setup-tests.js +30 -0
- package/dist/transformers/default-schema-transformer.d.ts +4 -0
- package/dist/transformers/default-schema-transformer.d.ts.map +1 -0
- package/dist/transformers/default-schema-transformer.js +308 -0
- package/dist/transformers/default-schema-transformer.test.js +319 -0
- package/dist/types/domain.d.ts +219 -0
- package/dist/types/domain.d.ts.map +1 -0
- package/dist/types/domain.js +1 -0
- package/dist/types/index.d.ts +126 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/schema.d.ts +243 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +1 -0
- package/dist/typings.d.js +1 -0
- package/dist/utils/boolean-utils.d.ts +13 -0
- package/dist/utils/boolean-utils.d.ts.map +1 -0
- package/dist/utils/boolean-utils.js +22 -0
- package/dist/utils/common-expression-helpers.d.ts +261 -0
- package/dist/utils/common-expression-helpers.d.ts.map +1 -0
- package/dist/utils/common-expression-helpers.js +538 -0
- package/dist/utils/common-expression-helpers.test.js +461 -0
- package/dist/utils/common-utils.d.ts +51 -0
- package/dist/utils/common-utils.d.ts.map +1 -0
- package/dist/utils/common-utils.js +102 -0
- package/dist/utils/common-utils.test.js +134 -0
- package/dist/utils/error-utils.d.ts +11 -0
- package/dist/utils/error-utils.d.ts.map +1 -0
- package/dist/utils/error-utils.js +34 -0
- package/dist/utils/expression-runner.d.ts +25 -0
- package/dist/utils/expression-runner.d.ts.map +1 -0
- package/dist/utils/expression-runner.js +104 -0
- package/dist/utils/expression-runner.test.js +362 -0
- package/dist/utils/form-helper.d.ts +37 -0
- package/dist/utils/form-helper.d.ts.map +1 -0
- package/dist/utils/form-helper.js +239 -0
- package/dist/utils/form-helper.test.js +638 -0
- package/dist/utils/form-page-utils.d.ts +3 -0
- package/dist/utils/form-page-utils.d.ts.map +1 -0
- package/dist/utils/form-page-utils.js +10 -0
- package/dist/utils/forms-loader.d.ts +45 -0
- package/dist/utils/forms-loader.d.ts.map +1 -0
- package/dist/utils/forms-loader.js +246 -0
- package/dist/utils/forms-loader.test.js +333 -0
- package/dist/utils/post-submission-action-helper.d.ts +2 -0
- package/dist/utils/post-submission-action-helper.d.ts.map +1 -0
- package/dist/utils/post-submission-action-helper.js +57 -0
- package/dist/utils/test-utils.js +66 -0
- package/dist/utils/zscore-service.d.ts +6 -0
- package/dist/utils/zscore-service.d.ts.map +1 -0
- package/dist/utils/zscore-service.js +54 -0
- package/dist/validators/conditional-answered-validator.d.ts +3 -0
- package/dist/validators/conditional-answered-validator.d.ts.map +1 -0
- package/dist/validators/conditional-answered-validator.js +18 -0
- package/dist/validators/conditional-answered-validator.test.js +63 -0
- package/dist/validators/date-validator.d.ts +3 -0
- package/dist/validators/date-validator.d.ts.map +1 -0
- package/dist/validators/date-validator.js +21 -0
- package/dist/validators/date-validator.test.js +53 -0
- package/dist/validators/default-value-validator.d.ts +3 -0
- package/dist/validators/default-value-validator.d.ts.map +1 -0
- package/dist/validators/default-value-validator.js +46 -0
- package/dist/validators/default-value-validator.test.js +95 -0
- package/dist/validators/form-validator.d.ts +9 -0
- package/dist/validators/form-validator.d.ts.map +1 -0
- package/dist/validators/form-validator.js +82 -0
- package/dist/validators/form-validator.test.js +158 -0
- package/dist/validators/js-expression-validator.d.ts +3 -0
- package/dist/validators/js-expression-validator.d.ts.map +1 -0
- package/dist/validators/js-expression-validator.js +25 -0
- package/dist/validators/js-expression-validator.test.js +115 -0
- package/dist/validators/schema.d.ts +1 -0
- package/dist/validators/schema.d.ts.map +1 -0
- package/dist/validators/schema.js +32 -0
- package/dist/zscore/bfa_boys_5_above.json +2522 -0
- package/dist/zscore/bfa_girls_5_above.json +2522 -0
- package/dist/zscore/hfa_boys_5_above.json +2186 -0
- package/dist/zscore/hfa_boys_below5.json +22286 -0
- package/dist/zscore/hfa_girls_5_above.json +2186 -0
- package/dist/zscore/hfa_girls_below5.json +22286 -0
- package/dist/zscore/wfl_boys_below5.json +7814 -0
- package/dist/zscore/wfl_girls_below5.json +7814 -0
- package/dist/zscore-tests/bmi-age.test.js +84 -0
- package/dist/zscore-tests/height-age.test.js +88 -0
- package/dist/zscore-tests/weight-height.test.js +82 -0
- package/package.json +105 -0
- package/src/adapters/control-adapter.ts +29 -0
- package/src/adapters/encounter-datetime-adapter.ts +38 -0
- package/src/adapters/encounter-diagnosis-adapter.ts +121 -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 +59 -0
- package/src/adapters/obs-adapter.ts +349 -0
- package/src/adapters/obs-comment-adapter.ts +61 -0
- package/src/adapters/orders-adapter.ts +82 -0
- package/src/adapters/patient-identifier-adapter.ts +46 -0
- package/src/adapters/person-attribute-adapter.ts +45 -0
- package/src/adapters/program-state-adapter.ts +57 -0
- package/src/api/index.ts +224 -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 +55 -0
- package/src/components/field-label/field-label.scss +15 -0
- package/src/components/group/obs-group.component.tsx +59 -0
- package/src/components/group/obs-group.scss +16 -0
- package/src/components/inputs/content-switcher/content-switcher.component.tsx +81 -0
- package/src/components/inputs/content-switcher/content-switcher.scss +55 -0
- package/src/components/inputs/date/date.component.tsx +141 -0
- package/src/components/inputs/date/date.scss +37 -0
- package/src/components/inputs/file/file-thumbnail.component.tsx +55 -0
- package/src/components/inputs/file/file-thumbnail.scss +42 -0
- package/src/components/inputs/file/file.component.tsx +98 -0
- package/src/components/inputs/file/file.scss +21 -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 +24 -0
- package/src/components/inputs/multi-select/multi-select.component.tsx +152 -0
- package/src/components/inputs/multi-select/multi-select.scss +25 -0
- package/src/components/inputs/number/number.component.tsx +85 -0
- package/src/components/inputs/number/number.scss +15 -0
- package/src/components/inputs/radio/radio.component.tsx +81 -0
- package/src/components/inputs/radio/radio.scss +36 -0
- package/src/components/inputs/select/dropdown.component.tsx +86 -0
- package/src/components/inputs/select/dropdown.scss +11 -0
- package/src/components/inputs/text/text.component.tsx +67 -0
- package/src/components/inputs/text/text.scss +15 -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/ui-select-extended/ui-select-extended.component.tsx +208 -0
- package/src/components/inputs/ui-select-extended/ui-select-extended.scss +19 -0
- package/src/components/inputs/unspecified/unspecified.component.tsx +84 -0
- package/src/components/inputs/unspecified/unspecified.scss +7 -0
- package/src/components/inputs/workspace-launcher/workspace-launcher.component.tsx +80 -0
- package/src/components/inputs/workspace-launcher/workspace-launcher.scss +21 -0
- package/src/components/label/label.component.tsx +20 -0
- package/src/components/label/label.scss +15 -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 +128 -0
- package/src/components/renderer/custom-hooks-renderer.component.tsx +30 -0
- package/src/components/renderer/field/fieldLogic.ts +314 -0
- package/src/components/renderer/field/fieldRenderUtils.ts +16 -0
- package/src/components/renderer/field/form-field-renderer.component.tsx +291 -0
- package/src/components/renderer/field/form-field-renderer.scss +5 -0
- package/src/components/renderer/form/form-renderer.component.tsx +123 -0
- package/src/components/renderer/form/state.ts +59 -0
- package/src/components/renderer/page/page.renderer.component.tsx +120 -0
- package/src/components/renderer/page/page.renderer.scss +74 -0
- package/src/components/renderer/section/section-renderer.component.tsx +25 -0
- package/src/components/renderer/section/section-renderer.scss +19 -0
- package/src/components/repeat/helpers.ts +77 -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 +206 -0
- package/src/components/repeat/repeat.scss +30 -0
- package/src/components/sidebar/page-observer.ts +58 -0
- package/src/components/sidebar/sidebar.component.tsx +121 -0
- package/src/components/sidebar/sidebar.scss +114 -0
- package/src/components/sidebar/useCurrentActivePage.ts +137 -0
- package/src/components/sidebar/usePageObserver.ts +45 -0
- package/src/components/value/value.component.tsx +30 -0
- package/src/components/value/value.scss +17 -0
- package/src/components/value/view/field-value-view.component.tsx +36 -0
- package/src/components/value/view/field-value-view.scss +31 -0
- package/src/constants.ts +14 -0
- package/src/datasources/concept-data-source.ts +42 -0
- package/src/datasources/data-source.ts +34 -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 +25 -0
- package/src/declarations.d.ts +2 -0
- package/src/external-function-context.tsx +8 -0
- package/src/form-engine.component.tsx +222 -0
- package/src/form-engine.scss +148 -0
- package/src/globals.ts +2 -0
- package/src/hooks/useConcepts.ts +58 -0
- package/src/hooks/useDataSourceDependentValue.ts +16 -0
- package/src/hooks/useEncounter.ts +40 -0
- package/src/hooks/useEncounterRole.ts +15 -0
- package/src/hooks/useEvaluateFormFieldExpressions.ts +152 -0
- package/src/hooks/useExternalFormAction.ts +94 -0
- package/src/hooks/useFormCollapse.ts +36 -0
- package/src/hooks/useFormFieldValidators.ts +22 -0
- package/src/hooks/useFormFieldValueAdapters.ts +23 -0
- package/src/hooks/useFormFields.ts +65 -0
- package/src/hooks/useFormFieldsMeta.ts +48 -0
- package/src/hooks/useFormJson.ts +257 -0
- package/src/hooks/useFormStateHelpers.ts +72 -0
- package/src/hooks/useFormWorkspaceSize.ts +52 -0
- package/src/hooks/useInitialValues.ts +39 -0
- package/src/hooks/usePatientData.ts +32 -0
- package/src/hooks/usePatientPrograms.ts +38 -0
- package/src/hooks/usePostSubmissionActions.ts +31 -0
- package/src/hooks/useProcessorDependencies.ts +42 -0
- package/src/index.ts +11 -0
- package/src/lifecycle.ts +32 -0
- package/src/post-submission-actions/mark-patient-deceased-action.ts +68 -0
- package/src/post-submission-actions/program-enrollment-action.ts +137 -0
- package/src/processors/encounter/encounter-form-processor.ts +445 -0
- package/src/processors/encounter/encounter-processor-helper.ts +387 -0
- package/src/processors/form-processor.ts +40 -0
- package/src/provider/form-factory-helper.ts +106 -0
- package/src/provider/form-factory-provider.tsx +211 -0
- package/src/provider/form-provider.tsx +39 -0
- package/src/registry/inbuilt-components/InbuiltPostSubmissionActions.ts +13 -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 +74 -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.ts +290 -0
- package/src/transformers/default-schema-transformer.ts +340 -0
- package/src/types/domain.ts +237 -0
- package/src/types/index.ts +153 -0
- package/src/types/schema.ts +259 -0
- package/src/typings.d.ts +9 -0
- package/src/utils/boolean-utils.ts +25 -0
- package/src/utils/common-expression-helpers.ts +636 -0
- package/src/utils/common-utils.ts +108 -0
- package/src/utils/error-utils.ts +37 -0
- package/src/utils/expression-runner.ts +172 -0
- package/src/utils/form-helper.ts +283 -0
- package/src/utils/form-page-utils.ts +13 -0
- package/src/utils/forms-loader.ts +306 -0
- package/src/utils/post-submission-action-helper.ts +71 -0
- package/src/utils/zscore-service.ts +59 -0
- package/src/validators/conditional-answered-validator.ts +17 -0
- package/src/validators/date-validator.ts +19 -0
- package/src/validators/default-value-validator.ts +36 -0
- package/src/validators/form-validator.ts +95 -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
|
@@ -0,0 +1,1253 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import dayjs from "dayjs";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
import { act, cleanup, render, screen, waitFor, within } from "@testing-library/react";
|
|
5
|
+
import { ExtensionSlot, OpenmrsDatePicker, openmrsFetch, restBaseUrl, usePatient, useSession } from "@openmrs/esm-framework";
|
|
6
|
+
import { when } from "jest-when";
|
|
7
|
+
import * as api from "./api/index.js";
|
|
8
|
+
import { assertFormHasAllFields, findCheckboxGroup, findSelectInput, findTextOrDateInput } from "./utils/test-utils.js";
|
|
9
|
+
import { evaluatePostSubmissionExpression } from "./utils/post-submission-action-helper.js";
|
|
10
|
+
import { mockConcepts, mockPatient, mockSessionDataResponse, mockVisit } from "__mocks__";
|
|
11
|
+
import { ageValidationForm, bmiForm, bsaForm, conditionalAnsweredForm, conditionalRequiredTestForm, defaultValuesForm, demoHtsForm, demoHtsOpenmrsForm, diagnosisForm, eddForm, externalDataSourceForm, filterAnswerOptionsTestForm, hidePagesAndSectionsForm, historicalExpressionsForm, htsPocForm, jsExpressionValidationForm, labourAndDeliveryTestForm, mockHxpEncounter, mockSaveEncounter, monthsOnArtForm, nextVisitForm, obsGroupTestForm, postSubmissionTestForm, readOnlyValidationForm, referenceByMappingForm, requiredTestForm, sampleFieldsForm, testEnrolmentForm, viralLoadStatusForm, expressionVisitObjectTestSchema } from "__mocks__/forms";
|
|
12
|
+
import { useEncounter } from "./hooks/useEncounter.js";
|
|
13
|
+
import FormEngine from "./form-engine.component.js";
|
|
14
|
+
const patientUUID = '8673ee4f-e2ab-4077-ba55-4980f408773e';
|
|
15
|
+
const visit = mockVisit;
|
|
16
|
+
const formsResourcePath = when((url)=>url.includes(`${restBaseUrl}/form/`));
|
|
17
|
+
const clobDataResourcePath = when((url)=>url.includes(`${restBaseUrl}/clobdata/`));
|
|
18
|
+
const mockOpenmrsFetch = jest.mocked(openmrsFetch);
|
|
19
|
+
const mockExtensionSlot = jest.mocked(ExtensionSlot);
|
|
20
|
+
const mockUsePatient = jest.mocked(usePatient);
|
|
21
|
+
const mockUseSession = jest.mocked(useSession);
|
|
22
|
+
const mockOpenmrsDatePicker = jest.mocked(OpenmrsDatePicker);
|
|
23
|
+
const mockUseEncounter = jest.mocked(useEncounter);
|
|
24
|
+
mockOpenmrsDatePicker.mockImplementation(({ id, labelText, value, onChange, isInvalid, invalidText })=>{
|
|
25
|
+
return /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement("label", {
|
|
26
|
+
htmlFor: id
|
|
27
|
+
}, labelText), /*#__PURE__*/ React.createElement("input", {
|
|
28
|
+
id: id,
|
|
29
|
+
value: value ? dayjs(value).format('DD/MM/YYYY') : '',
|
|
30
|
+
onChange: (evt)=>{
|
|
31
|
+
onChange(dayjs(evt.target.value).toDate());
|
|
32
|
+
}
|
|
33
|
+
}), isInvalid && /*#__PURE__*/ React.createElement("span", null, invalidText));
|
|
34
|
+
});
|
|
35
|
+
when(mockOpenmrsFetch).calledWith(formsResourcePath).mockReturnValue({
|
|
36
|
+
data: demoHtsOpenmrsForm
|
|
37
|
+
});
|
|
38
|
+
when(mockOpenmrsFetch).calledWith(clobDataResourcePath).mockReturnValue({
|
|
39
|
+
data: demoHtsForm
|
|
40
|
+
});
|
|
41
|
+
jest.mock('lodash-es/debounce', ()=>jest.fn((fn)=>fn));
|
|
42
|
+
jest.mock('lodash-es', ()=>({
|
|
43
|
+
...jest.requireActual('lodash-es'),
|
|
44
|
+
debounce: jest.fn((fn)=>fn)
|
|
45
|
+
}));
|
|
46
|
+
jest.mock('./registry/registry', ()=>{
|
|
47
|
+
const originalModule = jest.requireActual('./registry/registry');
|
|
48
|
+
return {
|
|
49
|
+
...originalModule,
|
|
50
|
+
getRegisteredDataSource: jest.fn().mockResolvedValue({
|
|
51
|
+
fetchData: jest.fn().mockImplementation((...args)=>{
|
|
52
|
+
if (args[1].class?.length && !args[1].referencedValue?.key) {
|
|
53
|
+
// concept DS
|
|
54
|
+
return Promise.resolve([
|
|
55
|
+
{
|
|
56
|
+
uuid: 'stage-1-uuid',
|
|
57
|
+
display: 'stage 1'
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
uuid: 'stage-2-uuid',
|
|
61
|
+
display: 'stage 2'
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
uuid: 'stage-3-uuid',
|
|
65
|
+
display: 'stage 3'
|
|
66
|
+
}
|
|
67
|
+
]);
|
|
68
|
+
}
|
|
69
|
+
}),
|
|
70
|
+
fetchSingleItem: jest.fn().mockImplementation((uuid)=>{
|
|
71
|
+
return Promise.resolve({
|
|
72
|
+
uuid,
|
|
73
|
+
display: 'stage 1'
|
|
74
|
+
});
|
|
75
|
+
}),
|
|
76
|
+
toUuidAndDisplay: (data)=>data
|
|
77
|
+
})
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
jest.mock('../src/api', ()=>{
|
|
81
|
+
const originalModule = jest.requireActual('../src/api');
|
|
82
|
+
return {
|
|
83
|
+
...originalModule,
|
|
84
|
+
getPreviousEncounter: jest.fn().mockImplementation(()=>Promise.resolve(mockHxpEncounter)),
|
|
85
|
+
getConcept: jest.fn().mockImplementation(()=>Promise.resolve(null)),
|
|
86
|
+
getLatestObs: jest.fn().mockImplementation(()=>Promise.resolve({
|
|
87
|
+
valueNumeric: 60
|
|
88
|
+
})),
|
|
89
|
+
saveEncounter: jest.fn().mockImplementation(()=>Promise.resolve(mockSaveEncounter)),
|
|
90
|
+
createProgramEnrollment: jest.fn()
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
jest.mock('./hooks/useEncounterRole', ()=>({
|
|
94
|
+
useEncounterRole: jest.fn().mockReturnValue({
|
|
95
|
+
isLoading: false,
|
|
96
|
+
encounterRole: {
|
|
97
|
+
name: 'Clinician',
|
|
98
|
+
uuid: 'clinician-uuid'
|
|
99
|
+
},
|
|
100
|
+
error: undefined
|
|
101
|
+
})
|
|
102
|
+
}));
|
|
103
|
+
jest.mock('./hooks/useConcepts', ()=>({
|
|
104
|
+
useConcepts: jest.fn().mockImplementation((references)=>{
|
|
105
|
+
if ([
|
|
106
|
+
...references
|
|
107
|
+
].join(',').includes('PIH:Occurrence of trauma,PIH:Yes,PIH:No,PIH:COUGH')) {
|
|
108
|
+
return {
|
|
109
|
+
isLoading: false,
|
|
110
|
+
concepts: mockConcepts.results,
|
|
111
|
+
error: undefined
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
isLoading: false,
|
|
116
|
+
concepts: undefined,
|
|
117
|
+
error: undefined
|
|
118
|
+
};
|
|
119
|
+
})
|
|
120
|
+
}));
|
|
121
|
+
jest.mock('./hooks/useEncounter', ()=>({
|
|
122
|
+
useEncounter: jest.fn().mockImplementation((formJson)=>{
|
|
123
|
+
return {
|
|
124
|
+
encounter: formJson.encounter ? mockHxpEncounter : null,
|
|
125
|
+
isLoading: false,
|
|
126
|
+
error: undefined
|
|
127
|
+
};
|
|
128
|
+
})
|
|
129
|
+
}));
|
|
130
|
+
describe('Form engine component', ()=>{
|
|
131
|
+
const user = userEvent.setup();
|
|
132
|
+
beforeEach(()=>{
|
|
133
|
+
mockExtensionSlot.mockImplementation((ext)=>/*#__PURE__*/ React.createElement(React.Fragment, null, ext.name));
|
|
134
|
+
mockUsePatient.mockImplementation(()=>({
|
|
135
|
+
patient: mockPatient,
|
|
136
|
+
isLoading: false,
|
|
137
|
+
error: undefined,
|
|
138
|
+
patientUuid: mockPatient.id
|
|
139
|
+
}));
|
|
140
|
+
mockUseSession.mockImplementation(()=>mockSessionDataResponse.data);
|
|
141
|
+
});
|
|
142
|
+
afterEach(()=>{
|
|
143
|
+
jest.useRealTimers();
|
|
144
|
+
});
|
|
145
|
+
it('should render the form schema without dying', async ()=>{
|
|
146
|
+
await act(async ()=>renderForm(null, htsPocForm));
|
|
147
|
+
await assertFormHasAllFields(screen, [
|
|
148
|
+
{
|
|
149
|
+
fieldName: 'When was the HIV test conducted? *',
|
|
150
|
+
fieldType: 'date'
|
|
151
|
+
}
|
|
152
|
+
]);
|
|
153
|
+
});
|
|
154
|
+
it('should render by the form UUID without dying', async ()=>{
|
|
155
|
+
await act(async ()=>{
|
|
156
|
+
renderForm('955ab92f-f93e-4dc0-9c68-b7b2346def55', null);
|
|
157
|
+
});
|
|
158
|
+
await assertFormHasAllFields(screen, [
|
|
159
|
+
{
|
|
160
|
+
fieldName: 'When was the HIV test conducted? *',
|
|
161
|
+
fieldType: 'date'
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
fieldName: 'Community service delivery point',
|
|
165
|
+
fieldType: 'select'
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
fieldName: 'TB screening',
|
|
169
|
+
fieldType: 'checkbox'
|
|
170
|
+
}
|
|
171
|
+
]);
|
|
172
|
+
});
|
|
173
|
+
it('should demonstrate behavior driven by form intents', async ()=>{
|
|
174
|
+
await act(async ()=>{
|
|
175
|
+
renderForm('955ab92f-f93e-4dc0-9c68-b7b2346def55', null, 'HTS_INTENT_A');
|
|
176
|
+
});
|
|
177
|
+
await assertFormHasAllFields(screen, [
|
|
178
|
+
{
|
|
179
|
+
fieldName: 'When was the HIV test conducted? *',
|
|
180
|
+
fieldType: 'date'
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
fieldName: 'TB screening',
|
|
184
|
+
fieldType: 'checkbox'
|
|
185
|
+
}
|
|
186
|
+
]);
|
|
187
|
+
try {
|
|
188
|
+
await findSelectInput(screen, 'Community service delivery point');
|
|
189
|
+
fail("Field with title 'Community service delivery point' should not be found");
|
|
190
|
+
} catch (err) {
|
|
191
|
+
expect(err.message.includes('Unable to find role="combobox" and name "Community service delivery point"')).toBeTruthy();
|
|
192
|
+
}
|
|
193
|
+
// cleanup
|
|
194
|
+
cleanup();
|
|
195
|
+
// HTS_INTENT_B
|
|
196
|
+
await act(async ()=>{
|
|
197
|
+
renderForm('955ab92f-f93e-4dc0-9c68-b7b2346def55', null, 'HTS_INTENT_B');
|
|
198
|
+
});
|
|
199
|
+
await assertFormHasAllFields(screen, [
|
|
200
|
+
{
|
|
201
|
+
fieldName: 'When was the HIV test conducted? *',
|
|
202
|
+
fieldType: 'date'
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
fieldName: 'Community service delivery point',
|
|
206
|
+
fieldType: 'select'
|
|
207
|
+
}
|
|
208
|
+
]);
|
|
209
|
+
try {
|
|
210
|
+
await findCheckboxGroup(screen, 'TB screening');
|
|
211
|
+
fail("Field with title 'TB screening' should not be found");
|
|
212
|
+
} catch (err) {
|
|
213
|
+
expect(err.message.includes('Unable to find role="group" and name `/TB screening/i`')).toBeTruthy();
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
describe('Question info', ()=>{
|
|
217
|
+
it('should ascertain that each field with questionInfo passed will display a tooltip', async ()=>{
|
|
218
|
+
await act(async ()=>{
|
|
219
|
+
renderForm(null, sampleFieldsForm);
|
|
220
|
+
});
|
|
221
|
+
screen.findByLabelText(/text question/i);
|
|
222
|
+
const textFieldTooltip = screen.getByTestId('id_text-label');
|
|
223
|
+
expect(textFieldTooltip).toBeInTheDocument();
|
|
224
|
+
const informationIcon = screen.getByTestId('id_text-information-icon');
|
|
225
|
+
expect(informationIcon).toBeInTheDocument();
|
|
226
|
+
await user.hover(textFieldTooltip);
|
|
227
|
+
await screen.findByText(/sample tooltip info for text/i);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
describe('conditional answered validation', ()=>{
|
|
231
|
+
it('should fail if the referenced field has a value that does not exist on the referenced answers array', async ()=>{
|
|
232
|
+
await act(async ()=>{
|
|
233
|
+
renderForm(null, conditionalAnsweredForm);
|
|
234
|
+
});
|
|
235
|
+
const hospitalizationHistoryDropdown = screen.getByRole('combobox', {
|
|
236
|
+
name: /was the patient hospitalized since last visit\?/i
|
|
237
|
+
});
|
|
238
|
+
const hospitalizationReasonDropdown = screen.getByRole('combobox', {
|
|
239
|
+
name: /reason for hospitalization:/i
|
|
240
|
+
});
|
|
241
|
+
expect(hospitalizationHistoryDropdown).toBeInTheDocument();
|
|
242
|
+
expect(hospitalizationReasonDropdown).toBeInTheDocument();
|
|
243
|
+
await user.click(hospitalizationHistoryDropdown);
|
|
244
|
+
expect(screen.getByText(/yes/i)).toBeInTheDocument();
|
|
245
|
+
expect(screen.getByText(/no/i)).toBeInTheDocument();
|
|
246
|
+
await user.click(screen.getByRole('option', {
|
|
247
|
+
name: /no/i
|
|
248
|
+
}));
|
|
249
|
+
await user.click(screen.getByText(/No/i));
|
|
250
|
+
await user.click(hospitalizationReasonDropdown);
|
|
251
|
+
expect(screen.getByText(/Maternal Visit/i)).toBeInTheDocument();
|
|
252
|
+
expect(screen.getByText(/Emergency Visit/i)).toBeInTheDocument();
|
|
253
|
+
expect(screen.getByText(/Unscheduled visit late/i)).toBeInTheDocument();
|
|
254
|
+
await user.click(screen.getByText(/Maternal Visit/i));
|
|
255
|
+
const errorMessage = screen.getByText(/Providing diagnosis but didn't answer that patient was hospitalized in question/i);
|
|
256
|
+
expect(errorMessage).toBeInTheDocument();
|
|
257
|
+
await user.click(hospitalizationHistoryDropdown);
|
|
258
|
+
await user.click(screen.getByText(/yes/i));
|
|
259
|
+
expect(errorMessage).not.toBeInTheDocument();
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
describe('js-expression based validation', ()=>{
|
|
263
|
+
it('should invoke validation when field value changes', async ()=>{
|
|
264
|
+
await act(async ()=>{
|
|
265
|
+
renderForm(null, jsExpressionValidationForm);
|
|
266
|
+
});
|
|
267
|
+
const textField = await findTextOrDateInput(screen, 'Question 1');
|
|
268
|
+
await user.type(textField, 'Some value');
|
|
269
|
+
// clear value
|
|
270
|
+
await user.clear(textField);
|
|
271
|
+
const errorMessage = await screen.findByText(/Empty value not allowed!/i);
|
|
272
|
+
expect(errorMessage).toBeInTheDocument();
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
describe('historical expressions', ()=>{
|
|
276
|
+
it('should ascertain getPreviousEncounter() returns an encounter and the historical expression displays on the UI', async ()=>{
|
|
277
|
+
renderForm(null, historicalExpressionsForm, 'COVID Assessment');
|
|
278
|
+
//ascertain form has rendered
|
|
279
|
+
const checkboxGroup = await findCheckboxGroup(screen, 'Reasons for assessment');
|
|
280
|
+
expect(checkboxGroup).toBeInTheDocument();
|
|
281
|
+
//ascertain function fetching the encounter has been called
|
|
282
|
+
expect(api.getPreviousEncounter).toHaveBeenCalled();
|
|
283
|
+
expect(api.getPreviousEncounter).toHaveReturnedWith(Promise.resolve(mockHxpEncounter));
|
|
284
|
+
expect(screen.getByRole('button', {
|
|
285
|
+
name: /reuse value/i
|
|
286
|
+
})).toBeInTheDocument;
|
|
287
|
+
expect(screen.getByText(/Entry into a country/i, {
|
|
288
|
+
selector: 'div.value'
|
|
289
|
+
}));
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
describe('Form submission', ()=>{
|
|
293
|
+
it('should validate required field on form submission', async ()=>{
|
|
294
|
+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
|
295
|
+
await act(async ()=>{
|
|
296
|
+
renderForm(null, requiredTestForm);
|
|
297
|
+
});
|
|
298
|
+
await user.click(screen.getByRole('button', {
|
|
299
|
+
name: /save/i
|
|
300
|
+
}));
|
|
301
|
+
const labels = screen.getAllByText(/Text question/i);
|
|
302
|
+
expect(labels).toHaveLength(2);
|
|
303
|
+
const requiredAsterisks = screen.getAllByText('*');
|
|
304
|
+
expect(requiredAsterisks).toHaveLength(2);
|
|
305
|
+
const inputFields = screen.getAllByLabelText(/Text question/i);
|
|
306
|
+
expect(inputFields).toHaveLength(2);
|
|
307
|
+
inputFields.forEach((inputField)=>{
|
|
308
|
+
expect(inputField).toHaveClass('cds--text-input--invalid');
|
|
309
|
+
});
|
|
310
|
+
const errorMessages = screen.getAllByText('Field is mandatory');
|
|
311
|
+
expect(errorMessages).toHaveLength(2);
|
|
312
|
+
errorMessages.forEach((errorMessage, index)=>{
|
|
313
|
+
expect(errorMessage).toBeInTheDocument();
|
|
314
|
+
});
|
|
315
|
+
expect(saveEncounterMock).toHaveBeenCalledTimes(0);
|
|
316
|
+
});
|
|
317
|
+
it('should validate conditional required field on form submission', async ()=>{
|
|
318
|
+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
|
319
|
+
await act(async ()=>{
|
|
320
|
+
renderForm(null, conditionalRequiredTestForm);
|
|
321
|
+
});
|
|
322
|
+
const visitScheduledDropdown = screen.getByRole('combobox', {
|
|
323
|
+
name: /Was this visit scheduled?/i
|
|
324
|
+
});
|
|
325
|
+
await user.click(visitScheduledDropdown);
|
|
326
|
+
expect(screen.queryByRole('option', {
|
|
327
|
+
name: /Unscheduled Visit Early/i
|
|
328
|
+
})).toBeInTheDocument();
|
|
329
|
+
expect(screen.queryByRole('option', {
|
|
330
|
+
name: /Unscheduled Visit Late/i
|
|
331
|
+
})).toBeInTheDocument();
|
|
332
|
+
expect(screen.getByRole('option', {
|
|
333
|
+
name: 'Scheduled visit'
|
|
334
|
+
})).toBeInTheDocument();
|
|
335
|
+
const options = screen.getAllByRole('option');
|
|
336
|
+
await user.click(options[2]);
|
|
337
|
+
await user.click(screen.getByRole('button', {
|
|
338
|
+
name: /save/i
|
|
339
|
+
}));
|
|
340
|
+
await assertFormHasAllFields(screen, [
|
|
341
|
+
{
|
|
342
|
+
fieldName: 'Was this visit scheduled?',
|
|
343
|
+
fieldType: 'select'
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
fieldName: 'If Unscheduled, actual text scheduled date *',
|
|
347
|
+
fieldType: 'text'
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
fieldName: 'If Unscheduled, actual scheduled date *',
|
|
351
|
+
fieldType: 'date'
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
fieldName: 'If Unscheduled, actual number scheduled date *',
|
|
355
|
+
fieldType: 'number'
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
fieldName: 'If Unscheduled, actual text area scheduled date *',
|
|
359
|
+
fieldType: 'textarea'
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
fieldName: 'Not required actual text area scheduled date',
|
|
363
|
+
fieldType: 'textarea'
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
fieldName: 'If Unscheduled, actual scheduled reason select',
|
|
367
|
+
fieldType: 'select'
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
fieldName: 'If Unscheduled, actual scheduled reason multi-select *',
|
|
371
|
+
fieldType: 'checkbox-searchable'
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
fieldName: 'If Unscheduled, actual scheduled reason radio *',
|
|
375
|
+
fieldType: 'radio'
|
|
376
|
+
}
|
|
377
|
+
]);
|
|
378
|
+
// TODO: Temporarily disabling this until the core date picker mock gets fixed
|
|
379
|
+
// Issue - https://openmrs.atlassian.net/browse/O3-3479
|
|
380
|
+
// Validate date field
|
|
381
|
+
// const dateInputField = await screen.getByLabelText(/If Unscheduled, actual scheduled date/i);
|
|
382
|
+
// expect(dateInputField).toHaveClass('cds--date-picker__input--invalid');
|
|
383
|
+
const errorMessage = await screen.findByText(/Patient visit marked as unscheduled. Please provide the scheduled date./i);
|
|
384
|
+
expect(errorMessage).toBeInTheDocument();
|
|
385
|
+
// Validate text field
|
|
386
|
+
const textInputField = screen.getByLabelText(/If Unscheduled, actual text scheduled date/i);
|
|
387
|
+
expect(textInputField).toHaveClass('cds--text-input--invalid');
|
|
388
|
+
const textErrorMessage = screen.getByText('Patient visit marked as unscheduled. Please provide the scheduled text date.');
|
|
389
|
+
expect(textErrorMessage).toBeInTheDocument();
|
|
390
|
+
// Validate number field
|
|
391
|
+
const numberInputField = screen.getByLabelText(/If Unscheduled, actual number scheduled date/i);
|
|
392
|
+
const dataInvalidValue = numberInputField.getAttribute('data-invalid');
|
|
393
|
+
expect(dataInvalidValue).toBe('true');
|
|
394
|
+
const numberErrorMessage = screen.getByText('Patient visit marked as unscheduled. Please provide the scheduled number');
|
|
395
|
+
expect(numberErrorMessage).toBeInTheDocument();
|
|
396
|
+
// Validate text area field
|
|
397
|
+
const textAreaInputField = screen.getByLabelText(/If Unscheduled, actual text area scheduled date/i);
|
|
398
|
+
expect(textAreaInputField).toHaveClass('cds--text-area cds--text-area--invalid');
|
|
399
|
+
const textAreaErrorMessage = screen.getByText('Patient visit marked as unscheduled. Please provide the scheduled text area date.');
|
|
400
|
+
expect(textAreaErrorMessage).toBeInTheDocument();
|
|
401
|
+
// Validate Select field
|
|
402
|
+
const selectInputField = screen.getByText('If Unscheduled, actual scheduled reason select', {
|
|
403
|
+
selector: 'span'
|
|
404
|
+
});
|
|
405
|
+
expect(selectInputField).toBeInTheDocument();
|
|
406
|
+
const selectErrorMessage = screen.getByText('Patient visit marked as unscheduled. Please provide the scheduled reason select');
|
|
407
|
+
expect(selectErrorMessage).toBeInTheDocument();
|
|
408
|
+
// Validate multi-select field
|
|
409
|
+
const multiSelectInputField = screen.getByText('If Unscheduled, actual scheduled reason multi-select', {
|
|
410
|
+
exact: true
|
|
411
|
+
});
|
|
412
|
+
expect(multiSelectInputField).toBeInTheDocument();
|
|
413
|
+
const multiSelectErrorMessage = screen.getByText('Patient visit marked as unscheduled. Please provide the scheduled multi-select reason.');
|
|
414
|
+
expect(multiSelectErrorMessage).toBeInTheDocument();
|
|
415
|
+
// Validate radio field
|
|
416
|
+
const radioInputField = screen.getByText('If Unscheduled, actual scheduled reason radio', {
|
|
417
|
+
selector: 'span'
|
|
418
|
+
});
|
|
419
|
+
expect(radioInputField).toBeInTheDocument();
|
|
420
|
+
const radioErrorMessage = screen.getByText('Patient visit marked as unscheduled. Please provide the scheduled radio reason.');
|
|
421
|
+
expect(radioErrorMessage).toBeInTheDocument();
|
|
422
|
+
expect(saveEncounterMock).toHaveBeenCalledTimes(0);
|
|
423
|
+
});
|
|
424
|
+
it('should validate form submission', async ()=>{
|
|
425
|
+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
|
426
|
+
await act(async ()=>{
|
|
427
|
+
renderForm(null, testEnrolmentForm);
|
|
428
|
+
});
|
|
429
|
+
screen.queryByRole('textbox', {
|
|
430
|
+
name: /enrolment date/i
|
|
431
|
+
});
|
|
432
|
+
const enrolmentDateField = screen.getByRole('textbox', {
|
|
433
|
+
name: /enrolment date/i
|
|
434
|
+
});
|
|
435
|
+
const uniqueIdField = screen.getByRole('textbox', {
|
|
436
|
+
name: /unique id/i
|
|
437
|
+
});
|
|
438
|
+
const motherEnrolledField = screen.getByRole('radio', {
|
|
439
|
+
name: /mother enrolled in pmtct program/i
|
|
440
|
+
});
|
|
441
|
+
const generalPopulationField = screen.getByRole('radio', {
|
|
442
|
+
name: /general population/i
|
|
443
|
+
});
|
|
444
|
+
await user.click(enrolmentDateField);
|
|
445
|
+
await user.paste('2023-09-09T00:00:00.000Z');
|
|
446
|
+
await user.type(uniqueIdField, 'U0-001109');
|
|
447
|
+
await user.click(motherEnrolledField);
|
|
448
|
+
await user.click(generalPopulationField);
|
|
449
|
+
await user.click(screen.getByRole('button', {
|
|
450
|
+
name: /save/i
|
|
451
|
+
}));
|
|
452
|
+
expect(saveEncounterMock).toHaveBeenCalledTimes(1);
|
|
453
|
+
expect(saveEncounterMock).toHaveBeenCalledWith(expect.any(AbortController), expect.any(Object), undefined);
|
|
454
|
+
expect(saveEncounterMock).toHaveReturned();
|
|
455
|
+
});
|
|
456
|
+
it('should clear stale submission validation errors', async ()=>{
|
|
457
|
+
await act(async ()=>{
|
|
458
|
+
renderForm(null, requiredTestForm);
|
|
459
|
+
});
|
|
460
|
+
await user.click(screen.getByRole('button', {
|
|
461
|
+
name: /save/i
|
|
462
|
+
}));
|
|
463
|
+
const inputFields = screen.getAllByLabelText(/Text question/i);
|
|
464
|
+
expect(inputFields).toHaveLength(2);
|
|
465
|
+
inputFields.forEach((inputField)=>{
|
|
466
|
+
expect(inputField).toHaveClass('cds--text-input--invalid');
|
|
467
|
+
});
|
|
468
|
+
let errorMessages = screen.getAllByText('Field is mandatory');
|
|
469
|
+
expect(errorMessages).toHaveLength(2);
|
|
470
|
+
// interact with first input
|
|
471
|
+
const textInput1 = inputFields[0];
|
|
472
|
+
await user.type(textInput1, 'Some value');
|
|
473
|
+
// assert validation errors were cleared for the first input
|
|
474
|
+
expect(textInput1).not.toHaveClass('cds--text-input--invalid');
|
|
475
|
+
errorMessages = screen.getAllByText('Field is mandatory');
|
|
476
|
+
expect(errorMessages).toHaveLength(1);
|
|
477
|
+
// interact with last input
|
|
478
|
+
const textInput2 = inputFields[1];
|
|
479
|
+
await user.type(textInput2, 'Some other value');
|
|
480
|
+
// assert validation errors were cleared
|
|
481
|
+
expect(textInput2).not.toHaveClass('cds--text-input--invalid');
|
|
482
|
+
errorMessages = screen.queryAllByText('Field is mandatory');
|
|
483
|
+
expect(errorMessages).toHaveLength(0);
|
|
484
|
+
});
|
|
485
|
+
it('should validate transient fields', async ()=>{
|
|
486
|
+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
|
487
|
+
await act(async ()=>{
|
|
488
|
+
renderForm(null, testEnrolmentForm);
|
|
489
|
+
});
|
|
490
|
+
const enrolmentDateField = screen.getByRole('textbox', {
|
|
491
|
+
name: /enrolment date/i
|
|
492
|
+
});
|
|
493
|
+
const uniqueIdField = screen.getByRole('textbox', {
|
|
494
|
+
name: /unique id/i
|
|
495
|
+
});
|
|
496
|
+
const motherEnrolledField = screen.getByRole('radio', {
|
|
497
|
+
name: /mother enrolled in pmtct program/i
|
|
498
|
+
});
|
|
499
|
+
const generalPopulationField = screen.getByRole('radio', {
|
|
500
|
+
name: /general population/i
|
|
501
|
+
});
|
|
502
|
+
await user.click(enrolmentDateField);
|
|
503
|
+
await user.paste('2023-09-09T00:00:00.000Z');
|
|
504
|
+
await user.type(uniqueIdField, 'U0-001109');
|
|
505
|
+
await user.click(motherEnrolledField);
|
|
506
|
+
await user.click(generalPopulationField);
|
|
507
|
+
await user.click(screen.getByRole('button', {
|
|
508
|
+
name: /save/i
|
|
509
|
+
}));
|
|
510
|
+
expect(enrolmentDateField).toHaveValue('09/09/2023');
|
|
511
|
+
const [abortController, encounter, encounterUuid] = saveEncounterMock.mock.calls[0];
|
|
512
|
+
expect(encounter.obs.length).toEqual(3);
|
|
513
|
+
expect(encounter.obs.find((obs)=>obs.formFieldPath === 'rfe-forms-hivEnrolmentDate')).toBeUndefined();
|
|
514
|
+
});
|
|
515
|
+
it('should evaluate post submission enabled flag expression', ()=>{
|
|
516
|
+
const encounters = [
|
|
517
|
+
{
|
|
518
|
+
uuid: '47cfe95b-357a-48f8-aa70-63eb5ae51916',
|
|
519
|
+
obs: [
|
|
520
|
+
{
|
|
521
|
+
formFieldPath: 'rfe-forms-tbProgramType',
|
|
522
|
+
value: {
|
|
523
|
+
display: 'Tuberculosis treatment program',
|
|
524
|
+
uuid: '160541AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
formFieldPath: 'rfe-forms-tbRegDate',
|
|
529
|
+
value: '2023-12-05T00:00:00.000+0000'
|
|
530
|
+
}
|
|
531
|
+
]
|
|
532
|
+
}
|
|
533
|
+
];
|
|
534
|
+
const expression1 = "tbProgramType === '160541AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'";
|
|
535
|
+
const expression2 = "tbProgramType === '160052AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'";
|
|
536
|
+
let enabled = evaluatePostSubmissionExpression(expression1, encounters);
|
|
537
|
+
expect(enabled).toEqual(true);
|
|
538
|
+
enabled = evaluatePostSubmissionExpression(expression2, encounters);
|
|
539
|
+
expect(enabled).toEqual(false);
|
|
540
|
+
});
|
|
541
|
+
it('should test post submission actions', async ()=>{
|
|
542
|
+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
|
543
|
+
await act(async ()=>renderForm(null, postSubmissionTestForm));
|
|
544
|
+
const drugSensitiveProgramField = screen.getByRole('radio', {
|
|
545
|
+
name: /drug-susceptible \(DS\) tb program/i
|
|
546
|
+
});
|
|
547
|
+
const treatmentNumber = screen.getByRole('spinbutton', {
|
|
548
|
+
name: /ds tb treatment number/i
|
|
549
|
+
});
|
|
550
|
+
await user.click(drugSensitiveProgramField);
|
|
551
|
+
await user.click(screen.getByRole('textbox', {
|
|
552
|
+
name: /date enrolled in tuberculosis \(TB\) care/i
|
|
553
|
+
}));
|
|
554
|
+
await user.paste('2023-12-12');
|
|
555
|
+
await user.click(treatmentNumber);
|
|
556
|
+
await user.paste('11200');
|
|
557
|
+
await user.click(screen.getByRole('button', {
|
|
558
|
+
name: /save/i
|
|
559
|
+
}));
|
|
560
|
+
expect(saveEncounterMock).toHaveBeenCalled();
|
|
561
|
+
});
|
|
562
|
+
it('should save on form submission on initial state', async ()=>{
|
|
563
|
+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
|
564
|
+
await act(async ()=>{
|
|
565
|
+
renderForm(null, conditionalRequiredTestForm);
|
|
566
|
+
});
|
|
567
|
+
await assertFormHasAllFields(screen, [
|
|
568
|
+
{
|
|
569
|
+
fieldName: 'Was this visit scheduled?',
|
|
570
|
+
fieldType: 'select'
|
|
571
|
+
}
|
|
572
|
+
]);
|
|
573
|
+
await user.click(screen.getByRole('button', {
|
|
574
|
+
name: /save/i
|
|
575
|
+
}));
|
|
576
|
+
expect(saveEncounterMock).toHaveBeenCalled();
|
|
577
|
+
expect(saveEncounterMock).toHaveBeenCalledWith(expect.any(AbortController), expect.any(Object), undefined);
|
|
578
|
+
expect(saveEncounterMock).toHaveReturned();
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
describe('Obs group count validation', ()=>{
|
|
582
|
+
it('should limit number of repeatable obs groups based on configured repeat limit', async ()=>{
|
|
583
|
+
await act(async ()=>renderForm(null, labourAndDeliveryTestForm));
|
|
584
|
+
const birthCount = screen.getByRole('spinbutton', {
|
|
585
|
+
name: /number of babies born from this pregnancy/i
|
|
586
|
+
});
|
|
587
|
+
expect(birthCount).toBeInTheDocument();
|
|
588
|
+
await user.type(birthCount, '2');
|
|
589
|
+
expect(birthCount).toHaveValue(2);
|
|
590
|
+
// Male radio button in 'sex at birth' field
|
|
591
|
+
const maleSexLabel = screen.getByRole('radio', {
|
|
592
|
+
name: /^male$/i
|
|
593
|
+
});
|
|
594
|
+
expect(maleSexLabel).toBeInTheDocument();
|
|
595
|
+
await user.click(maleSexLabel);
|
|
596
|
+
expect(maleSexLabel).toBeChecked();
|
|
597
|
+
// Missing radio button in "infant status" field
|
|
598
|
+
const infantStatus = screen.getByRole('group', {
|
|
599
|
+
name: /infant status at birth/i
|
|
600
|
+
});
|
|
601
|
+
const infantStatusMissingLabel = within(infantStatus).getByRole('radio', {
|
|
602
|
+
name: 'Missing'
|
|
603
|
+
});
|
|
604
|
+
expect(infantStatusMissingLabel).toBeInTheDocument();
|
|
605
|
+
await user.click(infantStatusMissingLabel);
|
|
606
|
+
expect(infantStatusMissingLabel).toBeChecked();
|
|
607
|
+
const dateOfBirth = screen.getByRole('textbox', {
|
|
608
|
+
name: /date of birth/i
|
|
609
|
+
});
|
|
610
|
+
expect(dateOfBirth).toBeInTheDocument();
|
|
611
|
+
await user.click(dateOfBirth);
|
|
612
|
+
await user.paste('2022-03-11T00:00:00.000Z');
|
|
613
|
+
await user.tab();
|
|
614
|
+
expect(dateOfBirth).toHaveValue('11/03/2022');
|
|
615
|
+
await user.click(screen.getByRole('button', {
|
|
616
|
+
name: 'Add'
|
|
617
|
+
}));
|
|
618
|
+
expect(screen.getByRole('button', {
|
|
619
|
+
name: 'Add'
|
|
620
|
+
})).toBeDisabled();
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
describe('Filter answer options', ()=>{
|
|
624
|
+
it('should filter dropdown options based on value in count input field', async ()=>{
|
|
625
|
+
await act(async ()=>renderForm(null, filterAnswerOptionsTestForm));
|
|
626
|
+
const recommendationDropdown = screen.getByRole('combobox', {
|
|
627
|
+
name: /Testing Recommendations/i
|
|
628
|
+
});
|
|
629
|
+
const testCountField = screen.getByRole('spinbutton', {
|
|
630
|
+
name: 'How many times have you tested in the past?'
|
|
631
|
+
});
|
|
632
|
+
await user.click(recommendationDropdown);
|
|
633
|
+
expect(screen.queryByRole('option', {
|
|
634
|
+
name: /Perfect testing/i
|
|
635
|
+
})).toBeInTheDocument();
|
|
636
|
+
expect(screen.queryByRole('option', {
|
|
637
|
+
name: /Minimal testing/i
|
|
638
|
+
})).toBeInTheDocument();
|
|
639
|
+
expect(screen.queryByRole('option', {
|
|
640
|
+
name: /Un-decisive/i
|
|
641
|
+
})).toBeInTheDocument();
|
|
642
|
+
expect(screen.queryByRole('option', {
|
|
643
|
+
name: /Not ideal/i
|
|
644
|
+
})).toBeInTheDocument();
|
|
645
|
+
await user.click(recommendationDropdown);
|
|
646
|
+
await user.type(testCountField, '6');
|
|
647
|
+
await user.click(recommendationDropdown);
|
|
648
|
+
expect(testCountField).toHaveValue(6);
|
|
649
|
+
expect(screen.queryByRole('option', {
|
|
650
|
+
name: /Perfect testing/i
|
|
651
|
+
})).toBeNull();
|
|
652
|
+
expect(screen.queryByRole('option', {
|
|
653
|
+
name: /Minimal testing/i
|
|
654
|
+
})).toBeNull();
|
|
655
|
+
expect(screen.queryByRole('option', {
|
|
656
|
+
name: /Un-decisive/i
|
|
657
|
+
})).toBeInTheDocument();
|
|
658
|
+
expect(screen.queryByRole('option', {
|
|
659
|
+
name: /Not ideal/i
|
|
660
|
+
})).toBeInTheDocument();
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
describe('Hide pages and sections', ()=>{
|
|
664
|
+
it('should hide/show section based on field value', async ()=>{
|
|
665
|
+
await act(async ()=>renderForm(null, hidePagesAndSectionsForm));
|
|
666
|
+
// assert section "Section 1B" is hidden at initial render
|
|
667
|
+
try {
|
|
668
|
+
await screen.findByText('Section 1B');
|
|
669
|
+
fail('The section named "Section 1B" should be hidden');
|
|
670
|
+
} catch (err) {
|
|
671
|
+
expect(err.message.includes('Unable to find an element with the text: Section 1B')).toBeTruthy();
|
|
672
|
+
}
|
|
673
|
+
// user interactions to make section visible
|
|
674
|
+
const hideSection1bField = await findTextOrDateInput(screen, 'Hide Section 1B');
|
|
675
|
+
await user.type(hideSection1bField, 'Some value');
|
|
676
|
+
const section1b = await screen.findByText('Section 1B');
|
|
677
|
+
expect(section1b).toBeInTheDocument();
|
|
678
|
+
});
|
|
679
|
+
it('should hide/show page based on field value', async ()=>{
|
|
680
|
+
await act(async ()=>renderForm(null, hidePagesAndSectionsForm));
|
|
681
|
+
// assert page "Page 2" is visible at initial render
|
|
682
|
+
const page2 = await screen.findByText('Page 2');
|
|
683
|
+
expect(page2).toBeInTheDocument();
|
|
684
|
+
// user interactions to hide page
|
|
685
|
+
const hideSection1bField = await findTextOrDateInput(screen, 'Hide Section 1B');
|
|
686
|
+
await user.type(hideSection1bField, 'Some value');
|
|
687
|
+
const choice2RadioOption = screen.getByRole('radio', {
|
|
688
|
+
name: /Choice 2/i
|
|
689
|
+
});
|
|
690
|
+
await user.click(choice2RadioOption);
|
|
691
|
+
// assert page is hidden
|
|
692
|
+
try {
|
|
693
|
+
await screen.findByText('Page 2');
|
|
694
|
+
fail('The page named "Page 2" should be hidden');
|
|
695
|
+
} catch (err) {
|
|
696
|
+
expect(err.message.includes('Unable to find an element with the text: Page 2')).toBeTruthy();
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
});
|
|
700
|
+
describe('Default values', ()=>{
|
|
701
|
+
let originalConsoleError;
|
|
702
|
+
beforeEach(()=>{
|
|
703
|
+
originalConsoleError = console.error;
|
|
704
|
+
console.error = jest.fn();
|
|
705
|
+
});
|
|
706
|
+
afterEach(()=>{
|
|
707
|
+
console.error = originalConsoleError;
|
|
708
|
+
});
|
|
709
|
+
it('should initialize fields with default values', async ()=>{
|
|
710
|
+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
|
711
|
+
await act(async ()=>renderForm(null, defaultValuesForm));
|
|
712
|
+
// text field
|
|
713
|
+
const textField = await findTextOrDateInput(screen, 'Text field with Default Value');
|
|
714
|
+
expect(textField).toHaveValue('Value text');
|
|
715
|
+
// dropdown field
|
|
716
|
+
const dropdownField = await findSelectInput(screen, 'Dropdown with Default Value');
|
|
717
|
+
expect(dropdownField.title).toBe('Choice 2');
|
|
718
|
+
// dropdown with an invalid default value
|
|
719
|
+
const invalidDropdownField = await findSelectInput(screen, 'Dropdown with an invalid Default Value');
|
|
720
|
+
expect(invalidDropdownField.title).toBe('Choose an option');
|
|
721
|
+
await user.click(screen.getByRole('button', {
|
|
722
|
+
name: /save/i
|
|
723
|
+
}));
|
|
724
|
+
const encounter = saveEncounterMock.mock.calls[0][1];
|
|
725
|
+
expect(encounter.obs).toEqual([
|
|
726
|
+
{
|
|
727
|
+
value: 'Value text',
|
|
728
|
+
concept: 'f82ba2b7-3849-4ad0-b867-36881e59f5c8',
|
|
729
|
+
formFieldNamespace: 'rfe-forms',
|
|
730
|
+
formFieldPath: 'rfe-forms-sampleQuestion'
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
value: '6b4e859c-86ca-41e5-b1c4-017889653b59',
|
|
734
|
+
concept: '8cdea80a-d167-431c-8278-246c7a1f913b',
|
|
735
|
+
formFieldNamespace: 'rfe-forms',
|
|
736
|
+
formFieldPath: 'rfe-forms-codedQuestion'
|
|
737
|
+
}
|
|
738
|
+
]);
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
describe('Calculated values', ()=>{
|
|
742
|
+
it('should evaluate BMI', async ()=>{
|
|
743
|
+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
|
744
|
+
await act(async ()=>renderForm(null, bmiForm));
|
|
745
|
+
const bmiField = screen.getByRole('textbox', {
|
|
746
|
+
name: /bmi/i
|
|
747
|
+
});
|
|
748
|
+
const heightField = screen.getByLabelText(/height/i);
|
|
749
|
+
const weightField = screen.getByLabelText(/weight/i);
|
|
750
|
+
await user.type(weightField, '50');
|
|
751
|
+
await user.type(heightField, '150');
|
|
752
|
+
await user.tab();
|
|
753
|
+
expect(heightField).toHaveValue(150);
|
|
754
|
+
expect(weightField).toHaveValue(50);
|
|
755
|
+
expect(bmiField).toHaveValue('22.2');
|
|
756
|
+
await user.click(screen.getByRole('button', {
|
|
757
|
+
name: /save/i
|
|
758
|
+
}));
|
|
759
|
+
const encounter = saveEncounterMock.mock.calls[0][1];
|
|
760
|
+
expect(encounter.obs.length).toEqual(3);
|
|
761
|
+
expect(encounter.obs.find((obs)=>obs.formFieldPath === 'rfe-forms-bmi').value).toBe(22.2);
|
|
762
|
+
});
|
|
763
|
+
it('should evaluate BSA', async ()=>{
|
|
764
|
+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
|
765
|
+
await act(async ()=>renderForm(null, bsaForm));
|
|
766
|
+
const bsaField = screen.getByRole('textbox', {
|
|
767
|
+
name: /bsa/i
|
|
768
|
+
});
|
|
769
|
+
const heightField = screen.getByRole('spinbutton', {
|
|
770
|
+
name: /height/i
|
|
771
|
+
});
|
|
772
|
+
const weightField = screen.getByRole('spinbutton', {
|
|
773
|
+
name: /weight/i
|
|
774
|
+
});
|
|
775
|
+
await user.type(heightField, '190.5');
|
|
776
|
+
await user.type(weightField, '95');
|
|
777
|
+
await user.tab();
|
|
778
|
+
expect(heightField).toHaveValue(190.5);
|
|
779
|
+
expect(weightField).toHaveValue(95);
|
|
780
|
+
expect(bsaField).toHaveValue('2.24');
|
|
781
|
+
await user.click(screen.getByRole('button', {
|
|
782
|
+
name: /save/i
|
|
783
|
+
}));
|
|
784
|
+
const encounter = saveEncounterMock.mock.calls[0][1];
|
|
785
|
+
expect(encounter.obs.length).toEqual(3);
|
|
786
|
+
expect(encounter.obs.find((obs)=>obs.formFieldPath === 'rfe-forms-bsa').value).toBe(2.24);
|
|
787
|
+
});
|
|
788
|
+
it('should evaluate EDD', async ()=>{
|
|
789
|
+
await act(async ()=>renderForm(null, eddForm));
|
|
790
|
+
const eddField = screen.getByRole('textbox', {
|
|
791
|
+
name: /edd/i
|
|
792
|
+
});
|
|
793
|
+
const lmpField = screen.getByRole('textbox', {
|
|
794
|
+
name: /lmp/i
|
|
795
|
+
});
|
|
796
|
+
await user.click(lmpField);
|
|
797
|
+
await user.paste('2022-07-06T00:00:00.000Z');
|
|
798
|
+
await user.tab();
|
|
799
|
+
expect(lmpField).toHaveValue('06/07/2022');
|
|
800
|
+
expect(eddField).toHaveValue('12/04/2023');
|
|
801
|
+
});
|
|
802
|
+
it('should evaluate months on ART', async ()=>{
|
|
803
|
+
await act(async ()=>renderForm(null, monthsOnArtForm));
|
|
804
|
+
jest.useFakeTimers({
|
|
805
|
+
doNotFake: [
|
|
806
|
+
'nextTick',
|
|
807
|
+
'setImmediate',
|
|
808
|
+
'clearImmediate',
|
|
809
|
+
'setInterval',
|
|
810
|
+
'clearInterval',
|
|
811
|
+
'setTimeout',
|
|
812
|
+
'clearTimeout'
|
|
813
|
+
]
|
|
814
|
+
}).setSystemTime(new Date(2022, 9, 1));
|
|
815
|
+
let artStartDateField = screen.getByRole('textbox', {
|
|
816
|
+
name: /antiretroviral treatment start date/i
|
|
817
|
+
});
|
|
818
|
+
let monthsOnArtField = screen.getByRole('spinbutton', {
|
|
819
|
+
name: /months on art/i
|
|
820
|
+
});
|
|
821
|
+
expect(artStartDateField).not.toHaveValue();
|
|
822
|
+
expect(monthsOnArtField).not.toHaveValue();
|
|
823
|
+
await user.click(artStartDateField);
|
|
824
|
+
await user.paste('2022-02-05');
|
|
825
|
+
await user.tab();
|
|
826
|
+
expect(monthsOnArtField).toHaveValue(7);
|
|
827
|
+
});
|
|
828
|
+
it('should evaluate viral load status', async ()=>{
|
|
829
|
+
renderForm(null, viralLoadStatusForm);
|
|
830
|
+
let viralLoadCountField = await screen.findByRole('spinbutton', {
|
|
831
|
+
name: /viral load count/i
|
|
832
|
+
});
|
|
833
|
+
let suppressedField = await screen.findByRole('radio', {
|
|
834
|
+
name: /^suppressed$/i
|
|
835
|
+
});
|
|
836
|
+
let unsuppressedField = await screen.findByRole('radio', {
|
|
837
|
+
name: /unsuppressed/i
|
|
838
|
+
});
|
|
839
|
+
await user.type(viralLoadCountField, '30');
|
|
840
|
+
await user.tab();
|
|
841
|
+
expect(viralLoadCountField).toHaveValue(30);
|
|
842
|
+
expect(suppressedField).toBeChecked();
|
|
843
|
+
expect(unsuppressedField).not.toBeChecked();
|
|
844
|
+
});
|
|
845
|
+
it('should only show question when age is under 5', async ()=>{
|
|
846
|
+
await act(async ()=>renderForm(null, ageValidationForm));
|
|
847
|
+
let enrollmentDate = screen.getByRole('textbox', {
|
|
848
|
+
name: /enrollmentDate/
|
|
849
|
+
});
|
|
850
|
+
expect(enrollmentDate).not.toHaveValue();
|
|
851
|
+
await user.click(enrollmentDate);
|
|
852
|
+
await user.paste('1975-07-06T00:00:00.000Z');
|
|
853
|
+
await user.tab();
|
|
854
|
+
let mrn = screen.getByRole('textbox', {
|
|
855
|
+
name: /mrn/i
|
|
856
|
+
});
|
|
857
|
+
expect(enrollmentDate).toHaveValue('06/07/1975');
|
|
858
|
+
expect(mrn).toBeVisible();
|
|
859
|
+
});
|
|
860
|
+
it('should load initial value from external arbitrary data source', async ()=>{
|
|
861
|
+
await act(async ()=>renderForm(null, externalDataSourceForm));
|
|
862
|
+
const bodyWeightField = screen.getByRole('spinbutton', {
|
|
863
|
+
name: /body weight/i
|
|
864
|
+
});
|
|
865
|
+
expect(bodyWeightField).toHaveValue(60);
|
|
866
|
+
});
|
|
867
|
+
it('should evaluate next visit date', async ()=>{
|
|
868
|
+
await act(async ()=>renderForm(null, nextVisitForm));
|
|
869
|
+
const followupDateField = screen.getByRole('textbox', {
|
|
870
|
+
name: /followup date/i
|
|
871
|
+
});
|
|
872
|
+
const nextVisitDateField = screen.getByRole('textbox', {
|
|
873
|
+
name: /next visit date/i
|
|
874
|
+
});
|
|
875
|
+
const arvDispensedInDaysField = screen.getByRole('spinbutton', {
|
|
876
|
+
name: /arv dispensed in days/i
|
|
877
|
+
});
|
|
878
|
+
await user.click(followupDateField);
|
|
879
|
+
await user.paste('2022-07-06T00:00:00.000Z');
|
|
880
|
+
await user.tab();
|
|
881
|
+
await user.click(arvDispensedInDaysField);
|
|
882
|
+
await user.type(arvDispensedInDaysField, '120');
|
|
883
|
+
await user.tab();
|
|
884
|
+
expect(arvDispensedInDaysField).toHaveValue(120);
|
|
885
|
+
expect(nextVisitDateField).toHaveValue('03/11/2022');
|
|
886
|
+
});
|
|
887
|
+
});
|
|
888
|
+
describe('Concept references', ()=>{
|
|
889
|
+
it('should add default labels based on concept display and substitute mapping references with uuids', async ()=>{
|
|
890
|
+
await act(async ()=>renderForm(null, referenceByMappingForm));
|
|
891
|
+
const yes = await screen.findAllByRole('radio', {
|
|
892
|
+
name: 'Yes'
|
|
893
|
+
});
|
|
894
|
+
const no = await screen.findAllByRole('radio', {
|
|
895
|
+
name: 'No'
|
|
896
|
+
});
|
|
897
|
+
await assertFormHasAllFields(screen, [
|
|
898
|
+
{
|
|
899
|
+
fieldName: 'Cough',
|
|
900
|
+
fieldType: 'radio'
|
|
901
|
+
},
|
|
902
|
+
{
|
|
903
|
+
fieldName: 'Occurrence of trauma',
|
|
904
|
+
fieldType: 'radio'
|
|
905
|
+
}
|
|
906
|
+
]);
|
|
907
|
+
expect(no[0].value).toBe('3cd6f86c-26fe-102b-80cb-0017a47871b2');
|
|
908
|
+
expect(no[1].value).toBe('3cd6f86c-26fe-102b-80cb-0017a47871b2');
|
|
909
|
+
expect(yes[0].value).toBe('3cd6f600-26fe-102b-80cb-0017a47871b2');
|
|
910
|
+
expect(yes[1].value).toBe('3cd6f600-26fe-102b-80cb-0017a47871b2');
|
|
911
|
+
});
|
|
912
|
+
});
|
|
913
|
+
describe('Obs group', ()=>{
|
|
914
|
+
it('should not render empty obs group', async ()=>{
|
|
915
|
+
await act(async ()=>{
|
|
916
|
+
renderForm(null, obsGroupTestForm);
|
|
917
|
+
});
|
|
918
|
+
// Check that only one obs group is initially rendered
|
|
919
|
+
const initialGroups = screen.getAllByRole('group', {
|
|
920
|
+
name: /My Group|Dependents Group/i
|
|
921
|
+
});
|
|
922
|
+
expect(initialGroups.length).toBe(1);
|
|
923
|
+
const dependentTypeRadios = screen.queryAllByRole('radio', {
|
|
924
|
+
name: /child|spouse/i
|
|
925
|
+
});
|
|
926
|
+
expect(dependentTypeRadios.length).toBe(0);
|
|
927
|
+
// Select "Yes" for having dependents
|
|
928
|
+
const yesRadio = screen.getByRole('radio', {
|
|
929
|
+
name: /yes/i
|
|
930
|
+
});
|
|
931
|
+
await user.click(yesRadio);
|
|
932
|
+
// Now the dependent type radios should be visible
|
|
933
|
+
const visibleDependentTypeRadios = screen.getAllByRole('radio', {
|
|
934
|
+
name: /child|spouse/i
|
|
935
|
+
});
|
|
936
|
+
expect(visibleDependentTypeRadios.length).toBe(2);
|
|
937
|
+
// Check that the group label is still hidden since it only has one visible field
|
|
938
|
+
const dependentsGroupResults = screen.queryAllByRole('group', {
|
|
939
|
+
name: /Dependents Group/i
|
|
940
|
+
});
|
|
941
|
+
expect(dependentsGroupResults.length).toBe(0);
|
|
942
|
+
// Check that dependent name and age are still hidden
|
|
943
|
+
const hiddenDependentNameInput = screen.queryByRole('textbox', {
|
|
944
|
+
name: /dependent name/i
|
|
945
|
+
});
|
|
946
|
+
const hiddenDependentAgeInput = screen.queryByRole('spinbutton', {
|
|
947
|
+
name: /dependent age/i
|
|
948
|
+
});
|
|
949
|
+
expect(hiddenDependentNameInput).toBeNull();
|
|
950
|
+
expect(hiddenDependentAgeInput).toBeNull();
|
|
951
|
+
// Select "Child" as dependent type
|
|
952
|
+
await user.click(visibleDependentTypeRadios[0]);
|
|
953
|
+
// Check the visibility of the group label
|
|
954
|
+
const dependentsGroup = screen.getAllByRole('group', {
|
|
955
|
+
name: /Dependents Group/i
|
|
956
|
+
})[0];
|
|
957
|
+
expect(dependentsGroup).toBeInTheDocument();
|
|
958
|
+
// Check that dependent name and age are now visible
|
|
959
|
+
const dependentNameInput = screen.getByRole('textbox', {
|
|
960
|
+
name: /dependent name/i
|
|
961
|
+
});
|
|
962
|
+
const dependentAgeInput = screen.getByRole('spinbutton', {
|
|
963
|
+
name: /dependent age/i
|
|
964
|
+
});
|
|
965
|
+
expect(dependentNameInput).toBeInTheDocument();
|
|
966
|
+
expect(dependentAgeInput).toBeInTheDocument();
|
|
967
|
+
});
|
|
968
|
+
it('should save obs group on form submission', async ()=>{
|
|
969
|
+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
|
970
|
+
await act(async ()=>{
|
|
971
|
+
renderForm(null, obsGroupTestForm);
|
|
972
|
+
});
|
|
973
|
+
// Fill out the obs group fields
|
|
974
|
+
const dateOfBirth = screen.getByRole('textbox', {
|
|
975
|
+
name: /date of birth/i
|
|
976
|
+
});
|
|
977
|
+
const maleRadio = screen.getByRole('radio', {
|
|
978
|
+
name: /^male$/i
|
|
979
|
+
});
|
|
980
|
+
await user.click(dateOfBirth);
|
|
981
|
+
await user.paste('2020-09-09T00:00:00.000Z');
|
|
982
|
+
await user.click(maleRadio);
|
|
983
|
+
// Submit the form
|
|
984
|
+
await user.click(screen.getByRole('button', {
|
|
985
|
+
name: /save/i
|
|
986
|
+
}));
|
|
987
|
+
// Verify the encounter was saved with the correct structure
|
|
988
|
+
expect(saveEncounterMock).toHaveBeenCalledTimes(1);
|
|
989
|
+
const [_, encounter] = saveEncounterMock.mock.calls[0];
|
|
990
|
+
expect(encounter.obs.length).toBe(1);
|
|
991
|
+
expect(encounter.obs[0]).toEqual({
|
|
992
|
+
groupMembers: [
|
|
993
|
+
{
|
|
994
|
+
value: '1534AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
995
|
+
concept: '1587AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
996
|
+
formFieldNamespace: 'rfe-forms',
|
|
997
|
+
formFieldPath: 'rfe-forms-childSex'
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
value: '2020-09-09',
|
|
1001
|
+
concept: '164802AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
1002
|
+
formFieldNamespace: 'rfe-forms',
|
|
1003
|
+
formFieldPath: 'rfe-forms-birthDate'
|
|
1004
|
+
}
|
|
1005
|
+
],
|
|
1006
|
+
concept: '1c70c490-cafa-4c95-9fdd-a30b62bb78b8',
|
|
1007
|
+
formFieldNamespace: 'rfe-forms',
|
|
1008
|
+
formFieldPath: 'rfe-forms-myGroup'
|
|
1009
|
+
});
|
|
1010
|
+
});
|
|
1011
|
+
it('should test addition of a repeating group', async ()=>{
|
|
1012
|
+
await act(async ()=>{
|
|
1013
|
+
renderForm(null, obsGroupTestForm);
|
|
1014
|
+
});
|
|
1015
|
+
const addButton = screen.getByRole('button', {
|
|
1016
|
+
name: 'Add'
|
|
1017
|
+
});
|
|
1018
|
+
expect(addButton).toBeInTheDocument();
|
|
1019
|
+
expect(screen.getByRole('textbox', {
|
|
1020
|
+
name: /date of birth/i
|
|
1021
|
+
})).toBeInTheDocument();
|
|
1022
|
+
expect(screen.getByRole('radio', {
|
|
1023
|
+
name: /^male$/i
|
|
1024
|
+
})).toBeInTheDocument();
|
|
1025
|
+
expect(screen.getByRole('radio', {
|
|
1026
|
+
name: /female/i
|
|
1027
|
+
})).toBeInTheDocument();
|
|
1028
|
+
await user.click(addButton);
|
|
1029
|
+
expect(screen.getByRole('button', {
|
|
1030
|
+
name: /Remove/i
|
|
1031
|
+
})).toBeInTheDocument();
|
|
1032
|
+
await waitFor(()=>{
|
|
1033
|
+
expect(screen.getAllByRole('radio', {
|
|
1034
|
+
name: /^male$/i
|
|
1035
|
+
})).toHaveLength(2);
|
|
1036
|
+
expect(screen.getAllByRole('radio', {
|
|
1037
|
+
name: /female/i
|
|
1038
|
+
})).toHaveLength(2);
|
|
1039
|
+
expect(screen.getAllByRole('textbox', {
|
|
1040
|
+
name: /date of birth/i
|
|
1041
|
+
})).toHaveLength(2);
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
1044
|
+
it('should test deletion of a group', async ()=>{
|
|
1045
|
+
await act(async ()=>{
|
|
1046
|
+
renderForm(null, obsGroupTestForm);
|
|
1047
|
+
});
|
|
1048
|
+
const addButton = screen.getByRole('button', {
|
|
1049
|
+
name: 'Add'
|
|
1050
|
+
});
|
|
1051
|
+
expect(addButton).toBeInTheDocument();
|
|
1052
|
+
expect(screen.queryByRole('textbox', {
|
|
1053
|
+
name: /date of birth/i
|
|
1054
|
+
})).toBeInTheDocument();
|
|
1055
|
+
expect(screen.getByRole('radio', {
|
|
1056
|
+
name: /^male$/i
|
|
1057
|
+
})).toBeInTheDocument();
|
|
1058
|
+
expect(screen.getByRole('radio', {
|
|
1059
|
+
name: /female/i
|
|
1060
|
+
})).toBeInTheDocument();
|
|
1061
|
+
const groups = screen.getAllByRole('group', {
|
|
1062
|
+
name: /my group/i
|
|
1063
|
+
});
|
|
1064
|
+
expect(groups.length).toBe(1);
|
|
1065
|
+
await user.click(addButton);
|
|
1066
|
+
const removeGroupButton = screen.getByRole('button', {
|
|
1067
|
+
name: /Remove/i
|
|
1068
|
+
});
|
|
1069
|
+
expect(removeGroupButton).toBeInTheDocument();
|
|
1070
|
+
await user.click(removeGroupButton);
|
|
1071
|
+
expect(removeGroupButton).not.toBeInTheDocument();
|
|
1072
|
+
});
|
|
1073
|
+
});
|
|
1074
|
+
describe('Read only mode', ()=>{
|
|
1075
|
+
it('should ensure that each read-only field is not editable', async ()=>{
|
|
1076
|
+
await act(async ()=>{
|
|
1077
|
+
renderForm(null, readOnlyValidationForm);
|
|
1078
|
+
});
|
|
1079
|
+
const visitPunctualityTextbox = screen.getByLabelText(/visit punctuality/i);
|
|
1080
|
+
expect(visitPunctualityTextbox).toHaveAttribute('readonly');
|
|
1081
|
+
const visitNotesTextbox = screen.getByLabelText(/visit notes/i);
|
|
1082
|
+
expect(visitNotesTextbox).toHaveAttribute('readonly');
|
|
1083
|
+
});
|
|
1084
|
+
});
|
|
1085
|
+
describe('Form view mode', ()=>{
|
|
1086
|
+
it('should ensure that the form is not editable in view mode', async ()=>{
|
|
1087
|
+
await act(async ()=>{
|
|
1088
|
+
renderForm(null, htsPocForm, null, 'view');
|
|
1089
|
+
});
|
|
1090
|
+
const testingHistoryButton = screen.getByRole('button', {
|
|
1091
|
+
name: /Testing history/i
|
|
1092
|
+
});
|
|
1093
|
+
expect(testingHistoryButton).toBeInTheDocument();
|
|
1094
|
+
const hivTestButton = screen.getByRole('button', {
|
|
1095
|
+
name: /When was the HIV test conducted\?:/i
|
|
1096
|
+
});
|
|
1097
|
+
expect(hivTestButton).toBeInTheDocument();
|
|
1098
|
+
const blankFields = screen.getAllByText(/\(Blank\)/i);
|
|
1099
|
+
blankFields.forEach((blankField)=>{
|
|
1100
|
+
expect(blankField).toBeInTheDocument();
|
|
1101
|
+
});
|
|
1102
|
+
const inputs = screen.queryAllByRole('textbox');
|
|
1103
|
+
inputs.forEach((input)=>{
|
|
1104
|
+
expect(input).toHaveAttribute('readonly');
|
|
1105
|
+
});
|
|
1106
|
+
const interactiveElements = screen.queryAllByRole('textbox', {
|
|
1107
|
+
hidden: false
|
|
1108
|
+
});
|
|
1109
|
+
expect(interactiveElements).toHaveLength(0);
|
|
1110
|
+
expect(screen.queryByRole('button', {
|
|
1111
|
+
name: /save/i
|
|
1112
|
+
})).toBeDisabled();
|
|
1113
|
+
});
|
|
1114
|
+
});
|
|
1115
|
+
describe('Encounter diagnosis', ()=>{
|
|
1116
|
+
it('should test addition of a diagnosis', async ()=>{
|
|
1117
|
+
await act(async ()=>{
|
|
1118
|
+
renderForm(null, diagnosisForm);
|
|
1119
|
+
});
|
|
1120
|
+
const testDiagnosis1AddButton = screen.getAllByRole('button', {
|
|
1121
|
+
name: 'Add'
|
|
1122
|
+
})[0];
|
|
1123
|
+
await user.click(testDiagnosis1AddButton);
|
|
1124
|
+
await waitFor(()=>{
|
|
1125
|
+
expect(screen.getAllByRole('combobox', {
|
|
1126
|
+
name: /^test diagnosis 1$/i
|
|
1127
|
+
}).length).toEqual(2);
|
|
1128
|
+
});
|
|
1129
|
+
expect(screen.getByRole('button', {
|
|
1130
|
+
name: /Remove/i
|
|
1131
|
+
})).toBeInTheDocument();
|
|
1132
|
+
});
|
|
1133
|
+
it('should render all diagnosis fields', async ()=>{
|
|
1134
|
+
await act(async ()=>{
|
|
1135
|
+
renderForm(null, diagnosisForm);
|
|
1136
|
+
});
|
|
1137
|
+
const diagnosisFields = screen.getAllByRole('combobox', {
|
|
1138
|
+
name: /test diagnosis 1|test diagnosis 2/i
|
|
1139
|
+
});
|
|
1140
|
+
expect(diagnosisFields.length).toBe(2);
|
|
1141
|
+
});
|
|
1142
|
+
it('should be possible to delete cloned fields', async ()=>{
|
|
1143
|
+
await act(async ()=>{
|
|
1144
|
+
renderForm(null, diagnosisForm);
|
|
1145
|
+
});
|
|
1146
|
+
const testDiagnosis1AddButton = screen.getAllByRole('button', {
|
|
1147
|
+
name: 'Add'
|
|
1148
|
+
})[0];
|
|
1149
|
+
await user.click(testDiagnosis1AddButton);
|
|
1150
|
+
await waitFor(()=>{
|
|
1151
|
+
expect(screen.getAllByRole('combobox', {
|
|
1152
|
+
name: /^test diagnosis 1$/i
|
|
1153
|
+
}).length).toEqual(2);
|
|
1154
|
+
});
|
|
1155
|
+
const removeButton = screen.getByRole('button', {
|
|
1156
|
+
name: /Remove/i
|
|
1157
|
+
});
|
|
1158
|
+
await user.click(removeButton);
|
|
1159
|
+
expect(removeButton).not.toBeInTheDocument();
|
|
1160
|
+
});
|
|
1161
|
+
it('should save diagnosis field on form submission', async ()=>{
|
|
1162
|
+
await act(async ()=>{
|
|
1163
|
+
renderForm(null, diagnosisForm);
|
|
1164
|
+
});
|
|
1165
|
+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
|
1166
|
+
const combobox = await findSelectInput(screen, 'Test Diagnosis 1');
|
|
1167
|
+
expect(combobox).toHaveAttribute('placeholder', 'Search...');
|
|
1168
|
+
await user.click(combobox);
|
|
1169
|
+
await user.type(combobox, 'stage');
|
|
1170
|
+
expect(screen.getByText(/stage 1/)).toBeInTheDocument();
|
|
1171
|
+
expect(screen.getByText(/stage 2/)).toBeInTheDocument();
|
|
1172
|
+
expect(screen.getByText(/stage 3/)).toBeInTheDocument();
|
|
1173
|
+
await user.click(screen.getByText('stage 1'));
|
|
1174
|
+
await user.click(screen.getByRole('button', {
|
|
1175
|
+
name: /save/i
|
|
1176
|
+
}));
|
|
1177
|
+
expect(saveEncounterMock).toHaveBeenCalledTimes(1);
|
|
1178
|
+
const [_, encounter] = saveEncounterMock.mock.calls[0];
|
|
1179
|
+
expect(encounter.diagnoses.length).toBe(1);
|
|
1180
|
+
expect(encounter.diagnoses[0]).toEqual({
|
|
1181
|
+
patient: '8673ee4f-e2ab-4077-ba55-4980f408773e',
|
|
1182
|
+
condition: null,
|
|
1183
|
+
diagnosis: {
|
|
1184
|
+
coded: 'stage-1-uuid'
|
|
1185
|
+
},
|
|
1186
|
+
certainty: 'CONFIRMED',
|
|
1187
|
+
rank: 1,
|
|
1188
|
+
formFieldPath: `rfe-forms-diagnosis1`,
|
|
1189
|
+
formFieldNamespace: 'rfe-forms'
|
|
1190
|
+
});
|
|
1191
|
+
});
|
|
1192
|
+
it('should edit diagnosis field on form submission', async ()=>{
|
|
1193
|
+
await act(async ()=>{
|
|
1194
|
+
renderForm(null, diagnosisForm, null, 'edit', mockHxpEncounter.uuid);
|
|
1195
|
+
});
|
|
1196
|
+
mockUseEncounter.mockImplementation(()=>({
|
|
1197
|
+
encounter: mockHxpEncounter,
|
|
1198
|
+
error: null,
|
|
1199
|
+
isLoading: false
|
|
1200
|
+
}));
|
|
1201
|
+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
|
1202
|
+
const field1 = await findSelectInput(screen, 'Test Diagnosis 1');
|
|
1203
|
+
expect(field1).toHaveValue('stage 1');
|
|
1204
|
+
await user.click(field1);
|
|
1205
|
+
await user.type(field1, 'stage');
|
|
1206
|
+
expect(screen.getByText(/stage 1/)).toBeInTheDocument();
|
|
1207
|
+
expect(screen.getByText(/stage 2/)).toBeInTheDocument();
|
|
1208
|
+
expect(screen.getByText(/stage 3/)).toBeInTheDocument();
|
|
1209
|
+
await user.click(screen.getByText(/stage 3/));
|
|
1210
|
+
await user.click(screen.getByRole('button', {
|
|
1211
|
+
name: /save/i
|
|
1212
|
+
}));
|
|
1213
|
+
expect(saveEncounterMock).toHaveBeenCalledTimes(1);
|
|
1214
|
+
const [_, encounter] = saveEncounterMock.mock.calls[0];
|
|
1215
|
+
expect(encounter.diagnoses.length).toBe(1);
|
|
1216
|
+
expect(encounter.diagnoses[0]).toEqual({
|
|
1217
|
+
patient: '8673ee4f-e2ab-4077-ba55-4980f408773e',
|
|
1218
|
+
condition: null,
|
|
1219
|
+
diagnosis: {
|
|
1220
|
+
coded: 'stage-3-uuid'
|
|
1221
|
+
},
|
|
1222
|
+
certainty: 'CONFIRMED',
|
|
1223
|
+
rank: 1,
|
|
1224
|
+
formFieldPath: `rfe-forms-diagnosis1`,
|
|
1225
|
+
formFieldNamespace: 'rfe-forms',
|
|
1226
|
+
uuid: '95690fb4-0398-42d9-9ffc-8a134e6d829d'
|
|
1227
|
+
});
|
|
1228
|
+
});
|
|
1229
|
+
});
|
|
1230
|
+
describe('Form calculate expression integration', ()=>{
|
|
1231
|
+
it('should calculate encounter date from visit startDatetime', async ()=>{
|
|
1232
|
+
await act(async ()=>{
|
|
1233
|
+
return renderForm(null, expressionVisitObjectTestSchema, 'enter', 'enter', null);
|
|
1234
|
+
});
|
|
1235
|
+
await waitFor(()=>{
|
|
1236
|
+
const dateInput = screen.getByLabelText('Encounter Date');
|
|
1237
|
+
expect(dateInput).toBeInTheDocument();
|
|
1238
|
+
expect(dateInput.value).toContain('28/07/2020');
|
|
1239
|
+
});
|
|
1240
|
+
});
|
|
1241
|
+
});
|
|
1242
|
+
function renderForm(formUUID, formJson, intent, mode, encounterUUID) {
|
|
1243
|
+
render(/*#__PURE__*/ React.createElement(FormEngine, {
|
|
1244
|
+
formJson: formJson,
|
|
1245
|
+
formUUID: formUUID,
|
|
1246
|
+
patientUUID: patientUUID,
|
|
1247
|
+
formSessionIntent: intent,
|
|
1248
|
+
visit: visit,
|
|
1249
|
+
encounterUUID: encounterUUID,
|
|
1250
|
+
mode: mode ? mode : 'enter'
|
|
1251
|
+
}));
|
|
1252
|
+
}
|
|
1253
|
+
});
|