@nocobase/client-v2 2.2.0-alpha.1 → 2.2.0-alpha.11
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/es/APIClient.d.ts +1 -3
- package/es/BaseApplication.d.ts +3 -0
- package/es/PluginSettingsManager.d.ts +33 -0
- package/es/RouteRepository.d.ts +29 -6
- package/es/RouterManager.d.ts +15 -0
- package/es/authRedirect.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
- package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
- package/es/components/category-tabs/index.d.ts +9 -0
- package/es/components/form/JsonTextArea.d.ts +2 -1
- package/es/components/form/ScanInput/useCodeScanner.d.ts +3 -2
- package/es/components/form/TypedVariableInput.d.ts +31 -5
- package/es/components/form/VariableJsonTextArea.d.ts +19 -0
- package/es/components/form/filter/CollectionFilter.d.ts +2 -0
- package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
- package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
- package/es/components/form/filter/index.d.ts +2 -0
- package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/components/index.d.ts +2 -0
- package/es/entry-actions/EntryActionManager.d.ts +24 -0
- package/es/entry-actions/index.d.ts +9 -0
- package/es/flow/actions/afterSuccess.d.ts +8 -1
- package/es/flow/actions/index.d.ts +2 -2
- package/es/flow/actions/linkageRules.d.ts +2 -0
- package/es/flow/admin-shell/BaseLayoutModel.d.ts +6 -0
- package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +7 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +3 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
- package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +5 -0
- package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +2 -1
- package/es/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.d.ts +24 -0
- package/es/flow/admin-shell/admin-layout/index.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
- package/es/flow/admin-shell/admin-layout/useApplications.d.ts +7 -2
- package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
- package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
- package/es/flow/components/FieldAssignValueInput.d.ts +9 -0
- package/es/flow/components/FlowRoute.d.ts +2 -2
- package/es/flow/components/RunJSValueEditor.d.ts +1 -0
- package/es/flow/components/filter/FilterGroup.d.ts +1 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
- package/es/flow/index.d.ts +1 -0
- package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
- package/es/flow/models/base/ActionModelCore.d.ts +2 -0
- package/es/flow/models/base/BlockGridModel.d.ts +3 -0
- package/es/flow/models/base/GridModel.d.ts +1 -1
- package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
- package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
- package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
- package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
- package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
- package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
- package/es/flow/models/blocks/form/submitHandler.d.ts +1 -1
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +30 -0
- package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
- package/es/flow/models/blocks/js-block/JSBlock.d.ts +1 -0
- package/es/flow/models/blocks/table/TableActionsColumnModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +1 -0
- package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +1 -1
- package/es/flow/models/blocks/table/dragSort/dragSortSettings.d.ts +2 -1
- package/es/flow/models/blocks/table/dragSort/dragSortUtils.d.ts +6 -4
- package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +6 -0
- package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
- package/es/flow/resolveViewParamsToViewList.d.ts +1 -1
- package/es/flow/routeTransientInputArgs.d.ts +14 -0
- package/es/flow/utils/dataScopeRowSnapshot.d.ts +59 -0
- package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
- package/es/flow/utils/formValueDeps.d.ts +32 -0
- package/es/flow-compat/FieldValidation.d.ts +2 -1
- package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
- package/es/flow-compat/routeTypes.d.ts +1 -0
- package/es/index.d.ts +8 -1
- package/es/index.mjs +367 -181
- package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
- package/es/utils/index.d.ts +1 -0
- package/es/utils/markdownSanitize.d.ts +11 -0
- package/lib/index.js +367 -181
- package/lib/locale/languageCodes.js +3 -1
- package/package.json +9 -7
- package/src/APIClient.ts +1 -10
- package/src/Application.tsx +4 -0
- package/src/BaseApplication.tsx +13 -6
- package/src/PluginSettingsManager.ts +53 -0
- package/src/RouteRepository.ts +151 -24
- package/src/RouterManager.tsx +142 -1
- package/src/__tests__/PluginSettingsManager.test.ts +13 -0
- package/src/__tests__/RouteRepository.test.ts +239 -0
- package/src/__tests__/RouterManager.test.ts +125 -0
- package/src/__tests__/app.test.tsx +73 -0
- package/src/__tests__/authRedirect.test.ts +17 -0
- package/src/__tests__/browserChecker.test.ts +61 -0
- package/src/__tests__/exports.test.ts +16 -0
- package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +234 -2
- package/src/__tests__/plugin-manager.test.tsx +44 -2
- package/src/__tests__/settings-center.test.tsx +40 -1
- package/src/authRedirect.ts +38 -0
- package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
- package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
- package/src/collection-manager/field-configure.ts +1 -1
- package/src/collection-manager/field-validation.ts +1 -1
- package/src/collection-manager/interfaces/id.ts +1 -1
- package/src/collection-manager/interfaces/m2m.tsx +2 -2
- package/src/collection-manager/interfaces/m2o.tsx +2 -2
- package/src/collection-manager/interfaces/nanoid.ts +1 -1
- package/src/collection-manager/interfaces/o2m.tsx +2 -2
- package/src/collection-manager/interfaces/obo.tsx +2 -2
- package/src/collection-manager/interfaces/oho.tsx +2 -2
- package/src/collection-manager/interfaces/properties/index.ts +2 -2
- package/src/collection-manager/interfaces/uuid.ts +1 -1
- package/src/components/AppComponents.tsx +19 -3
- package/src/components/README.md +7 -1
- package/src/components/README.zh-CN.md +6 -1
- package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
- package/src/components/category-tabs/index.ts +10 -0
- package/src/components/form/JsonTextArea.tsx +25 -11
- package/src/components/form/ScanInput/CodeScanner.tsx +79 -35
- package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +101 -0
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +284 -11
- package/src/components/form/ScanInput/useCodeScanner.ts +332 -24
- package/src/components/form/TypedVariableInput.tsx +452 -101
- package/src/components/form/VariableJsonTextArea.tsx +175 -0
- package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
- package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
- package/src/components/form/filter/CollectionFilter.tsx +4 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
- package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
- package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
- package/src/components/form/filter/index.ts +2 -0
- package/src/components/form/filter/useFilterActionProps.ts +37 -6
- package/src/components/form/index.tsx +1 -0
- package/src/components/index.ts +2 -0
- package/src/entry-actions/EntryActionManager.ts +76 -0
- package/src/entry-actions/index.ts +10 -0
- package/src/flow/FlowPage.tsx +38 -3
- package/src/flow/__tests__/FlowPage.test.tsx +201 -27
- package/src/flow/__tests__/FlowRoute.test.tsx +1181 -80
- package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
- package/src/flow/__tests__/getKey.test.ts +7 -0
- package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
- package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +115 -0
- package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
- package/src/flow/actions/__tests__/dataScopeFilter.test.ts +120 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +1124 -0
- package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
- package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
- package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
- package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
- package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
- package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +164 -0
- package/src/flow/actions/__tests__/subFormFieldLinkageRules.inputArgs.test.ts +2 -2
- package/src/flow/actions/afterSuccess.tsx +142 -3
- package/src/flow/actions/customVariable.tsx +1 -2
- package/src/flow/actions/dataScopeFilter.ts +20 -1
- package/src/flow/actions/dateTimeFormat.tsx +42 -28
- package/src/flow/actions/index.ts +3 -1
- package/src/flow/actions/linkageRules.tsx +209 -47
- package/src/flow/actions/linkageRulesFormValueRefresh.ts +22 -130
- package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
- package/src/flow/actions/openView.tsx +59 -5
- package/src/flow/actions/runjs.tsx +2 -14
- package/src/flow/actions/validation.tsx +62 -30
- package/src/flow/admin-shell/BaseLayoutModel.tsx +149 -3
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +187 -42
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1020 -110
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +74 -14
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +486 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +64 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +23 -9
- package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
- package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
- package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +435 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +518 -2
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +214 -2
- package/src/flow/admin-shell/admin-layout/index.ts +1 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
- package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
- package/src/flow/common/Markdown/Display.tsx +14 -3
- package/src/flow/common/Markdown/Edit.tsx +19 -7
- package/src/flow/common/Markdown/style.ts +4 -0
- package/src/flow/components/BlockItemCard.tsx +2 -2
- package/src/flow/components/DefaultValue.tsx +1 -2
- package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
- package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
- package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
- package/src/flow/components/FieldAssignValueInput.tsx +91 -20
- package/src/flow/components/FlowRoute.tsx +256 -53
- package/src/flow/components/RunJSValueEditor.tsx +9 -1
- package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +508 -4
- package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
- package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
- package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
- package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
- package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
- package/src/flow/components/fieldAssignOptions.ts +155 -27
- package/src/flow/components/filter/FilterGroup.tsx +33 -5
- package/src/flow/components/filter/VariableFilterItem.tsx +158 -10
- package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +70 -1
- package/src/flow/flows/editMarkdownFlow.tsx +1 -1
- package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
- package/src/flow/index.ts +4 -1
- package/src/flow/internal/components/Markdown/util.ts +2 -1
- package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
- package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
- package/src/flow/models/actions/JSActionModel.tsx +2 -7
- package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
- package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
- package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
- package/src/flow/models/base/ActionModel.tsx +21 -8
- package/src/flow/models/base/ActionModelCore.tsx +11 -4
- package/src/flow/models/base/BlockGridModel.tsx +26 -0
- package/src/flow/models/base/CollectionBlockModel.tsx +6 -2
- package/src/flow/models/base/GridModel.tsx +38 -7
- package/src/flow/models/base/PageModel/PageModel.tsx +387 -50
- package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
- package/src/flow/models/base/PageModel/PageTabModel.tsx +249 -18
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +817 -13
- package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
- package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
- package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +1075 -7
- package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
- package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +37 -0
- package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
- package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
- package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +59 -6
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
- package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +143 -4
- package/src/flow/models/blocks/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
- package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
- package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
- package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
- package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
- package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
- package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
- package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +54 -1
- package/src/flow/models/blocks/form/submitHandler.ts +17 -3
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +940 -75
- package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +348 -13
- package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
- package/src/flow/models/blocks/js-block/JSBlock.tsx +225 -9
- package/src/flow/models/blocks/js-block/__tests__/JSBlockModel.test.tsx +150 -0
- package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +38 -14
- package/src/flow/models/blocks/table/TableBlockModel.tsx +42 -19
- package/src/flow/models/blocks/table/TableColumnModel.tsx +72 -6
- package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +111 -1
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +243 -1
- package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
- package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
- package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
- package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +31 -7
- package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +61 -17
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +141 -19
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +197 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
- package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
- package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
- package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
- package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
- package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
- package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
- package/src/flow/models/fields/InputFieldModel.tsx +40 -2
- package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
- package/src/flow/models/fields/JSFieldModel.tsx +2 -7
- package/src/flow/models/fields/JSItemModel.tsx +2 -14
- package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
- package/src/flow/models/fields/TextareaFieldModel.tsx +72 -3
- package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
- package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
- package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +117 -1
- package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
- package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +131 -0
- package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
- package/src/flow/models/topbar/TopbarActionModel.tsx +168 -10
- package/src/flow/resolveViewParamsToViewList.tsx +11 -3
- package/src/flow/routeTransientInputArgs.ts +27 -0
- package/src/flow/utils/__tests__/dateTimeFormat.test.ts +300 -0
- package/src/flow/utils/dataScopeFormValueClear.ts +219 -81
- package/src/flow/utils/dataScopeRowSnapshot.ts +616 -0
- package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
- package/src/flow/utils/formValueDeps.ts +170 -0
- package/src/flow-compat/FieldValidation.tsx +122 -60
- package/src/flow-compat/Popover.tsx +43 -4
- package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
- package/src/flow-compat/fieldValidationConstants.ts +1 -1
- package/src/flow-compat/routeTypes.ts +1 -0
- package/src/index.ts +14 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
- package/src/locale/languageCodes.ts +3 -1
- package/src/nocobase-buildin-plugin/index.tsx +63 -15
- package/src/settings-center/AdminSettingsLayout.tsx +10 -2
- package/src/settings-center/SystemSettingsPage.tsx +1 -2
- package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
- package/src/settings-center/plugin-manager/index.tsx +3 -0
- package/src/settings-center/utils.tsx +0 -6
- package/src/utils/getRouteRuntimeVersion.ts +185 -0
- package/src/utils/index.tsx +1 -0
- package/src/utils/markdownSanitize.ts +88 -0
|
@@ -9,10 +9,12 @@
|
|
|
9
9
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
12
|
-
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
|
13
|
-
import { render, screen, waitFor } from '@testing-library/react';
|
|
12
|
+
import { MemoryRouter, Route, Router, Routes, useNavigate } from 'react-router-dom';
|
|
13
|
+
import { act, render, screen, waitFor } from '@testing-library/react';
|
|
14
14
|
import { FlowContextProvider, FlowEngine, FlowEngineProvider, type FlowModel } from '@nocobase/flow-engine';
|
|
15
15
|
import FlowRoute from '../components/FlowRoute';
|
|
16
|
+
import { RouteRepository } from '../../RouteRepository';
|
|
17
|
+
import { NocoBaseDesktopRouteType } from '../../flow-compat';
|
|
16
18
|
|
|
17
19
|
type MockAdminLayoutModel = FlowModel & {
|
|
18
20
|
registerRoutePage: ReturnType<typeof vi.fn>;
|
|
@@ -20,6 +22,8 @@ type MockAdminLayoutModel = FlowModel & {
|
|
|
20
22
|
unregisterRoutePage: ReturnType<typeof vi.fn>;
|
|
21
23
|
};
|
|
22
24
|
|
|
25
|
+
type RouterNavigator = React.ComponentProps<typeof Router>['navigator'];
|
|
26
|
+
|
|
23
27
|
const { hookState } = vi.hoisted(() => {
|
|
24
28
|
return {
|
|
25
29
|
hookState: {
|
|
@@ -112,6 +116,7 @@ describe('FlowRoute', () => {
|
|
|
112
116
|
}),
|
|
113
117
|
);
|
|
114
118
|
});
|
|
119
|
+
expect(engine.context.deviceType).toBe('computer');
|
|
115
120
|
|
|
116
121
|
rerender(
|
|
117
122
|
<FlowEngineProvider engine={engine}>
|
|
@@ -321,6 +326,94 @@ describe('FlowRoute', () => {
|
|
|
321
326
|
});
|
|
322
327
|
});
|
|
323
328
|
|
|
329
|
+
it('should ensure accessible routes for the current layout before bridging', async () => {
|
|
330
|
+
const engine = new FlowEngine();
|
|
331
|
+
const request = vi.fn().mockResolvedValue({
|
|
332
|
+
data: {
|
|
333
|
+
data: [
|
|
334
|
+
{
|
|
335
|
+
type: 'flowPage',
|
|
336
|
+
schemaUid: 'mobile-page',
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
const routeRepository = new RouteRepository({
|
|
342
|
+
api: {
|
|
343
|
+
request,
|
|
344
|
+
resource: vi.fn(),
|
|
345
|
+
},
|
|
346
|
+
} as never);
|
|
347
|
+
routeRepository.setRoutes([], 'admin-layout-model');
|
|
348
|
+
const deactivateLayout = routeRepository.activateLayout({
|
|
349
|
+
uid: 'mobile-layout-model',
|
|
350
|
+
});
|
|
351
|
+
engine.context.defineProperty('routeRepository', {
|
|
352
|
+
value: routeRepository,
|
|
353
|
+
});
|
|
354
|
+
engine.context.defineProperty('app', {
|
|
355
|
+
value: {
|
|
356
|
+
getPublicPath: () => '/v2/',
|
|
357
|
+
router: {
|
|
358
|
+
getBasename: () => '/v2',
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
const routeModel = engine.createModel({
|
|
363
|
+
uid: 'mobile-route-model',
|
|
364
|
+
use: 'FlowModel',
|
|
365
|
+
});
|
|
366
|
+
routeModel.context.defineProperty('layout', {
|
|
367
|
+
value: {
|
|
368
|
+
routeName: 'mobile',
|
|
369
|
+
routePath: '/mobile',
|
|
370
|
+
rootRouteName: 'mobile',
|
|
371
|
+
uid: 'mobile-layout-model',
|
|
372
|
+
layoutModelClass: 'MobileLayoutModel',
|
|
373
|
+
rootPageModelClass: 'MobileRootPageModel',
|
|
374
|
+
childPageModelClass: 'MobileChildPageModel',
|
|
375
|
+
authCheck: true,
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const mobileLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
380
|
+
engine.createModel({
|
|
381
|
+
uid: 'mobile-layout-model',
|
|
382
|
+
use: 'FlowModel',
|
|
383
|
+
}),
|
|
384
|
+
{
|
|
385
|
+
registerRoutePage: vi.fn(),
|
|
386
|
+
updateRoutePage: vi.fn(),
|
|
387
|
+
unregisterRoutePage: vi.fn(),
|
|
388
|
+
},
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
render(
|
|
392
|
+
<FlowEngineProvider engine={engine}>
|
|
393
|
+
<FlowContextProvider context={routeModel.context}>
|
|
394
|
+
<MemoryRouter initialEntries={['/mobile/mobile-page']}>
|
|
395
|
+
<Routes>
|
|
396
|
+
<Route path="/mobile/:name" element={<FlowRoute />} />
|
|
397
|
+
</Routes>
|
|
398
|
+
</MemoryRouter>
|
|
399
|
+
</FlowContextProvider>
|
|
400
|
+
</FlowEngineProvider>,
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
await waitFor(() => {
|
|
404
|
+
expect(request).toHaveBeenCalledWith({
|
|
405
|
+
url: '/desktopRoutes:listAccessible',
|
|
406
|
+
params: {
|
|
407
|
+
tree: true,
|
|
408
|
+
sort: 'sort',
|
|
409
|
+
layout: 'mobile-layout-model',
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
expect(mobileLayoutModel.registerRoutePage).toHaveBeenCalledWith('mobile-page', expect.any(Object));
|
|
413
|
+
});
|
|
414
|
+
deactivateLayout();
|
|
415
|
+
});
|
|
416
|
+
|
|
324
417
|
it('should fail fast when admin-layout-model is missing', () => {
|
|
325
418
|
const engine = new FlowEngine();
|
|
326
419
|
engine.context.defineProperty('route', {
|
|
@@ -420,14 +513,83 @@ describe('FlowRoute', () => {
|
|
|
420
513
|
});
|
|
421
514
|
});
|
|
422
515
|
|
|
423
|
-
it('should
|
|
516
|
+
it('should not rerun accessible guard when layout reference changes but fields stay the same', async () => {
|
|
517
|
+
const engine = new FlowEngine();
|
|
518
|
+
const ensureAccessibleLoaded = vi.fn().mockResolvedValue([]);
|
|
519
|
+
const getRouteBySchemaUid = vi.fn(() => ({ type: 'flowPage', schemaUid: 'test-page' }));
|
|
520
|
+
engine.context.defineProperty('routeRepository', {
|
|
521
|
+
value: {
|
|
522
|
+
refreshAccessible: hookState.refresh,
|
|
523
|
+
isAccessibleLoaded: () => true,
|
|
524
|
+
ensureAccessibleLoaded,
|
|
525
|
+
getRouteBySchemaUid,
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
engine.context.defineProperty('app', {
|
|
529
|
+
value: {
|
|
530
|
+
getPublicPath: () => '/v2/',
|
|
531
|
+
router: {
|
|
532
|
+
getBasename: () => '/v2',
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
538
|
+
engine.createModel({ uid: 'legacy-admin-layout-model', use: 'FlowModel' }),
|
|
539
|
+
{
|
|
540
|
+
registerRoutePage: vi.fn(),
|
|
541
|
+
updateRoutePage: vi.fn(),
|
|
542
|
+
unregisterRoutePage: vi.fn(),
|
|
543
|
+
},
|
|
544
|
+
);
|
|
545
|
+
let layoutReadCount = 0;
|
|
546
|
+
const stableLayout = {
|
|
547
|
+
uid: 'admin-layout-model',
|
|
548
|
+
routeName: 'admin',
|
|
549
|
+
routePath: '/admin',
|
|
550
|
+
authCheck: true,
|
|
551
|
+
};
|
|
552
|
+
Object.defineProperty(layoutModel, 'layout', {
|
|
553
|
+
get: () => {
|
|
554
|
+
layoutReadCount += 1;
|
|
555
|
+
return layoutReadCount <= 2 ? { ...stableLayout } : stableLayout;
|
|
556
|
+
},
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
const element = (
|
|
560
|
+
<FlowEngineProvider engine={engine}>
|
|
561
|
+
<MemoryRouter initialEntries={['/flow/test-page']}>
|
|
562
|
+
<Routes>
|
|
563
|
+
<Route path="/flow/:name" element={<FlowRoute getLayoutModel={() => layoutModel as any} />} />
|
|
564
|
+
</Routes>
|
|
565
|
+
</MemoryRouter>
|
|
566
|
+
</FlowEngineProvider>
|
|
567
|
+
);
|
|
568
|
+
const { rerender } = render(element);
|
|
569
|
+
|
|
570
|
+
await waitFor(() => {
|
|
571
|
+
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('test-page', expect.any(Object));
|
|
572
|
+
});
|
|
573
|
+
expect(ensureAccessibleLoaded).not.toHaveBeenCalled();
|
|
574
|
+
expect(getRouteBySchemaUid).toHaveBeenCalledTimes(1);
|
|
575
|
+
|
|
576
|
+
rerender(element);
|
|
577
|
+
|
|
578
|
+
await waitFor(() => {
|
|
579
|
+
expect(layoutModel.updateRoutePage).toHaveBeenCalled();
|
|
580
|
+
});
|
|
581
|
+
expect(ensureAccessibleLoaded).not.toHaveBeenCalled();
|
|
582
|
+
expect(getRouteBySchemaUid).toHaveBeenCalledTimes(1);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
it('should explain how to open a legacy page outside the v2 runtime', async () => {
|
|
424
586
|
const originalLocation = window.location;
|
|
425
587
|
const replace = vi.fn();
|
|
426
588
|
Object.defineProperty(window, 'location', {
|
|
427
589
|
configurable: true,
|
|
428
590
|
value: {
|
|
429
591
|
...originalLocation,
|
|
430
|
-
pathname: '/v2/admin/test-page/tab/tab-1',
|
|
592
|
+
pathname: '/nocobase/v2/apps/jhb20/admin/test-page/tab/tab-1',
|
|
431
593
|
search: '?from=direct',
|
|
432
594
|
hash: '#dialog',
|
|
433
595
|
replace,
|
|
@@ -446,9 +608,9 @@ describe('FlowRoute', () => {
|
|
|
446
608
|
});
|
|
447
609
|
engine.context.defineProperty('app', {
|
|
448
610
|
value: {
|
|
449
|
-
getPublicPath: () => '/v2/',
|
|
611
|
+
getPublicPath: () => '/nocobase/v2/',
|
|
450
612
|
router: {
|
|
451
|
-
getBasename: () => '/v2',
|
|
613
|
+
getBasename: () => '/nocobase/v2',
|
|
452
614
|
},
|
|
453
615
|
},
|
|
454
616
|
});
|
|
@@ -464,15 +626,25 @@ describe('FlowRoute', () => {
|
|
|
464
626
|
|
|
465
627
|
render(
|
|
466
628
|
<FlowEngineProvider engine={engine}>
|
|
467
|
-
<MemoryRouter initialEntries={['/
|
|
629
|
+
<MemoryRouter initialEntries={['/nocobase/v2/apps/jhb20/admin/test-page/tab/tab-1?from=direct#dialog']}>
|
|
468
630
|
<Routes>
|
|
469
|
-
<Route path="/
|
|
631
|
+
<Route path="/nocobase/v2/apps/jhb20/admin/:name/*" element={<FlowRoute />} />
|
|
470
632
|
</Routes>
|
|
471
633
|
</MemoryRouter>
|
|
472
634
|
</FlowEngineProvider>,
|
|
473
635
|
);
|
|
474
636
|
|
|
475
|
-
expect(await screen.findByText('
|
|
637
|
+
expect(await screen.findByText('This page is not supported in the /v2/ branch')).toBeInTheDocument();
|
|
638
|
+
expect(
|
|
639
|
+
screen.getByText(
|
|
640
|
+
'The /v2/ branch only supports new pages. This page is a legacy page. Please open it from the original entry.',
|
|
641
|
+
),
|
|
642
|
+
).toBeInTheDocument();
|
|
643
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
644
|
+
expect(screen.getByRole('link', { name: 'Open from the original entry' })).toHaveAttribute(
|
|
645
|
+
'href',
|
|
646
|
+
'/nocobase/apps/jhb20/admin/test-page/tab/tab-1?from=direct#dialog',
|
|
647
|
+
);
|
|
476
648
|
expect(replace).not.toHaveBeenCalled();
|
|
477
649
|
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
478
650
|
expect(adminLayoutModel.updateRoutePage).not.toHaveBeenCalled();
|
|
@@ -545,24 +717,42 @@ describe('FlowRoute', () => {
|
|
|
545
717
|
}
|
|
546
718
|
});
|
|
547
719
|
|
|
548
|
-
it('should
|
|
720
|
+
it('should render 404 for missing FlowModel in notFound mode without routeRepository', async () => {
|
|
549
721
|
const engine = new FlowEngine();
|
|
550
|
-
engine.
|
|
551
|
-
findOne: vi.fn().mockResolvedValue({
|
|
552
|
-
uid: 'public-form-1',
|
|
553
|
-
use: 'FlowModel',
|
|
554
|
-
}),
|
|
555
|
-
save: vi.fn(),
|
|
556
|
-
destroy: vi.fn(),
|
|
557
|
-
} as any);
|
|
558
|
-
engine.context.defineProperty('routeRepository', {
|
|
722
|
+
engine.context.defineProperty('app', {
|
|
559
723
|
value: {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
724
|
+
getPublicPath: () => '/v2/',
|
|
725
|
+
router: {
|
|
726
|
+
getBasename: () => '/v2',
|
|
727
|
+
},
|
|
564
728
|
},
|
|
565
729
|
});
|
|
730
|
+
|
|
731
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
732
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
733
|
+
{
|
|
734
|
+
registerRoutePage: vi.fn(),
|
|
735
|
+
updateRoutePage: vi.fn(),
|
|
736
|
+
unregisterRoutePage: vi.fn(),
|
|
737
|
+
},
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
render(
|
|
741
|
+
<FlowEngineProvider engine={engine}>
|
|
742
|
+
<MemoryRouter initialEntries={['/embed/missing-page']}>
|
|
743
|
+
<Routes>
|
|
744
|
+
<Route path="/embed/:name" element={<FlowRoute legacyPageBehavior="notFound" />} />
|
|
745
|
+
</Routes>
|
|
746
|
+
</MemoryRouter>
|
|
747
|
+
</FlowEngineProvider>,
|
|
748
|
+
);
|
|
749
|
+
|
|
750
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
751
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
it('should bridge by default when routeRepository does not exist', async () => {
|
|
755
|
+
const engine = new FlowEngine();
|
|
566
756
|
engine.context.defineProperty('app', {
|
|
567
757
|
value: {
|
|
568
758
|
getPublicPath: () => '/v2/',
|
|
@@ -572,8 +762,8 @@ describe('FlowRoute', () => {
|
|
|
572
762
|
},
|
|
573
763
|
});
|
|
574
764
|
|
|
575
|
-
const
|
|
576
|
-
engine.createModel({ uid: '
|
|
765
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
766
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
577
767
|
{
|
|
578
768
|
registerRoutePage: vi.fn(),
|
|
579
769
|
updateRoutePage: vi.fn(),
|
|
@@ -583,37 +773,64 @@ describe('FlowRoute', () => {
|
|
|
583
773
|
|
|
584
774
|
render(
|
|
585
775
|
<FlowEngineProvider engine={engine}>
|
|
586
|
-
<MemoryRouter initialEntries={['/
|
|
776
|
+
<MemoryRouter initialEntries={['/flow/missing-page']}>
|
|
587
777
|
<Routes>
|
|
588
|
-
<Route
|
|
589
|
-
path="/public-forms/:name"
|
|
590
|
-
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel} />}
|
|
591
|
-
/>
|
|
778
|
+
<Route path="/flow/:name" element={<FlowRoute />} />
|
|
592
779
|
</Routes>
|
|
593
780
|
</MemoryRouter>
|
|
594
781
|
</FlowEngineProvider>,
|
|
595
782
|
);
|
|
596
783
|
|
|
597
784
|
await waitFor(() => {
|
|
598
|
-
expect(
|
|
785
|
+
expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith('missing-page', expect.any(Object));
|
|
599
786
|
});
|
|
600
787
|
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
601
788
|
});
|
|
602
789
|
|
|
603
|
-
it('should
|
|
790
|
+
it('should bridge existing FlowModel in notFound mode without routeRepository', async () => {
|
|
604
791
|
const engine = new FlowEngine();
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
uid: 'public-form-1',
|
|
792
|
+
engine.createModel({ uid: 'test-page', use: 'FlowModel' });
|
|
793
|
+
engine.context.defineProperty('app', {
|
|
794
|
+
value: {
|
|
795
|
+
getPublicPath: () => '/v2/',
|
|
796
|
+
router: {
|
|
797
|
+
getBasename: () => '/v2',
|
|
612
798
|
},
|
|
613
799
|
},
|
|
614
800
|
});
|
|
801
|
+
|
|
802
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
803
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
804
|
+
{
|
|
805
|
+
registerRoutePage: vi.fn(),
|
|
806
|
+
updateRoutePage: vi.fn(),
|
|
807
|
+
unregisterRoutePage: vi.fn(),
|
|
808
|
+
},
|
|
809
|
+
);
|
|
810
|
+
|
|
811
|
+
render(
|
|
812
|
+
<FlowEngineProvider engine={engine}>
|
|
813
|
+
<MemoryRouter initialEntries={['/embed/test-page']}>
|
|
814
|
+
<Routes>
|
|
815
|
+
<Route path="/embed/:name" element={<FlowRoute legacyPageBehavior="notFound" />} />
|
|
816
|
+
</Routes>
|
|
817
|
+
</MemoryRouter>
|
|
818
|
+
</FlowEngineProvider>,
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
await waitFor(() => {
|
|
822
|
+
expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith('test-page', expect.any(Object));
|
|
823
|
+
});
|
|
824
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
it('should bridge existing FlowModel when behavior is notFound and routeRepository has no route', async () => {
|
|
828
|
+
const engine = new FlowEngine();
|
|
615
829
|
engine.setModelRepository({
|
|
616
|
-
findOne
|
|
830
|
+
findOne: vi.fn().mockResolvedValue({
|
|
831
|
+
uid: 'public-form-1',
|
|
832
|
+
use: 'FlowModel',
|
|
833
|
+
}),
|
|
617
834
|
save: vi.fn(),
|
|
618
835
|
destroy: vi.fn(),
|
|
619
836
|
} as any);
|
|
@@ -627,9 +844,6 @@ describe('FlowRoute', () => {
|
|
|
627
844
|
});
|
|
628
845
|
engine.context.defineProperty('app', {
|
|
629
846
|
value: {
|
|
630
|
-
apiClient: {
|
|
631
|
-
request,
|
|
632
|
-
},
|
|
633
847
|
getPublicPath: () => '/v2/',
|
|
634
848
|
router: {
|
|
635
849
|
getBasename: () => '/v2',
|
|
@@ -667,7 +881,10 @@ describe('FlowRoute', () => {
|
|
|
667
881
|
<FlowContextProvider context={routeModel.context}>
|
|
668
882
|
<MemoryRouter initialEntries={['/public-forms/public-form-1']}>
|
|
669
883
|
<Routes>
|
|
670
|
-
<Route
|
|
884
|
+
<Route
|
|
885
|
+
path="/public-forms/:name"
|
|
886
|
+
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel} />}
|
|
887
|
+
/>
|
|
671
888
|
</Routes>
|
|
672
889
|
</MemoryRouter>
|
|
673
890
|
</FlowContextProvider>
|
|
@@ -677,15 +894,11 @@ describe('FlowRoute', () => {
|
|
|
677
894
|
await waitFor(() => {
|
|
678
895
|
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('public-form-1', expect.any(Object));
|
|
679
896
|
});
|
|
680
|
-
expect(
|
|
681
|
-
expect(request).not.toHaveBeenCalled();
|
|
682
|
-
expect(engine.getModel('public-form-1')).toBeUndefined();
|
|
897
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
683
898
|
});
|
|
684
899
|
|
|
685
|
-
it('should
|
|
900
|
+
it('should use getLayoutModel layout authCheck when FlowContext has no layout', async () => {
|
|
686
901
|
const engine = new FlowEngine();
|
|
687
|
-
const ensureAccessibleLoaded = vi.fn().mockRejectedValue(new Error('cannot load accessible routes'));
|
|
688
|
-
const getRouteBySchemaUid = vi.fn();
|
|
689
902
|
engine.setModelRepository({
|
|
690
903
|
findOne: vi.fn().mockResolvedValue({
|
|
691
904
|
uid: 'public-form-1',
|
|
@@ -697,9 +910,9 @@ describe('FlowRoute', () => {
|
|
|
697
910
|
engine.context.defineProperty('routeRepository', {
|
|
698
911
|
value: {
|
|
699
912
|
refreshAccessible: hookState.refresh,
|
|
700
|
-
isAccessibleLoaded: () =>
|
|
701
|
-
ensureAccessibleLoaded,
|
|
702
|
-
getRouteBySchemaUid,
|
|
913
|
+
isAccessibleLoaded: () => true,
|
|
914
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
915
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
703
916
|
},
|
|
704
917
|
});
|
|
705
918
|
engine.context.defineProperty('app', {
|
|
@@ -710,11 +923,16 @@ describe('FlowRoute', () => {
|
|
|
710
923
|
},
|
|
711
924
|
},
|
|
712
925
|
});
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
use: 'FlowModel',
|
|
716
|
-
|
|
717
|
-
|
|
926
|
+
|
|
927
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
928
|
+
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
929
|
+
{
|
|
930
|
+
registerRoutePage: vi.fn(),
|
|
931
|
+
updateRoutePage: vi.fn(),
|
|
932
|
+
unregisterRoutePage: vi.fn(),
|
|
933
|
+
},
|
|
934
|
+
);
|
|
935
|
+
Object.defineProperty(layoutModel, 'layout', {
|
|
718
936
|
value: {
|
|
719
937
|
routeName: 'public-forms',
|
|
720
938
|
routePath: '/public-forms',
|
|
@@ -727,32 +945,916 @@ describe('FlowRoute', () => {
|
|
|
727
945
|
},
|
|
728
946
|
});
|
|
729
947
|
|
|
730
|
-
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
731
|
-
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
732
|
-
{
|
|
733
|
-
registerRoutePage: vi.fn(),
|
|
734
|
-
updateRoutePage: vi.fn(),
|
|
735
|
-
unregisterRoutePage: vi.fn(),
|
|
736
|
-
},
|
|
737
|
-
);
|
|
738
|
-
|
|
739
948
|
render(
|
|
740
949
|
<FlowEngineProvider engine={engine}>
|
|
741
|
-
<
|
|
742
|
-
<
|
|
743
|
-
<
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
950
|
+
<MemoryRouter initialEntries={['/public-forms/public-form-1']}>
|
|
951
|
+
<Routes>
|
|
952
|
+
<Route
|
|
953
|
+
path="/public-forms/:name"
|
|
954
|
+
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel as any} />}
|
|
955
|
+
/>
|
|
956
|
+
</Routes>
|
|
957
|
+
</MemoryRouter>
|
|
748
958
|
</FlowEngineProvider>,
|
|
749
959
|
);
|
|
750
960
|
|
|
751
961
|
await waitFor(() => {
|
|
752
962
|
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('public-form-1', expect.any(Object));
|
|
753
963
|
});
|
|
754
|
-
expect(
|
|
755
|
-
|
|
964
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
it('should render not found when admin route is not accessible', async () => {
|
|
968
|
+
const engine = new FlowEngine();
|
|
969
|
+
engine.setModelRepository({
|
|
970
|
+
findOne: vi.fn().mockResolvedValue({
|
|
971
|
+
uid: 'admin-denied-page',
|
|
972
|
+
use: 'FlowModel',
|
|
973
|
+
}),
|
|
974
|
+
save: vi.fn(),
|
|
975
|
+
destroy: vi.fn(),
|
|
976
|
+
} as any);
|
|
977
|
+
engine.context.defineProperty('routeRepository', {
|
|
978
|
+
value: {
|
|
979
|
+
refreshAccessible: hookState.refresh,
|
|
980
|
+
isAccessibleLoaded: () => true,
|
|
981
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
982
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
983
|
+
},
|
|
984
|
+
});
|
|
985
|
+
engine.context.defineProperty('app', {
|
|
986
|
+
value: {
|
|
987
|
+
getPublicPath: () => '/v2/',
|
|
988
|
+
router: {
|
|
989
|
+
getBasename: () => '/v2',
|
|
990
|
+
},
|
|
991
|
+
},
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
995
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
996
|
+
{
|
|
997
|
+
registerRoutePage: vi.fn(),
|
|
998
|
+
updateRoutePage: vi.fn(),
|
|
999
|
+
unregisterRoutePage: vi.fn(),
|
|
1000
|
+
},
|
|
1001
|
+
);
|
|
1002
|
+
|
|
1003
|
+
render(
|
|
1004
|
+
<FlowEngineProvider engine={engine}>
|
|
1005
|
+
<MemoryRouter initialEntries={['/admin/admin-denied-page']}>
|
|
1006
|
+
<Routes>
|
|
1007
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1008
|
+
</Routes>
|
|
1009
|
+
</MemoryRouter>
|
|
1010
|
+
</FlowEngineProvider>,
|
|
1011
|
+
);
|
|
1012
|
+
|
|
1013
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1014
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
it('should render blank content when current admin route matches an accessible empty group id', async () => {
|
|
1018
|
+
const engine = new FlowEngine();
|
|
1019
|
+
const listAccessible = vi.fn(() => [
|
|
1020
|
+
{
|
|
1021
|
+
id: 371750686228480,
|
|
1022
|
+
schemaUid: 'group-schema',
|
|
1023
|
+
title: '789',
|
|
1024
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1025
|
+
},
|
|
1026
|
+
]);
|
|
1027
|
+
engine.context.defineProperty('routeRepository', {
|
|
1028
|
+
value: {
|
|
1029
|
+
refreshAccessible: hookState.refresh,
|
|
1030
|
+
isAccessibleLoaded: () => true,
|
|
1031
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1032
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1033
|
+
listAccessible,
|
|
1034
|
+
},
|
|
1035
|
+
});
|
|
1036
|
+
engine.context.defineProperty('app', {
|
|
1037
|
+
value: {
|
|
1038
|
+
getPublicPath: () => '/v2/',
|
|
1039
|
+
router: {
|
|
1040
|
+
getBasename: () => '/v2',
|
|
1041
|
+
},
|
|
1042
|
+
},
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1046
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1047
|
+
{
|
|
1048
|
+
registerRoutePage: vi.fn(),
|
|
1049
|
+
updateRoutePage: vi.fn(),
|
|
1050
|
+
unregisterRoutePage: vi.fn(),
|
|
1051
|
+
},
|
|
1052
|
+
);
|
|
1053
|
+
|
|
1054
|
+
const { container } = render(
|
|
1055
|
+
<FlowEngineProvider engine={engine}>
|
|
1056
|
+
<MemoryRouter initialEntries={['/admin/371750686228480']}>
|
|
1057
|
+
<Routes>
|
|
1058
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1059
|
+
</Routes>
|
|
1060
|
+
</MemoryRouter>
|
|
1061
|
+
</FlowEngineProvider>,
|
|
1062
|
+
);
|
|
1063
|
+
|
|
1064
|
+
await waitFor(() => {
|
|
1065
|
+
expect(listAccessible).toHaveBeenCalled();
|
|
1066
|
+
});
|
|
1067
|
+
expect(container).toBeEmptyDOMElement();
|
|
1068
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
1069
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
it('should navigate a current admin group route to its first accessible flow page', async () => {
|
|
1073
|
+
const engine = new FlowEngine();
|
|
1074
|
+
const childRoute = {
|
|
1075
|
+
schemaUid: 'child-flow-page',
|
|
1076
|
+
title: 'Child flow page',
|
|
1077
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1078
|
+
};
|
|
1079
|
+
const groupRoute = {
|
|
1080
|
+
id: 371750686228480,
|
|
1081
|
+
schemaUid: 'group-schema',
|
|
1082
|
+
title: 'Group with child',
|
|
1083
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1084
|
+
children: [childRoute],
|
|
1085
|
+
};
|
|
1086
|
+
const getRouteBySchemaUid = vi.fn((schemaUid: string) =>
|
|
1087
|
+
schemaUid === 'child-flow-page' ? childRoute : undefined,
|
|
1088
|
+
);
|
|
1089
|
+
const listAccessible = vi.fn(() => [groupRoute]);
|
|
1090
|
+
engine.context.defineProperty('routeRepository', {
|
|
1091
|
+
value: {
|
|
1092
|
+
refreshAccessible: hookState.refresh,
|
|
1093
|
+
isAccessibleLoaded: () => true,
|
|
1094
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1095
|
+
getRouteBySchemaUid,
|
|
1096
|
+
listAccessible,
|
|
1097
|
+
},
|
|
1098
|
+
});
|
|
1099
|
+
engine.context.defineProperty('app', {
|
|
1100
|
+
value: {
|
|
1101
|
+
getPublicPath: () => '/v2/',
|
|
1102
|
+
router: {
|
|
1103
|
+
getBasename: () => '/v2',
|
|
1104
|
+
},
|
|
1105
|
+
},
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1109
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1110
|
+
{
|
|
1111
|
+
registerRoutePage: vi.fn(),
|
|
1112
|
+
updateRoutePage: vi.fn(),
|
|
1113
|
+
unregisterRoutePage: vi.fn(),
|
|
1114
|
+
},
|
|
1115
|
+
);
|
|
1116
|
+
|
|
1117
|
+
render(
|
|
1118
|
+
<FlowEngineProvider engine={engine}>
|
|
1119
|
+
<MemoryRouter initialEntries={['/admin/371750686228480']}>
|
|
1120
|
+
<Routes>
|
|
1121
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1122
|
+
</Routes>
|
|
1123
|
+
</MemoryRouter>
|
|
1124
|
+
</FlowEngineProvider>,
|
|
1125
|
+
);
|
|
1126
|
+
|
|
1127
|
+
await waitFor(() => {
|
|
1128
|
+
expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith('child-flow-page', expect.any(Object));
|
|
1129
|
+
});
|
|
1130
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('371750686228480', expect.any(Object));
|
|
1131
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
it('should keep rendering 404 when a group id is opened with a nested admin route path', async () => {
|
|
1135
|
+
const engine = new FlowEngine();
|
|
1136
|
+
const childRoute = {
|
|
1137
|
+
schemaUid: 'child-flow-page',
|
|
1138
|
+
title: 'Child flow page',
|
|
1139
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1140
|
+
};
|
|
1141
|
+
const groupRoute = {
|
|
1142
|
+
id: 371750686228480,
|
|
1143
|
+
schemaUid: 'group-schema',
|
|
1144
|
+
title: 'Group with child',
|
|
1145
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1146
|
+
children: [childRoute],
|
|
1147
|
+
};
|
|
1148
|
+
engine.context.defineProperty('routeRepository', {
|
|
1149
|
+
value: {
|
|
1150
|
+
refreshAccessible: hookState.refresh,
|
|
1151
|
+
isAccessibleLoaded: () => true,
|
|
1152
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1153
|
+
getRouteBySchemaUid: vi.fn((schemaUid: string) => (schemaUid === 'child-flow-page' ? childRoute : undefined)),
|
|
1154
|
+
listAccessible: vi.fn(() => [groupRoute]),
|
|
1155
|
+
},
|
|
1156
|
+
});
|
|
1157
|
+
engine.context.defineProperty('app', {
|
|
1158
|
+
value: {
|
|
1159
|
+
getPublicPath: () => '/v2/',
|
|
1160
|
+
router: {
|
|
1161
|
+
getBasename: () => '/v2',
|
|
1162
|
+
},
|
|
1163
|
+
},
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1167
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1168
|
+
{
|
|
1169
|
+
registerRoutePage: vi.fn(),
|
|
1170
|
+
updateRoutePage: vi.fn(),
|
|
1171
|
+
unregisterRoutePage: vi.fn(),
|
|
1172
|
+
},
|
|
1173
|
+
);
|
|
1174
|
+
|
|
1175
|
+
render(
|
|
1176
|
+
<FlowEngineProvider engine={engine}>
|
|
1177
|
+
<MemoryRouter initialEntries={['/admin/371750686228480/view/not-exists']}>
|
|
1178
|
+
<Routes>
|
|
1179
|
+
<Route path="/admin/:name/view/*" element={<FlowRoute />} />
|
|
1180
|
+
</Routes>
|
|
1181
|
+
</MemoryRouter>
|
|
1182
|
+
</FlowEngineProvider>,
|
|
1183
|
+
);
|
|
1184
|
+
|
|
1185
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1186
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('child-flow-page', expect.any(Object));
|
|
1187
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('371750686228480', expect.any(Object));
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
it('should keep rendering 404 when a group id is opened with a tab admin route path', async () => {
|
|
1191
|
+
const engine = new FlowEngine();
|
|
1192
|
+
const childRoute = {
|
|
1193
|
+
schemaUid: 'child-flow-page',
|
|
1194
|
+
title: 'Child flow page',
|
|
1195
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1196
|
+
};
|
|
1197
|
+
const groupRoute = {
|
|
1198
|
+
id: 371750686228480,
|
|
1199
|
+
schemaUid: 'group-schema',
|
|
1200
|
+
title: 'Group with child',
|
|
1201
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1202
|
+
children: [childRoute],
|
|
1203
|
+
};
|
|
1204
|
+
engine.context.defineProperty('routeRepository', {
|
|
1205
|
+
value: {
|
|
1206
|
+
refreshAccessible: hookState.refresh,
|
|
1207
|
+
isAccessibleLoaded: () => true,
|
|
1208
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1209
|
+
getRouteBySchemaUid: vi.fn((schemaUid: string) => (schemaUid === 'child-flow-page' ? childRoute : undefined)),
|
|
1210
|
+
listAccessible: vi.fn(() => [groupRoute]),
|
|
1211
|
+
},
|
|
1212
|
+
});
|
|
1213
|
+
engine.context.defineProperty('app', {
|
|
1214
|
+
value: {
|
|
1215
|
+
getPublicPath: () => '/v2/',
|
|
1216
|
+
router: {
|
|
1217
|
+
getBasename: () => '/v2',
|
|
1218
|
+
},
|
|
1219
|
+
},
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1223
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1224
|
+
{
|
|
1225
|
+
registerRoutePage: vi.fn(),
|
|
1226
|
+
updateRoutePage: vi.fn(),
|
|
1227
|
+
unregisterRoutePage: vi.fn(),
|
|
1228
|
+
},
|
|
1229
|
+
);
|
|
1230
|
+
|
|
1231
|
+
render(
|
|
1232
|
+
<FlowEngineProvider engine={engine}>
|
|
1233
|
+
<MemoryRouter initialEntries={['/admin/371750686228480/tab/not-exists']}>
|
|
1234
|
+
<Routes>
|
|
1235
|
+
<Route path="/admin/:name/tab/:tabUid" element={<FlowRoute />} />
|
|
1236
|
+
</Routes>
|
|
1237
|
+
</MemoryRouter>
|
|
1238
|
+
</FlowEngineProvider>,
|
|
1239
|
+
);
|
|
1240
|
+
|
|
1241
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1242
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('child-flow-page', expect.any(Object));
|
|
1243
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('371750686228480', expect.any(Object));
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
it('should keep blank content while a group child navigation is already pending for the current path', async () => {
|
|
1247
|
+
const engine = new FlowEngine();
|
|
1248
|
+
const childRoute = {
|
|
1249
|
+
schemaUid: 'child-flow-page',
|
|
1250
|
+
title: 'Child flow page',
|
|
1251
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1252
|
+
};
|
|
1253
|
+
const groupRoute = {
|
|
1254
|
+
id: 371750686228480,
|
|
1255
|
+
schemaUid: 'group-schema',
|
|
1256
|
+
title: 'Group with child',
|
|
1257
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1258
|
+
children: [childRoute],
|
|
1259
|
+
};
|
|
1260
|
+
engine.context.defineProperty('routeRepository', {
|
|
1261
|
+
value: {
|
|
1262
|
+
refreshAccessible: hookState.refresh,
|
|
1263
|
+
isAccessibleLoaded: () => true,
|
|
1264
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1265
|
+
getRouteBySchemaUid: vi.fn((schemaUid: string) => (schemaUid === 'child-flow-page' ? childRoute : undefined)),
|
|
1266
|
+
listAccessible: vi.fn(() => [groupRoute]),
|
|
1267
|
+
},
|
|
1268
|
+
});
|
|
1269
|
+
engine.context.defineProperty('app', {
|
|
1270
|
+
value: {
|
|
1271
|
+
getPublicPath: () => '/v2/',
|
|
1272
|
+
router: {
|
|
1273
|
+
getBasename: () => '/v2',
|
|
1274
|
+
},
|
|
1275
|
+
},
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
const replace = vi.fn();
|
|
1279
|
+
const navigator: RouterNavigator = {
|
|
1280
|
+
createHref: (to: { pathname?: string; search?: string; hash?: string } | string) =>
|
|
1281
|
+
typeof to === 'string' ? to : `${to.pathname || ''}${to.search || ''}${to.hash || ''}`,
|
|
1282
|
+
go: vi.fn(),
|
|
1283
|
+
push: vi.fn(),
|
|
1284
|
+
replace,
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
const RerenderAfterReplace = () => {
|
|
1288
|
+
const [rerendered, setRerendered] = React.useState(false);
|
|
1289
|
+
React.useEffect(() => {
|
|
1290
|
+
if (!rerendered && replace.mock.calls.length > 0) {
|
|
1291
|
+
setRerendered(true);
|
|
1292
|
+
}
|
|
1293
|
+
}, [rerendered]);
|
|
1294
|
+
|
|
1295
|
+
return <FlowRoute legacyPageBehavior={rerendered ? 'bridge' : undefined} />;
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
render(
|
|
1299
|
+
<FlowEngineProvider engine={engine}>
|
|
1300
|
+
<Router location="/admin/371750686228480" navigator={navigator}>
|
|
1301
|
+
<Routes>
|
|
1302
|
+
<Route path="/admin/:name" element={<RerenderAfterReplace />} />
|
|
1303
|
+
</Routes>
|
|
1304
|
+
</Router>
|
|
1305
|
+
</FlowEngineProvider>,
|
|
1306
|
+
);
|
|
1307
|
+
|
|
1308
|
+
await waitFor(() => {
|
|
1309
|
+
expect(replace).toHaveBeenCalled();
|
|
1310
|
+
});
|
|
1311
|
+
await act(async () => {
|
|
1312
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1313
|
+
});
|
|
1314
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
it('should reset group navigation guard when page uid changes in the same route instance', async () => {
|
|
1318
|
+
const engine = new FlowEngine();
|
|
1319
|
+
const childRoute1 = {
|
|
1320
|
+
schemaUid: 'child-flow-page-1',
|
|
1321
|
+
title: 'Child flow page 1',
|
|
1322
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1323
|
+
};
|
|
1324
|
+
const childRoute2 = {
|
|
1325
|
+
schemaUid: 'child-flow-page-2',
|
|
1326
|
+
title: 'Child flow page 2',
|
|
1327
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1328
|
+
};
|
|
1329
|
+
const groupRoute1 = {
|
|
1330
|
+
id: 371750686228480,
|
|
1331
|
+
schemaUid: 'group-schema-1',
|
|
1332
|
+
title: 'Group with child 1',
|
|
1333
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1334
|
+
children: [childRoute1],
|
|
1335
|
+
};
|
|
1336
|
+
const groupRoute2 = {
|
|
1337
|
+
id: 371750755434496,
|
|
1338
|
+
schemaUid: 'group-schema-2',
|
|
1339
|
+
title: 'Group with child 2',
|
|
1340
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1341
|
+
children: [childRoute2],
|
|
1342
|
+
};
|
|
1343
|
+
const routeMap: Record<string, typeof childRoute1 | typeof childRoute2> = {
|
|
1344
|
+
'child-flow-page-1': childRoute1,
|
|
1345
|
+
'child-flow-page-2': childRoute2,
|
|
1346
|
+
};
|
|
1347
|
+
const getRouteBySchemaUid = vi.fn((schemaUid: string) => routeMap[schemaUid]);
|
|
1348
|
+
const listAccessible = vi.fn(() => [groupRoute1, groupRoute2]);
|
|
1349
|
+
engine.context.defineProperty('routeRepository', {
|
|
1350
|
+
value: {
|
|
1351
|
+
refreshAccessible: hookState.refresh,
|
|
1352
|
+
isAccessibleLoaded: () => true,
|
|
1353
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1354
|
+
getRouteBySchemaUid,
|
|
1355
|
+
listAccessible,
|
|
1356
|
+
},
|
|
1357
|
+
});
|
|
1358
|
+
engine.context.defineProperty('app', {
|
|
1359
|
+
value: {
|
|
1360
|
+
getPublicPath: () => '/v2/',
|
|
1361
|
+
router: {
|
|
1362
|
+
getBasename: () => '/v2',
|
|
1363
|
+
},
|
|
1364
|
+
},
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1368
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1369
|
+
{
|
|
1370
|
+
registerRoutePage: vi.fn(),
|
|
1371
|
+
updateRoutePage: vi.fn(),
|
|
1372
|
+
unregisterRoutePage: vi.fn(),
|
|
1373
|
+
},
|
|
1374
|
+
);
|
|
1375
|
+
let navigateTo: (path: string) => void = () => {};
|
|
1376
|
+
const CaptureNavigate = () => {
|
|
1377
|
+
const navigate = useNavigate();
|
|
1378
|
+
React.useEffect(() => {
|
|
1379
|
+
navigateTo = navigate;
|
|
1380
|
+
}, [navigate]);
|
|
1381
|
+
return null;
|
|
1382
|
+
};
|
|
1383
|
+
|
|
1384
|
+
render(
|
|
1385
|
+
<FlowEngineProvider engine={engine}>
|
|
1386
|
+
<MemoryRouter initialEntries={['/admin/371750686228480']}>
|
|
1387
|
+
<CaptureNavigate />
|
|
1388
|
+
<Routes>
|
|
1389
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1390
|
+
</Routes>
|
|
1391
|
+
</MemoryRouter>
|
|
1392
|
+
</FlowEngineProvider>,
|
|
1393
|
+
);
|
|
1394
|
+
|
|
1395
|
+
await waitFor(() => {
|
|
1396
|
+
expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith('child-flow-page-1', expect.any(Object));
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
act(() => {
|
|
1400
|
+
navigateTo('/admin/371750755434496');
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
await waitFor(() => {
|
|
1404
|
+
expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith('child-flow-page-2', expect.any(Object));
|
|
1405
|
+
});
|
|
1406
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('371750755434496', expect.any(Object));
|
|
1407
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1410
|
+
it('should keep rendering 404 when current admin route matches no accessible schema uid or id', async () => {
|
|
1411
|
+
const engine = new FlowEngine();
|
|
1412
|
+
engine.context.defineProperty('routeRepository', {
|
|
1413
|
+
value: {
|
|
1414
|
+
refreshAccessible: hookState.refresh,
|
|
1415
|
+
isAccessibleLoaded: () => true,
|
|
1416
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1417
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1418
|
+
listAccessible: vi.fn(() => [
|
|
1419
|
+
{
|
|
1420
|
+
id: 371750686228480,
|
|
1421
|
+
schemaUid: 'group-schema',
|
|
1422
|
+
title: '789',
|
|
1423
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1424
|
+
},
|
|
1425
|
+
]),
|
|
1426
|
+
},
|
|
1427
|
+
});
|
|
1428
|
+
engine.context.defineProperty('app', {
|
|
1429
|
+
value: {
|
|
1430
|
+
getPublicPath: () => '/v2/',
|
|
1431
|
+
router: {
|
|
1432
|
+
getBasename: () => '/v2',
|
|
1433
|
+
},
|
|
1434
|
+
},
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1437
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1438
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1439
|
+
{
|
|
1440
|
+
registerRoutePage: vi.fn(),
|
|
1441
|
+
updateRoutePage: vi.fn(),
|
|
1442
|
+
unregisterRoutePage: vi.fn(),
|
|
1443
|
+
},
|
|
1444
|
+
);
|
|
1445
|
+
|
|
1446
|
+
render(
|
|
1447
|
+
<FlowEngineProvider engine={engine}>
|
|
1448
|
+
<MemoryRouter initialEntries={['/admin/not-exists-4822']}>
|
|
1449
|
+
<Routes>
|
|
1450
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1451
|
+
</Routes>
|
|
1452
|
+
</MemoryRouter>
|
|
1453
|
+
</FlowEngineProvider>,
|
|
1454
|
+
);
|
|
1455
|
+
|
|
1456
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1457
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1458
|
+
});
|
|
1459
|
+
|
|
1460
|
+
it('should keep rendering 404 when current admin route matches only a non-group route id', async () => {
|
|
1461
|
+
const engine = new FlowEngine();
|
|
1462
|
+
engine.context.defineProperty('routeRepository', {
|
|
1463
|
+
value: {
|
|
1464
|
+
refreshAccessible: hookState.refresh,
|
|
1465
|
+
isAccessibleLoaded: () => true,
|
|
1466
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1467
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1468
|
+
listAccessible: vi.fn(() => [
|
|
1469
|
+
{
|
|
1470
|
+
id: 10001,
|
|
1471
|
+
schemaUid: 'flow-page-schema',
|
|
1472
|
+
title: 'Flow page',
|
|
1473
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1474
|
+
},
|
|
1475
|
+
]),
|
|
1476
|
+
},
|
|
1477
|
+
});
|
|
1478
|
+
engine.context.defineProperty('app', {
|
|
1479
|
+
value: {
|
|
1480
|
+
getPublicPath: () => '/v2/',
|
|
1481
|
+
router: {
|
|
1482
|
+
getBasename: () => '/v2',
|
|
1483
|
+
},
|
|
1484
|
+
},
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1488
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1489
|
+
{
|
|
1490
|
+
registerRoutePage: vi.fn(),
|
|
1491
|
+
updateRoutePage: vi.fn(),
|
|
1492
|
+
unregisterRoutePage: vi.fn(),
|
|
1493
|
+
},
|
|
1494
|
+
);
|
|
1495
|
+
|
|
1496
|
+
render(
|
|
1497
|
+
<FlowEngineProvider engine={engine}>
|
|
1498
|
+
<MemoryRouter initialEntries={['/admin/10001']}>
|
|
1499
|
+
<Routes>
|
|
1500
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1501
|
+
</Routes>
|
|
1502
|
+
</MemoryRouter>
|
|
1503
|
+
</FlowEngineProvider>,
|
|
1504
|
+
);
|
|
1505
|
+
|
|
1506
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1507
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1508
|
+
});
|
|
1509
|
+
|
|
1510
|
+
it('should keep rendering 404 when current admin route matches only a group schema uid', async () => {
|
|
1511
|
+
const engine = new FlowEngine();
|
|
1512
|
+
const groupRoute = {
|
|
1513
|
+
id: 371750686228480,
|
|
1514
|
+
schemaUid: 'group-schema',
|
|
1515
|
+
title: '789',
|
|
1516
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1517
|
+
};
|
|
1518
|
+
engine.context.defineProperty('routeRepository', {
|
|
1519
|
+
value: {
|
|
1520
|
+
refreshAccessible: hookState.refresh,
|
|
1521
|
+
isAccessibleLoaded: () => true,
|
|
1522
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1523
|
+
getRouteBySchemaUid: vi.fn((schemaUid: string) => (schemaUid === 'group-schema' ? groupRoute : undefined)),
|
|
1524
|
+
listAccessible: vi.fn(() => [groupRoute]),
|
|
1525
|
+
},
|
|
1526
|
+
});
|
|
1527
|
+
engine.context.defineProperty('app', {
|
|
1528
|
+
value: {
|
|
1529
|
+
getPublicPath: () => '/v2/',
|
|
1530
|
+
router: {
|
|
1531
|
+
getBasename: () => '/v2',
|
|
1532
|
+
},
|
|
1533
|
+
},
|
|
1534
|
+
});
|
|
1535
|
+
|
|
1536
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1537
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1538
|
+
{
|
|
1539
|
+
registerRoutePage: vi.fn(),
|
|
1540
|
+
updateRoutePage: vi.fn(),
|
|
1541
|
+
unregisterRoutePage: vi.fn(),
|
|
1542
|
+
},
|
|
1543
|
+
);
|
|
1544
|
+
|
|
1545
|
+
render(
|
|
1546
|
+
<FlowEngineProvider engine={engine}>
|
|
1547
|
+
<MemoryRouter initialEntries={['/admin/group-schema']}>
|
|
1548
|
+
<Routes>
|
|
1549
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1550
|
+
</Routes>
|
|
1551
|
+
</MemoryRouter>
|
|
1552
|
+
</FlowEngineProvider>,
|
|
1553
|
+
);
|
|
1554
|
+
|
|
1555
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1556
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
it('should not use flowModels:findOne to bridge a missing route for protected layouts', async () => {
|
|
1560
|
+
const engine = new FlowEngine();
|
|
1561
|
+
const findOne = vi.fn().mockResolvedValue({
|
|
1562
|
+
uid: 'mobile-denied-page',
|
|
1563
|
+
use: 'FlowModel',
|
|
1564
|
+
});
|
|
1565
|
+
engine.setModelRepository({
|
|
1566
|
+
findOne,
|
|
1567
|
+
save: vi.fn(),
|
|
1568
|
+
destroy: vi.fn(),
|
|
1569
|
+
} as any);
|
|
1570
|
+
engine.context.defineProperty('routeRepository', {
|
|
1571
|
+
value: {
|
|
1572
|
+
refreshAccessible: hookState.refresh,
|
|
1573
|
+
isAccessibleLoaded: () => true,
|
|
1574
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1575
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1576
|
+
},
|
|
1577
|
+
});
|
|
1578
|
+
engine.context.defineProperty('app', {
|
|
1579
|
+
value: {
|
|
1580
|
+
getPublicPath: () => '/v2/',
|
|
1581
|
+
router: {
|
|
1582
|
+
getBasename: () => '/v2',
|
|
1583
|
+
},
|
|
1584
|
+
},
|
|
1585
|
+
});
|
|
1586
|
+
const routeModel = engine.createModel({
|
|
1587
|
+
uid: 'mobile-route-model',
|
|
1588
|
+
use: 'FlowModel',
|
|
1589
|
+
});
|
|
1590
|
+
routeModel.context.defineProperty('layout', {
|
|
1591
|
+
value: {
|
|
1592
|
+
routeName: 'mobile',
|
|
1593
|
+
routePath: '/mobile',
|
|
1594
|
+
rootRouteName: 'mobile',
|
|
1595
|
+
uid: 'mobile-layout-model',
|
|
1596
|
+
layoutModelClass: 'MobileLayoutModel',
|
|
1597
|
+
rootPageModelClass: 'MobileRootPageModel',
|
|
1598
|
+
childPageModelClass: 'MobileChildPageModel',
|
|
1599
|
+
authCheck: true,
|
|
1600
|
+
},
|
|
1601
|
+
});
|
|
1602
|
+
|
|
1603
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
1604
|
+
engine.createModel({ uid: 'mobile-layout-model', use: 'FlowModel' }),
|
|
1605
|
+
{
|
|
1606
|
+
registerRoutePage: vi.fn(),
|
|
1607
|
+
updateRoutePage: vi.fn(),
|
|
1608
|
+
unregisterRoutePage: vi.fn(),
|
|
1609
|
+
},
|
|
1610
|
+
);
|
|
1611
|
+
|
|
1612
|
+
render(
|
|
1613
|
+
<FlowEngineProvider engine={engine}>
|
|
1614
|
+
<FlowContextProvider context={routeModel.context}>
|
|
1615
|
+
<MemoryRouter initialEntries={['/mobile/mobile-denied-page']}>
|
|
1616
|
+
<Routes>
|
|
1617
|
+
<Route
|
|
1618
|
+
path="/mobile/:name"
|
|
1619
|
+
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel} />}
|
|
1620
|
+
/>
|
|
1621
|
+
</Routes>
|
|
1622
|
+
</MemoryRouter>
|
|
1623
|
+
</FlowContextProvider>
|
|
1624
|
+
</FlowEngineProvider>,
|
|
1625
|
+
);
|
|
1626
|
+
|
|
1627
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1628
|
+
expect(findOne).not.toHaveBeenCalled();
|
|
1629
|
+
expect(layoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1630
|
+
});
|
|
1631
|
+
|
|
1632
|
+
it('should not load a known page uid when a protected custom layout has no accessible route', async () => {
|
|
1633
|
+
const engine = new FlowEngine();
|
|
1634
|
+
const findOne = vi.fn().mockResolvedValue({
|
|
1635
|
+
uid: 'known-denied-custom-page',
|
|
1636
|
+
use: 'FlowModel',
|
|
1637
|
+
});
|
|
1638
|
+
engine.setModelRepository({
|
|
1639
|
+
findOne,
|
|
1640
|
+
save: vi.fn(),
|
|
1641
|
+
destroy: vi.fn(),
|
|
1642
|
+
} as any);
|
|
1643
|
+
engine.context.defineProperty('routeRepository', {
|
|
1644
|
+
value: {
|
|
1645
|
+
refreshAccessible: hookState.refresh,
|
|
1646
|
+
isAccessibleLoaded: () => true,
|
|
1647
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1648
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1649
|
+
},
|
|
1650
|
+
});
|
|
1651
|
+
engine.context.defineProperty('app', {
|
|
1652
|
+
value: {
|
|
1653
|
+
getPublicPath: () => '/v2/',
|
|
1654
|
+
router: {
|
|
1655
|
+
getBasename: () => '/v2',
|
|
1656
|
+
},
|
|
1657
|
+
},
|
|
1658
|
+
});
|
|
1659
|
+
const routeModel = engine.createModel({
|
|
1660
|
+
uid: 'custom-layout-route-model',
|
|
1661
|
+
use: 'FlowModel',
|
|
1662
|
+
});
|
|
1663
|
+
routeModel.context.defineProperty('layout', {
|
|
1664
|
+
value: {
|
|
1665
|
+
routeName: 'admin2',
|
|
1666
|
+
routePath: '/admin2',
|
|
1667
|
+
rootRouteName: 'admin2',
|
|
1668
|
+
uid: 'custom-protected-layout-model',
|
|
1669
|
+
layoutModelClass: 'AdminLayoutModel',
|
|
1670
|
+
rootPageModelClass: 'RootPageModel',
|
|
1671
|
+
childPageModelClass: 'ChildPageModel',
|
|
1672
|
+
authCheck: true,
|
|
1673
|
+
},
|
|
1674
|
+
});
|
|
1675
|
+
|
|
1676
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
1677
|
+
engine.createModel({ uid: 'custom-protected-layout-model', use: 'FlowModel' }),
|
|
1678
|
+
{
|
|
1679
|
+
registerRoutePage: vi.fn(),
|
|
1680
|
+
updateRoutePage: vi.fn(),
|
|
1681
|
+
unregisterRoutePage: vi.fn(),
|
|
1682
|
+
},
|
|
1683
|
+
);
|
|
1684
|
+
|
|
1685
|
+
render(
|
|
1686
|
+
<FlowEngineProvider engine={engine}>
|
|
1687
|
+
<FlowContextProvider context={routeModel.context}>
|
|
1688
|
+
<MemoryRouter initialEntries={['/admin2/known-denied-custom-page']}>
|
|
1689
|
+
<Routes>
|
|
1690
|
+
<Route
|
|
1691
|
+
path="/admin2/:name"
|
|
1692
|
+
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel} />}
|
|
1693
|
+
/>
|
|
1694
|
+
</Routes>
|
|
1695
|
+
</MemoryRouter>
|
|
1696
|
+
</FlowContextProvider>
|
|
1697
|
+
</FlowEngineProvider>,
|
|
1698
|
+
);
|
|
1699
|
+
|
|
1700
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1701
|
+
expect(findOne).not.toHaveBeenCalled();
|
|
1702
|
+
expect(layoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1703
|
+
});
|
|
1704
|
+
|
|
1705
|
+
it('should check model existence without occupying the route model uid', async () => {
|
|
1706
|
+
const engine = new FlowEngine();
|
|
1707
|
+
const findOne = vi.fn().mockResolvedValue({
|
|
1708
|
+
uid: 'public-form-1',
|
|
1709
|
+
});
|
|
1710
|
+
const request = vi.fn().mockResolvedValue({
|
|
1711
|
+
data: {
|
|
1712
|
+
data: {
|
|
1713
|
+
uid: 'public-form-1',
|
|
1714
|
+
},
|
|
1715
|
+
},
|
|
1716
|
+
});
|
|
1717
|
+
engine.setModelRepository({
|
|
1718
|
+
findOne,
|
|
1719
|
+
save: vi.fn(),
|
|
1720
|
+
destroy: vi.fn(),
|
|
1721
|
+
} as any);
|
|
1722
|
+
engine.context.defineProperty('routeRepository', {
|
|
1723
|
+
value: {
|
|
1724
|
+
refreshAccessible: hookState.refresh,
|
|
1725
|
+
isAccessibleLoaded: () => true,
|
|
1726
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1727
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1728
|
+
},
|
|
1729
|
+
});
|
|
1730
|
+
engine.context.defineProperty('app', {
|
|
1731
|
+
value: {
|
|
1732
|
+
apiClient: {
|
|
1733
|
+
request,
|
|
1734
|
+
},
|
|
1735
|
+
getPublicPath: () => '/v2/',
|
|
1736
|
+
router: {
|
|
1737
|
+
getBasename: () => '/v2',
|
|
1738
|
+
},
|
|
1739
|
+
},
|
|
1740
|
+
});
|
|
1741
|
+
const routeModel = engine.createModel({
|
|
1742
|
+
uid: 'public-form-route-model',
|
|
1743
|
+
use: 'FlowModel',
|
|
1744
|
+
});
|
|
1745
|
+
routeModel.context.defineProperty('layout', {
|
|
1746
|
+
value: {
|
|
1747
|
+
routeName: 'public-forms',
|
|
1748
|
+
routePath: '/public-forms',
|
|
1749
|
+
rootRouteName: 'public-forms',
|
|
1750
|
+
uid: 'public-form-layout-model',
|
|
1751
|
+
layoutModelClass: 'PublicFormLayoutModel',
|
|
1752
|
+
rootPageModelClass: 'PublicFormPageModel',
|
|
1753
|
+
childPageModelClass: 'ChildPageModel',
|
|
1754
|
+
authCheck: false,
|
|
1755
|
+
},
|
|
1756
|
+
});
|
|
1757
|
+
|
|
1758
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
1759
|
+
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
1760
|
+
{
|
|
1761
|
+
registerRoutePage: vi.fn(),
|
|
1762
|
+
updateRoutePage: vi.fn(),
|
|
1763
|
+
unregisterRoutePage: vi.fn(),
|
|
1764
|
+
},
|
|
1765
|
+
);
|
|
1766
|
+
|
|
1767
|
+
render(
|
|
1768
|
+
<FlowEngineProvider engine={engine}>
|
|
1769
|
+
<FlowContextProvider context={routeModel.context}>
|
|
1770
|
+
<MemoryRouter initialEntries={['/public-forms/public-form-1']}>
|
|
1771
|
+
<Routes>
|
|
1772
|
+
<Route path="/public-forms/:name" element={<FlowRoute legacyPageBehavior="notFound" />} />
|
|
1773
|
+
</Routes>
|
|
1774
|
+
</MemoryRouter>
|
|
1775
|
+
</FlowContextProvider>
|
|
1776
|
+
</FlowEngineProvider>,
|
|
1777
|
+
);
|
|
1778
|
+
|
|
1779
|
+
await waitFor(() => {
|
|
1780
|
+
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('public-form-1', expect.any(Object));
|
|
1781
|
+
});
|
|
1782
|
+
expect(findOne).toHaveBeenCalledWith({ uid: 'public-form-1' });
|
|
1783
|
+
expect(request).not.toHaveBeenCalled();
|
|
1784
|
+
expect(engine.getModel('public-form-1')).toBeUndefined();
|
|
1785
|
+
});
|
|
1786
|
+
|
|
1787
|
+
it('should skip accessible route loading when layout authCheck is false', async () => {
|
|
1788
|
+
const engine = new FlowEngine();
|
|
1789
|
+
const ensureAccessibleLoaded = vi.fn().mockRejectedValue(new Error('cannot load accessible routes'));
|
|
1790
|
+
const getRouteBySchemaUid = vi.fn();
|
|
1791
|
+
engine.setModelRepository({
|
|
1792
|
+
findOne: vi.fn().mockResolvedValue({
|
|
1793
|
+
uid: 'public-form-1',
|
|
1794
|
+
use: 'FlowModel',
|
|
1795
|
+
}),
|
|
1796
|
+
save: vi.fn(),
|
|
1797
|
+
destroy: vi.fn(),
|
|
1798
|
+
} as any);
|
|
1799
|
+
engine.context.defineProperty('routeRepository', {
|
|
1800
|
+
value: {
|
|
1801
|
+
refreshAccessible: hookState.refresh,
|
|
1802
|
+
isAccessibleLoaded: () => false,
|
|
1803
|
+
ensureAccessibleLoaded,
|
|
1804
|
+
getRouteBySchemaUid,
|
|
1805
|
+
},
|
|
1806
|
+
});
|
|
1807
|
+
engine.context.defineProperty('app', {
|
|
1808
|
+
value: {
|
|
1809
|
+
getPublicPath: () => '/v2/',
|
|
1810
|
+
router: {
|
|
1811
|
+
getBasename: () => '/v2',
|
|
1812
|
+
},
|
|
1813
|
+
},
|
|
1814
|
+
});
|
|
1815
|
+
const routeModel = engine.createModel({
|
|
1816
|
+
uid: 'public-form-route-model',
|
|
1817
|
+
use: 'FlowModel',
|
|
1818
|
+
});
|
|
1819
|
+
routeModel.context.defineProperty('layout', {
|
|
1820
|
+
value: {
|
|
1821
|
+
routeName: 'public-forms',
|
|
1822
|
+
routePath: '/public-forms',
|
|
1823
|
+
rootRouteName: 'public-forms',
|
|
1824
|
+
uid: 'public-form-layout-model',
|
|
1825
|
+
layoutModelClass: 'PublicFormLayoutModel',
|
|
1826
|
+
rootPageModelClass: 'PublicFormPageModel',
|
|
1827
|
+
childPageModelClass: 'ChildPageModel',
|
|
1828
|
+
authCheck: false,
|
|
1829
|
+
},
|
|
1830
|
+
});
|
|
1831
|
+
|
|
1832
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
1833
|
+
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
1834
|
+
{
|
|
1835
|
+
registerRoutePage: vi.fn(),
|
|
1836
|
+
updateRoutePage: vi.fn(),
|
|
1837
|
+
unregisterRoutePage: vi.fn(),
|
|
1838
|
+
},
|
|
1839
|
+
);
|
|
1840
|
+
|
|
1841
|
+
render(
|
|
1842
|
+
<FlowEngineProvider engine={engine}>
|
|
1843
|
+
<FlowContextProvider context={routeModel.context}>
|
|
1844
|
+
<MemoryRouter initialEntries={['/public-forms/public-form-1']}>
|
|
1845
|
+
<Routes>
|
|
1846
|
+
<Route path="/public-forms/:name" element={<FlowRoute legacyPageBehavior="notFound" />} />
|
|
1847
|
+
</Routes>
|
|
1848
|
+
</MemoryRouter>
|
|
1849
|
+
</FlowContextProvider>
|
|
1850
|
+
</FlowEngineProvider>,
|
|
1851
|
+
);
|
|
1852
|
+
|
|
1853
|
+
await waitFor(() => {
|
|
1854
|
+
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('public-form-1', expect.any(Object));
|
|
1855
|
+
});
|
|
1856
|
+
expect(ensureAccessibleLoaded).not.toHaveBeenCalled();
|
|
1857
|
+
expect(getRouteBySchemaUid).not.toHaveBeenCalled();
|
|
756
1858
|
});
|
|
757
1859
|
|
|
758
1860
|
it('should bridge legacy page when behavior is bridge', async () => {
|
|
@@ -817,7 +1919,7 @@ describe('FlowRoute', () => {
|
|
|
817
1919
|
}
|
|
818
1920
|
});
|
|
819
1921
|
|
|
820
|
-
it('should
|
|
1922
|
+
it('should render 404 when route and FlowModel do not exist', async () => {
|
|
821
1923
|
const originalLocation = window.location;
|
|
822
1924
|
const replace = vi.fn();
|
|
823
1925
|
Object.defineProperty(window, 'location', {
|
|
@@ -866,9 +1968,8 @@ describe('FlowRoute', () => {
|
|
|
866
1968
|
</FlowEngineProvider>,
|
|
867
1969
|
);
|
|
868
1970
|
|
|
869
|
-
await
|
|
870
|
-
|
|
871
|
-
});
|
|
1971
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1972
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
872
1973
|
expect(replace).not.toHaveBeenCalled();
|
|
873
1974
|
} finally {
|
|
874
1975
|
Object.defineProperty(window, 'location', {
|