@nocobase/client-v2 2.2.0-beta.1 → 2.2.0-beta.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/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/form/JsonTextArea.d.ts +2 -1
- package/es/components/form/VariableJsonTextArea.d.ts +19 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/components/index.d.ts +1 -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 +1 -1
- 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 +2 -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/FieldAssignRulesEditor.d.ts +3 -1
- package/es/flow/models/base/ActionModelCore.d.ts +2 -0
- package/es/flow/models/base/GridModel.d.ts +1 -1
- 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/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/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/index.d.ts +4 -1
- package/es/index.mjs +315 -167
- package/es/utils/markdownSanitize.d.ts +11 -0
- package/lib/index.js +315 -167
- package/package.json +8 -7
- package/src/BaseApplication.tsx +12 -5
- 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 +55 -0
- package/src/__tests__/authRedirect.test.ts +17 -0
- package/src/__tests__/exports.test.ts +16 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +199 -0
- 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/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/form/JsonTextArea.tsx +21 -11
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +145 -2
- package/src/components/form/ScanInput/useCodeScanner.ts +154 -2
- package/src/components/form/VariableJsonTextArea.tsx +175 -0
- package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
- package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
- package/src/components/form/index.tsx +1 -0
- package/src/components/index.ts +1 -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 +1088 -105
- package/src/flow/__tests__/getKey.test.ts +7 -0
- package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -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 +1022 -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__/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 +1 -1
- package/src/flow/actions/linkageRules.tsx +123 -35
- 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 -13
- 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 +188 -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/components/DefaultValue.tsx +1 -2
- package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
- package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
- package/src/flow/components/FieldAssignValueInput.tsx +15 -11
- package/src/flow/components/FlowRoute.tsx +186 -25
- 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 +134 -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/flows/editMarkdownFlow.tsx +1 -1
- package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
- package/src/flow/index.ts +1 -1
- package/src/flow/internal/components/Markdown/util.ts +2 -1
- 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/GridModel.tsx +38 -7
- package/src/flow/models/base/PageModel/PageModel.tsx +4 -1
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +28 -4
- 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__/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 +32 -3
- 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 +111 -0
- 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 +38 -17
- package/src/flow/models/blocks/table/TableColumnModel.tsx +42 -5
- 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.rowSelection.test.tsx +1 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +210 -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 +30 -6
- 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/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/__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/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 +218 -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/index.ts +10 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
- package/src/nocobase-buildin-plugin/index.tsx +51 -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/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: {
|
|
@@ -321,6 +325,94 @@ describe('FlowRoute', () => {
|
|
|
321
325
|
});
|
|
322
326
|
});
|
|
323
327
|
|
|
328
|
+
it('should ensure accessible routes for the current layout before bridging', async () => {
|
|
329
|
+
const engine = new FlowEngine();
|
|
330
|
+
const request = vi.fn().mockResolvedValue({
|
|
331
|
+
data: {
|
|
332
|
+
data: [
|
|
333
|
+
{
|
|
334
|
+
type: 'flowPage',
|
|
335
|
+
schemaUid: 'mobile-page',
|
|
336
|
+
},
|
|
337
|
+
],
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
const routeRepository = new RouteRepository({
|
|
341
|
+
api: {
|
|
342
|
+
request,
|
|
343
|
+
resource: vi.fn(),
|
|
344
|
+
},
|
|
345
|
+
} as never);
|
|
346
|
+
routeRepository.setRoutes([], 'admin-layout-model');
|
|
347
|
+
const deactivateLayout = routeRepository.activateLayout({
|
|
348
|
+
uid: 'mobile-layout-model',
|
|
349
|
+
});
|
|
350
|
+
engine.context.defineProperty('routeRepository', {
|
|
351
|
+
value: routeRepository,
|
|
352
|
+
});
|
|
353
|
+
engine.context.defineProperty('app', {
|
|
354
|
+
value: {
|
|
355
|
+
getPublicPath: () => '/v2/',
|
|
356
|
+
router: {
|
|
357
|
+
getBasename: () => '/v2',
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
const routeModel = engine.createModel({
|
|
362
|
+
uid: 'mobile-route-model',
|
|
363
|
+
use: 'FlowModel',
|
|
364
|
+
});
|
|
365
|
+
routeModel.context.defineProperty('layout', {
|
|
366
|
+
value: {
|
|
367
|
+
routeName: 'mobile',
|
|
368
|
+
routePath: '/mobile',
|
|
369
|
+
rootRouteName: 'mobile',
|
|
370
|
+
uid: 'mobile-layout-model',
|
|
371
|
+
layoutModelClass: 'MobileLayoutModel',
|
|
372
|
+
rootPageModelClass: 'MobileRootPageModel',
|
|
373
|
+
childPageModelClass: 'MobileChildPageModel',
|
|
374
|
+
authCheck: true,
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const mobileLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
379
|
+
engine.createModel({
|
|
380
|
+
uid: 'mobile-layout-model',
|
|
381
|
+
use: 'FlowModel',
|
|
382
|
+
}),
|
|
383
|
+
{
|
|
384
|
+
registerRoutePage: vi.fn(),
|
|
385
|
+
updateRoutePage: vi.fn(),
|
|
386
|
+
unregisterRoutePage: vi.fn(),
|
|
387
|
+
},
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
render(
|
|
391
|
+
<FlowEngineProvider engine={engine}>
|
|
392
|
+
<FlowContextProvider context={routeModel.context}>
|
|
393
|
+
<MemoryRouter initialEntries={['/mobile/mobile-page']}>
|
|
394
|
+
<Routes>
|
|
395
|
+
<Route path="/mobile/:name" element={<FlowRoute />} />
|
|
396
|
+
</Routes>
|
|
397
|
+
</MemoryRouter>
|
|
398
|
+
</FlowContextProvider>
|
|
399
|
+
</FlowEngineProvider>,
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
await waitFor(() => {
|
|
403
|
+
expect(request).toHaveBeenCalledWith({
|
|
404
|
+
url: '/desktopRoutes:listAccessible',
|
|
405
|
+
params: {
|
|
406
|
+
tree: true,
|
|
407
|
+
sort: 'sort',
|
|
408
|
+
layout: 'mobile-layout-model',
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
expect(mobileLayoutModel.registerRoutePage).toHaveBeenCalledWith('mobile-page', expect.any(Object));
|
|
412
|
+
});
|
|
413
|
+
deactivateLayout();
|
|
414
|
+
});
|
|
415
|
+
|
|
324
416
|
it('should fail fast when admin-layout-model is missing', () => {
|
|
325
417
|
const engine = new FlowEngine();
|
|
326
418
|
engine.context.defineProperty('route', {
|
|
@@ -420,6 +512,75 @@ describe('FlowRoute', () => {
|
|
|
420
512
|
});
|
|
421
513
|
});
|
|
422
514
|
|
|
515
|
+
it('should not rerun accessible guard when layout reference changes but fields stay the same', async () => {
|
|
516
|
+
const engine = new FlowEngine();
|
|
517
|
+
const ensureAccessibleLoaded = vi.fn().mockResolvedValue([]);
|
|
518
|
+
const getRouteBySchemaUid = vi.fn(() => ({ type: 'flowPage', schemaUid: 'test-page' }));
|
|
519
|
+
engine.context.defineProperty('routeRepository', {
|
|
520
|
+
value: {
|
|
521
|
+
refreshAccessible: hookState.refresh,
|
|
522
|
+
isAccessibleLoaded: () => true,
|
|
523
|
+
ensureAccessibleLoaded,
|
|
524
|
+
getRouteBySchemaUid,
|
|
525
|
+
},
|
|
526
|
+
});
|
|
527
|
+
engine.context.defineProperty('app', {
|
|
528
|
+
value: {
|
|
529
|
+
getPublicPath: () => '/v2/',
|
|
530
|
+
router: {
|
|
531
|
+
getBasename: () => '/v2',
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
537
|
+
engine.createModel({ uid: 'legacy-admin-layout-model', use: 'FlowModel' }),
|
|
538
|
+
{
|
|
539
|
+
registerRoutePage: vi.fn(),
|
|
540
|
+
updateRoutePage: vi.fn(),
|
|
541
|
+
unregisterRoutePage: vi.fn(),
|
|
542
|
+
},
|
|
543
|
+
);
|
|
544
|
+
let layoutReadCount = 0;
|
|
545
|
+
const stableLayout = {
|
|
546
|
+
uid: 'admin-layout-model',
|
|
547
|
+
routeName: 'admin',
|
|
548
|
+
routePath: '/admin',
|
|
549
|
+
authCheck: true,
|
|
550
|
+
};
|
|
551
|
+
Object.defineProperty(layoutModel, 'layout', {
|
|
552
|
+
get: () => {
|
|
553
|
+
layoutReadCount += 1;
|
|
554
|
+
return layoutReadCount <= 2 ? { ...stableLayout } : stableLayout;
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
const element = (
|
|
559
|
+
<FlowEngineProvider engine={engine}>
|
|
560
|
+
<MemoryRouter initialEntries={['/flow/test-page']}>
|
|
561
|
+
<Routes>
|
|
562
|
+
<Route path="/flow/:name" element={<FlowRoute getLayoutModel={() => layoutModel as any} />} />
|
|
563
|
+
</Routes>
|
|
564
|
+
</MemoryRouter>
|
|
565
|
+
</FlowEngineProvider>
|
|
566
|
+
);
|
|
567
|
+
const { rerender } = render(element);
|
|
568
|
+
|
|
569
|
+
await waitFor(() => {
|
|
570
|
+
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('test-page', expect.any(Object));
|
|
571
|
+
});
|
|
572
|
+
expect(ensureAccessibleLoaded).not.toHaveBeenCalled();
|
|
573
|
+
expect(getRouteBySchemaUid).toHaveBeenCalledTimes(1);
|
|
574
|
+
|
|
575
|
+
rerender(element);
|
|
576
|
+
|
|
577
|
+
await waitFor(() => {
|
|
578
|
+
expect(layoutModel.updateRoutePage).toHaveBeenCalled();
|
|
579
|
+
});
|
|
580
|
+
expect(ensureAccessibleLoaded).not.toHaveBeenCalled();
|
|
581
|
+
expect(getRouteBySchemaUid).toHaveBeenCalledTimes(1);
|
|
582
|
+
});
|
|
583
|
+
|
|
423
584
|
it('should show 404 when current route is a legacy page in v2 runtime', async () => {
|
|
424
585
|
const originalLocation = window.location;
|
|
425
586
|
const replace = vi.fn();
|
|
@@ -571,6 +732,22 @@ describe('FlowRoute', () => {
|
|
|
571
732
|
},
|
|
572
733
|
},
|
|
573
734
|
});
|
|
735
|
+
const routeModel = engine.createModel({
|
|
736
|
+
uid: 'public-form-route-model',
|
|
737
|
+
use: 'FlowModel',
|
|
738
|
+
});
|
|
739
|
+
routeModel.context.defineProperty('layout', {
|
|
740
|
+
value: {
|
|
741
|
+
routeName: 'public-forms',
|
|
742
|
+
routePath: '/public-forms',
|
|
743
|
+
rootRouteName: 'public-forms',
|
|
744
|
+
uid: 'public-form-layout-model',
|
|
745
|
+
layoutModelClass: 'PublicFormLayoutModel',
|
|
746
|
+
rootPageModelClass: 'PublicFormPageModel',
|
|
747
|
+
childPageModelClass: 'ChildPageModel',
|
|
748
|
+
authCheck: false,
|
|
749
|
+
},
|
|
750
|
+
});
|
|
574
751
|
|
|
575
752
|
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
576
753
|
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
@@ -583,14 +760,16 @@ describe('FlowRoute', () => {
|
|
|
583
760
|
|
|
584
761
|
render(
|
|
585
762
|
<FlowEngineProvider engine={engine}>
|
|
586
|
-
<
|
|
587
|
-
<
|
|
588
|
-
<
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
763
|
+
<FlowContextProvider context={routeModel.context}>
|
|
764
|
+
<MemoryRouter initialEntries={['/public-forms/public-form-1']}>
|
|
765
|
+
<Routes>
|
|
766
|
+
<Route
|
|
767
|
+
path="/public-forms/:name"
|
|
768
|
+
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel} />}
|
|
769
|
+
/>
|
|
770
|
+
</Routes>
|
|
771
|
+
</MemoryRouter>
|
|
772
|
+
</FlowContextProvider>
|
|
594
773
|
</FlowEngineProvider>,
|
|
595
774
|
);
|
|
596
775
|
|
|
@@ -600,20 +779,13 @@ describe('FlowRoute', () => {
|
|
|
600
779
|
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
601
780
|
});
|
|
602
781
|
|
|
603
|
-
it('should
|
|
782
|
+
it('should use getLayoutModel layout authCheck when FlowContext has no layout', async () => {
|
|
604
783
|
const engine = new FlowEngine();
|
|
605
|
-
const findOne = vi.fn().mockResolvedValue({
|
|
606
|
-
uid: 'public-form-1',
|
|
607
|
-
});
|
|
608
|
-
const request = vi.fn().mockResolvedValue({
|
|
609
|
-
data: {
|
|
610
|
-
data: {
|
|
611
|
-
uid: 'public-form-1',
|
|
612
|
-
},
|
|
613
|
-
},
|
|
614
|
-
});
|
|
615
784
|
engine.setModelRepository({
|
|
616
|
-
findOne
|
|
785
|
+
findOne: vi.fn().mockResolvedValue({
|
|
786
|
+
uid: 'public-form-1',
|
|
787
|
+
use: 'FlowModel',
|
|
788
|
+
}),
|
|
617
789
|
save: vi.fn(),
|
|
618
790
|
destroy: vi.fn(),
|
|
619
791
|
} as any);
|
|
@@ -627,20 +799,22 @@ describe('FlowRoute', () => {
|
|
|
627
799
|
});
|
|
628
800
|
engine.context.defineProperty('app', {
|
|
629
801
|
value: {
|
|
630
|
-
apiClient: {
|
|
631
|
-
request,
|
|
632
|
-
},
|
|
633
802
|
getPublicPath: () => '/v2/',
|
|
634
803
|
router: {
|
|
635
804
|
getBasename: () => '/v2',
|
|
636
805
|
},
|
|
637
806
|
},
|
|
638
807
|
});
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
use: 'FlowModel',
|
|
642
|
-
|
|
643
|
-
|
|
808
|
+
|
|
809
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
810
|
+
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
811
|
+
{
|
|
812
|
+
registerRoutePage: vi.fn(),
|
|
813
|
+
updateRoutePage: vi.fn(),
|
|
814
|
+
unregisterRoutePage: vi.fn(),
|
|
815
|
+
},
|
|
816
|
+
);
|
|
817
|
+
Object.defineProperty(layoutModel, 'layout', {
|
|
644
818
|
value: {
|
|
645
819
|
routeName: 'public-forms',
|
|
646
820
|
routePath: '/public-forms',
|
|
@@ -653,42 +827,30 @@ describe('FlowRoute', () => {
|
|
|
653
827
|
},
|
|
654
828
|
});
|
|
655
829
|
|
|
656
|
-
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
657
|
-
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
658
|
-
{
|
|
659
|
-
registerRoutePage: vi.fn(),
|
|
660
|
-
updateRoutePage: vi.fn(),
|
|
661
|
-
unregisterRoutePage: vi.fn(),
|
|
662
|
-
},
|
|
663
|
-
);
|
|
664
|
-
|
|
665
830
|
render(
|
|
666
831
|
<FlowEngineProvider engine={engine}>
|
|
667
|
-
<
|
|
668
|
-
<
|
|
669
|
-
<
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
832
|
+
<MemoryRouter initialEntries={['/public-forms/public-form-1']}>
|
|
833
|
+
<Routes>
|
|
834
|
+
<Route
|
|
835
|
+
path="/public-forms/:name"
|
|
836
|
+
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel as any} />}
|
|
837
|
+
/>
|
|
838
|
+
</Routes>
|
|
839
|
+
</MemoryRouter>
|
|
674
840
|
</FlowEngineProvider>,
|
|
675
841
|
);
|
|
676
842
|
|
|
677
843
|
await waitFor(() => {
|
|
678
844
|
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('public-form-1', expect.any(Object));
|
|
679
845
|
});
|
|
680
|
-
expect(
|
|
681
|
-
expect(request).not.toHaveBeenCalled();
|
|
682
|
-
expect(engine.getModel('public-form-1')).toBeUndefined();
|
|
846
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
683
847
|
});
|
|
684
848
|
|
|
685
|
-
it('should not
|
|
849
|
+
it('should render not found when admin route is not accessible', async () => {
|
|
686
850
|
const engine = new FlowEngine();
|
|
687
|
-
const ensureAccessibleLoaded = vi.fn().mockRejectedValue(new Error('cannot load accessible routes'));
|
|
688
|
-
const getRouteBySchemaUid = vi.fn();
|
|
689
851
|
engine.setModelRepository({
|
|
690
852
|
findOne: vi.fn().mockResolvedValue({
|
|
691
|
-
uid: '
|
|
853
|
+
uid: 'admin-denied-page',
|
|
692
854
|
use: 'FlowModel',
|
|
693
855
|
}),
|
|
694
856
|
save: vi.fn(),
|
|
@@ -697,9 +859,9 @@ describe('FlowRoute', () => {
|
|
|
697
859
|
engine.context.defineProperty('routeRepository', {
|
|
698
860
|
value: {
|
|
699
861
|
refreshAccessible: hookState.refresh,
|
|
700
|
-
isAccessibleLoaded: () =>
|
|
701
|
-
ensureAccessibleLoaded,
|
|
702
|
-
getRouteBySchemaUid,
|
|
862
|
+
isAccessibleLoaded: () => true,
|
|
863
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
864
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
703
865
|
},
|
|
704
866
|
});
|
|
705
867
|
engine.context.defineProperty('app', {
|
|
@@ -710,25 +872,9 @@ describe('FlowRoute', () => {
|
|
|
710
872
|
},
|
|
711
873
|
},
|
|
712
874
|
});
|
|
713
|
-
const routeModel = engine.createModel({
|
|
714
|
-
uid: 'public-form-route-model',
|
|
715
|
-
use: 'FlowModel',
|
|
716
|
-
});
|
|
717
|
-
routeModel.context.defineProperty('layout', {
|
|
718
|
-
value: {
|
|
719
|
-
routeName: 'public-forms',
|
|
720
|
-
routePath: '/public-forms',
|
|
721
|
-
rootRouteName: 'public-forms',
|
|
722
|
-
uid: 'public-form-layout-model',
|
|
723
|
-
layoutModelClass: 'PublicFormLayoutModel',
|
|
724
|
-
rootPageModelClass: 'PublicFormPageModel',
|
|
725
|
-
childPageModelClass: 'ChildPageModel',
|
|
726
|
-
authCheck: false,
|
|
727
|
-
},
|
|
728
|
-
});
|
|
729
875
|
|
|
730
|
-
const
|
|
731
|
-
engine.createModel({ uid: '
|
|
876
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
877
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
732
878
|
{
|
|
733
879
|
registerRoutePage: vi.fn(),
|
|
734
880
|
updateRoutePage: vi.fn(),
|
|
@@ -738,41 +884,879 @@ describe('FlowRoute', () => {
|
|
|
738
884
|
|
|
739
885
|
render(
|
|
740
886
|
<FlowEngineProvider engine={engine}>
|
|
741
|
-
<
|
|
742
|
-
<
|
|
743
|
-
<
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
</MemoryRouter>
|
|
747
|
-
</FlowContextProvider>
|
|
887
|
+
<MemoryRouter initialEntries={['/admin/admin-denied-page']}>
|
|
888
|
+
<Routes>
|
|
889
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
890
|
+
</Routes>
|
|
891
|
+
</MemoryRouter>
|
|
748
892
|
</FlowEngineProvider>,
|
|
749
893
|
);
|
|
750
894
|
|
|
751
|
-
await
|
|
752
|
-
|
|
753
|
-
});
|
|
754
|
-
expect(ensureAccessibleLoaded).toHaveBeenCalledTimes(1);
|
|
755
|
-
expect(getRouteBySchemaUid).not.toHaveBeenCalled();
|
|
895
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
896
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
756
897
|
});
|
|
757
898
|
|
|
758
|
-
it('should
|
|
759
|
-
const
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
899
|
+
it('should render blank content when current admin route matches an accessible empty group id', async () => {
|
|
900
|
+
const engine = new FlowEngine();
|
|
901
|
+
const listAccessible = vi.fn(() => [
|
|
902
|
+
{
|
|
903
|
+
id: 371750686228480,
|
|
904
|
+
schemaUid: 'group-schema',
|
|
905
|
+
title: '789',
|
|
906
|
+
type: NocoBaseDesktopRouteType.group,
|
|
907
|
+
},
|
|
908
|
+
]);
|
|
909
|
+
engine.context.defineProperty('routeRepository', {
|
|
763
910
|
value: {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
911
|
+
refreshAccessible: hookState.refresh,
|
|
912
|
+
isAccessibleLoaded: () => true,
|
|
913
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
914
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
915
|
+
listAccessible,
|
|
767
916
|
},
|
|
768
917
|
});
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
918
|
+
engine.context.defineProperty('app', {
|
|
919
|
+
value: {
|
|
920
|
+
getPublicPath: () => '/v2/',
|
|
921
|
+
router: {
|
|
922
|
+
getBasename: () => '/v2',
|
|
923
|
+
},
|
|
924
|
+
},
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
928
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
929
|
+
{
|
|
930
|
+
registerRoutePage: vi.fn(),
|
|
931
|
+
updateRoutePage: vi.fn(),
|
|
932
|
+
unregisterRoutePage: vi.fn(),
|
|
933
|
+
},
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
const { container } = render(
|
|
937
|
+
<FlowEngineProvider engine={engine}>
|
|
938
|
+
<MemoryRouter initialEntries={['/admin/371750686228480']}>
|
|
939
|
+
<Routes>
|
|
940
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
941
|
+
</Routes>
|
|
942
|
+
</MemoryRouter>
|
|
943
|
+
</FlowEngineProvider>,
|
|
944
|
+
);
|
|
945
|
+
|
|
946
|
+
await waitFor(() => {
|
|
947
|
+
expect(listAccessible).toHaveBeenCalled();
|
|
948
|
+
});
|
|
949
|
+
expect(container).toBeEmptyDOMElement();
|
|
950
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
951
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
it('should navigate a current admin group route to its first accessible flow page', async () => {
|
|
955
|
+
const engine = new FlowEngine();
|
|
956
|
+
const childRoute = {
|
|
957
|
+
schemaUid: 'child-flow-page',
|
|
958
|
+
title: 'Child flow page',
|
|
959
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
960
|
+
};
|
|
961
|
+
const groupRoute = {
|
|
962
|
+
id: 371750686228480,
|
|
963
|
+
schemaUid: 'group-schema',
|
|
964
|
+
title: 'Group with child',
|
|
965
|
+
type: NocoBaseDesktopRouteType.group,
|
|
966
|
+
children: [childRoute],
|
|
967
|
+
};
|
|
968
|
+
const getRouteBySchemaUid = vi.fn((schemaUid: string) =>
|
|
969
|
+
schemaUid === 'child-flow-page' ? childRoute : undefined,
|
|
970
|
+
);
|
|
971
|
+
const listAccessible = vi.fn(() => [groupRoute]);
|
|
972
|
+
engine.context.defineProperty('routeRepository', {
|
|
973
|
+
value: {
|
|
974
|
+
refreshAccessible: hookState.refresh,
|
|
975
|
+
isAccessibleLoaded: () => true,
|
|
976
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
977
|
+
getRouteBySchemaUid,
|
|
978
|
+
listAccessible,
|
|
979
|
+
},
|
|
980
|
+
});
|
|
981
|
+
engine.context.defineProperty('app', {
|
|
982
|
+
value: {
|
|
983
|
+
getPublicPath: () => '/v2/',
|
|
984
|
+
router: {
|
|
985
|
+
getBasename: () => '/v2',
|
|
986
|
+
},
|
|
987
|
+
},
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
991
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
992
|
+
{
|
|
993
|
+
registerRoutePage: vi.fn(),
|
|
994
|
+
updateRoutePage: vi.fn(),
|
|
995
|
+
unregisterRoutePage: vi.fn(),
|
|
996
|
+
},
|
|
997
|
+
);
|
|
998
|
+
|
|
999
|
+
render(
|
|
1000
|
+
<FlowEngineProvider engine={engine}>
|
|
1001
|
+
<MemoryRouter initialEntries={['/admin/371750686228480']}>
|
|
1002
|
+
<Routes>
|
|
1003
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1004
|
+
</Routes>
|
|
1005
|
+
</MemoryRouter>
|
|
1006
|
+
</FlowEngineProvider>,
|
|
1007
|
+
);
|
|
1008
|
+
|
|
1009
|
+
await waitFor(() => {
|
|
1010
|
+
expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith('child-flow-page', expect.any(Object));
|
|
1011
|
+
});
|
|
1012
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('371750686228480', expect.any(Object));
|
|
1013
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
it('should keep rendering 404 when a group id is opened with a nested admin route path', async () => {
|
|
1017
|
+
const engine = new FlowEngine();
|
|
1018
|
+
const childRoute = {
|
|
1019
|
+
schemaUid: 'child-flow-page',
|
|
1020
|
+
title: 'Child flow page',
|
|
1021
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1022
|
+
};
|
|
1023
|
+
const groupRoute = {
|
|
1024
|
+
id: 371750686228480,
|
|
1025
|
+
schemaUid: 'group-schema',
|
|
1026
|
+
title: 'Group with child',
|
|
1027
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1028
|
+
children: [childRoute],
|
|
1029
|
+
};
|
|
1030
|
+
engine.context.defineProperty('routeRepository', {
|
|
1031
|
+
value: {
|
|
1032
|
+
refreshAccessible: hookState.refresh,
|
|
1033
|
+
isAccessibleLoaded: () => true,
|
|
1034
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1035
|
+
getRouteBySchemaUid: vi.fn((schemaUid: string) => (schemaUid === 'child-flow-page' ? childRoute : undefined)),
|
|
1036
|
+
listAccessible: vi.fn(() => [groupRoute]),
|
|
1037
|
+
},
|
|
1038
|
+
});
|
|
1039
|
+
engine.context.defineProperty('app', {
|
|
1040
|
+
value: {
|
|
1041
|
+
getPublicPath: () => '/v2/',
|
|
1042
|
+
router: {
|
|
1043
|
+
getBasename: () => '/v2',
|
|
1044
|
+
},
|
|
1045
|
+
},
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1049
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1050
|
+
{
|
|
1051
|
+
registerRoutePage: vi.fn(),
|
|
1052
|
+
updateRoutePage: vi.fn(),
|
|
1053
|
+
unregisterRoutePage: vi.fn(),
|
|
1054
|
+
},
|
|
1055
|
+
);
|
|
1056
|
+
|
|
1057
|
+
render(
|
|
1058
|
+
<FlowEngineProvider engine={engine}>
|
|
1059
|
+
<MemoryRouter initialEntries={['/admin/371750686228480/view/not-exists']}>
|
|
1060
|
+
<Routes>
|
|
1061
|
+
<Route path="/admin/:name/view/*" element={<FlowRoute />} />
|
|
1062
|
+
</Routes>
|
|
1063
|
+
</MemoryRouter>
|
|
1064
|
+
</FlowEngineProvider>,
|
|
1065
|
+
);
|
|
1066
|
+
|
|
1067
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1068
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('child-flow-page', expect.any(Object));
|
|
1069
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('371750686228480', expect.any(Object));
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
it('should keep rendering 404 when a group id is opened with a tab admin route path', 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
|
+
engine.context.defineProperty('routeRepository', {
|
|
1087
|
+
value: {
|
|
1088
|
+
refreshAccessible: hookState.refresh,
|
|
1089
|
+
isAccessibleLoaded: () => true,
|
|
1090
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1091
|
+
getRouteBySchemaUid: vi.fn((schemaUid: string) => (schemaUid === 'child-flow-page' ? childRoute : undefined)),
|
|
1092
|
+
listAccessible: vi.fn(() => [groupRoute]),
|
|
1093
|
+
},
|
|
1094
|
+
});
|
|
1095
|
+
engine.context.defineProperty('app', {
|
|
1096
|
+
value: {
|
|
1097
|
+
getPublicPath: () => '/v2/',
|
|
1098
|
+
router: {
|
|
1099
|
+
getBasename: () => '/v2',
|
|
1100
|
+
},
|
|
1101
|
+
},
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1105
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1106
|
+
{
|
|
1107
|
+
registerRoutePage: vi.fn(),
|
|
1108
|
+
updateRoutePage: vi.fn(),
|
|
1109
|
+
unregisterRoutePage: vi.fn(),
|
|
1110
|
+
},
|
|
1111
|
+
);
|
|
1112
|
+
|
|
1113
|
+
render(
|
|
1114
|
+
<FlowEngineProvider engine={engine}>
|
|
1115
|
+
<MemoryRouter initialEntries={['/admin/371750686228480/tab/not-exists']}>
|
|
1116
|
+
<Routes>
|
|
1117
|
+
<Route path="/admin/:name/tab/:tabUid" element={<FlowRoute />} />
|
|
1118
|
+
</Routes>
|
|
1119
|
+
</MemoryRouter>
|
|
1120
|
+
</FlowEngineProvider>,
|
|
1121
|
+
);
|
|
1122
|
+
|
|
1123
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1124
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('child-flow-page', expect.any(Object));
|
|
1125
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('371750686228480', expect.any(Object));
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
it('should keep blank content while a group child navigation is already pending for the current path', async () => {
|
|
1129
|
+
const engine = new FlowEngine();
|
|
1130
|
+
const childRoute = {
|
|
1131
|
+
schemaUid: 'child-flow-page',
|
|
1132
|
+
title: 'Child flow page',
|
|
1133
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1134
|
+
};
|
|
1135
|
+
const groupRoute = {
|
|
1136
|
+
id: 371750686228480,
|
|
1137
|
+
schemaUid: 'group-schema',
|
|
1138
|
+
title: 'Group with child',
|
|
1139
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1140
|
+
children: [childRoute],
|
|
1141
|
+
};
|
|
1142
|
+
engine.context.defineProperty('routeRepository', {
|
|
1143
|
+
value: {
|
|
1144
|
+
refreshAccessible: hookState.refresh,
|
|
1145
|
+
isAccessibleLoaded: () => true,
|
|
1146
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1147
|
+
getRouteBySchemaUid: vi.fn((schemaUid: string) => (schemaUid === 'child-flow-page' ? childRoute : undefined)),
|
|
1148
|
+
listAccessible: vi.fn(() => [groupRoute]),
|
|
1149
|
+
},
|
|
1150
|
+
});
|
|
1151
|
+
engine.context.defineProperty('app', {
|
|
1152
|
+
value: {
|
|
1153
|
+
getPublicPath: () => '/v2/',
|
|
1154
|
+
router: {
|
|
1155
|
+
getBasename: () => '/v2',
|
|
1156
|
+
},
|
|
1157
|
+
},
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
const replace = vi.fn();
|
|
1161
|
+
const navigator: RouterNavigator = {
|
|
1162
|
+
createHref: (to: { pathname?: string; search?: string; hash?: string } | string) =>
|
|
1163
|
+
typeof to === 'string' ? to : `${to.pathname || ''}${to.search || ''}${to.hash || ''}`,
|
|
1164
|
+
go: vi.fn(),
|
|
1165
|
+
push: vi.fn(),
|
|
1166
|
+
replace,
|
|
1167
|
+
};
|
|
1168
|
+
|
|
1169
|
+
const RerenderAfterReplace = () => {
|
|
1170
|
+
const [rerendered, setRerendered] = React.useState(false);
|
|
1171
|
+
React.useEffect(() => {
|
|
1172
|
+
if (!rerendered && replace.mock.calls.length > 0) {
|
|
1173
|
+
setRerendered(true);
|
|
1174
|
+
}
|
|
1175
|
+
}, [rerendered]);
|
|
1176
|
+
|
|
1177
|
+
return <FlowRoute legacyPageBehavior={rerendered ? 'bridge' : undefined} />;
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
render(
|
|
1181
|
+
<FlowEngineProvider engine={engine}>
|
|
1182
|
+
<Router location="/admin/371750686228480" navigator={navigator}>
|
|
1183
|
+
<Routes>
|
|
1184
|
+
<Route path="/admin/:name" element={<RerenderAfterReplace />} />
|
|
1185
|
+
</Routes>
|
|
1186
|
+
</Router>
|
|
1187
|
+
</FlowEngineProvider>,
|
|
1188
|
+
);
|
|
1189
|
+
|
|
1190
|
+
await waitFor(() => {
|
|
1191
|
+
expect(replace).toHaveBeenCalled();
|
|
1192
|
+
});
|
|
1193
|
+
await act(async () => {
|
|
1194
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1195
|
+
});
|
|
1196
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
it('should reset group navigation guard when page uid changes in the same route instance', async () => {
|
|
1200
|
+
const engine = new FlowEngine();
|
|
1201
|
+
const childRoute1 = {
|
|
1202
|
+
schemaUid: 'child-flow-page-1',
|
|
1203
|
+
title: 'Child flow page 1',
|
|
1204
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1205
|
+
};
|
|
1206
|
+
const childRoute2 = {
|
|
1207
|
+
schemaUid: 'child-flow-page-2',
|
|
1208
|
+
title: 'Child flow page 2',
|
|
1209
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1210
|
+
};
|
|
1211
|
+
const groupRoute1 = {
|
|
1212
|
+
id: 371750686228480,
|
|
1213
|
+
schemaUid: 'group-schema-1',
|
|
1214
|
+
title: 'Group with child 1',
|
|
1215
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1216
|
+
children: [childRoute1],
|
|
1217
|
+
};
|
|
1218
|
+
const groupRoute2 = {
|
|
1219
|
+
id: 371750755434496,
|
|
1220
|
+
schemaUid: 'group-schema-2',
|
|
1221
|
+
title: 'Group with child 2',
|
|
1222
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1223
|
+
children: [childRoute2],
|
|
1224
|
+
};
|
|
1225
|
+
const routeMap: Record<string, typeof childRoute1 | typeof childRoute2> = {
|
|
1226
|
+
'child-flow-page-1': childRoute1,
|
|
1227
|
+
'child-flow-page-2': childRoute2,
|
|
1228
|
+
};
|
|
1229
|
+
const getRouteBySchemaUid = vi.fn((schemaUid: string) => routeMap[schemaUid]);
|
|
1230
|
+
const listAccessible = vi.fn(() => [groupRoute1, groupRoute2]);
|
|
1231
|
+
engine.context.defineProperty('routeRepository', {
|
|
1232
|
+
value: {
|
|
1233
|
+
refreshAccessible: hookState.refresh,
|
|
1234
|
+
isAccessibleLoaded: () => true,
|
|
1235
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1236
|
+
getRouteBySchemaUid,
|
|
1237
|
+
listAccessible,
|
|
1238
|
+
},
|
|
1239
|
+
});
|
|
1240
|
+
engine.context.defineProperty('app', {
|
|
1241
|
+
value: {
|
|
1242
|
+
getPublicPath: () => '/v2/',
|
|
1243
|
+
router: {
|
|
1244
|
+
getBasename: () => '/v2',
|
|
1245
|
+
},
|
|
1246
|
+
},
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1250
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1251
|
+
{
|
|
1252
|
+
registerRoutePage: vi.fn(),
|
|
1253
|
+
updateRoutePage: vi.fn(),
|
|
1254
|
+
unregisterRoutePage: vi.fn(),
|
|
1255
|
+
},
|
|
1256
|
+
);
|
|
1257
|
+
let navigateTo: (path: string) => void = () => {};
|
|
1258
|
+
const CaptureNavigate = () => {
|
|
1259
|
+
const navigate = useNavigate();
|
|
1260
|
+
React.useEffect(() => {
|
|
1261
|
+
navigateTo = navigate;
|
|
1262
|
+
}, [navigate]);
|
|
1263
|
+
return null;
|
|
1264
|
+
};
|
|
1265
|
+
|
|
1266
|
+
render(
|
|
1267
|
+
<FlowEngineProvider engine={engine}>
|
|
1268
|
+
<MemoryRouter initialEntries={['/admin/371750686228480']}>
|
|
1269
|
+
<CaptureNavigate />
|
|
1270
|
+
<Routes>
|
|
1271
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1272
|
+
</Routes>
|
|
1273
|
+
</MemoryRouter>
|
|
1274
|
+
</FlowEngineProvider>,
|
|
1275
|
+
);
|
|
1276
|
+
|
|
1277
|
+
await waitFor(() => {
|
|
1278
|
+
expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith('child-flow-page-1', expect.any(Object));
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
act(() => {
|
|
1282
|
+
navigateTo('/admin/371750755434496');
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
await waitFor(() => {
|
|
1286
|
+
expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith('child-flow-page-2', expect.any(Object));
|
|
1287
|
+
});
|
|
1288
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('371750755434496', expect.any(Object));
|
|
1289
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
it('should keep rendering 404 when current admin route matches no accessible schema uid or id', async () => {
|
|
1293
|
+
const engine = new FlowEngine();
|
|
1294
|
+
engine.context.defineProperty('routeRepository', {
|
|
1295
|
+
value: {
|
|
1296
|
+
refreshAccessible: hookState.refresh,
|
|
1297
|
+
isAccessibleLoaded: () => true,
|
|
1298
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1299
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1300
|
+
listAccessible: vi.fn(() => [
|
|
1301
|
+
{
|
|
1302
|
+
id: 371750686228480,
|
|
1303
|
+
schemaUid: 'group-schema',
|
|
1304
|
+
title: '789',
|
|
1305
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1306
|
+
},
|
|
1307
|
+
]),
|
|
1308
|
+
},
|
|
1309
|
+
});
|
|
1310
|
+
engine.context.defineProperty('app', {
|
|
1311
|
+
value: {
|
|
1312
|
+
getPublicPath: () => '/v2/',
|
|
1313
|
+
router: {
|
|
1314
|
+
getBasename: () => '/v2',
|
|
1315
|
+
},
|
|
1316
|
+
},
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1319
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1320
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1321
|
+
{
|
|
1322
|
+
registerRoutePage: vi.fn(),
|
|
1323
|
+
updateRoutePage: vi.fn(),
|
|
1324
|
+
unregisterRoutePage: vi.fn(),
|
|
1325
|
+
},
|
|
1326
|
+
);
|
|
1327
|
+
|
|
1328
|
+
render(
|
|
1329
|
+
<FlowEngineProvider engine={engine}>
|
|
1330
|
+
<MemoryRouter initialEntries={['/admin/not-exists-4822']}>
|
|
1331
|
+
<Routes>
|
|
1332
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1333
|
+
</Routes>
|
|
1334
|
+
</MemoryRouter>
|
|
1335
|
+
</FlowEngineProvider>,
|
|
1336
|
+
);
|
|
1337
|
+
|
|
1338
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1339
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
it('should keep rendering 404 when current admin route matches only a non-group route id', async () => {
|
|
1343
|
+
const engine = new FlowEngine();
|
|
1344
|
+
engine.context.defineProperty('routeRepository', {
|
|
1345
|
+
value: {
|
|
1346
|
+
refreshAccessible: hookState.refresh,
|
|
1347
|
+
isAccessibleLoaded: () => true,
|
|
1348
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1349
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1350
|
+
listAccessible: vi.fn(() => [
|
|
1351
|
+
{
|
|
1352
|
+
id: 10001,
|
|
1353
|
+
schemaUid: 'flow-page-schema',
|
|
1354
|
+
title: 'Flow page',
|
|
1355
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1356
|
+
},
|
|
1357
|
+
]),
|
|
1358
|
+
},
|
|
1359
|
+
});
|
|
1360
|
+
engine.context.defineProperty('app', {
|
|
1361
|
+
value: {
|
|
1362
|
+
getPublicPath: () => '/v2/',
|
|
1363
|
+
router: {
|
|
1364
|
+
getBasename: () => '/v2',
|
|
1365
|
+
},
|
|
1366
|
+
},
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1370
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1371
|
+
{
|
|
1372
|
+
registerRoutePage: vi.fn(),
|
|
1373
|
+
updateRoutePage: vi.fn(),
|
|
1374
|
+
unregisterRoutePage: vi.fn(),
|
|
1375
|
+
},
|
|
1376
|
+
);
|
|
1377
|
+
|
|
1378
|
+
render(
|
|
1379
|
+
<FlowEngineProvider engine={engine}>
|
|
1380
|
+
<MemoryRouter initialEntries={['/admin/10001']}>
|
|
1381
|
+
<Routes>
|
|
1382
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1383
|
+
</Routes>
|
|
1384
|
+
</MemoryRouter>
|
|
1385
|
+
</FlowEngineProvider>,
|
|
1386
|
+
);
|
|
1387
|
+
|
|
1388
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1389
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
it('should keep rendering 404 when current admin route matches only a group schema uid', async () => {
|
|
1393
|
+
const engine = new FlowEngine();
|
|
1394
|
+
const groupRoute = {
|
|
1395
|
+
id: 371750686228480,
|
|
1396
|
+
schemaUid: 'group-schema',
|
|
1397
|
+
title: '789',
|
|
1398
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1399
|
+
};
|
|
1400
|
+
engine.context.defineProperty('routeRepository', {
|
|
1401
|
+
value: {
|
|
1402
|
+
refreshAccessible: hookState.refresh,
|
|
1403
|
+
isAccessibleLoaded: () => true,
|
|
1404
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1405
|
+
getRouteBySchemaUid: vi.fn((schemaUid: string) => (schemaUid === 'group-schema' ? groupRoute : undefined)),
|
|
1406
|
+
listAccessible: vi.fn(() => [groupRoute]),
|
|
1407
|
+
},
|
|
1408
|
+
});
|
|
1409
|
+
engine.context.defineProperty('app', {
|
|
1410
|
+
value: {
|
|
1411
|
+
getPublicPath: () => '/v2/',
|
|
1412
|
+
router: {
|
|
1413
|
+
getBasename: () => '/v2',
|
|
1414
|
+
},
|
|
1415
|
+
},
|
|
1416
|
+
});
|
|
1417
|
+
|
|
1418
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1419
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1420
|
+
{
|
|
1421
|
+
registerRoutePage: vi.fn(),
|
|
1422
|
+
updateRoutePage: vi.fn(),
|
|
1423
|
+
unregisterRoutePage: vi.fn(),
|
|
1424
|
+
},
|
|
1425
|
+
);
|
|
1426
|
+
|
|
1427
|
+
render(
|
|
1428
|
+
<FlowEngineProvider engine={engine}>
|
|
1429
|
+
<MemoryRouter initialEntries={['/admin/group-schema']}>
|
|
1430
|
+
<Routes>
|
|
1431
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1432
|
+
</Routes>
|
|
1433
|
+
</MemoryRouter>
|
|
1434
|
+
</FlowEngineProvider>,
|
|
1435
|
+
);
|
|
1436
|
+
|
|
1437
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1438
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1439
|
+
});
|
|
1440
|
+
|
|
1441
|
+
it('should not use flowModels:findOne to bridge a missing route for protected layouts', async () => {
|
|
1442
|
+
const engine = new FlowEngine();
|
|
1443
|
+
const findOne = vi.fn().mockResolvedValue({
|
|
1444
|
+
uid: 'mobile-denied-page',
|
|
1445
|
+
use: 'FlowModel',
|
|
1446
|
+
});
|
|
1447
|
+
engine.setModelRepository({
|
|
1448
|
+
findOne,
|
|
1449
|
+
save: vi.fn(),
|
|
1450
|
+
destroy: vi.fn(),
|
|
1451
|
+
} as any);
|
|
1452
|
+
engine.context.defineProperty('routeRepository', {
|
|
1453
|
+
value: {
|
|
1454
|
+
refreshAccessible: hookState.refresh,
|
|
1455
|
+
isAccessibleLoaded: () => true,
|
|
1456
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1457
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1458
|
+
},
|
|
1459
|
+
});
|
|
1460
|
+
engine.context.defineProperty('app', {
|
|
1461
|
+
value: {
|
|
1462
|
+
getPublicPath: () => '/v2/',
|
|
1463
|
+
router: {
|
|
1464
|
+
getBasename: () => '/v2',
|
|
1465
|
+
},
|
|
1466
|
+
},
|
|
1467
|
+
});
|
|
1468
|
+
const routeModel = engine.createModel({
|
|
1469
|
+
uid: 'mobile-route-model',
|
|
1470
|
+
use: 'FlowModel',
|
|
1471
|
+
});
|
|
1472
|
+
routeModel.context.defineProperty('layout', {
|
|
1473
|
+
value: {
|
|
1474
|
+
routeName: 'mobile',
|
|
1475
|
+
routePath: '/mobile',
|
|
1476
|
+
rootRouteName: 'mobile',
|
|
1477
|
+
uid: 'mobile-layout-model',
|
|
1478
|
+
layoutModelClass: 'MobileLayoutModel',
|
|
1479
|
+
rootPageModelClass: 'MobileRootPageModel',
|
|
1480
|
+
childPageModelClass: 'MobileChildPageModel',
|
|
1481
|
+
authCheck: true,
|
|
1482
|
+
},
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
1486
|
+
engine.createModel({ uid: 'mobile-layout-model', use: 'FlowModel' }),
|
|
1487
|
+
{
|
|
1488
|
+
registerRoutePage: vi.fn(),
|
|
1489
|
+
updateRoutePage: vi.fn(),
|
|
1490
|
+
unregisterRoutePage: vi.fn(),
|
|
1491
|
+
},
|
|
1492
|
+
);
|
|
1493
|
+
|
|
1494
|
+
render(
|
|
1495
|
+
<FlowEngineProvider engine={engine}>
|
|
1496
|
+
<FlowContextProvider context={routeModel.context}>
|
|
1497
|
+
<MemoryRouter initialEntries={['/mobile/mobile-denied-page']}>
|
|
1498
|
+
<Routes>
|
|
1499
|
+
<Route
|
|
1500
|
+
path="/mobile/:name"
|
|
1501
|
+
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel} />}
|
|
1502
|
+
/>
|
|
1503
|
+
</Routes>
|
|
1504
|
+
</MemoryRouter>
|
|
1505
|
+
</FlowContextProvider>
|
|
1506
|
+
</FlowEngineProvider>,
|
|
1507
|
+
);
|
|
1508
|
+
|
|
1509
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1510
|
+
expect(findOne).not.toHaveBeenCalled();
|
|
1511
|
+
expect(layoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1512
|
+
});
|
|
1513
|
+
|
|
1514
|
+
it('should not load a known page uid when a protected custom layout has no accessible route', async () => {
|
|
1515
|
+
const engine = new FlowEngine();
|
|
1516
|
+
const findOne = vi.fn().mockResolvedValue({
|
|
1517
|
+
uid: 'known-denied-custom-page',
|
|
1518
|
+
use: 'FlowModel',
|
|
1519
|
+
});
|
|
1520
|
+
engine.setModelRepository({
|
|
1521
|
+
findOne,
|
|
1522
|
+
save: vi.fn(),
|
|
1523
|
+
destroy: vi.fn(),
|
|
1524
|
+
} as any);
|
|
1525
|
+
engine.context.defineProperty('routeRepository', {
|
|
1526
|
+
value: {
|
|
1527
|
+
refreshAccessible: hookState.refresh,
|
|
1528
|
+
isAccessibleLoaded: () => true,
|
|
1529
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1530
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1531
|
+
},
|
|
1532
|
+
});
|
|
1533
|
+
engine.context.defineProperty('app', {
|
|
1534
|
+
value: {
|
|
1535
|
+
getPublicPath: () => '/v2/',
|
|
1536
|
+
router: {
|
|
1537
|
+
getBasename: () => '/v2',
|
|
1538
|
+
},
|
|
1539
|
+
},
|
|
1540
|
+
});
|
|
1541
|
+
const routeModel = engine.createModel({
|
|
1542
|
+
uid: 'custom-layout-route-model',
|
|
1543
|
+
use: 'FlowModel',
|
|
1544
|
+
});
|
|
1545
|
+
routeModel.context.defineProperty('layout', {
|
|
1546
|
+
value: {
|
|
1547
|
+
routeName: 'admin2',
|
|
1548
|
+
routePath: '/admin2',
|
|
1549
|
+
rootRouteName: 'admin2',
|
|
1550
|
+
uid: 'custom-protected-layout-model',
|
|
1551
|
+
layoutModelClass: 'AdminLayoutModel',
|
|
1552
|
+
rootPageModelClass: 'RootPageModel',
|
|
1553
|
+
childPageModelClass: 'ChildPageModel',
|
|
1554
|
+
authCheck: true,
|
|
1555
|
+
},
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
1559
|
+
engine.createModel({ uid: 'custom-protected-layout-model', use: 'FlowModel' }),
|
|
1560
|
+
{
|
|
1561
|
+
registerRoutePage: vi.fn(),
|
|
1562
|
+
updateRoutePage: vi.fn(),
|
|
1563
|
+
unregisterRoutePage: vi.fn(),
|
|
1564
|
+
},
|
|
1565
|
+
);
|
|
1566
|
+
|
|
1567
|
+
render(
|
|
1568
|
+
<FlowEngineProvider engine={engine}>
|
|
1569
|
+
<FlowContextProvider context={routeModel.context}>
|
|
1570
|
+
<MemoryRouter initialEntries={['/admin2/known-denied-custom-page']}>
|
|
1571
|
+
<Routes>
|
|
1572
|
+
<Route
|
|
1573
|
+
path="/admin2/:name"
|
|
1574
|
+
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel} />}
|
|
1575
|
+
/>
|
|
1576
|
+
</Routes>
|
|
1577
|
+
</MemoryRouter>
|
|
1578
|
+
</FlowContextProvider>
|
|
1579
|
+
</FlowEngineProvider>,
|
|
1580
|
+
);
|
|
1581
|
+
|
|
1582
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1583
|
+
expect(findOne).not.toHaveBeenCalled();
|
|
1584
|
+
expect(layoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1585
|
+
});
|
|
1586
|
+
|
|
1587
|
+
it('should check model existence without occupying the route model uid', async () => {
|
|
1588
|
+
const engine = new FlowEngine();
|
|
1589
|
+
const findOne = vi.fn().mockResolvedValue({
|
|
1590
|
+
uid: 'public-form-1',
|
|
1591
|
+
});
|
|
1592
|
+
const request = vi.fn().mockResolvedValue({
|
|
1593
|
+
data: {
|
|
1594
|
+
data: {
|
|
1595
|
+
uid: 'public-form-1',
|
|
1596
|
+
},
|
|
1597
|
+
},
|
|
1598
|
+
});
|
|
1599
|
+
engine.setModelRepository({
|
|
1600
|
+
findOne,
|
|
1601
|
+
save: vi.fn(),
|
|
1602
|
+
destroy: vi.fn(),
|
|
1603
|
+
} as any);
|
|
1604
|
+
engine.context.defineProperty('routeRepository', {
|
|
1605
|
+
value: {
|
|
1606
|
+
refreshAccessible: hookState.refresh,
|
|
1607
|
+
isAccessibleLoaded: () => true,
|
|
1608
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1609
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1610
|
+
},
|
|
1611
|
+
});
|
|
1612
|
+
engine.context.defineProperty('app', {
|
|
1613
|
+
value: {
|
|
1614
|
+
apiClient: {
|
|
1615
|
+
request,
|
|
1616
|
+
},
|
|
1617
|
+
getPublicPath: () => '/v2/',
|
|
1618
|
+
router: {
|
|
1619
|
+
getBasename: () => '/v2',
|
|
1620
|
+
},
|
|
1621
|
+
},
|
|
1622
|
+
});
|
|
1623
|
+
const routeModel = engine.createModel({
|
|
1624
|
+
uid: 'public-form-route-model',
|
|
1625
|
+
use: 'FlowModel',
|
|
1626
|
+
});
|
|
1627
|
+
routeModel.context.defineProperty('layout', {
|
|
1628
|
+
value: {
|
|
1629
|
+
routeName: 'public-forms',
|
|
1630
|
+
routePath: '/public-forms',
|
|
1631
|
+
rootRouteName: 'public-forms',
|
|
1632
|
+
uid: 'public-form-layout-model',
|
|
1633
|
+
layoutModelClass: 'PublicFormLayoutModel',
|
|
1634
|
+
rootPageModelClass: 'PublicFormPageModel',
|
|
1635
|
+
childPageModelClass: 'ChildPageModel',
|
|
1636
|
+
authCheck: false,
|
|
1637
|
+
},
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
1641
|
+
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
1642
|
+
{
|
|
1643
|
+
registerRoutePage: vi.fn(),
|
|
1644
|
+
updateRoutePage: vi.fn(),
|
|
1645
|
+
unregisterRoutePage: vi.fn(),
|
|
1646
|
+
},
|
|
1647
|
+
);
|
|
1648
|
+
|
|
1649
|
+
render(
|
|
1650
|
+
<FlowEngineProvider engine={engine}>
|
|
1651
|
+
<FlowContextProvider context={routeModel.context}>
|
|
1652
|
+
<MemoryRouter initialEntries={['/public-forms/public-form-1']}>
|
|
1653
|
+
<Routes>
|
|
1654
|
+
<Route path="/public-forms/:name" element={<FlowRoute legacyPageBehavior="notFound" />} />
|
|
1655
|
+
</Routes>
|
|
1656
|
+
</MemoryRouter>
|
|
1657
|
+
</FlowContextProvider>
|
|
1658
|
+
</FlowEngineProvider>,
|
|
1659
|
+
);
|
|
1660
|
+
|
|
1661
|
+
await waitFor(() => {
|
|
1662
|
+
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('public-form-1', expect.any(Object));
|
|
1663
|
+
});
|
|
1664
|
+
expect(findOne).toHaveBeenCalledWith({ uid: 'public-form-1' });
|
|
1665
|
+
expect(request).not.toHaveBeenCalled();
|
|
1666
|
+
expect(engine.getModel('public-form-1')).toBeUndefined();
|
|
1667
|
+
});
|
|
1668
|
+
|
|
1669
|
+
it('should skip accessible route loading when layout authCheck is false', async () => {
|
|
1670
|
+
const engine = new FlowEngine();
|
|
1671
|
+
const ensureAccessibleLoaded = vi.fn().mockRejectedValue(new Error('cannot load accessible routes'));
|
|
1672
|
+
const getRouteBySchemaUid = vi.fn();
|
|
1673
|
+
engine.setModelRepository({
|
|
1674
|
+
findOne: vi.fn().mockResolvedValue({
|
|
1675
|
+
uid: 'public-form-1',
|
|
1676
|
+
use: 'FlowModel',
|
|
1677
|
+
}),
|
|
1678
|
+
save: vi.fn(),
|
|
1679
|
+
destroy: vi.fn(),
|
|
1680
|
+
} as any);
|
|
1681
|
+
engine.context.defineProperty('routeRepository', {
|
|
1682
|
+
value: {
|
|
1683
|
+
refreshAccessible: hookState.refresh,
|
|
1684
|
+
isAccessibleLoaded: () => false,
|
|
1685
|
+
ensureAccessibleLoaded,
|
|
1686
|
+
getRouteBySchemaUid,
|
|
1687
|
+
},
|
|
1688
|
+
});
|
|
1689
|
+
engine.context.defineProperty('app', {
|
|
1690
|
+
value: {
|
|
1691
|
+
getPublicPath: () => '/v2/',
|
|
1692
|
+
router: {
|
|
1693
|
+
getBasename: () => '/v2',
|
|
1694
|
+
},
|
|
1695
|
+
},
|
|
1696
|
+
});
|
|
1697
|
+
const routeModel = engine.createModel({
|
|
1698
|
+
uid: 'public-form-route-model',
|
|
1699
|
+
use: 'FlowModel',
|
|
1700
|
+
});
|
|
1701
|
+
routeModel.context.defineProperty('layout', {
|
|
1702
|
+
value: {
|
|
1703
|
+
routeName: 'public-forms',
|
|
1704
|
+
routePath: '/public-forms',
|
|
1705
|
+
rootRouteName: 'public-forms',
|
|
1706
|
+
uid: 'public-form-layout-model',
|
|
1707
|
+
layoutModelClass: 'PublicFormLayoutModel',
|
|
1708
|
+
rootPageModelClass: 'PublicFormPageModel',
|
|
1709
|
+
childPageModelClass: 'ChildPageModel',
|
|
1710
|
+
authCheck: false,
|
|
1711
|
+
},
|
|
1712
|
+
});
|
|
1713
|
+
|
|
1714
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
1715
|
+
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
1716
|
+
{
|
|
1717
|
+
registerRoutePage: vi.fn(),
|
|
1718
|
+
updateRoutePage: vi.fn(),
|
|
1719
|
+
unregisterRoutePage: vi.fn(),
|
|
1720
|
+
},
|
|
1721
|
+
);
|
|
1722
|
+
|
|
1723
|
+
render(
|
|
1724
|
+
<FlowEngineProvider engine={engine}>
|
|
1725
|
+
<FlowContextProvider context={routeModel.context}>
|
|
1726
|
+
<MemoryRouter initialEntries={['/public-forms/public-form-1']}>
|
|
1727
|
+
<Routes>
|
|
1728
|
+
<Route path="/public-forms/:name" element={<FlowRoute legacyPageBehavior="notFound" />} />
|
|
1729
|
+
</Routes>
|
|
1730
|
+
</MemoryRouter>
|
|
1731
|
+
</FlowContextProvider>
|
|
1732
|
+
</FlowEngineProvider>,
|
|
1733
|
+
);
|
|
1734
|
+
|
|
1735
|
+
await waitFor(() => {
|
|
1736
|
+
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('public-form-1', expect.any(Object));
|
|
1737
|
+
});
|
|
1738
|
+
expect(ensureAccessibleLoaded).not.toHaveBeenCalled();
|
|
1739
|
+
expect(getRouteBySchemaUid).not.toHaveBeenCalled();
|
|
1740
|
+
});
|
|
1741
|
+
|
|
1742
|
+
it('should bridge legacy page when behavior is bridge', async () => {
|
|
1743
|
+
const originalLocation = window.location;
|
|
1744
|
+
const replace = vi.fn();
|
|
1745
|
+
Object.defineProperty(window, 'location', {
|
|
1746
|
+
configurable: true,
|
|
1747
|
+
value: {
|
|
1748
|
+
...originalLocation,
|
|
1749
|
+
pathname: '/v2/admin/test-page',
|
|
1750
|
+
replace,
|
|
1751
|
+
},
|
|
1752
|
+
});
|
|
1753
|
+
|
|
1754
|
+
try {
|
|
1755
|
+
const engine = new FlowEngine();
|
|
1756
|
+
engine.context.defineProperty('routeRepository', {
|
|
1757
|
+
value: {
|
|
1758
|
+
refreshAccessible: hookState.refresh,
|
|
1759
|
+
isAccessibleLoaded: () => true,
|
|
776
1760
|
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
777
1761
|
getRouteBySchemaUid: vi.fn(() => ({ type: 'page', schemaUid: 'test-page' })),
|
|
778
1762
|
},
|
|
@@ -817,7 +1801,7 @@ describe('FlowRoute', () => {
|
|
|
817
1801
|
}
|
|
818
1802
|
});
|
|
819
1803
|
|
|
820
|
-
it('should not
|
|
1804
|
+
it('should render not found without redirecting when admin route does not exist', async () => {
|
|
821
1805
|
const originalLocation = window.location;
|
|
822
1806
|
const replace = vi.fn();
|
|
823
1807
|
Object.defineProperty(window, 'location', {
|
|
@@ -866,9 +1850,8 @@ describe('FlowRoute', () => {
|
|
|
866
1850
|
</FlowEngineProvider>,
|
|
867
1851
|
);
|
|
868
1852
|
|
|
869
|
-
await
|
|
870
|
-
|
|
871
|
-
});
|
|
1853
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1854
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
872
1855
|
expect(replace).not.toHaveBeenCalled();
|
|
873
1856
|
} finally {
|
|
874
1857
|
Object.defineProperty(window, 'location', {
|