@nocobase/client-v2 2.2.0-alpha.1 → 2.2.0-alpha.10
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,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
11
11
|
import type { DragEndEvent } from '@dnd-kit/core';
|
|
12
|
+
import { render, waitFor } from '@testing-library/react';
|
|
12
13
|
|
|
13
14
|
// Mock FlowModel and other dependencies
|
|
14
15
|
vi.mock('@nocobase/flow-engine', () => {
|
|
@@ -140,13 +141,9 @@ vi.mock('@nocobase/flow-engine', () => {
|
|
|
140
141
|
jioToJoiSchema: () => ({
|
|
141
142
|
validate: (value: any) => ({ value }),
|
|
142
143
|
}),
|
|
143
|
-
createSafeWindow: () => window,
|
|
144
|
-
createSafeDocument: () => document,
|
|
145
|
-
createSafeNavigator: () => navigator,
|
|
146
144
|
isRunJSValue: () => false,
|
|
147
145
|
isVariableExpression: () => false,
|
|
148
146
|
normalizeRunJSValue: (value: any) => value,
|
|
149
|
-
runjsWithSafeGlobals: async (handler: any, ...args: any[]) => handler?.(...args),
|
|
150
147
|
parseCtxDateExpression: (value: any) => value,
|
|
151
148
|
setupRunJSContexts: () => undefined,
|
|
152
149
|
getRunJSScenesForContext: () => [],
|
|
@@ -225,6 +222,17 @@ describe('PageModel', () => {
|
|
|
225
222
|
let pageModel: InstanceType<typeof PageModelClass>;
|
|
226
223
|
let mockDragEndEvent: DragEndEvent;
|
|
227
224
|
|
|
225
|
+
const getDndChildren = (result: { props: { children?: unknown } }) => {
|
|
226
|
+
const children = result.props.children;
|
|
227
|
+
return Array.isArray(children) ? children : [children].filter(Boolean);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const getTabsElement = (result: { props: { children?: unknown } }) => getDndChildren(result)[1] as any;
|
|
231
|
+
|
|
232
|
+
const getTabSyncElement = (result: { props: { children?: unknown } }) => getDndChildren(result)[0] as any;
|
|
233
|
+
|
|
234
|
+
const getHiddenActiveTabContent = (result: { props: { children?: unknown } }) => getDndChildren(result)[2];
|
|
235
|
+
|
|
228
236
|
beforeEach(async () => {
|
|
229
237
|
vi.resetModules();
|
|
230
238
|
vi.clearAllMocks();
|
|
@@ -303,9 +311,82 @@ describe('PageModel', () => {
|
|
|
303
311
|
expect(blockOnActive).toHaveBeenCalledWith(true);
|
|
304
312
|
expect(blockOnInactive).toHaveBeenCalledWith(false);
|
|
305
313
|
});
|
|
314
|
+
|
|
315
|
+
it('should use the first actual tab for lifecycle when tabs are disabled even if it is hidden', () => {
|
|
316
|
+
const hiddenFirstTab = {
|
|
317
|
+
uid: 'tab-hidden-first',
|
|
318
|
+
hidden: true,
|
|
319
|
+
context: {},
|
|
320
|
+
subModels: { grid: { mapSubModels: vi.fn() } },
|
|
321
|
+
};
|
|
322
|
+
const visibleSecondTab = {
|
|
323
|
+
uid: 'tab-visible-second',
|
|
324
|
+
context: {},
|
|
325
|
+
subModels: { grid: { mapSubModels: vi.fn() } },
|
|
326
|
+
};
|
|
327
|
+
(pageModel as any).subModels = { tabs: [hiddenFirstTab, visibleSecondTab] };
|
|
328
|
+
pageModel.props = { enableTabs: false, tabActiveKey: 'tab-visible-second' } as any;
|
|
329
|
+
(pageModel as any).context = { view: { navigation: null } };
|
|
330
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod').mockImplementation(() => undefined);
|
|
331
|
+
|
|
332
|
+
pageModel.activateCurrentTab();
|
|
333
|
+
pageModel.deactivateCurrentTab();
|
|
334
|
+
|
|
335
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(1, 'tab-hidden-first', 'onActive', false);
|
|
336
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(2, 'tab-hidden-first', 'onInactive');
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('should keep a hidden active tab responsible for lifecycle when tabs are enabled', () => {
|
|
340
|
+
const hiddenActiveTab = {
|
|
341
|
+
uid: 'tab-hidden-active',
|
|
342
|
+
hidden: true,
|
|
343
|
+
context: {},
|
|
344
|
+
subModels: { grid: { mapSubModels: vi.fn() } },
|
|
345
|
+
};
|
|
346
|
+
const visibleTab = {
|
|
347
|
+
uid: 'tab-visible',
|
|
348
|
+
context: {},
|
|
349
|
+
subModels: { grid: { mapSubModels: vi.fn() } },
|
|
350
|
+
};
|
|
351
|
+
(pageModel as any).subModels = { tabs: [hiddenActiveTab, visibleTab] };
|
|
352
|
+
pageModel.props = { enableTabs: true, tabActiveKey: 'tab-hidden-active' } as any;
|
|
353
|
+
(pageModel as any).context = {
|
|
354
|
+
view: { navigation: { viewParams: { tabUid: 'tab-hidden-active' } } },
|
|
355
|
+
};
|
|
356
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod').mockImplementation(() => undefined);
|
|
357
|
+
|
|
358
|
+
pageModel.activateCurrentTab();
|
|
359
|
+
pageModel.deactivateCurrentTab();
|
|
360
|
+
|
|
361
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(1, 'tab-hidden-active', 'onActive', false);
|
|
362
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(2, 'tab-hidden-active', 'onInactive');
|
|
363
|
+
});
|
|
306
364
|
});
|
|
307
365
|
|
|
308
366
|
describe('renderTabs activeKey logic', () => {
|
|
367
|
+
const createTab = (uid: string, hidden = false) => ({
|
|
368
|
+
uid,
|
|
369
|
+
hidden,
|
|
370
|
+
context: {},
|
|
371
|
+
subModels: { grid: { mapSubModels: vi.fn() } },
|
|
372
|
+
renderChildren: vi.fn(() => uid),
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
const initialSyncState = {
|
|
376
|
+
previousEffectiveActiveKey: undefined,
|
|
377
|
+
previousAllHidden: false,
|
|
378
|
+
lastCorrectionSignature: undefined,
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const runTabSync = (
|
|
382
|
+
result: { props: { children?: unknown } },
|
|
383
|
+
state: typeof initialSyncState | Record<string, unknown> = initialSyncState,
|
|
384
|
+
) => {
|
|
385
|
+
const syncElement = getTabSyncElement(result);
|
|
386
|
+
expect(typeof syncElement?.props?.onSync).toBe('function');
|
|
387
|
+
return syncElement.props.onSync(state);
|
|
388
|
+
};
|
|
389
|
+
|
|
309
390
|
beforeEach(() => {
|
|
310
391
|
// Mock mapTabs to avoid complex rendering logic inside it
|
|
311
392
|
pageModel.mapTabs = vi.fn().mockReturnValue([]);
|
|
@@ -329,7 +410,7 @@ describe('PageModel', () => {
|
|
|
329
410
|
|
|
330
411
|
const result = pageModel.renderTabs() as any;
|
|
331
412
|
// result is <DndProvider><Tabs ... /></DndProvider>
|
|
332
|
-
const tabsElement = result
|
|
413
|
+
const tabsElement = getTabsElement(result);
|
|
333
414
|
|
|
334
415
|
expect(tabsElement.props.activeKey).toBe('tab-from-params');
|
|
335
416
|
});
|
|
@@ -345,10 +426,589 @@ describe('PageModel', () => {
|
|
|
345
426
|
};
|
|
346
427
|
|
|
347
428
|
const result = pageModel.renderTabs() as any;
|
|
348
|
-
const tabsElement = result
|
|
429
|
+
const tabsElement = getTabsElement(result);
|
|
349
430
|
expect(tabsElement.props.activeKey).toBe('first-tab-uid');
|
|
350
431
|
});
|
|
351
432
|
|
|
433
|
+
it('should use the first visible tab when activeKey is unspecified', () => {
|
|
434
|
+
const hiddenTab = createTab('tab-hidden', true);
|
|
435
|
+
const visibleTab = createTab('tab-visible');
|
|
436
|
+
(pageModel as any).subModels = { tabs: [hiddenTab, visibleTab] };
|
|
437
|
+
pageModel.context.view = {
|
|
438
|
+
navigation: {
|
|
439
|
+
viewParams: {},
|
|
440
|
+
changeTo: vi.fn(),
|
|
441
|
+
},
|
|
442
|
+
} as any;
|
|
443
|
+
|
|
444
|
+
const tabsElement = getTabsElement(pageModel.renderTabs() as any);
|
|
445
|
+
|
|
446
|
+
expect(tabsElement.props.activeKey).toBe('tab-visible');
|
|
447
|
+
expect(pageModel.context.view.navigation.changeTo).not.toHaveBeenCalled();
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it('should keep a visible URL activeKey unchanged', () => {
|
|
451
|
+
(pageModel as any).subModels = { tabs: [createTab('tab-1'), createTab('tab-2')] };
|
|
452
|
+
const changeTo = vi.fn();
|
|
453
|
+
pageModel.context.view = {
|
|
454
|
+
navigation: {
|
|
455
|
+
viewParams: { tabUid: 'tab-2' },
|
|
456
|
+
changeTo,
|
|
457
|
+
},
|
|
458
|
+
} as any;
|
|
459
|
+
|
|
460
|
+
const result = pageModel.renderTabs() as any;
|
|
461
|
+
const tabsElement = getTabsElement(result);
|
|
462
|
+
const nextState = runTabSync(result);
|
|
463
|
+
|
|
464
|
+
expect(tabsElement.props.activeKey).toBe('tab-2');
|
|
465
|
+
expect(nextState.effectiveActiveKey).toBe('tab-2');
|
|
466
|
+
expect(changeTo).not.toHaveBeenCalled();
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('should keep an unknown URL activeKey for the existing deep-link behavior', () => {
|
|
470
|
+
(pageModel as any).subModels = { tabs: [createTab('tab-1'), createTab('tab-2')] };
|
|
471
|
+
const changeTo = vi.fn();
|
|
472
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod');
|
|
473
|
+
pageModel.context.view = {
|
|
474
|
+
navigation: {
|
|
475
|
+
viewParams: { tabUid: 'unknown-tab' },
|
|
476
|
+
changeTo,
|
|
477
|
+
},
|
|
478
|
+
} as any;
|
|
479
|
+
|
|
480
|
+
const result = pageModel.renderTabs() as any;
|
|
481
|
+
const tabsElement = getTabsElement(result);
|
|
482
|
+
const nextState = runTabSync(result);
|
|
483
|
+
|
|
484
|
+
expect(tabsElement.props.activeKey).toBe('unknown-tab');
|
|
485
|
+
expect(nextState.effectiveActiveKey).toBe('unknown-tab');
|
|
486
|
+
expect(changeTo).not.toHaveBeenCalled();
|
|
487
|
+
expect(invokeSpy).not.toHaveBeenCalled();
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('should keep a hidden URL activeKey and render its content without exposing a tab item', () => {
|
|
491
|
+
(pageModel as any).subModels = { tabs: [createTab('tab-hidden', true), createTab('tab-visible')] };
|
|
492
|
+
pageModel.mapTabs = PageModelClass.prototype.mapTabs.bind(pageModel);
|
|
493
|
+
pageModel.props = { tabActiveKey: 'tab-hidden' } as any;
|
|
494
|
+
const changeTo = vi.fn();
|
|
495
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod').mockImplementation(() => undefined);
|
|
496
|
+
pageModel.context.view = {
|
|
497
|
+
navigation: {
|
|
498
|
+
viewParams: { tabUid: 'tab-hidden' },
|
|
499
|
+
changeTo,
|
|
500
|
+
},
|
|
501
|
+
} as any;
|
|
502
|
+
|
|
503
|
+
const result = pageModel.renderTabs() as any;
|
|
504
|
+
const tabsElement = getTabsElement(result);
|
|
505
|
+
const nextState = runTabSync(result, {
|
|
506
|
+
...initialSyncState,
|
|
507
|
+
previousEffectiveActiveKey: 'tab-hidden',
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
expect(tabsElement.props.activeKey).toBe('tab-hidden');
|
|
511
|
+
expect(tabsElement.props.items.map((item: { key: string }) => item.key)).toEqual(['tab-hidden', 'tab-visible']);
|
|
512
|
+
expect(tabsElement.props.items[0].children).toBe('tab-hidden');
|
|
513
|
+
expect(getHiddenActiveTabContent(result)).toBeUndefined();
|
|
514
|
+
expect(typeof tabsElement.props.renderTabBar).toBe('function');
|
|
515
|
+
expect(changeTo).not.toHaveBeenCalled();
|
|
516
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-hidden');
|
|
517
|
+
expect(invokeSpy).not.toHaveBeenCalled();
|
|
518
|
+
expect(nextState.effectiveActiveKey).toBe('tab-hidden');
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it('should keep a hidden explicit props activeKey and render its content without lifecycle switching', () => {
|
|
522
|
+
(pageModel as any).subModels = { tabs: [createTab('tab-hidden', true), createTab('tab-visible')] };
|
|
523
|
+
pageModel.props = { tabActiveKey: 'tab-hidden' } as any;
|
|
524
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod').mockImplementation(() => undefined);
|
|
525
|
+
pageModel.context.view = { navigation: null } as any;
|
|
526
|
+
|
|
527
|
+
const result = pageModel.renderTabs() as any;
|
|
528
|
+
const nextState = runTabSync(result, {
|
|
529
|
+
...initialSyncState,
|
|
530
|
+
previousEffectiveActiveKey: 'tab-hidden',
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
expect(getTabsElement(result).props.activeKey).toBe('tab-hidden');
|
|
534
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-hidden');
|
|
535
|
+
expect(invokeSpy).not.toHaveBeenCalled();
|
|
536
|
+
expect(nextState.effectiveActiveKey).toBe('tab-hidden');
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('should not switch when a non-active tab becomes hidden', () => {
|
|
540
|
+
(pageModel as any).subModels = { tabs: [createTab('tab-active'), createTab('tab-hidden', true)] };
|
|
541
|
+
pageModel.props = { tabActiveKey: 'tab-active' } as any;
|
|
542
|
+
const changeTo = vi.fn();
|
|
543
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod');
|
|
544
|
+
pageModel.context.view = {
|
|
545
|
+
navigation: {
|
|
546
|
+
viewParams: { tabUid: 'tab-active' },
|
|
547
|
+
changeTo,
|
|
548
|
+
},
|
|
549
|
+
} as any;
|
|
550
|
+
|
|
551
|
+
const nextState = runTabSync(pageModel.renderTabs() as any, {
|
|
552
|
+
...initialSyncState,
|
|
553
|
+
previousEffectiveActiveKey: 'tab-active',
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
expect(nextState.effectiveActiveKey).toBe('tab-active');
|
|
557
|
+
expect(changeTo).not.toHaveBeenCalled();
|
|
558
|
+
expect(invokeSpy).not.toHaveBeenCalled();
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it('should switch an implicitly active tab when it becomes hidden without adding tabUid to URL', () => {
|
|
562
|
+
const oldTab = createTab('tab-old');
|
|
563
|
+
(pageModel as any).subModels = { tabs: [oldTab, createTab('tab-new')] };
|
|
564
|
+
const changeTo = vi.fn();
|
|
565
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod');
|
|
566
|
+
const updateTitleSpy = vi.spyOn(pageModel as any, 'updateDocumentTitle').mockResolvedValue(undefined);
|
|
567
|
+
pageModel.context.view = {
|
|
568
|
+
navigation: {
|
|
569
|
+
viewParams: {},
|
|
570
|
+
changeTo,
|
|
571
|
+
},
|
|
572
|
+
} as any;
|
|
573
|
+
|
|
574
|
+
let syncState = runTabSync(pageModel.renderTabs() as any);
|
|
575
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-old');
|
|
576
|
+
|
|
577
|
+
invokeSpy.mockClear();
|
|
578
|
+
updateTitleSpy.mockClear();
|
|
579
|
+
oldTab.hidden = true;
|
|
580
|
+
const result = pageModel.renderTabs() as any;
|
|
581
|
+
syncState = runTabSync(result, syncState);
|
|
582
|
+
|
|
583
|
+
expect(changeTo).not.toHaveBeenCalled();
|
|
584
|
+
expect(getTabsElement(result).props.activeKey).toBe('tab-new');
|
|
585
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-new');
|
|
586
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(1, 'tab-new', 'onActive');
|
|
587
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(2, 'tab-old', 'onInactive');
|
|
588
|
+
expect(updateTitleSpy).toHaveBeenCalledWith('tab-new', 0);
|
|
589
|
+
expect(syncState.effectiveActiveKey).toBe('tab-new');
|
|
590
|
+
|
|
591
|
+
invokeSpy.mockClear();
|
|
592
|
+
updateTitleSpy.mockClear();
|
|
593
|
+
syncState = runTabSync(pageModel.renderTabs() as any, syncState);
|
|
594
|
+
|
|
595
|
+
expect(invokeSpy).not.toHaveBeenCalled();
|
|
596
|
+
expect(updateTitleSpy).not.toHaveBeenCalled();
|
|
597
|
+
expect(syncState.effectiveActiveKey).toBe('tab-new');
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it('should switch the implicit default in UI Editor while keeping hidden tab items', () => {
|
|
601
|
+
const oldTab = createTab('tab-old');
|
|
602
|
+
(pageModel as any).subModels = { tabs: [oldTab, createTab('tab-new')] };
|
|
603
|
+
pageModel.mapTabs = PageModelClass.prototype.mapTabs.bind(pageModel);
|
|
604
|
+
const changeTo = vi.fn();
|
|
605
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod');
|
|
606
|
+
const updateTitleSpy = vi.spyOn(pageModel as any, 'updateDocumentTitle').mockResolvedValue(undefined);
|
|
607
|
+
pageModel.context = {
|
|
608
|
+
t: (value: string) => value,
|
|
609
|
+
flowSettingsEnabled: true,
|
|
610
|
+
view: {
|
|
611
|
+
navigation: {
|
|
612
|
+
viewParams: {},
|
|
613
|
+
changeTo,
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
} as any;
|
|
617
|
+
|
|
618
|
+
let syncState = runTabSync(pageModel.renderTabs() as any);
|
|
619
|
+
invokeSpy.mockClear();
|
|
620
|
+
updateTitleSpy.mockClear();
|
|
621
|
+
|
|
622
|
+
oldTab.hidden = true;
|
|
623
|
+
const result = pageModel.renderTabs() as any;
|
|
624
|
+
syncState = runTabSync(result, syncState);
|
|
625
|
+
|
|
626
|
+
const tabsElement = getTabsElement(result);
|
|
627
|
+
expect(tabsElement.props.items.map((item: { key: string }) => item.key)).toEqual(['tab-old', 'tab-new']);
|
|
628
|
+
expect(tabsElement.props.activeKey).toBe('tab-new');
|
|
629
|
+
expect(changeTo).not.toHaveBeenCalled();
|
|
630
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-new');
|
|
631
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(1, 'tab-new', 'onActive');
|
|
632
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(2, 'tab-old', 'onInactive');
|
|
633
|
+
expect(updateTitleSpy).toHaveBeenCalledWith('tab-new', 0);
|
|
634
|
+
expect(syncState.effectiveActiveKey).toBe('tab-new');
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
it('should distinguish an implicit activeKey from explicit props without route navigation', () => {
|
|
638
|
+
const oldTab = createTab('tab-old');
|
|
639
|
+
(pageModel as any).subModels = { tabs: [oldTab, createTab('tab-new')] };
|
|
640
|
+
pageModel.context.view = { navigation: null } as any;
|
|
641
|
+
|
|
642
|
+
let syncState = runTabSync(pageModel.renderTabs() as any);
|
|
643
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-old');
|
|
644
|
+
|
|
645
|
+
oldTab.hidden = true;
|
|
646
|
+
const result = pageModel.renderTabs() as any;
|
|
647
|
+
syncState = runTabSync(result, syncState);
|
|
648
|
+
|
|
649
|
+
expect(getTabsElement(result).props.activeKey).toBe('tab-new');
|
|
650
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-new');
|
|
651
|
+
expect(syncState.effectiveActiveKey).toBe('tab-new');
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
it('should preserve an explicit props update after establishing an implicit activeKey', () => {
|
|
655
|
+
const hiddenTab = createTab('tab-hidden', true);
|
|
656
|
+
(pageModel as any).subModels = { tabs: [createTab('tab-default'), hiddenTab] };
|
|
657
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod').mockImplementation(() => undefined);
|
|
658
|
+
pageModel.context.view = { navigation: null } as any;
|
|
659
|
+
|
|
660
|
+
let syncState = runTabSync(pageModel.renderTabs() as any);
|
|
661
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-default');
|
|
662
|
+
|
|
663
|
+
invokeSpy.mockClear();
|
|
664
|
+
pageModel.setProps('tabActiveKey', 'tab-hidden');
|
|
665
|
+
const result = pageModel.renderTabs() as any;
|
|
666
|
+
syncState = runTabSync(result, syncState);
|
|
667
|
+
|
|
668
|
+
expect(getTabsElement(result).props.activeKey).toBe('tab-hidden');
|
|
669
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-hidden');
|
|
670
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(1, 'tab-hidden', 'onActive');
|
|
671
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(2, 'tab-default', 'onInactive');
|
|
672
|
+
expect(syncState.effectiveActiveKey).toBe('tab-hidden');
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
it('should treat an input tabUid as explicit when a page model mounts again', () => {
|
|
676
|
+
const oldTab = createTab('tab-old');
|
|
677
|
+
(pageModel as any).subModels = { tabs: [oldTab, createTab('tab-new')] };
|
|
678
|
+
vi.spyOn(pageModel as any, 'updateDocumentTitle').mockResolvedValue(undefined);
|
|
679
|
+
pageModel.context.view = { navigation: null, inputArgs: {} } as any;
|
|
680
|
+
|
|
681
|
+
runTabSync(pageModel.renderTabs() as any);
|
|
682
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-old');
|
|
683
|
+
|
|
684
|
+
oldTab.hidden = true;
|
|
685
|
+
pageModel.context.view.inputArgs.tabUid = 'tab-old';
|
|
686
|
+
pageModel.onMount();
|
|
687
|
+
|
|
688
|
+
expect(getTabsElement(pageModel.renderTabs() as any).props.activeKey).toBe('tab-old');
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
it('should replace an implicit activeKey when its tab no longer exists', () => {
|
|
692
|
+
const oldTab = createTab('tab-old');
|
|
693
|
+
const newTab = createTab('tab-new');
|
|
694
|
+
(pageModel as any).subModels = { tabs: [oldTab, newTab] };
|
|
695
|
+
(pageModel as any).flowEngine = { getModel: vi.fn(() => undefined) };
|
|
696
|
+
pageModel.context.view = {
|
|
697
|
+
navigation: {
|
|
698
|
+
viewParams: {},
|
|
699
|
+
changeTo: vi.fn(),
|
|
700
|
+
},
|
|
701
|
+
} as any;
|
|
702
|
+
|
|
703
|
+
let syncState = runTabSync(pageModel.renderTabs() as any);
|
|
704
|
+
(pageModel as any).subModels = { tabs: [newTab] };
|
|
705
|
+
const result = pageModel.renderTabs() as any;
|
|
706
|
+
syncState = runTabSync(result, syncState);
|
|
707
|
+
|
|
708
|
+
expect(getTabsElement(result).props.activeKey).toBe('tab-new');
|
|
709
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-new');
|
|
710
|
+
expect(syncState.effectiveActiveKey).toBe('tab-new');
|
|
711
|
+
expect(pageModel.context.view.navigation.changeTo).not.toHaveBeenCalled();
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
it('should clear an implicit activeKey and restore the page title when its last tab no longer exists', async () => {
|
|
715
|
+
const oldTab = createTab('tab-old');
|
|
716
|
+
(pageModel as any).subModels = { tabs: [oldTab] };
|
|
717
|
+
pageModel.props = { enableTabs: true, title: 'Page title' } as any;
|
|
718
|
+
(pageModel as any).flowEngine = { context: {}, getModel: vi.fn(() => undefined) };
|
|
719
|
+
const changeTo = vi.fn();
|
|
720
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod').mockImplementation(() => undefined);
|
|
721
|
+
const updateTitleSpy = vi.spyOn(pageModel as any, 'updateDocumentTitle');
|
|
722
|
+
pageModel.context.view = {
|
|
723
|
+
navigation: {
|
|
724
|
+
viewParams: {},
|
|
725
|
+
changeTo,
|
|
726
|
+
},
|
|
727
|
+
} as any;
|
|
728
|
+
|
|
729
|
+
let syncState = runTabSync(pageModel.renderTabs() as any);
|
|
730
|
+
invokeSpy.mockClear();
|
|
731
|
+
updateTitleSpy.mockClear();
|
|
732
|
+
|
|
733
|
+
(pageModel as any).subModels = { tabs: [] };
|
|
734
|
+
syncState = runTabSync(pageModel.renderTabs() as any, syncState);
|
|
735
|
+
|
|
736
|
+
expect(pageModel.props.tabActiveKey).toBeUndefined();
|
|
737
|
+
expect(changeTo).not.toHaveBeenCalled();
|
|
738
|
+
expect(invokeSpy).toHaveBeenCalledWith('tab-old', 'onInactive');
|
|
739
|
+
expect(updateTitleSpy).toHaveBeenCalledWith(undefined, 0);
|
|
740
|
+
expect(syncState.effectiveActiveKey).toBeUndefined();
|
|
741
|
+
await waitFor(() => expect(document.title).toBe('Page title'));
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
it('should synchronize an implicit hidden tab through the mounted effect', async () => {
|
|
745
|
+
const oldTab = createTab('tab-old');
|
|
746
|
+
(pageModel as any).subModels = { tabs: [oldTab, createTab('tab-new')] };
|
|
747
|
+
const changeTo = vi.fn();
|
|
748
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod');
|
|
749
|
+
const updateTitleSpy = vi.spyOn(pageModel as any, 'updateDocumentTitle').mockResolvedValue(undefined);
|
|
750
|
+
pageModel.context.view = {
|
|
751
|
+
navigation: {
|
|
752
|
+
viewParams: {},
|
|
753
|
+
changeTo,
|
|
754
|
+
},
|
|
755
|
+
} as any;
|
|
756
|
+
|
|
757
|
+
const view = render(pageModel.renderTabs() as any);
|
|
758
|
+
await waitFor(() => expect(pageModel.props.tabActiveKey).toBe('tab-old'));
|
|
759
|
+
|
|
760
|
+
invokeSpy.mockClear();
|
|
761
|
+
updateTitleSpy.mockClear();
|
|
762
|
+
oldTab.hidden = true;
|
|
763
|
+
view.rerender(pageModel.renderTabs() as any);
|
|
764
|
+
|
|
765
|
+
await waitFor(() => expect(pageModel.props.tabActiveKey).toBe('tab-new'));
|
|
766
|
+
expect(changeTo).not.toHaveBeenCalled();
|
|
767
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(1, 'tab-new', 'onActive');
|
|
768
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(2, 'tab-old', 'onInactive');
|
|
769
|
+
expect(updateTitleSpy).toHaveBeenCalledWith('tab-new', 0);
|
|
770
|
+
|
|
771
|
+
invokeSpy.mockClear();
|
|
772
|
+
updateTitleSpy.mockClear();
|
|
773
|
+
view.rerender(pageModel.renderTabs() as any);
|
|
774
|
+
await waitFor(() => expect(pageModel.props.tabActiveKey).toBe('tab-new'));
|
|
775
|
+
expect(invokeSpy).not.toHaveBeenCalled();
|
|
776
|
+
expect(updateTitleSpy).not.toHaveBeenCalled();
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
it('should clear an implicit activeKey when all tabs become hidden', () => {
|
|
780
|
+
const firstTab = createTab('tab-1');
|
|
781
|
+
const secondTab = createTab('tab-2');
|
|
782
|
+
(pageModel as any).subModels = { tabs: [firstTab, secondTab] };
|
|
783
|
+
const changeTo = vi.fn();
|
|
784
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod');
|
|
785
|
+
const updateTitleSpy = vi.spyOn(pageModel as any, 'updateDocumentTitle').mockResolvedValue(undefined);
|
|
786
|
+
pageModel.context.view = {
|
|
787
|
+
navigation: {
|
|
788
|
+
viewParams: {},
|
|
789
|
+
changeTo,
|
|
790
|
+
},
|
|
791
|
+
} as any;
|
|
792
|
+
|
|
793
|
+
let syncState = runTabSync(pageModel.renderTabs() as any);
|
|
794
|
+
invokeSpy.mockClear();
|
|
795
|
+
updateTitleSpy.mockClear();
|
|
796
|
+
|
|
797
|
+
firstTab.hidden = true;
|
|
798
|
+
secondTab.hidden = true;
|
|
799
|
+
const result = pageModel.renderTabs() as any;
|
|
800
|
+
syncState = runTabSync(result, syncState);
|
|
801
|
+
|
|
802
|
+
expect(getTabsElement(result).props.activeKey).not.toBe('tab-1');
|
|
803
|
+
expect(getTabsElement(result).props.activeKey).not.toBe('tab-2');
|
|
804
|
+
expect(pageModel.props.tabActiveKey).toBeUndefined();
|
|
805
|
+
expect(changeTo).not.toHaveBeenCalled();
|
|
806
|
+
expect(invokeSpy).toHaveBeenCalledWith('tab-1', 'onInactive');
|
|
807
|
+
expect(updateTitleSpy).toHaveBeenCalledWith(undefined, 0);
|
|
808
|
+
expect(syncState).toMatchObject({ effectiveActiveKey: undefined, previousAllHidden: true });
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it('should restore the implicit default when a tab becomes visible after all tabs were hidden', () => {
|
|
812
|
+
const firstTab = createTab('tab-1');
|
|
813
|
+
const secondTab = createTab('tab-2');
|
|
814
|
+
(pageModel as any).subModels = { tabs: [firstTab, secondTab] };
|
|
815
|
+
const changeTo = vi.fn();
|
|
816
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod');
|
|
817
|
+
pageModel.context.view = {
|
|
818
|
+
navigation: {
|
|
819
|
+
viewParams: {},
|
|
820
|
+
changeTo,
|
|
821
|
+
},
|
|
822
|
+
} as any;
|
|
823
|
+
|
|
824
|
+
let syncState = runTabSync(pageModel.renderTabs() as any);
|
|
825
|
+
firstTab.hidden = true;
|
|
826
|
+
secondTab.hidden = true;
|
|
827
|
+
syncState = runTabSync(pageModel.renderTabs() as any, syncState);
|
|
828
|
+
invokeSpy.mockClear();
|
|
829
|
+
|
|
830
|
+
secondTab.hidden = false;
|
|
831
|
+
const result = pageModel.renderTabs() as any;
|
|
832
|
+
syncState = runTabSync(result, syncState);
|
|
833
|
+
|
|
834
|
+
expect(getTabsElement(result).props.activeKey).toBe('tab-2');
|
|
835
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-2');
|
|
836
|
+
expect(changeTo).not.toHaveBeenCalled();
|
|
837
|
+
expect(invokeSpy).toHaveBeenCalledWith('tab-2', 'onActive');
|
|
838
|
+
expect(syncState).toMatchObject({ effectiveActiveKey: 'tab-2', previousAllHidden: false });
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
it('should keep an explicitly requested activeKey when all tabs are hidden', () => {
|
|
842
|
+
(pageModel as any).subModels = { tabs: [createTab('tab-1', true), createTab('tab-2', true)] };
|
|
843
|
+
pageModel.mapTabs = PageModelClass.prototype.mapTabs.bind(pageModel);
|
|
844
|
+
pageModel.props = { tabActiveKey: 'tab-1' } as any;
|
|
845
|
+
const changeTo = vi.fn();
|
|
846
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod').mockImplementation(() => undefined);
|
|
847
|
+
pageModel.context.view = {
|
|
848
|
+
navigation: {
|
|
849
|
+
viewParams: { tabUid: 'tab-1' },
|
|
850
|
+
changeTo,
|
|
851
|
+
},
|
|
852
|
+
} as any;
|
|
853
|
+
|
|
854
|
+
const result = pageModel.renderTabs() as any;
|
|
855
|
+
const nextState = runTabSync(result, {
|
|
856
|
+
...initialSyncState,
|
|
857
|
+
previousEffectiveActiveKey: 'tab-1',
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
expect(getTabsElement(result).props.activeKey).toBe('tab-1');
|
|
861
|
+
expect(getTabsElement(result).props.items.map((item: { key: string }) => item.key)).toEqual(['tab-1', 'tab-2']);
|
|
862
|
+
expect(getTabsElement(result).props.items[0].children).toBe('tab-1');
|
|
863
|
+
expect(getHiddenActiveTabContent(result)).toBeUndefined();
|
|
864
|
+
expect(changeTo).not.toHaveBeenCalled();
|
|
865
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-1');
|
|
866
|
+
expect(invokeSpy).not.toHaveBeenCalled();
|
|
867
|
+
expect(nextState).toMatchObject({ effectiveActiveKey: 'tab-1', previousAllHidden: true });
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
it('should not implicitly activate a hidden pane when all entries are hidden and activeKey is unspecified', () => {
|
|
871
|
+
(pageModel as any).subModels = { tabs: [createTab('tab-1', true), createTab('tab-2', true)] };
|
|
872
|
+
pageModel.mapTabs = PageModelClass.prototype.mapTabs.bind(pageModel);
|
|
873
|
+
pageModel.context.view = {
|
|
874
|
+
navigation: {
|
|
875
|
+
viewParams: {},
|
|
876
|
+
changeTo: vi.fn(),
|
|
877
|
+
},
|
|
878
|
+
} as any;
|
|
879
|
+
|
|
880
|
+
const tabsElement = getTabsElement(pageModel.renderTabs() as any);
|
|
881
|
+
|
|
882
|
+
expect(tabsElement.props.items.map((item: { key: string }) => item.key)).toEqual(['tab-1', 'tab-2']);
|
|
883
|
+
expect(tabsElement.props.activeKey).not.toBeUndefined();
|
|
884
|
+
expect(tabsElement.props.activeKey).not.toBe('tab-1');
|
|
885
|
+
expect(tabsElement.props.activeKey).not.toBe('tab-2');
|
|
886
|
+
expect(pageModel.context.view.navigation.changeTo).not.toHaveBeenCalled();
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
it('should keep the requested hidden tab active when other tab entries become visible again', () => {
|
|
890
|
+
const firstTab = createTab('tab-1', true);
|
|
891
|
+
const secondTab = createTab('tab-2', true);
|
|
892
|
+
(pageModel as any).subModels = { tabs: [firstTab, secondTab] };
|
|
893
|
+
pageModel.props = { tabActiveKey: 'tab-1' } as any;
|
|
894
|
+
const changeTo = vi.fn();
|
|
895
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod').mockImplementation(() => undefined);
|
|
896
|
+
vi.spyOn(pageModel as any, 'updateDocumentTitle').mockResolvedValue(undefined);
|
|
897
|
+
pageModel.context.view = {
|
|
898
|
+
navigation: {
|
|
899
|
+
viewParams: { tabUid: 'tab-1' },
|
|
900
|
+
changeTo,
|
|
901
|
+
},
|
|
902
|
+
} as any;
|
|
903
|
+
|
|
904
|
+
let syncState = runTabSync(pageModel.renderTabs() as any, {
|
|
905
|
+
...initialSyncState,
|
|
906
|
+
previousEffectiveActiveKey: 'tab-1',
|
|
907
|
+
});
|
|
908
|
+
secondTab.hidden = false;
|
|
909
|
+
syncState = runTabSync(pageModel.renderTabs() as any, syncState);
|
|
910
|
+
|
|
911
|
+
expect(changeTo).not.toHaveBeenCalled();
|
|
912
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-1');
|
|
913
|
+
expect(invokeSpy).not.toHaveBeenCalled();
|
|
914
|
+
expect(syncState.effectiveActiveKey).toBe('tab-1');
|
|
915
|
+
|
|
916
|
+
firstTab.hidden = false;
|
|
917
|
+
syncState = runTabSync(pageModel.renderTabs() as any, syncState);
|
|
918
|
+
|
|
919
|
+
expect(syncState.effectiveActiveKey).toBe('tab-1');
|
|
920
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-1');
|
|
921
|
+
expect(changeTo).not.toHaveBeenCalled();
|
|
922
|
+
expect(invokeSpy).not.toHaveBeenCalled();
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
it('should keep the hidden active tab after UI Editor closes', () => {
|
|
926
|
+
(pageModel as any).subModels = { tabs: [createTab('tab-hidden', true), createTab('tab-visible')] };
|
|
927
|
+
pageModel.props = { tabActiveKey: 'tab-hidden' } as any;
|
|
928
|
+
const changeTo = vi.fn();
|
|
929
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod').mockImplementation(() => undefined);
|
|
930
|
+
pageModel.context = {
|
|
931
|
+
t: (value: string) => value,
|
|
932
|
+
flowSettingsEnabled: true,
|
|
933
|
+
view: {
|
|
934
|
+
navigation: {
|
|
935
|
+
viewParams: { tabUid: 'tab-hidden' },
|
|
936
|
+
changeTo,
|
|
937
|
+
},
|
|
938
|
+
},
|
|
939
|
+
} as any;
|
|
940
|
+
|
|
941
|
+
let syncState = runTabSync(pageModel.renderTabs() as any, {
|
|
942
|
+
...initialSyncState,
|
|
943
|
+
previousEffectiveActiveKey: 'tab-hidden',
|
|
944
|
+
});
|
|
945
|
+
expect(changeTo).not.toHaveBeenCalled();
|
|
946
|
+
|
|
947
|
+
pageModel.context.flowSettingsEnabled = false;
|
|
948
|
+
syncState = runTabSync(pageModel.renderTabs() as any, syncState);
|
|
949
|
+
|
|
950
|
+
expect(syncState.effectiveActiveKey).toBe('tab-hidden');
|
|
951
|
+
expect(changeTo).not.toHaveBeenCalled();
|
|
952
|
+
expect(invokeSpy).not.toHaveBeenCalled();
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
it('should keep hidden active synchronization idempotent across repeated effect passes', () => {
|
|
956
|
+
(pageModel as any).subModels = { tabs: [createTab('tab-hidden', true), createTab('tab-visible')] };
|
|
957
|
+
pageModel.props = { tabActiveKey: 'tab-hidden' } as any;
|
|
958
|
+
const changeTo = vi.fn();
|
|
959
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod').mockImplementation(() => undefined);
|
|
960
|
+
pageModel.context.view = {
|
|
961
|
+
navigation: {
|
|
962
|
+
viewParams: { tabUid: 'tab-hidden' },
|
|
963
|
+
changeTo,
|
|
964
|
+
},
|
|
965
|
+
} as any;
|
|
966
|
+
|
|
967
|
+
let syncState = runTabSync(pageModel.renderTabs() as any, {
|
|
968
|
+
...initialSyncState,
|
|
969
|
+
previousEffectiveActiveKey: 'tab-hidden',
|
|
970
|
+
});
|
|
971
|
+
syncState = runTabSync(pageModel.renderTabs() as any, syncState);
|
|
972
|
+
|
|
973
|
+
expect(syncState.effectiveActiveKey).toBe('tab-hidden');
|
|
974
|
+
expect(changeTo).not.toHaveBeenCalled();
|
|
975
|
+
expect(invokeSpy).not.toHaveBeenCalled();
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
it('should switch lifecycle and title when URL navigation selects a hidden tab', () => {
|
|
979
|
+
(pageModel as any).subModels = { tabs: [createTab('tab-visible'), createTab('tab-hidden', true)] };
|
|
980
|
+
pageModel.props = { tabActiveKey: 'tab-visible' } as any;
|
|
981
|
+
const changeTo = vi.fn();
|
|
982
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod');
|
|
983
|
+
const updateTitleSpy = vi.spyOn(pageModel as any, 'updateDocumentTitle').mockResolvedValue(undefined);
|
|
984
|
+
pageModel.context.view = {
|
|
985
|
+
navigation: {
|
|
986
|
+
viewParams: { tabUid: 'tab-hidden' },
|
|
987
|
+
changeTo,
|
|
988
|
+
},
|
|
989
|
+
} as any;
|
|
990
|
+
|
|
991
|
+
let syncState = runTabSync(pageModel.renderTabs() as any, {
|
|
992
|
+
...initialSyncState,
|
|
993
|
+
previousEffectiveActiveKey: 'tab-visible',
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
expect(changeTo).not.toHaveBeenCalled();
|
|
997
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-hidden');
|
|
998
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(1, 'tab-hidden', 'onActive');
|
|
999
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(2, 'tab-visible', 'onInactive');
|
|
1000
|
+
expect(updateTitleSpy).toHaveBeenCalledWith('tab-hidden', 0);
|
|
1001
|
+
expect(syncState.effectiveActiveKey).toBe('tab-hidden');
|
|
1002
|
+
|
|
1003
|
+
invokeSpy.mockClear();
|
|
1004
|
+
updateTitleSpy.mockClear();
|
|
1005
|
+
syncState = runTabSync(pageModel.renderTabs() as any, syncState);
|
|
1006
|
+
|
|
1007
|
+
expect(invokeSpy).not.toHaveBeenCalled();
|
|
1008
|
+
expect(updateTitleSpy).not.toHaveBeenCalled();
|
|
1009
|
+
expect(syncState.effectiveActiveKey).toBe('tab-hidden');
|
|
1010
|
+
});
|
|
1011
|
+
|
|
352
1012
|
it('should use props.tabActiveKey if viewParams is missing', () => {
|
|
353
1013
|
pageModel.props = { tabActiveKey: 'tab-from-props' } as any;
|
|
354
1014
|
// @ts-ignore
|
|
@@ -357,7 +1017,7 @@ describe('PageModel', () => {
|
|
|
357
1017
|
};
|
|
358
1018
|
|
|
359
1019
|
const result = pageModel.renderTabs() as any;
|
|
360
|
-
const tabsElement = result
|
|
1020
|
+
const tabsElement = getTabsElement(result);
|
|
361
1021
|
expect(tabsElement.props.activeKey).toBe('tab-from-props');
|
|
362
1022
|
});
|
|
363
1023
|
|
|
@@ -370,7 +1030,7 @@ describe('PageModel', () => {
|
|
|
370
1030
|
} as any;
|
|
371
1031
|
|
|
372
1032
|
const result = pageModel.renderTabs() as any;
|
|
373
|
-
const tabsElement = result
|
|
1033
|
+
const tabsElement = getTabsElement(result);
|
|
374
1034
|
|
|
375
1035
|
expect(tabsElement.props.className).toBeUndefined();
|
|
376
1036
|
});
|
|
@@ -384,7 +1044,7 @@ describe('PageModel', () => {
|
|
|
384
1044
|
} as any;
|
|
385
1045
|
|
|
386
1046
|
const result = pageModel.renderTabs() as any;
|
|
387
|
-
const tabsElement = result
|
|
1047
|
+
const tabsElement = getTabsElement(result);
|
|
388
1048
|
|
|
389
1049
|
expect(tabsElement.props.className).toBeUndefined();
|
|
390
1050
|
});
|
|
@@ -398,7 +1058,7 @@ describe('PageModel', () => {
|
|
|
398
1058
|
} as any;
|
|
399
1059
|
|
|
400
1060
|
const result = pageModel.renderTabs() as any;
|
|
401
|
-
const tabsElement = result
|
|
1061
|
+
const tabsElement = getTabsElement(result);
|
|
402
1062
|
|
|
403
1063
|
expect(tabsElement.props.tabBarExtraContent.left).toBeTruthy();
|
|
404
1064
|
});
|
|
@@ -413,7 +1073,7 @@ describe('PageModel', () => {
|
|
|
413
1073
|
pageModel.tabBarExtraContent = { left: 'back' } as any;
|
|
414
1074
|
|
|
415
1075
|
const result = pageModel.renderTabs() as any;
|
|
416
|
-
const tabsElement = result
|
|
1076
|
+
const tabsElement = getTabsElement(result);
|
|
417
1077
|
|
|
418
1078
|
expect(tabsElement.props.tabBarExtraContent.left).toBe('back');
|
|
419
1079
|
});
|
|
@@ -428,7 +1088,7 @@ describe('PageModel', () => {
|
|
|
428
1088
|
pageModel.tabBarExtraContent = { right: 'custom-right' } as any;
|
|
429
1089
|
|
|
430
1090
|
const result = pageModel.renderTabs() as any;
|
|
431
|
-
const tabsElement = result
|
|
1091
|
+
const tabsElement = getTabsElement(result);
|
|
432
1092
|
|
|
433
1093
|
expect(tabsElement.props.tabBarExtraContent.right).toBe('custom-right');
|
|
434
1094
|
});
|
|
@@ -443,7 +1103,7 @@ describe('PageModel', () => {
|
|
|
443
1103
|
} as any;
|
|
444
1104
|
|
|
445
1105
|
const result = pageModel.renderTabs() as any;
|
|
446
|
-
const tabsElement = result
|
|
1106
|
+
const tabsElement = getTabsElement(result);
|
|
447
1107
|
const rightExtraContent = tabsElement.props.tabBarExtraContent.right;
|
|
448
1108
|
|
|
449
1109
|
expect(rightExtraContent).toBeTruthy();
|
|
@@ -452,6 +1112,57 @@ describe('PageModel', () => {
|
|
|
452
1112
|
marginInlineEnd: 24,
|
|
453
1113
|
});
|
|
454
1114
|
});
|
|
1115
|
+
|
|
1116
|
+
it('should deactivate the previous tab when navigation updates active tab synchronously', () => {
|
|
1117
|
+
pageModel.props = { tabActiveKey: 'tab-old' } as any;
|
|
1118
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod').mockImplementation(() => undefined);
|
|
1119
|
+
const changeTo = vi.fn((params: { tabUid: string }) => {
|
|
1120
|
+
pageModel.props.tabActiveKey = params.tabUid;
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
pageModel.context.view = {
|
|
1124
|
+
navigation: {
|
|
1125
|
+
viewParams: {
|
|
1126
|
+
tabUid: 'tab-old',
|
|
1127
|
+
},
|
|
1128
|
+
changeTo,
|
|
1129
|
+
},
|
|
1130
|
+
} as any;
|
|
1131
|
+
|
|
1132
|
+
const result = pageModel.renderTabs() as any;
|
|
1133
|
+
const tabsElement = getTabsElement(result);
|
|
1134
|
+
|
|
1135
|
+
tabsElement.props.onChange('tab-new');
|
|
1136
|
+
|
|
1137
|
+
expect(changeTo).toHaveBeenCalledWith({ tabUid: 'tab-new' });
|
|
1138
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(1, 'tab-new', 'onActive');
|
|
1139
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(2, 'tab-old', 'onInactive');
|
|
1140
|
+
expect(invokeSpy).not.toHaveBeenCalledWith('tab-new', 'onInactive');
|
|
1141
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-new');
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
it('should not repeat lifecycle when synchronization observes a locally committed tab change', () => {
|
|
1145
|
+
(pageModel as any).subModels = { tabs: [createTab('tab-old'), createTab('tab-new')] };
|
|
1146
|
+
pageModel.props = { tabActiveKey: 'tab-old' } as any;
|
|
1147
|
+
pageModel.context.view = { navigation: null } as any;
|
|
1148
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod').mockImplementation(() => undefined);
|
|
1149
|
+
|
|
1150
|
+
let result = pageModel.renderTabs() as any;
|
|
1151
|
+
let syncState = runTabSync(result, {
|
|
1152
|
+
...initialSyncState,
|
|
1153
|
+
previousEffectiveActiveKey: 'tab-old',
|
|
1154
|
+
});
|
|
1155
|
+
getTabsElement(result).props.onChange('tab-new');
|
|
1156
|
+
|
|
1157
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(1, 'tab-new', 'onActive');
|
|
1158
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(2, 'tab-old', 'onInactive');
|
|
1159
|
+
|
|
1160
|
+
result = pageModel.renderTabs() as any;
|
|
1161
|
+
syncState = runTabSync(result, syncState);
|
|
1162
|
+
|
|
1163
|
+
expect(invokeSpy).toHaveBeenCalledTimes(2);
|
|
1164
|
+
expect(syncState.effectiveActiveKey).toBe('tab-new');
|
|
1165
|
+
});
|
|
455
1166
|
});
|
|
456
1167
|
|
|
457
1168
|
describe('render header spacing with tabs', () => {
|
|
@@ -769,6 +1480,99 @@ describe('PageModel', () => {
|
|
|
769
1480
|
expect(document.title).toBe('Fallback tab title');
|
|
770
1481
|
});
|
|
771
1482
|
|
|
1483
|
+
it('should use the hidden active tab documentTitle when its entry is hidden', async () => {
|
|
1484
|
+
pageModel.props = { enableTabs: true, tabActiveKey: 'tab-hidden' } as any;
|
|
1485
|
+
const hiddenTab = {
|
|
1486
|
+
uid: 'tab-hidden',
|
|
1487
|
+
hidden: true,
|
|
1488
|
+
stepParams: {
|
|
1489
|
+
pageTabSettings: {
|
|
1490
|
+
tab: {
|
|
1491
|
+
documentTitle: 'Hidden tab document title',
|
|
1492
|
+
},
|
|
1493
|
+
},
|
|
1494
|
+
},
|
|
1495
|
+
getTabTitle: vi.fn(() => 'Hidden tab'),
|
|
1496
|
+
};
|
|
1497
|
+
const visibleTab = {
|
|
1498
|
+
uid: 'tab-visible',
|
|
1499
|
+
stepParams: {
|
|
1500
|
+
pageTabSettings: {
|
|
1501
|
+
tab: {
|
|
1502
|
+
documentTitle: 'Visible tab document title',
|
|
1503
|
+
},
|
|
1504
|
+
},
|
|
1505
|
+
},
|
|
1506
|
+
getTabTitle: vi.fn(() => 'Visible tab'),
|
|
1507
|
+
};
|
|
1508
|
+
(pageModel as any).subModels = { tabs: [hiddenTab, visibleTab] };
|
|
1509
|
+
(pageModel as any).context.view.navigation = {
|
|
1510
|
+
viewParams: { tabUid: 'tab-hidden' },
|
|
1511
|
+
};
|
|
1512
|
+
|
|
1513
|
+
await (pageModel as any).updateDocumentTitle();
|
|
1514
|
+
|
|
1515
|
+
expect(document.title).toBe('Hidden tab document title');
|
|
1516
|
+
});
|
|
1517
|
+
|
|
1518
|
+
it('should use page documentTitle when tabs are enabled but all tabs are hidden', async () => {
|
|
1519
|
+
pageModel.props = { enableTabs: true, title: 'Page title' } as any;
|
|
1520
|
+
const hiddenTab = {
|
|
1521
|
+
uid: 'tab-hidden',
|
|
1522
|
+
hidden: true,
|
|
1523
|
+
stepParams: {
|
|
1524
|
+
pageTabSettings: {
|
|
1525
|
+
tab: {
|
|
1526
|
+
documentTitle: 'Hidden tab title',
|
|
1527
|
+
},
|
|
1528
|
+
},
|
|
1529
|
+
},
|
|
1530
|
+
getTabTitle: vi.fn(() => 'Hidden tab'),
|
|
1531
|
+
};
|
|
1532
|
+
(pageModel as any).subModels = { tabs: [hiddenTab] };
|
|
1533
|
+
(pageModel as any).stepParams = {
|
|
1534
|
+
pageSettings: {
|
|
1535
|
+
general: {
|
|
1536
|
+
documentTitle: 'Page document title',
|
|
1537
|
+
},
|
|
1538
|
+
},
|
|
1539
|
+
};
|
|
1540
|
+
(pageModel as any).flowEngine.getModel = vi.fn(() => hiddenTab);
|
|
1541
|
+
|
|
1542
|
+
await (pageModel as any).updateDocumentTitle();
|
|
1543
|
+
|
|
1544
|
+
expect(document.title).toBe('Page document title');
|
|
1545
|
+
});
|
|
1546
|
+
|
|
1547
|
+
it('should fallback to page title when all tabs are hidden and page documentTitle is empty', async () => {
|
|
1548
|
+
pageModel.props = { enableTabs: true, title: 'Fallback page title' } as any;
|
|
1549
|
+
const hiddenTab = {
|
|
1550
|
+
uid: 'tab-hidden',
|
|
1551
|
+
hidden: true,
|
|
1552
|
+
stepParams: {
|
|
1553
|
+
pageTabSettings: {
|
|
1554
|
+
tab: {
|
|
1555
|
+
documentTitle: 'Hidden tab title',
|
|
1556
|
+
},
|
|
1557
|
+
},
|
|
1558
|
+
},
|
|
1559
|
+
getTabTitle: vi.fn(() => 'Hidden tab'),
|
|
1560
|
+
};
|
|
1561
|
+
(pageModel as any).subModels = { tabs: [hiddenTab] };
|
|
1562
|
+
(pageModel as any).stepParams = {
|
|
1563
|
+
pageSettings: {
|
|
1564
|
+
general: {
|
|
1565
|
+
documentTitle: '',
|
|
1566
|
+
},
|
|
1567
|
+
},
|
|
1568
|
+
};
|
|
1569
|
+
(pageModel as any).flowEngine.getModel = vi.fn(() => hiddenTab);
|
|
1570
|
+
|
|
1571
|
+
await (pageModel as any).updateDocumentTitle();
|
|
1572
|
+
|
|
1573
|
+
expect(document.title).toBe('Fallback page title');
|
|
1574
|
+
});
|
|
1575
|
+
|
|
772
1576
|
it('should also update document title for closable page (popup)', async () => {
|
|
773
1577
|
pageModel.props = { enableTabs: false, title: 'Popup page title' } as any;
|
|
774
1578
|
(pageModel as any).context = {
|