@nocobase/client-v2 2.1.0-alpha.31 → 2.1.0-alpha.33
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/components/form/JsonTextArea.d.ts +18 -0
- package/es/components/index.d.ts +1 -0
- package/es/flow/actions/dateRangeLimit.d.ts +9 -0
- package/es/flow/actions/index.d.ts +2 -1
- package/es/flow/actions/linkageRules.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +4 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutModel.d.ts +7 -0
- package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +5 -0
- package/es/flow/models/base/PageModel/PageModel.d.ts +4 -0
- package/es/flow/models/base/PageModel/RootPageModel.d.ts +9 -0
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +7 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +5 -0
- package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +20 -0
- package/es/flow/models/fields/JSEditableFieldModel.d.ts +4 -0
- package/es/index.mjs +79 -67
- package/lib/index.js +80 -68
- package/package.json +6 -5
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +67 -46
- package/src/__tests__/settings-center.test.tsx +30 -0
- package/src/components/form/JsonTextArea.tsx +129 -0
- package/src/components/index.ts +1 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +4 -5
- package/src/flow/actions/__tests__/actionLinkageRules.race.repro.test.ts +199 -0
- package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +478 -0
- package/src/flow/actions/__tests__/linkageRules.formValueDrivenRefresh.test.ts +6 -1
- package/src/flow/actions/__tests__/linkageRules.menu.test.ts +90 -0
- package/src/flow/actions/__tests__/pattern.test.ts +190 -0
- package/src/flow/actions/dateRangeLimit.tsx +66 -0
- package/src/flow/actions/index.ts +3 -0
- package/src/flow/actions/linkageRules.tsx +194 -42
- package/src/flow/actions/linkageRulesFormValueRefresh.ts +2 -8
- package/src/flow/actions/openView.tsx +2 -1
- package/src/flow/actions/pattern.tsx +25 -2
- package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +7 -1
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +117 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +8 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +70 -12
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +26 -87
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +11 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +5 -1
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +292 -31
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +50 -12
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +77 -56
- package/src/flow/components/AdminLayout.tsx +2 -2
- package/src/flow/components/FlowRoute.tsx +17 -4
- package/src/flow/models/base/PageModel/PageModel.tsx +15 -3
- package/src/flow/models/base/PageModel/RootPageModel.tsx +37 -2
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +73 -0
- package/src/flow/models/base/PageModel/__tests__/RootPageModel.test.ts +116 -0
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +167 -1
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +103 -11
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +4 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +34 -3
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +47 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +42 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableField.refresh.test.tsx +122 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -1
- package/src/flow/models/fields/ClickableFieldModel.tsx +21 -9
- package/src/flow/models/fields/DateTimeFieldModel/DateOnlyFieldModel.tsx +9 -0
- package/src/flow/models/fields/DateTimeFieldModel/DateTimeFieldModel.tsx +4 -0
- package/src/flow/models/fields/DateTimeFieldModel/DateTimeNoTzFieldModel.tsx +9 -0
- package/src/flow/models/fields/DateTimeFieldModel/DateTimeTzFieldModel.tsx +9 -0
- package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +242 -0
- package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +152 -0
- package/src/flow/models/fields/JSEditableFieldModel.tsx +110 -14
- package/src/flow/models/fields/__tests__/ClickableFieldModel.test.ts +87 -0
- package/src/flow/models/fields/__tests__/JSEditableFieldModel.test.tsx +210 -0
- package/src/flow/system-settings/useSystemSettings.tsx +36 -1
|
@@ -14,6 +14,7 @@ import { Result, theme as antdTheme } from 'antd';
|
|
|
14
14
|
import React, { FC, useCallback, useMemo } from 'react';
|
|
15
15
|
import { useTranslation } from 'react-i18next';
|
|
16
16
|
import { Outlet, useLocation } from 'react-router-dom';
|
|
17
|
+
import { isV2AdminRuntime, isV2MenuRoute } from './resolveAdminRouteRuntimeTarget';
|
|
17
18
|
|
|
18
19
|
type AdminLayoutContentProps = {
|
|
19
20
|
onContentElementChange?: (element: HTMLDivElement | null) => void;
|
|
@@ -67,9 +68,12 @@ const ShowTipWhenNoPages = observer(() => {
|
|
|
67
68
|
const { t } = useTranslation();
|
|
68
69
|
const location = useLocation();
|
|
69
70
|
const allAccessRoutes = flowEngine.context.routeRepository?.listAccessible?.() || [];
|
|
71
|
+
const visibleRoutes = isV2AdminRuntime(flowEngine.context.app)
|
|
72
|
+
? allAccessRoutes.filter((route) => isV2MenuRoute(route))
|
|
73
|
+
: allAccessRoutes;
|
|
70
74
|
const designable = !!flowEngine.context.flowSettingsEnabled;
|
|
71
75
|
|
|
72
|
-
if (
|
|
76
|
+
if (visibleRoutes.length === 0 && !designable && ['/admin', '/admin/'].includes(location.pathname)) {
|
|
73
77
|
return (
|
|
74
78
|
<Result
|
|
75
79
|
icon={<HighlightOutlined style={{ fontSize: '8em', color: token.colorText }} />}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
AdminLayoutMenuItemRenderer,
|
|
19
19
|
AdminLayoutMenuItemModel,
|
|
20
20
|
AdminLayoutModel,
|
|
21
|
+
ADMIN_LAYOUT_MODEL_UID,
|
|
21
22
|
getAdminLayoutMenuMovePositionOptions,
|
|
22
23
|
normalizeAdminLayoutMenuLegacyVariables,
|
|
23
24
|
openAdminLayoutMenuLink,
|
|
@@ -211,7 +212,7 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
211
212
|
id: 11,
|
|
212
213
|
title: 'Page 1',
|
|
213
214
|
schemaUid: 'page-1',
|
|
214
|
-
type: NocoBaseDesktopRouteType.
|
|
215
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
215
216
|
},
|
|
216
217
|
],
|
|
217
218
|
},
|
|
@@ -264,7 +265,7 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
264
265
|
id: 11,
|
|
265
266
|
title: 'Page 1',
|
|
266
267
|
schemaUid: 'page-1',
|
|
267
|
-
type: NocoBaseDesktopRouteType.
|
|
268
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
268
269
|
},
|
|
269
270
|
],
|
|
270
271
|
},
|
|
@@ -285,21 +286,21 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
285
286
|
expect(route.children).toHaveLength(2);
|
|
286
287
|
expect(route.children[0].path).toBe('/admin/1');
|
|
287
288
|
expect(route.children[0].redirect).toBe('/admin/page-1');
|
|
288
|
-
expect(route.children[0]._runtimePath).toBe('/apps/demo/admin/page-1');
|
|
289
|
-
expect(route.children[0]._navigationMode).toBe('
|
|
290
|
-
expect(route.children[0]._isLegacy).toBe(
|
|
289
|
+
expect(route.children[0]._runtimePath).toBe('/apps/demo/v2/admin/page-1');
|
|
290
|
+
expect(route.children[0]._navigationMode).toBe('spa');
|
|
291
|
+
expect(route.children[0]._isLegacy).toBe(false);
|
|
291
292
|
expect(route.children[0]._depth).toBe(0);
|
|
292
293
|
expect(route.children[0]._route).toMatchObject({ id: 1, type: NocoBaseDesktopRouteType.group });
|
|
293
294
|
expect(route.children[0]._model).toBe(adminLayoutModel.subModels.menuItems?.[0]);
|
|
294
295
|
expect(route.children[0].routes).toHaveLength(1);
|
|
295
296
|
expect(route.children[0].routes?.[0].path).toBe('/admin/page-1');
|
|
296
297
|
expect(route.children[0].routes?.[0].redirect).toBe('/admin/page-1');
|
|
297
|
-
expect(route.children[0].routes?.[0]._runtimePath).toBe('/apps/demo/admin/page-1');
|
|
298
|
-
expect(route.children[0].routes?.[0]._navigationMode).toBe('
|
|
298
|
+
expect(route.children[0].routes?.[0]._runtimePath).toBe('/apps/demo/v2/admin/page-1');
|
|
299
|
+
expect(route.children[0].routes?.[0]._navigationMode).toBe('spa');
|
|
299
300
|
expect(route.children[0].routes?.[0]._depth).toBe(1);
|
|
300
301
|
expect(route.children[0].routes?.[0]._route).toMatchObject({
|
|
301
302
|
schemaUid: 'page-1',
|
|
302
|
-
type: NocoBaseDesktopRouteType.
|
|
303
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
303
304
|
});
|
|
304
305
|
expect(route.children[0].routes?.[0]._model).toBe(
|
|
305
306
|
adminLayoutModel.subModels.menuItems?.[0].subModels.menuItems?.[0],
|
|
@@ -310,6 +311,84 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
310
311
|
expect(route.children[1]._model).toBe(adminLayoutModel.subModels.menuItems?.[1]);
|
|
311
312
|
});
|
|
312
313
|
|
|
314
|
+
it('should filter legacy page menu routes in v2 admin layout', () => {
|
|
315
|
+
const adminLayoutModel = engine.createModel<AdminLayoutModel>({
|
|
316
|
+
uid: 'admin-layout-model',
|
|
317
|
+
use: AdminLayoutModel,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
adminLayoutModel.syncMenuRoutes([
|
|
321
|
+
{
|
|
322
|
+
id: 1,
|
|
323
|
+
title: 'Legacy page',
|
|
324
|
+
schemaUid: 'legacy-page',
|
|
325
|
+
type: NocoBaseDesktopRouteType.page,
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
id: 2,
|
|
329
|
+
title: 'Legacy only group',
|
|
330
|
+
type: NocoBaseDesktopRouteType.group,
|
|
331
|
+
children: [
|
|
332
|
+
{
|
|
333
|
+
id: 21,
|
|
334
|
+
title: 'Nested legacy page',
|
|
335
|
+
schemaUid: 'nested-legacy-page',
|
|
336
|
+
type: NocoBaseDesktopRouteType.page,
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
id: 3,
|
|
342
|
+
title: 'Mixed group',
|
|
343
|
+
type: NocoBaseDesktopRouteType.group,
|
|
344
|
+
children: [
|
|
345
|
+
{
|
|
346
|
+
id: 31,
|
|
347
|
+
title: 'Nested legacy page',
|
|
348
|
+
schemaUid: 'nested-legacy-page-2',
|
|
349
|
+
type: NocoBaseDesktopRouteType.page,
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
id: 32,
|
|
353
|
+
title: 'Nested flow page',
|
|
354
|
+
schemaUid: 'nested-flow-page',
|
|
355
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
356
|
+
},
|
|
357
|
+
],
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
id: 4,
|
|
361
|
+
title: 'Link',
|
|
362
|
+
type: NocoBaseDesktopRouteType.link,
|
|
363
|
+
},
|
|
364
|
+
]);
|
|
365
|
+
|
|
366
|
+
const route = adminLayoutModel.toProLayoutRoute({
|
|
367
|
+
designable: false,
|
|
368
|
+
isMobile: false,
|
|
369
|
+
t: (title) => title,
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
expect(route.children).toHaveLength(2);
|
|
373
|
+
expect(route.children[0]).toMatchObject({
|
|
374
|
+
path: '/admin/3',
|
|
375
|
+
redirect: '/admin/nested-flow-page',
|
|
376
|
+
_runtimePath: '/apps/demo/v2/admin/nested-flow-page',
|
|
377
|
+
_navigationMode: 'spa',
|
|
378
|
+
_isLegacy: false,
|
|
379
|
+
});
|
|
380
|
+
expect(route.children[0].routes).toHaveLength(1);
|
|
381
|
+
expect(route.children[0].routes?.[0]).toMatchObject({
|
|
382
|
+
path: '/admin/nested-flow-page',
|
|
383
|
+
_runtimePath: '/apps/demo/v2/admin/nested-flow-page',
|
|
384
|
+
_navigationMode: 'spa',
|
|
385
|
+
_isLegacy: false,
|
|
386
|
+
});
|
|
387
|
+
expect(route.children[1]).toMatchObject({
|
|
388
|
+
path: '/admin/__admin_layout__/link/4',
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
313
392
|
it('should resolve modern flowPage menu runtime target to v2 path', () => {
|
|
314
393
|
const model = engine.createModel<AdminLayoutMenuItemModel>({
|
|
315
394
|
uid: 'menu-item-flow-page',
|
|
@@ -406,7 +485,7 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
406
485
|
);
|
|
407
486
|
});
|
|
408
487
|
|
|
409
|
-
it('should render
|
|
488
|
+
it('should render document menu item as native anchor and use assign on left click', () => {
|
|
410
489
|
const assign = vi.fn();
|
|
411
490
|
Object.defineProperty(window, 'location', {
|
|
412
491
|
configurable: true,
|
|
@@ -452,15 +531,8 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
452
531
|
fireEvent.click(link, { button: 0 });
|
|
453
532
|
|
|
454
533
|
return waitFor(() => {
|
|
455
|
-
expect(modalConfirmMock).toHaveBeenCalledWith(
|
|
456
|
-
expect.objectContaining({
|
|
457
|
-
title: 'Open classic page access',
|
|
458
|
-
content: 'This page requires the classic version to open properly. Do you want to go there now?',
|
|
459
|
-
okText: 'Yes',
|
|
460
|
-
cancelText: 'Cancel',
|
|
461
|
-
}),
|
|
462
|
-
);
|
|
463
534
|
expect(assign).toHaveBeenCalledWith('/apps/demo/admin/legacy-page');
|
|
535
|
+
expect(modalConfirmMock).not.toHaveBeenCalled();
|
|
464
536
|
expect(navigateMock).not.toHaveBeenCalled();
|
|
465
537
|
});
|
|
466
538
|
});
|
|
@@ -518,7 +590,7 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
518
590
|
}
|
|
519
591
|
});
|
|
520
592
|
|
|
521
|
-
it('should not
|
|
593
|
+
it('should not ask for confirmation before document navigation', async () => {
|
|
522
594
|
modalConfirmMock.mockResolvedValue(false);
|
|
523
595
|
const assign = vi.fn();
|
|
524
596
|
Object.defineProperty(window, 'location', {
|
|
@@ -562,9 +634,9 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
562
634
|
fireEvent.click(screen.getByRole('link', { name: 'Legacy page' }), { button: 0 });
|
|
563
635
|
|
|
564
636
|
await waitFor(() => {
|
|
565
|
-
expect(
|
|
637
|
+
expect(assign).toHaveBeenCalledWith('/apps/demo/admin/legacy-page');
|
|
566
638
|
});
|
|
567
|
-
expect(
|
|
639
|
+
expect(modalConfirmMock).not.toHaveBeenCalled();
|
|
568
640
|
expect(navigateMock).not.toHaveBeenCalled();
|
|
569
641
|
});
|
|
570
642
|
|
|
@@ -684,8 +756,8 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
684
756
|
|
|
685
757
|
fireEvent.click(screen.getByRole('link', { name: 'Legacy group-landing-entry' }), { button: 0 });
|
|
686
758
|
return waitFor(() => {
|
|
687
|
-
expect(modalConfirmMock).toHaveBeenCalledTimes(1);
|
|
688
759
|
expect(assign).toHaveBeenCalledWith('/apps/demo/admin/legacy-page');
|
|
760
|
+
expect(modalConfirmMock).not.toHaveBeenCalled();
|
|
689
761
|
});
|
|
690
762
|
});
|
|
691
763
|
|
|
@@ -741,7 +813,7 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
741
813
|
id: 11,
|
|
742
814
|
title: 'Page 1',
|
|
743
815
|
schemaUid: 'page-1',
|
|
744
|
-
type: NocoBaseDesktopRouteType.
|
|
816
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
745
817
|
},
|
|
746
818
|
],
|
|
747
819
|
},
|
|
@@ -801,7 +873,7 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
801
873
|
id: 1,
|
|
802
874
|
title: 'Page 1',
|
|
803
875
|
schemaUid: 'page-1',
|
|
804
|
-
type: NocoBaseDesktopRouteType.
|
|
876
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
805
877
|
},
|
|
806
878
|
]);
|
|
807
879
|
});
|
|
@@ -837,6 +909,195 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
837
909
|
});
|
|
838
910
|
});
|
|
839
911
|
|
|
912
|
+
it('should expose menu linkage rules only for existing menu items', async () => {
|
|
913
|
+
const menuSettingsFlow = AdminLayoutMenuItemModel.globalFlowRegistry.getFlow('menuSettings');
|
|
914
|
+
expect(menuSettingsFlow?.steps?.linkageRules?.use).toBe('menuLinkageRules');
|
|
915
|
+
|
|
916
|
+
const model = engine.createModel<AdminLayoutMenuItemModel>({
|
|
917
|
+
uid: 'menu-item-linkage-settings',
|
|
918
|
+
use: AdminLayoutMenuItemModel,
|
|
919
|
+
props: {
|
|
920
|
+
route: createRoute(),
|
|
921
|
+
},
|
|
922
|
+
});
|
|
923
|
+
const creationModel = engine.createModel<AdminLayoutMenuItemModel>({
|
|
924
|
+
uid: 'menu-item-linkage-creation-settings',
|
|
925
|
+
use: AdminLayoutMenuItemModel,
|
|
926
|
+
props: {
|
|
927
|
+
creationMeta: {
|
|
928
|
+
menuType: 'link',
|
|
929
|
+
source: 'header',
|
|
930
|
+
},
|
|
931
|
+
},
|
|
932
|
+
});
|
|
933
|
+
const hideInSettings = menuSettingsFlow?.steps?.linkageRules?.hideInSettings as
|
|
934
|
+
| ((ctx: any) => Promise<boolean>)
|
|
935
|
+
| undefined;
|
|
936
|
+
|
|
937
|
+
await expect(hideInSettings?.({ model })).resolves.toBe(false);
|
|
938
|
+
await expect(hideInSettings?.({ model: creationModel })).resolves.toBe(true);
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
it('should persist menu linkage rules through flowModels and route flag', async () => {
|
|
942
|
+
const saveModel = vi.spyOn(engine, 'saveModel').mockResolvedValue(undefined as any);
|
|
943
|
+
const updateRoute = vi.fn().mockResolvedValue(undefined);
|
|
944
|
+
engine.context.routeRepository.updateRoute = updateRoute;
|
|
945
|
+
|
|
946
|
+
const model = engine.createModel<AdminLayoutMenuItemModel>({
|
|
947
|
+
uid: 'menu-item-linkage-persist',
|
|
948
|
+
use: AdminLayoutMenuItemModel,
|
|
949
|
+
props: {
|
|
950
|
+
route: createRoute(),
|
|
951
|
+
},
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
model.setStepParams('menuSettings', 'linkageRules', {
|
|
955
|
+
value: [
|
|
956
|
+
{ key: 'r1', title: 'Hide menu item', enable: true, condition: { logic: '$and', items: [] }, actions: [] },
|
|
957
|
+
],
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
await model.saveStepParams();
|
|
961
|
+
|
|
962
|
+
expect(saveModel).toHaveBeenCalledWith(model, { onlyStepParams: true });
|
|
963
|
+
expect(updateRoute).toHaveBeenCalledWith(1, {
|
|
964
|
+
options: {
|
|
965
|
+
hasPersistedMenuInstanceFlow: true,
|
|
966
|
+
},
|
|
967
|
+
});
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
it('should clear persisted menu linkage rules when no persisted state remains', async () => {
|
|
971
|
+
const saveModel = vi.spyOn(engine, 'saveModel').mockResolvedValue(undefined as any);
|
|
972
|
+
const destroy = vi.fn().mockResolvedValue(true);
|
|
973
|
+
const updateRoute = vi.fn().mockResolvedValue(undefined);
|
|
974
|
+
engine.setModelRepository({ destroy } as any);
|
|
975
|
+
engine.context.routeRepository.updateRoute = updateRoute;
|
|
976
|
+
|
|
977
|
+
const model = engine.createModel<AdminLayoutMenuItemModel>({
|
|
978
|
+
uid: 'menu-item-linkage-clear',
|
|
979
|
+
use: AdminLayoutMenuItemModel,
|
|
980
|
+
props: {
|
|
981
|
+
route: createRoute({
|
|
982
|
+
options: {
|
|
983
|
+
hasPersistedMenuInstanceFlow: true,
|
|
984
|
+
},
|
|
985
|
+
}),
|
|
986
|
+
},
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
model.setStepParams('menuSettings', 'linkageRules', { value: [] });
|
|
990
|
+
await model.saveStepParams();
|
|
991
|
+
|
|
992
|
+
expect(saveModel).not.toHaveBeenCalled();
|
|
993
|
+
expect(destroy).toHaveBeenCalledWith('menu-item-linkage-clear');
|
|
994
|
+
expect(updateRoute).toHaveBeenCalledWith(1, {
|
|
995
|
+
options: undefined,
|
|
996
|
+
});
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
it('should restore menu linkage rules and rerender after hydrate', async () => {
|
|
1000
|
+
engine.context.defineProperty('flowSettingsEnabled', {
|
|
1001
|
+
value: true,
|
|
1002
|
+
});
|
|
1003
|
+
engine.setModelRepository({
|
|
1004
|
+
findOne: vi.fn().mockResolvedValue({
|
|
1005
|
+
uid: 'menu-item-linkage-hydrate',
|
|
1006
|
+
use: 'AdminLayoutMenuItemModel',
|
|
1007
|
+
stepParams: {
|
|
1008
|
+
menuSettings: {
|
|
1009
|
+
linkageRules: {
|
|
1010
|
+
value: [
|
|
1011
|
+
{
|
|
1012
|
+
key: 'r1',
|
|
1013
|
+
title: 'Persisted linkage',
|
|
1014
|
+
enable: true,
|
|
1015
|
+
condition: { logic: '$and', items: [] },
|
|
1016
|
+
actions: [],
|
|
1017
|
+
},
|
|
1018
|
+
],
|
|
1019
|
+
},
|
|
1020
|
+
},
|
|
1021
|
+
},
|
|
1022
|
+
}),
|
|
1023
|
+
} as any);
|
|
1024
|
+
const rerenderSpy = vi.spyOn(AdminLayoutMenuItemModel.prototype, 'rerender').mockResolvedValue(undefined as any);
|
|
1025
|
+
|
|
1026
|
+
const model = engine.createModel<AdminLayoutMenuItemModel>({
|
|
1027
|
+
uid: 'menu-item-linkage-hydrate',
|
|
1028
|
+
use: AdminLayoutMenuItemModel,
|
|
1029
|
+
props: {
|
|
1030
|
+
route: createRoute(),
|
|
1031
|
+
},
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
await waitFor(() => {
|
|
1035
|
+
expect(model.getStepParams('menuSettings', 'linkageRules')).toMatchObject({
|
|
1036
|
+
value: [{ key: 'r1' }],
|
|
1037
|
+
});
|
|
1038
|
+
});
|
|
1039
|
+
expect(rerenderSpy).toHaveBeenCalledTimes(1);
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
it('should hide menu route dynamically in runtime mode and refresh layout route tree', () => {
|
|
1043
|
+
const adminLayoutModel = engine.createModel<AdminLayoutModel>({
|
|
1044
|
+
uid: ADMIN_LAYOUT_MODEL_UID,
|
|
1045
|
+
use: AdminLayoutModel,
|
|
1046
|
+
});
|
|
1047
|
+
const model = engine.createModel<AdminLayoutMenuItemModel>({
|
|
1048
|
+
uid: 'menu-item-dynamic-hidden',
|
|
1049
|
+
use: AdminLayoutMenuItemModel,
|
|
1050
|
+
props: {
|
|
1051
|
+
route: createRoute(),
|
|
1052
|
+
},
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
const refreshBefore = adminLayoutModel.menuRouteRefreshVersion;
|
|
1056
|
+
|
|
1057
|
+
model.setHidden(true);
|
|
1058
|
+
const runtimeRoute = model.toProLayoutRoute({
|
|
1059
|
+
designable: false,
|
|
1060
|
+
isMobile: false,
|
|
1061
|
+
t: (title) => title,
|
|
1062
|
+
});
|
|
1063
|
+
const designableRoute = model.toProLayoutRoute({
|
|
1064
|
+
designable: true,
|
|
1065
|
+
isMobile: false,
|
|
1066
|
+
t: (title) => title,
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
expect(runtimeRoute).toBeNull();
|
|
1070
|
+
expect(designableRoute?.hideInMenu).toBeFalsy();
|
|
1071
|
+
expect(adminLayoutModel.menuRouteRefreshVersion).toBe(refreshBefore + 1);
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
it('should render hidden menu item with opacity and keep original title in config mode', () => {
|
|
1075
|
+
const model = engine.createModel<AdminLayoutMenuItemModel>({
|
|
1076
|
+
uid: 'menu-item-hidden-in-config',
|
|
1077
|
+
use: AdminLayoutMenuItemModel,
|
|
1078
|
+
props: {
|
|
1079
|
+
route: createRoute(),
|
|
1080
|
+
},
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
model.setProps({
|
|
1084
|
+
item: {
|
|
1085
|
+
name: 'Page 1',
|
|
1086
|
+
path: '/admin/page-1',
|
|
1087
|
+
_route: createRoute(),
|
|
1088
|
+
_model: model,
|
|
1089
|
+
},
|
|
1090
|
+
dom: React.createElement('span', null, 'Page 1'),
|
|
1091
|
+
options: { isMobile: false, collapsed: false },
|
|
1092
|
+
renderType: 'item',
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
const rendered = (model as any).renderHiddenInConfig();
|
|
1096
|
+
|
|
1097
|
+
expect(rendered?.props?.style).toMatchObject({ opacity: 0.3 });
|
|
1098
|
+
expect(rendered?.props?.children?.props?.dom?.props?.children).toBe('Page 1');
|
|
1099
|
+
});
|
|
1100
|
+
|
|
840
1101
|
it('should keep variable-aware editors for link menu settings', async () => {
|
|
841
1102
|
const model = engine.createModel<AdminLayoutMenuItemModel>({
|
|
842
1103
|
uid: 'menu-item-link-edit',
|
|
@@ -1630,7 +1891,7 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
1630
1891
|
id: 2,
|
|
1631
1892
|
title: 'Next page',
|
|
1632
1893
|
schemaUid: 'next-page',
|
|
1633
|
-
type: NocoBaseDesktopRouteType.
|
|
1894
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1634
1895
|
},
|
|
1635
1896
|
];
|
|
1636
1897
|
engine.context.api.resource = vi.fn(() => ({
|
|
@@ -1655,8 +1916,8 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
1655
1916
|
|
|
1656
1917
|
expect(deleteRoute).toHaveBeenCalledWith(1);
|
|
1657
1918
|
expect(removeSchema).not.toHaveBeenCalled();
|
|
1658
|
-
expect(assign).
|
|
1659
|
-
expect(navigate).
|
|
1919
|
+
expect(assign).not.toHaveBeenCalled();
|
|
1920
|
+
expect(navigate).toHaveBeenCalledWith('/apps/demo/v2/admin/next-page');
|
|
1660
1921
|
});
|
|
1661
1922
|
|
|
1662
1923
|
it('should match current route with router basename before navigating away after delete', async () => {
|
|
@@ -1684,16 +1945,16 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
1684
1945
|
id: 2,
|
|
1685
1946
|
title: 'Next page',
|
|
1686
1947
|
schemaUid: 'next-page',
|
|
1687
|
-
type: NocoBaseDesktopRouteType.
|
|
1948
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
1688
1949
|
},
|
|
1689
1950
|
];
|
|
1690
1951
|
engine.context.api.resource = vi.fn(() => ({
|
|
1691
1952
|
'remove/current-page': removeSchema,
|
|
1692
1953
|
}));
|
|
1693
|
-
engine.context.location.pathname = '/apps/demo/admin/current-page';
|
|
1954
|
+
engine.context.location.pathname = '/apps/demo/v2/admin/current-page';
|
|
1694
1955
|
engine.context.defineProperty('router', {
|
|
1695
1956
|
value: {
|
|
1696
|
-
basename: '/apps/demo',
|
|
1957
|
+
basename: '/apps/demo/v2',
|
|
1697
1958
|
navigate,
|
|
1698
1959
|
},
|
|
1699
1960
|
});
|
|
@@ -1715,8 +1976,8 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
1715
1976
|
|
|
1716
1977
|
expect(deleteRoute).toHaveBeenCalledWith(1);
|
|
1717
1978
|
expect(removeSchema).not.toHaveBeenCalled();
|
|
1718
|
-
expect(assign).
|
|
1719
|
-
expect(navigate).
|
|
1979
|
+
expect(assign).not.toHaveBeenCalled();
|
|
1980
|
+
expect(navigate).toHaveBeenCalledWith('/admin/next-page');
|
|
1720
1981
|
});
|
|
1721
1982
|
|
|
1722
1983
|
it('should reject inner move when target is not a group', async () => {
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
import { NocoBaseDesktopRouteType, type NocoBaseDesktopRoute } from '../../../flow-compat';
|
|
11
11
|
import {
|
|
12
12
|
findFirstAccessiblePageRoute,
|
|
13
|
+
findFirstV2LandingRoute,
|
|
14
|
+
isV2MenuRoute,
|
|
13
15
|
resolveAdminRouteRuntimeTarget,
|
|
14
16
|
toRouterNavigationPath,
|
|
15
17
|
} from './resolveAdminRouteRuntimeTarget';
|
|
@@ -35,10 +37,11 @@ describe('resolveAdminRouteRuntimeTarget', () => {
|
|
|
35
37
|
runtimePath: '/nocobase/v2/admin/flow-page-1',
|
|
36
38
|
navigationMode: 'spa',
|
|
37
39
|
isLegacy: false,
|
|
40
|
+
reason: 'ok',
|
|
38
41
|
});
|
|
39
42
|
});
|
|
40
43
|
|
|
41
|
-
it('should
|
|
44
|
+
it('should mark page unsupported in v2 admin runtime', () => {
|
|
42
45
|
expect(
|
|
43
46
|
resolveAdminRouteRuntimeTarget({
|
|
44
47
|
app,
|
|
@@ -48,9 +51,10 @@ describe('resolveAdminRouteRuntimeTarget', () => {
|
|
|
48
51
|
},
|
|
49
52
|
}),
|
|
50
53
|
).toEqual({
|
|
51
|
-
runtimePath:
|
|
52
|
-
navigationMode: '
|
|
53
|
-
isLegacy:
|
|
54
|
+
runtimePath: null,
|
|
55
|
+
navigationMode: 'spa',
|
|
56
|
+
isLegacy: false,
|
|
57
|
+
reason: 'unsupportedV2Runtime',
|
|
54
58
|
});
|
|
55
59
|
});
|
|
56
60
|
|
|
@@ -74,10 +78,11 @@ describe('resolveAdminRouteRuntimeTarget', () => {
|
|
|
74
78
|
runtimePath: '/apps/demo/admin/legacy-page-1',
|
|
75
79
|
navigationMode: 'spa',
|
|
76
80
|
isLegacy: false,
|
|
81
|
+
reason: 'ok',
|
|
77
82
|
});
|
|
78
83
|
});
|
|
79
84
|
|
|
80
|
-
it('should preserve current search and hash
|
|
85
|
+
it('should not preserve current search and hash when direct legacy page is unsupported', () => {
|
|
81
86
|
expect(
|
|
82
87
|
resolveAdminRouteRuntimeTarget({
|
|
83
88
|
app,
|
|
@@ -93,13 +98,14 @@ describe('resolveAdminRouteRuntimeTarget', () => {
|
|
|
93
98
|
preserveLocationState: true,
|
|
94
99
|
}),
|
|
95
100
|
).toEqual({
|
|
96
|
-
runtimePath:
|
|
97
|
-
navigationMode: '
|
|
98
|
-
isLegacy:
|
|
101
|
+
runtimePath: null,
|
|
102
|
+
navigationMode: 'spa',
|
|
103
|
+
isLegacy: false,
|
|
104
|
+
reason: 'unsupportedV2Runtime',
|
|
99
105
|
});
|
|
100
106
|
});
|
|
101
107
|
|
|
102
|
-
it('should resolve group by DFS first
|
|
108
|
+
it('should resolve group by DFS first v2 landing route in v2 runtime', () => {
|
|
103
109
|
const route: NocoBaseDesktopRoute = {
|
|
104
110
|
type: NocoBaseDesktopRouteType.group,
|
|
105
111
|
children: [
|
|
@@ -124,9 +130,10 @@ describe('resolveAdminRouteRuntimeTarget', () => {
|
|
|
124
130
|
};
|
|
125
131
|
|
|
126
132
|
expect(resolveAdminRouteRuntimeTarget({ app, route })).toEqual({
|
|
127
|
-
runtimePath: '/nocobase/admin/
|
|
128
|
-
navigationMode: '
|
|
129
|
-
isLegacy:
|
|
133
|
+
runtimePath: '/nocobase/v2/admin/flow-page-2',
|
|
134
|
+
navigationMode: 'spa',
|
|
135
|
+
isLegacy: false,
|
|
136
|
+
reason: 'ok',
|
|
130
137
|
});
|
|
131
138
|
});
|
|
132
139
|
|
|
@@ -158,6 +165,35 @@ describe('resolveAdminRouteRuntimeTarget', () => {
|
|
|
158
165
|
});
|
|
159
166
|
});
|
|
160
167
|
|
|
168
|
+
it('should find v2 landing route and skip legacy page routes', () => {
|
|
169
|
+
const routes = [
|
|
170
|
+
{
|
|
171
|
+
type: NocoBaseDesktopRouteType.page,
|
|
172
|
+
schemaUid: 'legacy-page',
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
type: NocoBaseDesktopRouteType.group,
|
|
176
|
+
children: [
|
|
177
|
+
{
|
|
178
|
+
type: NocoBaseDesktopRouteType.page,
|
|
179
|
+
schemaUid: 'nested-legacy-page',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
183
|
+
schemaUid: 'nested-flow-page',
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
];
|
|
188
|
+
|
|
189
|
+
expect(findFirstV2LandingRoute(routes)).toMatchObject({
|
|
190
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
191
|
+
schemaUid: 'nested-flow-page',
|
|
192
|
+
});
|
|
193
|
+
expect(isV2MenuRoute(routes[0])).toBe(false);
|
|
194
|
+
expect(isV2MenuRoute(routes[1])).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
|
|
161
197
|
it('should return empty target when group has no accessible landing page', () => {
|
|
162
198
|
expect(
|
|
163
199
|
resolveAdminRouteRuntimeTarget({
|
|
@@ -181,6 +217,7 @@ describe('resolveAdminRouteRuntimeTarget', () => {
|
|
|
181
217
|
runtimePath: null,
|
|
182
218
|
navigationMode: 'spa',
|
|
183
219
|
isLegacy: false,
|
|
220
|
+
reason: 'emptyGroup',
|
|
184
221
|
});
|
|
185
222
|
});
|
|
186
223
|
|
|
@@ -199,6 +236,7 @@ describe('resolveAdminRouteRuntimeTarget', () => {
|
|
|
199
236
|
runtimePath: null,
|
|
200
237
|
navigationMode: 'spa',
|
|
201
238
|
isLegacy: false,
|
|
239
|
+
reason: 'missingSchemaUid',
|
|
202
240
|
});
|
|
203
241
|
expect(log).toHaveBeenCalledWith(
|
|
204
242
|
'[NocoBase] Admin route runtime target:',
|