@nocobase/client-v2 2.1.31 → 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 +2 -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/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
- package/es/components/category-tabs/index.d.ts +9 -0
- package/es/components/form/TypedVariableInput.d.ts +31 -5
- 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/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/index.d.ts +1 -1
- package/es/flow/actions/linkageRules.d.ts +2 -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/FieldAssignValueInput.d.ts +9 -0
- 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/models/base/ActionModelCore.d.ts +2 -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/form/value-runtime/runtime.d.ts +9 -0
- package/es/flow-compat/routeTypes.d.ts +1 -0
- package/es/index.d.ts +7 -1
- package/es/index.mjs +223 -135
- package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
- package/es/utils/index.d.ts +1 -0
- package/lib/index.js +220 -132
- package/package.json +7 -7
- package/src/APIClient.ts +1 -10
- package/src/Application.tsx +4 -0
- package/src/BaseApplication.tsx +2 -0
- 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 +18 -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 +28 -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/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 +4 -0
- package/src/components/form/TypedVariableInput.tsx +452 -101
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
- 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/index.ts +2 -0
- package/src/entry-actions/EntryActionManager.ts +76 -0
- package/src/entry-actions/index.ts +10 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +1084 -145
- package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
- package/src/flow/actions/customVariable.tsx +1 -2
- package/src/flow/actions/index.ts +2 -0
- package/src/flow/actions/linkageRules.tsx +65 -8
- package/src/flow/actions/runjs.tsx +2 -14
- package/src/flow/admin-shell/BaseLayoutModel.tsx +13 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +46 -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/__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 +77 -0
- 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/components/DefaultValue.tsx +1 -2
- package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
- package/src/flow/components/FieldAssignValueInput.tsx +76 -9
- package/src/flow/components/FlowRoute.tsx +185 -28
- package/src/flow/components/RunJSValueEditor.tsx +9 -1
- package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +84 -2
- 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/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/index.ts +1 -0
- 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 +5 -0
- package/src/flow/models/base/GridModel.tsx +38 -7
- package/src/flow/models/base/PageModel/PageModel.tsx +387 -53
- package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
- package/src/flow/models/base/PageModel/PageTabModel.tsx +186 -3
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +790 -14
- 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 +586 -1
- package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -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 +1 -2
- package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
- package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +87 -0
- package/src/flow/models/blocks/form/value-runtime/rules.ts +2 -2
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +91 -0
- package/src/flow/models/blocks/js-block/JSBlock.tsx +2 -7
- package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +1 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +13 -3
- package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +54 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
- 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/VariableFieldFormModel.tsx +3 -3
- package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
- package/src/flow/models/topbar/TopbarActionModel.tsx +168 -10
- package/src/flow-compat/Popover.tsx +43 -4
- package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
- package/src/flow-compat/routeTypes.ts +1 -0
- package/src/index.ts +13 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
- package/src/nocobase-buildin-plugin/index.tsx +19 -3
- package/src/settings-center/AdminSettingsLayout.tsx +10 -2
- package/src/settings-center/SystemSettingsPage.tsx +0 -1
- package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
- package/src/settings-center/utils.tsx +0 -6
- package/src/utils/getRouteRuntimeVersion.ts +185 -0
- package/src/utils/index.tsx +1 -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: {
|
|
@@ -322,6 +326,94 @@ describe('FlowRoute', () => {
|
|
|
322
326
|
});
|
|
323
327
|
});
|
|
324
328
|
|
|
329
|
+
it('should ensure accessible routes for the current layout before bridging', async () => {
|
|
330
|
+
const engine = new FlowEngine();
|
|
331
|
+
const request = vi.fn().mockResolvedValue({
|
|
332
|
+
data: {
|
|
333
|
+
data: [
|
|
334
|
+
{
|
|
335
|
+
type: 'flowPage',
|
|
336
|
+
schemaUid: 'mobile-page',
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
const routeRepository = new RouteRepository({
|
|
342
|
+
api: {
|
|
343
|
+
request,
|
|
344
|
+
resource: vi.fn(),
|
|
345
|
+
},
|
|
346
|
+
} as never);
|
|
347
|
+
routeRepository.setRoutes([], 'admin-layout-model');
|
|
348
|
+
const deactivateLayout = routeRepository.activateLayout({
|
|
349
|
+
uid: 'mobile-layout-model',
|
|
350
|
+
});
|
|
351
|
+
engine.context.defineProperty('routeRepository', {
|
|
352
|
+
value: routeRepository,
|
|
353
|
+
});
|
|
354
|
+
engine.context.defineProperty('app', {
|
|
355
|
+
value: {
|
|
356
|
+
getPublicPath: () => '/v2/',
|
|
357
|
+
router: {
|
|
358
|
+
getBasename: () => '/v2',
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
const routeModel = engine.createModel({
|
|
363
|
+
uid: 'mobile-route-model',
|
|
364
|
+
use: 'FlowModel',
|
|
365
|
+
});
|
|
366
|
+
routeModel.context.defineProperty('layout', {
|
|
367
|
+
value: {
|
|
368
|
+
routeName: 'mobile',
|
|
369
|
+
routePath: '/mobile',
|
|
370
|
+
rootRouteName: 'mobile',
|
|
371
|
+
uid: 'mobile-layout-model',
|
|
372
|
+
layoutModelClass: 'MobileLayoutModel',
|
|
373
|
+
rootPageModelClass: 'MobileRootPageModel',
|
|
374
|
+
childPageModelClass: 'MobileChildPageModel',
|
|
375
|
+
authCheck: true,
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const mobileLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
380
|
+
engine.createModel({
|
|
381
|
+
uid: 'mobile-layout-model',
|
|
382
|
+
use: 'FlowModel',
|
|
383
|
+
}),
|
|
384
|
+
{
|
|
385
|
+
registerRoutePage: vi.fn(),
|
|
386
|
+
updateRoutePage: vi.fn(),
|
|
387
|
+
unregisterRoutePage: vi.fn(),
|
|
388
|
+
},
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
render(
|
|
392
|
+
<FlowEngineProvider engine={engine}>
|
|
393
|
+
<FlowContextProvider context={routeModel.context}>
|
|
394
|
+
<MemoryRouter initialEntries={['/mobile/mobile-page']}>
|
|
395
|
+
<Routes>
|
|
396
|
+
<Route path="/mobile/:name" element={<FlowRoute />} />
|
|
397
|
+
</Routes>
|
|
398
|
+
</MemoryRouter>
|
|
399
|
+
</FlowContextProvider>
|
|
400
|
+
</FlowEngineProvider>,
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
await waitFor(() => {
|
|
404
|
+
expect(request).toHaveBeenCalledWith({
|
|
405
|
+
url: '/desktopRoutes:listAccessible',
|
|
406
|
+
params: {
|
|
407
|
+
tree: true,
|
|
408
|
+
sort: 'sort',
|
|
409
|
+
layout: 'mobile-layout-model',
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
expect(mobileLayoutModel.registerRoutePage).toHaveBeenCalledWith('mobile-page', expect.any(Object));
|
|
413
|
+
});
|
|
414
|
+
deactivateLayout();
|
|
415
|
+
});
|
|
416
|
+
|
|
325
417
|
it('should fail fast when admin-layout-model is missing', () => {
|
|
326
418
|
const engine = new FlowEngine();
|
|
327
419
|
engine.context.defineProperty('route', {
|
|
@@ -421,6 +513,75 @@ describe('FlowRoute', () => {
|
|
|
421
513
|
});
|
|
422
514
|
});
|
|
423
515
|
|
|
516
|
+
it('should not rerun accessible guard when layout reference changes but fields stay the same', async () => {
|
|
517
|
+
const engine = new FlowEngine();
|
|
518
|
+
const ensureAccessibleLoaded = vi.fn().mockResolvedValue([]);
|
|
519
|
+
const getRouteBySchemaUid = vi.fn(() => ({ type: 'flowPage', schemaUid: 'test-page' }));
|
|
520
|
+
engine.context.defineProperty('routeRepository', {
|
|
521
|
+
value: {
|
|
522
|
+
refreshAccessible: hookState.refresh,
|
|
523
|
+
isAccessibleLoaded: () => true,
|
|
524
|
+
ensureAccessibleLoaded,
|
|
525
|
+
getRouteBySchemaUid,
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
engine.context.defineProperty('app', {
|
|
529
|
+
value: {
|
|
530
|
+
getPublicPath: () => '/v2/',
|
|
531
|
+
router: {
|
|
532
|
+
getBasename: () => '/v2',
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
538
|
+
engine.createModel({ uid: 'legacy-admin-layout-model', use: 'FlowModel' }),
|
|
539
|
+
{
|
|
540
|
+
registerRoutePage: vi.fn(),
|
|
541
|
+
updateRoutePage: vi.fn(),
|
|
542
|
+
unregisterRoutePage: vi.fn(),
|
|
543
|
+
},
|
|
544
|
+
);
|
|
545
|
+
let layoutReadCount = 0;
|
|
546
|
+
const stableLayout = {
|
|
547
|
+
uid: 'admin-layout-model',
|
|
548
|
+
routeName: 'admin',
|
|
549
|
+
routePath: '/admin',
|
|
550
|
+
authCheck: true,
|
|
551
|
+
};
|
|
552
|
+
Object.defineProperty(layoutModel, 'layout', {
|
|
553
|
+
get: () => {
|
|
554
|
+
layoutReadCount += 1;
|
|
555
|
+
return layoutReadCount <= 2 ? { ...stableLayout } : stableLayout;
|
|
556
|
+
},
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
const element = (
|
|
560
|
+
<FlowEngineProvider engine={engine}>
|
|
561
|
+
<MemoryRouter initialEntries={['/flow/test-page']}>
|
|
562
|
+
<Routes>
|
|
563
|
+
<Route path="/flow/:name" element={<FlowRoute getLayoutModel={() => layoutModel as any} />} />
|
|
564
|
+
</Routes>
|
|
565
|
+
</MemoryRouter>
|
|
566
|
+
</FlowEngineProvider>
|
|
567
|
+
);
|
|
568
|
+
const { rerender } = render(element);
|
|
569
|
+
|
|
570
|
+
await waitFor(() => {
|
|
571
|
+
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('test-page', expect.any(Object));
|
|
572
|
+
});
|
|
573
|
+
expect(ensureAccessibleLoaded).not.toHaveBeenCalled();
|
|
574
|
+
expect(getRouteBySchemaUid).toHaveBeenCalledTimes(1);
|
|
575
|
+
|
|
576
|
+
rerender(element);
|
|
577
|
+
|
|
578
|
+
await waitFor(() => {
|
|
579
|
+
expect(layoutModel.updateRoutePage).toHaveBeenCalled();
|
|
580
|
+
});
|
|
581
|
+
expect(ensureAccessibleLoaded).not.toHaveBeenCalled();
|
|
582
|
+
expect(getRouteBySchemaUid).toHaveBeenCalledTimes(1);
|
|
583
|
+
});
|
|
584
|
+
|
|
424
585
|
it('should explain how to open a legacy page outside the v2 runtime', async () => {
|
|
425
586
|
const originalLocation = window.location;
|
|
426
587
|
const replace = vi.fn();
|
|
@@ -689,6 +850,22 @@ describe('FlowRoute', () => {
|
|
|
689
850
|
},
|
|
690
851
|
},
|
|
691
852
|
});
|
|
853
|
+
const routeModel = engine.createModel({
|
|
854
|
+
uid: 'public-form-route-model',
|
|
855
|
+
use: 'FlowModel',
|
|
856
|
+
});
|
|
857
|
+
routeModel.context.defineProperty('layout', {
|
|
858
|
+
value: {
|
|
859
|
+
routeName: 'public-forms',
|
|
860
|
+
routePath: '/public-forms',
|
|
861
|
+
rootRouteName: 'public-forms',
|
|
862
|
+
uid: 'public-form-layout-model',
|
|
863
|
+
layoutModelClass: 'PublicFormLayoutModel',
|
|
864
|
+
rootPageModelClass: 'PublicFormPageModel',
|
|
865
|
+
childPageModelClass: 'ChildPageModel',
|
|
866
|
+
authCheck: false,
|
|
867
|
+
},
|
|
868
|
+
});
|
|
692
869
|
|
|
693
870
|
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
694
871
|
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
@@ -701,14 +878,16 @@ describe('FlowRoute', () => {
|
|
|
701
878
|
|
|
702
879
|
render(
|
|
703
880
|
<FlowEngineProvider engine={engine}>
|
|
704
|
-
<
|
|
705
|
-
<
|
|
706
|
-
<
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
881
|
+
<FlowContextProvider context={routeModel.context}>
|
|
882
|
+
<MemoryRouter initialEntries={['/public-forms/public-form-1']}>
|
|
883
|
+
<Routes>
|
|
884
|
+
<Route
|
|
885
|
+
path="/public-forms/:name"
|
|
886
|
+
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel} />}
|
|
887
|
+
/>
|
|
888
|
+
</Routes>
|
|
889
|
+
</MemoryRouter>
|
|
890
|
+
</FlowContextProvider>
|
|
712
891
|
</FlowEngineProvider>,
|
|
713
892
|
);
|
|
714
893
|
|
|
@@ -718,20 +897,13 @@ describe('FlowRoute', () => {
|
|
|
718
897
|
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
719
898
|
});
|
|
720
899
|
|
|
721
|
-
it('should
|
|
900
|
+
it('should use getLayoutModel layout authCheck when FlowContext has no layout', async () => {
|
|
722
901
|
const engine = new FlowEngine();
|
|
723
|
-
const findOne = vi.fn().mockResolvedValue({
|
|
724
|
-
uid: 'public-form-1',
|
|
725
|
-
});
|
|
726
|
-
const request = vi.fn().mockResolvedValue({
|
|
727
|
-
data: {
|
|
728
|
-
data: {
|
|
729
|
-
uid: 'public-form-1',
|
|
730
|
-
},
|
|
731
|
-
},
|
|
732
|
-
});
|
|
733
902
|
engine.setModelRepository({
|
|
734
|
-
findOne
|
|
903
|
+
findOne: vi.fn().mockResolvedValue({
|
|
904
|
+
uid: 'public-form-1',
|
|
905
|
+
use: 'FlowModel',
|
|
906
|
+
}),
|
|
735
907
|
save: vi.fn(),
|
|
736
908
|
destroy: vi.fn(),
|
|
737
909
|
} as any);
|
|
@@ -745,20 +917,22 @@ describe('FlowRoute', () => {
|
|
|
745
917
|
});
|
|
746
918
|
engine.context.defineProperty('app', {
|
|
747
919
|
value: {
|
|
748
|
-
apiClient: {
|
|
749
|
-
request,
|
|
750
|
-
},
|
|
751
920
|
getPublicPath: () => '/v2/',
|
|
752
921
|
router: {
|
|
753
922
|
getBasename: () => '/v2',
|
|
754
923
|
},
|
|
755
924
|
},
|
|
756
925
|
});
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
use: 'FlowModel',
|
|
760
|
-
|
|
761
|
-
|
|
926
|
+
|
|
927
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
928
|
+
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
929
|
+
{
|
|
930
|
+
registerRoutePage: vi.fn(),
|
|
931
|
+
updateRoutePage: vi.fn(),
|
|
932
|
+
unregisterRoutePage: vi.fn(),
|
|
933
|
+
},
|
|
934
|
+
);
|
|
935
|
+
Object.defineProperty(layoutModel, 'layout', {
|
|
762
936
|
value: {
|
|
763
937
|
routeName: 'public-forms',
|
|
764
938
|
routePath: '/public-forms',
|
|
@@ -771,42 +945,30 @@ describe('FlowRoute', () => {
|
|
|
771
945
|
},
|
|
772
946
|
});
|
|
773
947
|
|
|
774
|
-
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
775
|
-
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
776
|
-
{
|
|
777
|
-
registerRoutePage: vi.fn(),
|
|
778
|
-
updateRoutePage: vi.fn(),
|
|
779
|
-
unregisterRoutePage: vi.fn(),
|
|
780
|
-
},
|
|
781
|
-
);
|
|
782
|
-
|
|
783
948
|
render(
|
|
784
949
|
<FlowEngineProvider engine={engine}>
|
|
785
|
-
<
|
|
786
|
-
<
|
|
787
|
-
<
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
950
|
+
<MemoryRouter initialEntries={['/public-forms/public-form-1']}>
|
|
951
|
+
<Routes>
|
|
952
|
+
<Route
|
|
953
|
+
path="/public-forms/:name"
|
|
954
|
+
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel as any} />}
|
|
955
|
+
/>
|
|
956
|
+
</Routes>
|
|
957
|
+
</MemoryRouter>
|
|
792
958
|
</FlowEngineProvider>,
|
|
793
959
|
);
|
|
794
960
|
|
|
795
961
|
await waitFor(() => {
|
|
796
962
|
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('public-form-1', expect.any(Object));
|
|
797
963
|
});
|
|
798
|
-
expect(
|
|
799
|
-
expect(request).not.toHaveBeenCalled();
|
|
800
|
-
expect(engine.getModel('public-form-1')).toBeUndefined();
|
|
964
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
801
965
|
});
|
|
802
966
|
|
|
803
|
-
it('should not
|
|
967
|
+
it('should render not found when admin route is not accessible', async () => {
|
|
804
968
|
const engine = new FlowEngine();
|
|
805
|
-
const ensureAccessibleLoaded = vi.fn().mockRejectedValue(new Error('cannot load accessible routes'));
|
|
806
|
-
const getRouteBySchemaUid = vi.fn();
|
|
807
969
|
engine.setModelRepository({
|
|
808
970
|
findOne: vi.fn().mockResolvedValue({
|
|
809
|
-
uid: '
|
|
971
|
+
uid: 'admin-denied-page',
|
|
810
972
|
use: 'FlowModel',
|
|
811
973
|
}),
|
|
812
974
|
save: vi.fn(),
|
|
@@ -815,9 +977,9 @@ describe('FlowRoute', () => {
|
|
|
815
977
|
engine.context.defineProperty('routeRepository', {
|
|
816
978
|
value: {
|
|
817
979
|
refreshAccessible: hookState.refresh,
|
|
818
|
-
isAccessibleLoaded: () =>
|
|
819
|
-
ensureAccessibleLoaded,
|
|
820
|
-
getRouteBySchemaUid,
|
|
980
|
+
isAccessibleLoaded: () => true,
|
|
981
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
982
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
821
983
|
},
|
|
822
984
|
});
|
|
823
985
|
engine.context.defineProperty('app', {
|
|
@@ -828,25 +990,9 @@ describe('FlowRoute', () => {
|
|
|
828
990
|
},
|
|
829
991
|
},
|
|
830
992
|
});
|
|
831
|
-
const routeModel = engine.createModel({
|
|
832
|
-
uid: 'public-form-route-model',
|
|
833
|
-
use: 'FlowModel',
|
|
834
|
-
});
|
|
835
|
-
routeModel.context.defineProperty('layout', {
|
|
836
|
-
value: {
|
|
837
|
-
routeName: 'public-forms',
|
|
838
|
-
routePath: '/public-forms',
|
|
839
|
-
rootRouteName: 'public-forms',
|
|
840
|
-
uid: 'public-form-layout-model',
|
|
841
|
-
layoutModelClass: 'PublicFormLayoutModel',
|
|
842
|
-
rootPageModelClass: 'PublicFormPageModel',
|
|
843
|
-
childPageModelClass: 'ChildPageModel',
|
|
844
|
-
authCheck: false,
|
|
845
|
-
},
|
|
846
|
-
});
|
|
847
993
|
|
|
848
|
-
const
|
|
849
|
-
engine.createModel({ uid: '
|
|
994
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
995
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
850
996
|
{
|
|
851
997
|
registerRoutePage: vi.fn(),
|
|
852
998
|
updateRoutePage: vi.fn(),
|
|
@@ -856,40 +1002,878 @@ describe('FlowRoute', () => {
|
|
|
856
1002
|
|
|
857
1003
|
render(
|
|
858
1004
|
<FlowEngineProvider engine={engine}>
|
|
859
|
-
<
|
|
860
|
-
<
|
|
861
|
-
<
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
</MemoryRouter>
|
|
865
|
-
</FlowContextProvider>
|
|
1005
|
+
<MemoryRouter initialEntries={['/admin/admin-denied-page']}>
|
|
1006
|
+
<Routes>
|
|
1007
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1008
|
+
</Routes>
|
|
1009
|
+
</MemoryRouter>
|
|
866
1010
|
</FlowEngineProvider>,
|
|
867
1011
|
);
|
|
868
1012
|
|
|
869
|
-
await
|
|
870
|
-
|
|
871
|
-
});
|
|
872
|
-
expect(ensureAccessibleLoaded).toHaveBeenCalledTimes(1);
|
|
873
|
-
expect(getRouteBySchemaUid).not.toHaveBeenCalled();
|
|
1013
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1014
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
874
1015
|
});
|
|
875
1016
|
|
|
876
|
-
it('should
|
|
877
|
-
const
|
|
878
|
-
const
|
|
879
|
-
|
|
880
|
-
|
|
1017
|
+
it('should render blank content when current admin route matches an accessible empty group id', async () => {
|
|
1018
|
+
const engine = new FlowEngine();
|
|
1019
|
+
const listAccessible = vi.fn(() => [
|
|
1020
|
+
{
|
|
1021
|
+
id: 371750686228480,
|
|
1022
|
+
schemaUid: 'group-schema',
|
|
1023
|
+
title: '789',
|
|
1024
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1025
|
+
},
|
|
1026
|
+
]);
|
|
1027
|
+
engine.context.defineProperty('routeRepository', {
|
|
881
1028
|
value: {
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1029
|
+
refreshAccessible: hookState.refresh,
|
|
1030
|
+
isAccessibleLoaded: () => true,
|
|
1031
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1032
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1033
|
+
listAccessible,
|
|
885
1034
|
},
|
|
886
1035
|
});
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1036
|
+
engine.context.defineProperty('app', {
|
|
1037
|
+
value: {
|
|
1038
|
+
getPublicPath: () => '/v2/',
|
|
1039
|
+
router: {
|
|
1040
|
+
getBasename: () => '/v2',
|
|
1041
|
+
},
|
|
1042
|
+
},
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1046
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1047
|
+
{
|
|
1048
|
+
registerRoutePage: vi.fn(),
|
|
1049
|
+
updateRoutePage: vi.fn(),
|
|
1050
|
+
unregisterRoutePage: vi.fn(),
|
|
1051
|
+
},
|
|
1052
|
+
);
|
|
1053
|
+
|
|
1054
|
+
const { container } = render(
|
|
1055
|
+
<FlowEngineProvider engine={engine}>
|
|
1056
|
+
<MemoryRouter initialEntries={['/admin/371750686228480']}>
|
|
1057
|
+
<Routes>
|
|
1058
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1059
|
+
</Routes>
|
|
1060
|
+
</MemoryRouter>
|
|
1061
|
+
</FlowEngineProvider>,
|
|
1062
|
+
);
|
|
1063
|
+
|
|
1064
|
+
await waitFor(() => {
|
|
1065
|
+
expect(listAccessible).toHaveBeenCalled();
|
|
1066
|
+
});
|
|
1067
|
+
expect(container).toBeEmptyDOMElement();
|
|
1068
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
1069
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
it('should navigate a current admin group route to its first accessible flow page', async () => {
|
|
1073
|
+
const engine = new FlowEngine();
|
|
1074
|
+
const childRoute = {
|
|
1075
|
+
schemaUid: 'child-flow-page',
|
|
1076
|
+
title: 'Child flow page',
|
|
1077
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1078
|
+
};
|
|
1079
|
+
const groupRoute = {
|
|
1080
|
+
id: 371750686228480,
|
|
1081
|
+
schemaUid: 'group-schema',
|
|
1082
|
+
title: 'Group with child',
|
|
1083
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1084
|
+
children: [childRoute],
|
|
1085
|
+
};
|
|
1086
|
+
const getRouteBySchemaUid = vi.fn((schemaUid: string) =>
|
|
1087
|
+
schemaUid === 'child-flow-page' ? childRoute : undefined,
|
|
1088
|
+
);
|
|
1089
|
+
const listAccessible = vi.fn(() => [groupRoute]);
|
|
1090
|
+
engine.context.defineProperty('routeRepository', {
|
|
1091
|
+
value: {
|
|
1092
|
+
refreshAccessible: hookState.refresh,
|
|
1093
|
+
isAccessibleLoaded: () => true,
|
|
1094
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1095
|
+
getRouteBySchemaUid,
|
|
1096
|
+
listAccessible,
|
|
1097
|
+
},
|
|
1098
|
+
});
|
|
1099
|
+
engine.context.defineProperty('app', {
|
|
1100
|
+
value: {
|
|
1101
|
+
getPublicPath: () => '/v2/',
|
|
1102
|
+
router: {
|
|
1103
|
+
getBasename: () => '/v2',
|
|
1104
|
+
},
|
|
1105
|
+
},
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1109
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1110
|
+
{
|
|
1111
|
+
registerRoutePage: vi.fn(),
|
|
1112
|
+
updateRoutePage: vi.fn(),
|
|
1113
|
+
unregisterRoutePage: vi.fn(),
|
|
1114
|
+
},
|
|
1115
|
+
);
|
|
1116
|
+
|
|
1117
|
+
render(
|
|
1118
|
+
<FlowEngineProvider engine={engine}>
|
|
1119
|
+
<MemoryRouter initialEntries={['/admin/371750686228480']}>
|
|
1120
|
+
<Routes>
|
|
1121
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1122
|
+
</Routes>
|
|
1123
|
+
</MemoryRouter>
|
|
1124
|
+
</FlowEngineProvider>,
|
|
1125
|
+
);
|
|
1126
|
+
|
|
1127
|
+
await waitFor(() => {
|
|
1128
|
+
expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith('child-flow-page', expect.any(Object));
|
|
1129
|
+
});
|
|
1130
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('371750686228480', expect.any(Object));
|
|
1131
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
it('should keep rendering 404 when a group id is opened with a nested admin route path', async () => {
|
|
1135
|
+
const engine = new FlowEngine();
|
|
1136
|
+
const childRoute = {
|
|
1137
|
+
schemaUid: 'child-flow-page',
|
|
1138
|
+
title: 'Child flow page',
|
|
1139
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1140
|
+
};
|
|
1141
|
+
const groupRoute = {
|
|
1142
|
+
id: 371750686228480,
|
|
1143
|
+
schemaUid: 'group-schema',
|
|
1144
|
+
title: 'Group with child',
|
|
1145
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1146
|
+
children: [childRoute],
|
|
1147
|
+
};
|
|
1148
|
+
engine.context.defineProperty('routeRepository', {
|
|
1149
|
+
value: {
|
|
1150
|
+
refreshAccessible: hookState.refresh,
|
|
1151
|
+
isAccessibleLoaded: () => true,
|
|
1152
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1153
|
+
getRouteBySchemaUid: vi.fn((schemaUid: string) => (schemaUid === 'child-flow-page' ? childRoute : undefined)),
|
|
1154
|
+
listAccessible: vi.fn(() => [groupRoute]),
|
|
1155
|
+
},
|
|
1156
|
+
});
|
|
1157
|
+
engine.context.defineProperty('app', {
|
|
1158
|
+
value: {
|
|
1159
|
+
getPublicPath: () => '/v2/',
|
|
1160
|
+
router: {
|
|
1161
|
+
getBasename: () => '/v2',
|
|
1162
|
+
},
|
|
1163
|
+
},
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1167
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1168
|
+
{
|
|
1169
|
+
registerRoutePage: vi.fn(),
|
|
1170
|
+
updateRoutePage: vi.fn(),
|
|
1171
|
+
unregisterRoutePage: vi.fn(),
|
|
1172
|
+
},
|
|
1173
|
+
);
|
|
1174
|
+
|
|
1175
|
+
render(
|
|
1176
|
+
<FlowEngineProvider engine={engine}>
|
|
1177
|
+
<MemoryRouter initialEntries={['/admin/371750686228480/view/not-exists']}>
|
|
1178
|
+
<Routes>
|
|
1179
|
+
<Route path="/admin/:name/view/*" element={<FlowRoute />} />
|
|
1180
|
+
</Routes>
|
|
1181
|
+
</MemoryRouter>
|
|
1182
|
+
</FlowEngineProvider>,
|
|
1183
|
+
);
|
|
1184
|
+
|
|
1185
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1186
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('child-flow-page', expect.any(Object));
|
|
1187
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('371750686228480', expect.any(Object));
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
it('should keep rendering 404 when a group id is opened with a tab admin route path', async () => {
|
|
1191
|
+
const engine = new FlowEngine();
|
|
1192
|
+
const childRoute = {
|
|
1193
|
+
schemaUid: 'child-flow-page',
|
|
1194
|
+
title: 'Child flow page',
|
|
1195
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1196
|
+
};
|
|
1197
|
+
const groupRoute = {
|
|
1198
|
+
id: 371750686228480,
|
|
1199
|
+
schemaUid: 'group-schema',
|
|
1200
|
+
title: 'Group with child',
|
|
1201
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1202
|
+
children: [childRoute],
|
|
1203
|
+
};
|
|
1204
|
+
engine.context.defineProperty('routeRepository', {
|
|
1205
|
+
value: {
|
|
1206
|
+
refreshAccessible: hookState.refresh,
|
|
1207
|
+
isAccessibleLoaded: () => true,
|
|
1208
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1209
|
+
getRouteBySchemaUid: vi.fn((schemaUid: string) => (schemaUid === 'child-flow-page' ? childRoute : undefined)),
|
|
1210
|
+
listAccessible: vi.fn(() => [groupRoute]),
|
|
1211
|
+
},
|
|
1212
|
+
});
|
|
1213
|
+
engine.context.defineProperty('app', {
|
|
1214
|
+
value: {
|
|
1215
|
+
getPublicPath: () => '/v2/',
|
|
1216
|
+
router: {
|
|
1217
|
+
getBasename: () => '/v2',
|
|
1218
|
+
},
|
|
1219
|
+
},
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1223
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1224
|
+
{
|
|
1225
|
+
registerRoutePage: vi.fn(),
|
|
1226
|
+
updateRoutePage: vi.fn(),
|
|
1227
|
+
unregisterRoutePage: vi.fn(),
|
|
1228
|
+
},
|
|
1229
|
+
);
|
|
1230
|
+
|
|
1231
|
+
render(
|
|
1232
|
+
<FlowEngineProvider engine={engine}>
|
|
1233
|
+
<MemoryRouter initialEntries={['/admin/371750686228480/tab/not-exists']}>
|
|
1234
|
+
<Routes>
|
|
1235
|
+
<Route path="/admin/:name/tab/:tabUid" element={<FlowRoute />} />
|
|
1236
|
+
</Routes>
|
|
1237
|
+
</MemoryRouter>
|
|
1238
|
+
</FlowEngineProvider>,
|
|
1239
|
+
);
|
|
1240
|
+
|
|
1241
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1242
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('child-flow-page', expect.any(Object));
|
|
1243
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('371750686228480', expect.any(Object));
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
it('should keep blank content while a group child navigation is already pending for the current path', async () => {
|
|
1247
|
+
const engine = new FlowEngine();
|
|
1248
|
+
const childRoute = {
|
|
1249
|
+
schemaUid: 'child-flow-page',
|
|
1250
|
+
title: 'Child flow page',
|
|
1251
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1252
|
+
};
|
|
1253
|
+
const groupRoute = {
|
|
1254
|
+
id: 371750686228480,
|
|
1255
|
+
schemaUid: 'group-schema',
|
|
1256
|
+
title: 'Group with child',
|
|
1257
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1258
|
+
children: [childRoute],
|
|
1259
|
+
};
|
|
1260
|
+
engine.context.defineProperty('routeRepository', {
|
|
1261
|
+
value: {
|
|
1262
|
+
refreshAccessible: hookState.refresh,
|
|
1263
|
+
isAccessibleLoaded: () => true,
|
|
1264
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1265
|
+
getRouteBySchemaUid: vi.fn((schemaUid: string) => (schemaUid === 'child-flow-page' ? childRoute : undefined)),
|
|
1266
|
+
listAccessible: vi.fn(() => [groupRoute]),
|
|
1267
|
+
},
|
|
1268
|
+
});
|
|
1269
|
+
engine.context.defineProperty('app', {
|
|
1270
|
+
value: {
|
|
1271
|
+
getPublicPath: () => '/v2/',
|
|
1272
|
+
router: {
|
|
1273
|
+
getBasename: () => '/v2',
|
|
1274
|
+
},
|
|
1275
|
+
},
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
const replace = vi.fn();
|
|
1279
|
+
const navigator: RouterNavigator = {
|
|
1280
|
+
createHref: (to: { pathname?: string; search?: string; hash?: string } | string) =>
|
|
1281
|
+
typeof to === 'string' ? to : `${to.pathname || ''}${to.search || ''}${to.hash || ''}`,
|
|
1282
|
+
go: vi.fn(),
|
|
1283
|
+
push: vi.fn(),
|
|
1284
|
+
replace,
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
const RerenderAfterReplace = () => {
|
|
1288
|
+
const [rerendered, setRerendered] = React.useState(false);
|
|
1289
|
+
React.useEffect(() => {
|
|
1290
|
+
if (!rerendered && replace.mock.calls.length > 0) {
|
|
1291
|
+
setRerendered(true);
|
|
1292
|
+
}
|
|
1293
|
+
}, [rerendered]);
|
|
1294
|
+
|
|
1295
|
+
return <FlowRoute legacyPageBehavior={rerendered ? 'bridge' : undefined} />;
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
render(
|
|
1299
|
+
<FlowEngineProvider engine={engine}>
|
|
1300
|
+
<Router location="/admin/371750686228480" navigator={navigator}>
|
|
1301
|
+
<Routes>
|
|
1302
|
+
<Route path="/admin/:name" element={<RerenderAfterReplace />} />
|
|
1303
|
+
</Routes>
|
|
1304
|
+
</Router>
|
|
1305
|
+
</FlowEngineProvider>,
|
|
1306
|
+
);
|
|
1307
|
+
|
|
1308
|
+
await waitFor(() => {
|
|
1309
|
+
expect(replace).toHaveBeenCalled();
|
|
1310
|
+
});
|
|
1311
|
+
await act(async () => {
|
|
1312
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1313
|
+
});
|
|
1314
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
it('should reset group navigation guard when page uid changes in the same route instance', async () => {
|
|
1318
|
+
const engine = new FlowEngine();
|
|
1319
|
+
const childRoute1 = {
|
|
1320
|
+
schemaUid: 'child-flow-page-1',
|
|
1321
|
+
title: 'Child flow page 1',
|
|
1322
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1323
|
+
};
|
|
1324
|
+
const childRoute2 = {
|
|
1325
|
+
schemaUid: 'child-flow-page-2',
|
|
1326
|
+
title: 'Child flow page 2',
|
|
1327
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1328
|
+
};
|
|
1329
|
+
const groupRoute1 = {
|
|
1330
|
+
id: 371750686228480,
|
|
1331
|
+
schemaUid: 'group-schema-1',
|
|
1332
|
+
title: 'Group with child 1',
|
|
1333
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1334
|
+
children: [childRoute1],
|
|
1335
|
+
};
|
|
1336
|
+
const groupRoute2 = {
|
|
1337
|
+
id: 371750755434496,
|
|
1338
|
+
schemaUid: 'group-schema-2',
|
|
1339
|
+
title: 'Group with child 2',
|
|
1340
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1341
|
+
children: [childRoute2],
|
|
1342
|
+
};
|
|
1343
|
+
const routeMap: Record<string, typeof childRoute1 | typeof childRoute2> = {
|
|
1344
|
+
'child-flow-page-1': childRoute1,
|
|
1345
|
+
'child-flow-page-2': childRoute2,
|
|
1346
|
+
};
|
|
1347
|
+
const getRouteBySchemaUid = vi.fn((schemaUid: string) => routeMap[schemaUid]);
|
|
1348
|
+
const listAccessible = vi.fn(() => [groupRoute1, groupRoute2]);
|
|
1349
|
+
engine.context.defineProperty('routeRepository', {
|
|
1350
|
+
value: {
|
|
1351
|
+
refreshAccessible: hookState.refresh,
|
|
1352
|
+
isAccessibleLoaded: () => true,
|
|
1353
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1354
|
+
getRouteBySchemaUid,
|
|
1355
|
+
listAccessible,
|
|
1356
|
+
},
|
|
1357
|
+
});
|
|
1358
|
+
engine.context.defineProperty('app', {
|
|
1359
|
+
value: {
|
|
1360
|
+
getPublicPath: () => '/v2/',
|
|
1361
|
+
router: {
|
|
1362
|
+
getBasename: () => '/v2',
|
|
1363
|
+
},
|
|
1364
|
+
},
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1368
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1369
|
+
{
|
|
1370
|
+
registerRoutePage: vi.fn(),
|
|
1371
|
+
updateRoutePage: vi.fn(),
|
|
1372
|
+
unregisterRoutePage: vi.fn(),
|
|
1373
|
+
},
|
|
1374
|
+
);
|
|
1375
|
+
let navigateTo: (path: string) => void = () => {};
|
|
1376
|
+
const CaptureNavigate = () => {
|
|
1377
|
+
const navigate = useNavigate();
|
|
1378
|
+
React.useEffect(() => {
|
|
1379
|
+
navigateTo = navigate;
|
|
1380
|
+
}, [navigate]);
|
|
1381
|
+
return null;
|
|
1382
|
+
};
|
|
1383
|
+
|
|
1384
|
+
render(
|
|
1385
|
+
<FlowEngineProvider engine={engine}>
|
|
1386
|
+
<MemoryRouter initialEntries={['/admin/371750686228480']}>
|
|
1387
|
+
<CaptureNavigate />
|
|
1388
|
+
<Routes>
|
|
1389
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1390
|
+
</Routes>
|
|
1391
|
+
</MemoryRouter>
|
|
1392
|
+
</FlowEngineProvider>,
|
|
1393
|
+
);
|
|
1394
|
+
|
|
1395
|
+
await waitFor(() => {
|
|
1396
|
+
expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith('child-flow-page-1', expect.any(Object));
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
act(() => {
|
|
1400
|
+
navigateTo('/admin/371750755434496');
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
await waitFor(() => {
|
|
1404
|
+
expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith('child-flow-page-2', expect.any(Object));
|
|
1405
|
+
});
|
|
1406
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalledWith('371750755434496', expect.any(Object));
|
|
1407
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1410
|
+
it('should keep rendering 404 when current admin route matches no accessible schema uid or id', async () => {
|
|
1411
|
+
const engine = new FlowEngine();
|
|
1412
|
+
engine.context.defineProperty('routeRepository', {
|
|
1413
|
+
value: {
|
|
1414
|
+
refreshAccessible: hookState.refresh,
|
|
1415
|
+
isAccessibleLoaded: () => true,
|
|
1416
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1417
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1418
|
+
listAccessible: vi.fn(() => [
|
|
1419
|
+
{
|
|
1420
|
+
id: 371750686228480,
|
|
1421
|
+
schemaUid: 'group-schema',
|
|
1422
|
+
title: '789',
|
|
1423
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1424
|
+
},
|
|
1425
|
+
]),
|
|
1426
|
+
},
|
|
1427
|
+
});
|
|
1428
|
+
engine.context.defineProperty('app', {
|
|
1429
|
+
value: {
|
|
1430
|
+
getPublicPath: () => '/v2/',
|
|
1431
|
+
router: {
|
|
1432
|
+
getBasename: () => '/v2',
|
|
1433
|
+
},
|
|
1434
|
+
},
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1437
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1438
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1439
|
+
{
|
|
1440
|
+
registerRoutePage: vi.fn(),
|
|
1441
|
+
updateRoutePage: vi.fn(),
|
|
1442
|
+
unregisterRoutePage: vi.fn(),
|
|
1443
|
+
},
|
|
1444
|
+
);
|
|
1445
|
+
|
|
1446
|
+
render(
|
|
1447
|
+
<FlowEngineProvider engine={engine}>
|
|
1448
|
+
<MemoryRouter initialEntries={['/admin/not-exists-4822']}>
|
|
1449
|
+
<Routes>
|
|
1450
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1451
|
+
</Routes>
|
|
1452
|
+
</MemoryRouter>
|
|
1453
|
+
</FlowEngineProvider>,
|
|
1454
|
+
);
|
|
1455
|
+
|
|
1456
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1457
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1458
|
+
});
|
|
1459
|
+
|
|
1460
|
+
it('should keep rendering 404 when current admin route matches only a non-group route id', async () => {
|
|
1461
|
+
const engine = new FlowEngine();
|
|
1462
|
+
engine.context.defineProperty('routeRepository', {
|
|
1463
|
+
value: {
|
|
1464
|
+
refreshAccessible: hookState.refresh,
|
|
1465
|
+
isAccessibleLoaded: () => true,
|
|
1466
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1467
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1468
|
+
listAccessible: vi.fn(() => [
|
|
1469
|
+
{
|
|
1470
|
+
id: 10001,
|
|
1471
|
+
schemaUid: 'flow-page-schema',
|
|
1472
|
+
title: 'Flow page',
|
|
1473
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1474
|
+
},
|
|
1475
|
+
]),
|
|
1476
|
+
},
|
|
1477
|
+
});
|
|
1478
|
+
engine.context.defineProperty('app', {
|
|
1479
|
+
value: {
|
|
1480
|
+
getPublicPath: () => '/v2/',
|
|
1481
|
+
router: {
|
|
1482
|
+
getBasename: () => '/v2',
|
|
1483
|
+
},
|
|
1484
|
+
},
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1488
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1489
|
+
{
|
|
1490
|
+
registerRoutePage: vi.fn(),
|
|
1491
|
+
updateRoutePage: vi.fn(),
|
|
1492
|
+
unregisterRoutePage: vi.fn(),
|
|
1493
|
+
},
|
|
1494
|
+
);
|
|
1495
|
+
|
|
1496
|
+
render(
|
|
1497
|
+
<FlowEngineProvider engine={engine}>
|
|
1498
|
+
<MemoryRouter initialEntries={['/admin/10001']}>
|
|
1499
|
+
<Routes>
|
|
1500
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1501
|
+
</Routes>
|
|
1502
|
+
</MemoryRouter>
|
|
1503
|
+
</FlowEngineProvider>,
|
|
1504
|
+
);
|
|
1505
|
+
|
|
1506
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1507
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1508
|
+
});
|
|
1509
|
+
|
|
1510
|
+
it('should keep rendering 404 when current admin route matches only a group schema uid', async () => {
|
|
1511
|
+
const engine = new FlowEngine();
|
|
1512
|
+
const groupRoute = {
|
|
1513
|
+
id: 371750686228480,
|
|
1514
|
+
schemaUid: 'group-schema',
|
|
1515
|
+
title: '789',
|
|
1516
|
+
type: NocoBaseDesktopRouteType.group,
|
|
1517
|
+
};
|
|
1518
|
+
engine.context.defineProperty('routeRepository', {
|
|
1519
|
+
value: {
|
|
1520
|
+
refreshAccessible: hookState.refresh,
|
|
1521
|
+
isAccessibleLoaded: () => true,
|
|
1522
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1523
|
+
getRouteBySchemaUid: vi.fn((schemaUid: string) => (schemaUid === 'group-schema' ? groupRoute : undefined)),
|
|
1524
|
+
listAccessible: vi.fn(() => [groupRoute]),
|
|
1525
|
+
},
|
|
1526
|
+
});
|
|
1527
|
+
engine.context.defineProperty('app', {
|
|
1528
|
+
value: {
|
|
1529
|
+
getPublicPath: () => '/v2/',
|
|
1530
|
+
router: {
|
|
1531
|
+
getBasename: () => '/v2',
|
|
1532
|
+
},
|
|
1533
|
+
},
|
|
1534
|
+
});
|
|
1535
|
+
|
|
1536
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1537
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1538
|
+
{
|
|
1539
|
+
registerRoutePage: vi.fn(),
|
|
1540
|
+
updateRoutePage: vi.fn(),
|
|
1541
|
+
unregisterRoutePage: vi.fn(),
|
|
1542
|
+
},
|
|
1543
|
+
);
|
|
1544
|
+
|
|
1545
|
+
render(
|
|
1546
|
+
<FlowEngineProvider engine={engine}>
|
|
1547
|
+
<MemoryRouter initialEntries={['/admin/group-schema']}>
|
|
1548
|
+
<Routes>
|
|
1549
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
1550
|
+
</Routes>
|
|
1551
|
+
</MemoryRouter>
|
|
1552
|
+
</FlowEngineProvider>,
|
|
1553
|
+
);
|
|
1554
|
+
|
|
1555
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1556
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
it('should not use flowModels:findOne to bridge a missing route for protected layouts', async () => {
|
|
1560
|
+
const engine = new FlowEngine();
|
|
1561
|
+
const findOne = vi.fn().mockResolvedValue({
|
|
1562
|
+
uid: 'mobile-denied-page',
|
|
1563
|
+
use: 'FlowModel',
|
|
1564
|
+
});
|
|
1565
|
+
engine.setModelRepository({
|
|
1566
|
+
findOne,
|
|
1567
|
+
save: vi.fn(),
|
|
1568
|
+
destroy: vi.fn(),
|
|
1569
|
+
} as any);
|
|
1570
|
+
engine.context.defineProperty('routeRepository', {
|
|
1571
|
+
value: {
|
|
1572
|
+
refreshAccessible: hookState.refresh,
|
|
1573
|
+
isAccessibleLoaded: () => true,
|
|
1574
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1575
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1576
|
+
},
|
|
1577
|
+
});
|
|
1578
|
+
engine.context.defineProperty('app', {
|
|
1579
|
+
value: {
|
|
1580
|
+
getPublicPath: () => '/v2/',
|
|
1581
|
+
router: {
|
|
1582
|
+
getBasename: () => '/v2',
|
|
1583
|
+
},
|
|
1584
|
+
},
|
|
1585
|
+
});
|
|
1586
|
+
const routeModel = engine.createModel({
|
|
1587
|
+
uid: 'mobile-route-model',
|
|
1588
|
+
use: 'FlowModel',
|
|
1589
|
+
});
|
|
1590
|
+
routeModel.context.defineProperty('layout', {
|
|
1591
|
+
value: {
|
|
1592
|
+
routeName: 'mobile',
|
|
1593
|
+
routePath: '/mobile',
|
|
1594
|
+
rootRouteName: 'mobile',
|
|
1595
|
+
uid: 'mobile-layout-model',
|
|
1596
|
+
layoutModelClass: 'MobileLayoutModel',
|
|
1597
|
+
rootPageModelClass: 'MobileRootPageModel',
|
|
1598
|
+
childPageModelClass: 'MobileChildPageModel',
|
|
1599
|
+
authCheck: true,
|
|
1600
|
+
},
|
|
1601
|
+
});
|
|
1602
|
+
|
|
1603
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
1604
|
+
engine.createModel({ uid: 'mobile-layout-model', use: 'FlowModel' }),
|
|
1605
|
+
{
|
|
1606
|
+
registerRoutePage: vi.fn(),
|
|
1607
|
+
updateRoutePage: vi.fn(),
|
|
1608
|
+
unregisterRoutePage: vi.fn(),
|
|
1609
|
+
},
|
|
1610
|
+
);
|
|
1611
|
+
|
|
1612
|
+
render(
|
|
1613
|
+
<FlowEngineProvider engine={engine}>
|
|
1614
|
+
<FlowContextProvider context={routeModel.context}>
|
|
1615
|
+
<MemoryRouter initialEntries={['/mobile/mobile-denied-page']}>
|
|
1616
|
+
<Routes>
|
|
1617
|
+
<Route
|
|
1618
|
+
path="/mobile/:name"
|
|
1619
|
+
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel} />}
|
|
1620
|
+
/>
|
|
1621
|
+
</Routes>
|
|
1622
|
+
</MemoryRouter>
|
|
1623
|
+
</FlowContextProvider>
|
|
1624
|
+
</FlowEngineProvider>,
|
|
1625
|
+
);
|
|
1626
|
+
|
|
1627
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1628
|
+
expect(findOne).not.toHaveBeenCalled();
|
|
1629
|
+
expect(layoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1630
|
+
});
|
|
1631
|
+
|
|
1632
|
+
it('should not load a known page uid when a protected custom layout has no accessible route', async () => {
|
|
1633
|
+
const engine = new FlowEngine();
|
|
1634
|
+
const findOne = vi.fn().mockResolvedValue({
|
|
1635
|
+
uid: 'known-denied-custom-page',
|
|
1636
|
+
use: 'FlowModel',
|
|
1637
|
+
});
|
|
1638
|
+
engine.setModelRepository({
|
|
1639
|
+
findOne,
|
|
1640
|
+
save: vi.fn(),
|
|
1641
|
+
destroy: vi.fn(),
|
|
1642
|
+
} as any);
|
|
1643
|
+
engine.context.defineProperty('routeRepository', {
|
|
1644
|
+
value: {
|
|
1645
|
+
refreshAccessible: hookState.refresh,
|
|
1646
|
+
isAccessibleLoaded: () => true,
|
|
1647
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1648
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1649
|
+
},
|
|
1650
|
+
});
|
|
1651
|
+
engine.context.defineProperty('app', {
|
|
1652
|
+
value: {
|
|
1653
|
+
getPublicPath: () => '/v2/',
|
|
1654
|
+
router: {
|
|
1655
|
+
getBasename: () => '/v2',
|
|
1656
|
+
},
|
|
1657
|
+
},
|
|
1658
|
+
});
|
|
1659
|
+
const routeModel = engine.createModel({
|
|
1660
|
+
uid: 'custom-layout-route-model',
|
|
1661
|
+
use: 'FlowModel',
|
|
1662
|
+
});
|
|
1663
|
+
routeModel.context.defineProperty('layout', {
|
|
1664
|
+
value: {
|
|
1665
|
+
routeName: 'admin2',
|
|
1666
|
+
routePath: '/admin2',
|
|
1667
|
+
rootRouteName: 'admin2',
|
|
1668
|
+
uid: 'custom-protected-layout-model',
|
|
1669
|
+
layoutModelClass: 'AdminLayoutModel',
|
|
1670
|
+
rootPageModelClass: 'RootPageModel',
|
|
1671
|
+
childPageModelClass: 'ChildPageModel',
|
|
1672
|
+
authCheck: true,
|
|
1673
|
+
},
|
|
1674
|
+
});
|
|
1675
|
+
|
|
1676
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
1677
|
+
engine.createModel({ uid: 'custom-protected-layout-model', use: 'FlowModel' }),
|
|
1678
|
+
{
|
|
1679
|
+
registerRoutePage: vi.fn(),
|
|
1680
|
+
updateRoutePage: vi.fn(),
|
|
1681
|
+
unregisterRoutePage: vi.fn(),
|
|
1682
|
+
},
|
|
1683
|
+
);
|
|
1684
|
+
|
|
1685
|
+
render(
|
|
1686
|
+
<FlowEngineProvider engine={engine}>
|
|
1687
|
+
<FlowContextProvider context={routeModel.context}>
|
|
1688
|
+
<MemoryRouter initialEntries={['/admin2/known-denied-custom-page']}>
|
|
1689
|
+
<Routes>
|
|
1690
|
+
<Route
|
|
1691
|
+
path="/admin2/:name"
|
|
1692
|
+
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel} />}
|
|
1693
|
+
/>
|
|
1694
|
+
</Routes>
|
|
1695
|
+
</MemoryRouter>
|
|
1696
|
+
</FlowContextProvider>
|
|
1697
|
+
</FlowEngineProvider>,
|
|
1698
|
+
);
|
|
1699
|
+
|
|
1700
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1701
|
+
expect(findOne).not.toHaveBeenCalled();
|
|
1702
|
+
expect(layoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1703
|
+
});
|
|
1704
|
+
|
|
1705
|
+
it('should check model existence without occupying the route model uid', async () => {
|
|
1706
|
+
const engine = new FlowEngine();
|
|
1707
|
+
const findOne = vi.fn().mockResolvedValue({
|
|
1708
|
+
uid: 'public-form-1',
|
|
1709
|
+
});
|
|
1710
|
+
const request = vi.fn().mockResolvedValue({
|
|
1711
|
+
data: {
|
|
1712
|
+
data: {
|
|
1713
|
+
uid: 'public-form-1',
|
|
1714
|
+
},
|
|
1715
|
+
},
|
|
1716
|
+
});
|
|
1717
|
+
engine.setModelRepository({
|
|
1718
|
+
findOne,
|
|
1719
|
+
save: vi.fn(),
|
|
1720
|
+
destroy: vi.fn(),
|
|
1721
|
+
} as any);
|
|
1722
|
+
engine.context.defineProperty('routeRepository', {
|
|
1723
|
+
value: {
|
|
1724
|
+
refreshAccessible: hookState.refresh,
|
|
1725
|
+
isAccessibleLoaded: () => true,
|
|
1726
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1727
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1728
|
+
},
|
|
1729
|
+
});
|
|
1730
|
+
engine.context.defineProperty('app', {
|
|
1731
|
+
value: {
|
|
1732
|
+
apiClient: {
|
|
1733
|
+
request,
|
|
1734
|
+
},
|
|
1735
|
+
getPublicPath: () => '/v2/',
|
|
1736
|
+
router: {
|
|
1737
|
+
getBasename: () => '/v2',
|
|
1738
|
+
},
|
|
1739
|
+
},
|
|
1740
|
+
});
|
|
1741
|
+
const routeModel = engine.createModel({
|
|
1742
|
+
uid: 'public-form-route-model',
|
|
1743
|
+
use: 'FlowModel',
|
|
1744
|
+
});
|
|
1745
|
+
routeModel.context.defineProperty('layout', {
|
|
1746
|
+
value: {
|
|
1747
|
+
routeName: 'public-forms',
|
|
1748
|
+
routePath: '/public-forms',
|
|
1749
|
+
rootRouteName: 'public-forms',
|
|
1750
|
+
uid: 'public-form-layout-model',
|
|
1751
|
+
layoutModelClass: 'PublicFormLayoutModel',
|
|
1752
|
+
rootPageModelClass: 'PublicFormPageModel',
|
|
1753
|
+
childPageModelClass: 'ChildPageModel',
|
|
1754
|
+
authCheck: false,
|
|
1755
|
+
},
|
|
1756
|
+
});
|
|
1757
|
+
|
|
1758
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
1759
|
+
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
1760
|
+
{
|
|
1761
|
+
registerRoutePage: vi.fn(),
|
|
1762
|
+
updateRoutePage: vi.fn(),
|
|
1763
|
+
unregisterRoutePage: vi.fn(),
|
|
1764
|
+
},
|
|
1765
|
+
);
|
|
1766
|
+
|
|
1767
|
+
render(
|
|
1768
|
+
<FlowEngineProvider engine={engine}>
|
|
1769
|
+
<FlowContextProvider context={routeModel.context}>
|
|
1770
|
+
<MemoryRouter initialEntries={['/public-forms/public-form-1']}>
|
|
1771
|
+
<Routes>
|
|
1772
|
+
<Route path="/public-forms/:name" element={<FlowRoute legacyPageBehavior="notFound" />} />
|
|
1773
|
+
</Routes>
|
|
1774
|
+
</MemoryRouter>
|
|
1775
|
+
</FlowContextProvider>
|
|
1776
|
+
</FlowEngineProvider>,
|
|
1777
|
+
);
|
|
1778
|
+
|
|
1779
|
+
await waitFor(() => {
|
|
1780
|
+
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('public-form-1', expect.any(Object));
|
|
1781
|
+
});
|
|
1782
|
+
expect(findOne).toHaveBeenCalledWith({ uid: 'public-form-1' });
|
|
1783
|
+
expect(request).not.toHaveBeenCalled();
|
|
1784
|
+
expect(engine.getModel('public-form-1')).toBeUndefined();
|
|
1785
|
+
});
|
|
1786
|
+
|
|
1787
|
+
it('should skip accessible route loading when layout authCheck is false', async () => {
|
|
1788
|
+
const engine = new FlowEngine();
|
|
1789
|
+
const ensureAccessibleLoaded = vi.fn().mockRejectedValue(new Error('cannot load accessible routes'));
|
|
1790
|
+
const getRouteBySchemaUid = vi.fn();
|
|
1791
|
+
engine.setModelRepository({
|
|
1792
|
+
findOne: vi.fn().mockResolvedValue({
|
|
1793
|
+
uid: 'public-form-1',
|
|
1794
|
+
use: 'FlowModel',
|
|
1795
|
+
}),
|
|
1796
|
+
save: vi.fn(),
|
|
1797
|
+
destroy: vi.fn(),
|
|
1798
|
+
} as any);
|
|
1799
|
+
engine.context.defineProperty('routeRepository', {
|
|
1800
|
+
value: {
|
|
1801
|
+
refreshAccessible: hookState.refresh,
|
|
1802
|
+
isAccessibleLoaded: () => false,
|
|
1803
|
+
ensureAccessibleLoaded,
|
|
1804
|
+
getRouteBySchemaUid,
|
|
1805
|
+
},
|
|
1806
|
+
});
|
|
1807
|
+
engine.context.defineProperty('app', {
|
|
1808
|
+
value: {
|
|
1809
|
+
getPublicPath: () => '/v2/',
|
|
1810
|
+
router: {
|
|
1811
|
+
getBasename: () => '/v2',
|
|
1812
|
+
},
|
|
1813
|
+
},
|
|
1814
|
+
});
|
|
1815
|
+
const routeModel = engine.createModel({
|
|
1816
|
+
uid: 'public-form-route-model',
|
|
1817
|
+
use: 'FlowModel',
|
|
1818
|
+
});
|
|
1819
|
+
routeModel.context.defineProperty('layout', {
|
|
1820
|
+
value: {
|
|
1821
|
+
routeName: 'public-forms',
|
|
1822
|
+
routePath: '/public-forms',
|
|
1823
|
+
rootRouteName: 'public-forms',
|
|
1824
|
+
uid: 'public-form-layout-model',
|
|
1825
|
+
layoutModelClass: 'PublicFormLayoutModel',
|
|
1826
|
+
rootPageModelClass: 'PublicFormPageModel',
|
|
1827
|
+
childPageModelClass: 'ChildPageModel',
|
|
1828
|
+
authCheck: false,
|
|
1829
|
+
},
|
|
1830
|
+
});
|
|
1831
|
+
|
|
1832
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
1833
|
+
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
1834
|
+
{
|
|
1835
|
+
registerRoutePage: vi.fn(),
|
|
1836
|
+
updateRoutePage: vi.fn(),
|
|
1837
|
+
unregisterRoutePage: vi.fn(),
|
|
1838
|
+
},
|
|
1839
|
+
);
|
|
1840
|
+
|
|
1841
|
+
render(
|
|
1842
|
+
<FlowEngineProvider engine={engine}>
|
|
1843
|
+
<FlowContextProvider context={routeModel.context}>
|
|
1844
|
+
<MemoryRouter initialEntries={['/public-forms/public-form-1']}>
|
|
1845
|
+
<Routes>
|
|
1846
|
+
<Route path="/public-forms/:name" element={<FlowRoute legacyPageBehavior="notFound" />} />
|
|
1847
|
+
</Routes>
|
|
1848
|
+
</MemoryRouter>
|
|
1849
|
+
</FlowContextProvider>
|
|
1850
|
+
</FlowEngineProvider>,
|
|
1851
|
+
);
|
|
1852
|
+
|
|
1853
|
+
await waitFor(() => {
|
|
1854
|
+
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('public-form-1', expect.any(Object));
|
|
1855
|
+
});
|
|
1856
|
+
expect(ensureAccessibleLoaded).not.toHaveBeenCalled();
|
|
1857
|
+
expect(getRouteBySchemaUid).not.toHaveBeenCalled();
|
|
1858
|
+
});
|
|
1859
|
+
|
|
1860
|
+
it('should bridge legacy page when behavior is bridge', async () => {
|
|
1861
|
+
const originalLocation = window.location;
|
|
1862
|
+
const replace = vi.fn();
|
|
1863
|
+
Object.defineProperty(window, 'location', {
|
|
1864
|
+
configurable: true,
|
|
1865
|
+
value: {
|
|
1866
|
+
...originalLocation,
|
|
1867
|
+
pathname: '/v2/admin/test-page',
|
|
1868
|
+
replace,
|
|
1869
|
+
},
|
|
1870
|
+
});
|
|
1871
|
+
|
|
1872
|
+
try {
|
|
1873
|
+
const engine = new FlowEngine();
|
|
1874
|
+
engine.context.defineProperty('routeRepository', {
|
|
1875
|
+
value: {
|
|
1876
|
+
refreshAccessible: hookState.refresh,
|
|
893
1877
|
isAccessibleLoaded: () => true,
|
|
894
1878
|
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
895
1879
|
getRouteBySchemaUid: vi.fn(() => ({ type: 'page', schemaUid: 'test-page' })),
|
|
@@ -995,51 +1979,6 @@ describe('FlowRoute', () => {
|
|
|
995
1979
|
}
|
|
996
1980
|
});
|
|
997
1981
|
|
|
998
|
-
it('should bridge existing FlowModel when route metadata does not exist', async () => {
|
|
999
|
-
const engine = new FlowEngine();
|
|
1000
|
-
engine.createModel({ uid: 'test-page', use: 'FlowModel' });
|
|
1001
|
-
engine.context.defineProperty('routeRepository', {
|
|
1002
|
-
value: {
|
|
1003
|
-
refreshAccessible: hookState.refresh,
|
|
1004
|
-
isAccessibleLoaded: () => true,
|
|
1005
|
-
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
1006
|
-
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
1007
|
-
},
|
|
1008
|
-
});
|
|
1009
|
-
engine.context.defineProperty('app', {
|
|
1010
|
-
value: {
|
|
1011
|
-
getPublicPath: () => '/v2/',
|
|
1012
|
-
router: {
|
|
1013
|
-
getBasename: () => '/v2',
|
|
1014
|
-
},
|
|
1015
|
-
},
|
|
1016
|
-
});
|
|
1017
|
-
|
|
1018
|
-
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
1019
|
-
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
1020
|
-
{
|
|
1021
|
-
registerRoutePage: vi.fn(),
|
|
1022
|
-
updateRoutePage: vi.fn(),
|
|
1023
|
-
unregisterRoutePage: vi.fn(),
|
|
1024
|
-
},
|
|
1025
|
-
);
|
|
1026
|
-
|
|
1027
|
-
render(
|
|
1028
|
-
<FlowEngineProvider engine={engine}>
|
|
1029
|
-
<MemoryRouter initialEntries={['/flow/test-page']}>
|
|
1030
|
-
<Routes>
|
|
1031
|
-
<Route path="/flow/:name" element={<FlowRoute />} />
|
|
1032
|
-
</Routes>
|
|
1033
|
-
</MemoryRouter>
|
|
1034
|
-
</FlowEngineProvider>,
|
|
1035
|
-
);
|
|
1036
|
-
|
|
1037
|
-
await waitFor(() => {
|
|
1038
|
-
expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith('test-page', expect.any(Object));
|
|
1039
|
-
});
|
|
1040
|
-
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
1041
|
-
});
|
|
1042
|
-
|
|
1043
1982
|
it('should keep sub app page inside spa when basename does not include /v2/', async () => {
|
|
1044
1983
|
const originalLocation = window.location;
|
|
1045
1984
|
const replace = vi.fn();
|