@nocobase/client-v2 2.1.0-alpha.32 → 2.1.0-alpha.34
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/flow/actions/index.d.ts +1 -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/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +3 -0
- package/es/index.mjs +78 -73
- package/lib/index.js +59 -54
- package/package.json +5 -5
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +67 -46
- package/src/__tests__/settings-center.test.tsx +30 -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__/linkageRules.formValueDrivenRefresh.test.ts +6 -1
- package/src/flow/actions/__tests__/linkageRules.menu.test.ts +90 -0
- package/src/flow/actions/index.ts +2 -0
- package/src/flow/actions/linkageRules.tsx +77 -23
- package/src/flow/actions/linkageRulesFormValueRefresh.ts +2 -8
- 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/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +4 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +7 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +4 -1
- package/src/flow/system-settings/useSystemSettings.tsx +36 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/client-v2",
|
|
3
|
-
"version": "2.1.0-alpha.
|
|
3
|
+
"version": "2.1.0-alpha.34",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "es/index.mjs",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"@formily/antd-v5": "1.2.3",
|
|
25
25
|
"@formily/react": "^2.2.27",
|
|
26
26
|
"@formily/shared": "^2.2.27",
|
|
27
|
-
"@nocobase/flow-engine": "2.1.0-alpha.
|
|
28
|
-
"@nocobase/sdk": "2.1.0-alpha.
|
|
29
|
-
"@nocobase/shared": "2.1.0-alpha.
|
|
27
|
+
"@nocobase/flow-engine": "2.1.0-alpha.34",
|
|
28
|
+
"@nocobase/sdk": "2.1.0-alpha.34",
|
|
29
|
+
"@nocobase/shared": "2.1.0-alpha.34",
|
|
30
30
|
"ahooks": "^3.7.2",
|
|
31
31
|
"antd": "5.24.2",
|
|
32
32
|
"classnames": "^2.3.1",
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
"react-i18next": "^11.15.1",
|
|
38
38
|
"react-router-dom": "^6.30.1"
|
|
39
39
|
},
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "59970f73bdbea2656e20cd24357bacf78a3f759f"
|
|
41
41
|
}
|
|
@@ -8,13 +8,29 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { createMockClient } from '@nocobase/client-v2';
|
|
11
|
-
import { render, waitFor } from '@testing-library/react';
|
|
11
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
12
12
|
import React from 'react';
|
|
13
13
|
import { NocoBaseBuildInPlugin } from '../nocobase-buildin-plugin';
|
|
14
14
|
|
|
15
15
|
describe('nocobase buildin plugin auth redirect', () => {
|
|
16
16
|
const originalLocation = globalThis.window.location;
|
|
17
17
|
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
Object.defineProperty(globalThis.window, 'matchMedia', {
|
|
20
|
+
configurable: true,
|
|
21
|
+
value: vi.fn().mockImplementation((query: string) => ({
|
|
22
|
+
matches: false,
|
|
23
|
+
media: query,
|
|
24
|
+
onchange: null,
|
|
25
|
+
addListener: vi.fn(),
|
|
26
|
+
removeListener: vi.fn(),
|
|
27
|
+
addEventListener: vi.fn(),
|
|
28
|
+
removeEventListener: vi.fn(),
|
|
29
|
+
dispatchEvent: vi.fn(),
|
|
30
|
+
})),
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
18
34
|
afterEach(() => {
|
|
19
35
|
Object.defineProperty(globalThis.window, 'location', {
|
|
20
36
|
configurable: true,
|
|
@@ -92,7 +108,7 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
92
108
|
});
|
|
93
109
|
});
|
|
94
110
|
|
|
95
|
-
it('should
|
|
111
|
+
it('should render v2 admin root without redirecting to legacy default page', async () => {
|
|
96
112
|
const replace = vi.fn();
|
|
97
113
|
Object.defineProperty(globalThis.window, 'location', {
|
|
98
114
|
configurable: true,
|
|
@@ -118,6 +134,7 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
118
134
|
},
|
|
119
135
|
});
|
|
120
136
|
app.apiMock.onGet('/auth:check').reply(200, { data: { id: 1 } });
|
|
137
|
+
app.apiMock.onGet('systemSettings:get').reply(200, { data: {} });
|
|
121
138
|
app.apiMock.onGet('/desktopRoutes:listAccessible').reply(200, {
|
|
122
139
|
data: [
|
|
123
140
|
{
|
|
@@ -133,53 +150,57 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
133
150
|
const { container } = render(<Root />);
|
|
134
151
|
|
|
135
152
|
await waitFor(() => {
|
|
136
|
-
expect(
|
|
153
|
+
expect(container.innerHTML).toContain('No pages yet, please configure first');
|
|
137
154
|
});
|
|
155
|
+
expect(replace).not.toHaveBeenCalled();
|
|
138
156
|
expect(container.innerHTML).not.toContain('Legacy page');
|
|
139
157
|
});
|
|
140
158
|
|
|
141
|
-
it('
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const app = createMockClient({
|
|
155
|
-
publicPath: '/v2/',
|
|
156
|
-
plugins: [NocoBaseBuildInPlugin as any],
|
|
157
|
-
router: { type: 'memory', initialEntries: ['/v2/admin/legacy-page/tab/tab-1'] },
|
|
158
|
-
});
|
|
159
|
-
app.apiMock.onGet('app:getLang').reply(200, {
|
|
160
|
-
data: {
|
|
161
|
-
lang: 'en-US',
|
|
162
|
-
resources: { client: {} },
|
|
163
|
-
cron: {},
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
app.apiMock.onGet('/auth:check').reply(200, { data: { id: 1 } });
|
|
167
|
-
app.apiMock.onGet('/desktopRoutes:listAccessible').reply(200, {
|
|
168
|
-
data: [
|
|
169
|
-
{
|
|
170
|
-
id: 1,
|
|
171
|
-
title: 'Legacy page',
|
|
172
|
-
schemaUid: 'legacy-page',
|
|
173
|
-
type: 'page',
|
|
159
|
+
it.each(['/v2/admin/legacy-page/tab/tab-1', '/v2/admin/legacy-page/view/detail'])(
|
|
160
|
+
'should show 404 for authenticated direct legacy v2 page access: %s',
|
|
161
|
+
async (pathname) => {
|
|
162
|
+
const replace = vi.fn();
|
|
163
|
+
Object.defineProperty(globalThis.window, 'location', {
|
|
164
|
+
configurable: true,
|
|
165
|
+
value: {
|
|
166
|
+
...originalLocation,
|
|
167
|
+
pathname,
|
|
168
|
+
search: '?from=direct',
|
|
169
|
+
hash: '#dialog',
|
|
170
|
+
replace,
|
|
174
171
|
},
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const app = createMockClient({
|
|
175
|
+
publicPath: '/v2/',
|
|
176
|
+
plugins: [NocoBaseBuildInPlugin as any],
|
|
177
|
+
router: { type: 'memory', initialEntries: [pathname] },
|
|
178
|
+
});
|
|
179
|
+
app.apiMock.onGet('app:getLang').reply(200, {
|
|
180
|
+
data: {
|
|
181
|
+
lang: 'en-US',
|
|
182
|
+
resources: { client: {} },
|
|
183
|
+
cron: {},
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
app.apiMock.onGet('/auth:check').reply(200, { data: { id: 1 } });
|
|
187
|
+
app.apiMock.onGet('systemSettings:get').reply(200, { data: {} });
|
|
188
|
+
app.apiMock.onGet('/desktopRoutes:listAccessible').reply(200, {
|
|
189
|
+
data: [
|
|
190
|
+
{
|
|
191
|
+
id: 1,
|
|
192
|
+
title: 'Legacy page',
|
|
193
|
+
schemaUid: 'legacy-page',
|
|
194
|
+
type: 'page',
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const Root = app.getRootComponent();
|
|
200
|
+
render(<Root />);
|
|
201
|
+
|
|
202
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
203
|
+
expect(replace).not.toHaveBeenCalled();
|
|
204
|
+
},
|
|
205
|
+
);
|
|
185
206
|
});
|
|
@@ -158,6 +158,36 @@ describe('settings center', () => {
|
|
|
158
158
|
expect(await screen.findByDisplayValue('NocoBase')).toBeInTheDocument();
|
|
159
159
|
});
|
|
160
160
|
|
|
161
|
+
it('should expose current language variable as enabled-language selector', async () => {
|
|
162
|
+
const app = createMockClient({
|
|
163
|
+
plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
|
|
164
|
+
router: { type: 'memory', initialEntries: ['/admin/settings/system-settings'] },
|
|
165
|
+
});
|
|
166
|
+
mockAdminRuntime(app, {
|
|
167
|
+
systemSettings: {
|
|
168
|
+
enabledLanguages: ['en-US', 'zh-CN'],
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await renderApp(app);
|
|
173
|
+
await waitForGetRequests(app, ['/auth:check', 'roles:check', 'systemSettings:get']);
|
|
174
|
+
|
|
175
|
+
await waitFor(() => {
|
|
176
|
+
const localeNode = app.flowEngine.context.getPropertyMetaTree().find((node) => node.name === 'locale');
|
|
177
|
+
expect(localeNode).toMatchObject({
|
|
178
|
+
name: 'locale',
|
|
179
|
+
title: '{{t("Current language")}}',
|
|
180
|
+
interface: 'select',
|
|
181
|
+
uiSchema: {
|
|
182
|
+
enum: [
|
|
183
|
+
{ label: 'English', value: 'en-US' },
|
|
184
|
+
{ label: '简体中文', value: 'zh-CN' },
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
161
191
|
it('should fallback to plugin-manager when system-settings is not allowed', async () => {
|
|
162
192
|
const app = createMockClient({
|
|
163
193
|
plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
12
12
|
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
|
13
|
-
import { render, waitFor } from '@testing-library/react';
|
|
13
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
14
14
|
import { FlowEngine, FlowEngineProvider, type FlowModel } from '@nocobase/flow-engine';
|
|
15
15
|
import FlowRoute from '../components/FlowRoute';
|
|
16
16
|
|
|
@@ -219,7 +219,7 @@ describe('FlowRoute', () => {
|
|
|
219
219
|
});
|
|
220
220
|
});
|
|
221
221
|
|
|
222
|
-
it('should
|
|
222
|
+
it('should show 404 when current route is a legacy page in v2 runtime', async () => {
|
|
223
223
|
const originalLocation = window.location;
|
|
224
224
|
const replace = vi.fn();
|
|
225
225
|
Object.defineProperty(window, 'location', {
|
|
@@ -271,9 +271,8 @@ describe('FlowRoute', () => {
|
|
|
271
271
|
</FlowEngineProvider>,
|
|
272
272
|
);
|
|
273
273
|
|
|
274
|
-
await
|
|
275
|
-
|
|
276
|
-
});
|
|
274
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
275
|
+
expect(replace).not.toHaveBeenCalled();
|
|
277
276
|
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
278
277
|
expect(adminLayoutModel.updateRoutePage).not.toHaveBeenCalled();
|
|
279
278
|
} finally {
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
11
|
+
import { actionLinkageRules } from '../linkageRules';
|
|
12
|
+
|
|
13
|
+
function createActionModel() {
|
|
14
|
+
const model: any = {
|
|
15
|
+
uid: 'edit-action',
|
|
16
|
+
hidden: false,
|
|
17
|
+
props: {
|
|
18
|
+
title: 'Edit',
|
|
19
|
+
},
|
|
20
|
+
context: {},
|
|
21
|
+
setProps: vi.fn((keyOrProps: string | Record<string, any>, value?: any) => {
|
|
22
|
+
if (typeof keyOrProps === 'string') {
|
|
23
|
+
model.props[keyOrProps] = value;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
model.props = {
|
|
27
|
+
...model.props,
|
|
28
|
+
...keyOrProps,
|
|
29
|
+
};
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return model;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function createLinkageParams(actions: any[] = []) {
|
|
37
|
+
return {
|
|
38
|
+
value: actions.length
|
|
39
|
+
? [
|
|
40
|
+
{
|
|
41
|
+
key: 'hide-edit',
|
|
42
|
+
title: 'hide edit',
|
|
43
|
+
enable: true,
|
|
44
|
+
condition: {
|
|
45
|
+
logic: '$and',
|
|
46
|
+
items: [],
|
|
47
|
+
},
|
|
48
|
+
actions,
|
|
49
|
+
},
|
|
50
|
+
]
|
|
51
|
+
: [],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function createRuntime(model: any, options: { pauseHiddenAction?: boolean } = {}) {
|
|
56
|
+
const { pauseHiddenAction = true } = options;
|
|
57
|
+
let releaseHiddenAction: () => void = () => {};
|
|
58
|
+
let hiddenActionCount = 0;
|
|
59
|
+
const hiddenActionWaiters: Array<{ count: number; resolve: () => void }> = [];
|
|
60
|
+
const waitForHiddenActionCount = (count: number) => {
|
|
61
|
+
if (hiddenActionCount >= count) {
|
|
62
|
+
return Promise.resolve();
|
|
63
|
+
}
|
|
64
|
+
return new Promise<void>((resolve) => {
|
|
65
|
+
hiddenActionWaiters.push({ count, resolve });
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
const hiddenActionReachedPromise = waitForHiddenActionCount(1);
|
|
69
|
+
const releaseHiddenActionPromise = new Promise<void>((resolve) => {
|
|
70
|
+
releaseHiddenAction = resolve;
|
|
71
|
+
});
|
|
72
|
+
const notifyHiddenActionReached = () => {
|
|
73
|
+
hiddenActionCount += 1;
|
|
74
|
+
for (let i = hiddenActionWaiters.length - 1; i >= 0; i--) {
|
|
75
|
+
const waiter = hiddenActionWaiters[i];
|
|
76
|
+
if (hiddenActionCount < waiter.count) continue;
|
|
77
|
+
hiddenActionWaiters.splice(i, 1);
|
|
78
|
+
waiter.resolve();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const ctx: any = {
|
|
83
|
+
flowKey: 'buttonSettings',
|
|
84
|
+
model,
|
|
85
|
+
app: {
|
|
86
|
+
jsonLogic: {
|
|
87
|
+
apply: vi.fn(() => true),
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
t: (s: string) => s,
|
|
91
|
+
resolveJsonTemplate: vi.fn(async (value: any) => value),
|
|
92
|
+
getAction: (name: string) => {
|
|
93
|
+
if (name !== 'linkageSetActionProps') return undefined;
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
handler: async (_ctx: any, params: any) => {
|
|
97
|
+
params.setProps(model, {
|
|
98
|
+
hiddenModel: params.value === 'hidden',
|
|
99
|
+
disabled: false,
|
|
100
|
+
hiddenText: false,
|
|
101
|
+
});
|
|
102
|
+
notifyHiddenActionReached();
|
|
103
|
+
if (pauseHiddenAction) {
|
|
104
|
+
await releaseHiddenActionPromise;
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
ctx,
|
|
113
|
+
hiddenActionReachedPromise,
|
|
114
|
+
waitForHiddenActionCount,
|
|
115
|
+
releaseHiddenAction,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
describe('actionLinkageRules props patch isolation', () => {
|
|
120
|
+
async function runReproOnce() {
|
|
121
|
+
const model = createActionModel();
|
|
122
|
+
const { ctx, hiddenActionReachedPromise, releaseHiddenAction } = createRuntime(model);
|
|
123
|
+
|
|
124
|
+
const hideRun = actionLinkageRules.handler(
|
|
125
|
+
ctx,
|
|
126
|
+
createLinkageParams([
|
|
127
|
+
{
|
|
128
|
+
name: 'linkageSetActionProps',
|
|
129
|
+
params: {
|
|
130
|
+
value: 'hidden',
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
]),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
await hiddenActionReachedPromise;
|
|
137
|
+
expect(model.hidden).toBe(false);
|
|
138
|
+
|
|
139
|
+
await actionLinkageRules.handler(ctx, createLinkageParams());
|
|
140
|
+
expect(model.hidden).toBe(false);
|
|
141
|
+
|
|
142
|
+
releaseHiddenAction();
|
|
143
|
+
await hideRun;
|
|
144
|
+
|
|
145
|
+
expect(model.hidden).toBe(true);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
it('keeps a concurrent empty run from clearing another run props patch', async () => {
|
|
149
|
+
for (let i = 0; i < 50; i++) {
|
|
150
|
+
await runReproOnce();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('still restores original props when a later completed run has no matched actions', async () => {
|
|
155
|
+
const model = createActionModel();
|
|
156
|
+
const { ctx } = createRuntime(model, { pauseHiddenAction: false });
|
|
157
|
+
|
|
158
|
+
await actionLinkageRules.handler(
|
|
159
|
+
ctx,
|
|
160
|
+
createLinkageParams([
|
|
161
|
+
{
|
|
162
|
+
name: 'linkageSetActionProps',
|
|
163
|
+
params: {
|
|
164
|
+
value: 'hidden',
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
]),
|
|
168
|
+
);
|
|
169
|
+
expect(model.hidden).toBe(true);
|
|
170
|
+
|
|
171
|
+
await actionLinkageRules.handler(ctx, createLinkageParams());
|
|
172
|
+
expect(model.hidden).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('keeps concurrent matched runs from clearing each other props patch', async () => {
|
|
176
|
+
const model = createActionModel();
|
|
177
|
+
const { ctx, waitForHiddenActionCount, releaseHiddenAction } = createRuntime(model);
|
|
178
|
+
const params = createLinkageParams([
|
|
179
|
+
{
|
|
180
|
+
name: 'linkageSetActionProps',
|
|
181
|
+
params: {
|
|
182
|
+
value: 'hidden',
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
const firstRun = actionLinkageRules.handler(ctx, params);
|
|
188
|
+
await waitForHiddenActionCount(1);
|
|
189
|
+
|
|
190
|
+
const secondRun = actionLinkageRules.handler(ctx, params);
|
|
191
|
+
await waitForHiddenActionCount(2);
|
|
192
|
+
|
|
193
|
+
releaseHiddenAction();
|
|
194
|
+
await firstRun;
|
|
195
|
+
await secondRun;
|
|
196
|
+
|
|
197
|
+
expect(model.hidden).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
@@ -26,7 +26,12 @@ function createRule(overrides: any = {}) {
|
|
|
26
26
|
|
|
27
27
|
function createRuntime(
|
|
28
28
|
params: any,
|
|
29
|
-
options: {
|
|
29
|
+
options: {
|
|
30
|
+
fieldIndex?: string[];
|
|
31
|
+
fieldIndexRef?: { current: string[] };
|
|
32
|
+
actionHandler?: any;
|
|
33
|
+
engineEmitter?: any;
|
|
34
|
+
} = {},
|
|
30
35
|
) {
|
|
31
36
|
const formEmitter = new EventEmitter();
|
|
32
37
|
const formBlock: any = {
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { ActionScene } from '@nocobase/flow-engine';
|
|
11
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
12
|
+
|
|
13
|
+
import * as registeredActions from '../index';
|
|
14
|
+
import { linkageRunjs, linkageSetMenuItemProps, menuLinkageRules } from '../linkageRules';
|
|
15
|
+
|
|
16
|
+
describe('menu linkage rules', () => {
|
|
17
|
+
it('should register menu linkage actions from the actions entry', () => {
|
|
18
|
+
expect(registeredActions.menuLinkageRules).toBe(menuLinkageRules);
|
|
19
|
+
expect(registeredActions.linkageSetMenuItemProps).toBe(linkageSetMenuItemProps);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should expose menu state and runjs actions in ui schema', () => {
|
|
23
|
+
const schema = menuLinkageRules.uiSchema?.({
|
|
24
|
+
getActions: () =>
|
|
25
|
+
new Map([
|
|
26
|
+
['linkageSetMenuItemProps', linkageSetMenuItemProps],
|
|
27
|
+
['linkageRunjs', linkageRunjs],
|
|
28
|
+
[
|
|
29
|
+
'actionOnly',
|
|
30
|
+
{
|
|
31
|
+
name: 'actionOnly',
|
|
32
|
+
scene: ActionScene.ACTION_LINKAGE_RULES,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
]),
|
|
36
|
+
} as any) as any;
|
|
37
|
+
|
|
38
|
+
expect(schema?.value?.['x-component-props']?.supportedActions).toEqual(['linkageSetMenuItemProps', 'linkageRunjs']);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should set menu model hidden through common linkage handler', async () => {
|
|
42
|
+
const setProps = vi.fn(function (this: any, props: any) {
|
|
43
|
+
this.props = {
|
|
44
|
+
...this.props,
|
|
45
|
+
...props,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
const setHidden = vi.fn(function (this: any, value: boolean) {
|
|
49
|
+
this.hidden = value;
|
|
50
|
+
});
|
|
51
|
+
const model: any = {
|
|
52
|
+
uid: 'menu-item-1',
|
|
53
|
+
props: {},
|
|
54
|
+
hidden: false,
|
|
55
|
+
__allModels: [],
|
|
56
|
+
setProps,
|
|
57
|
+
setHidden,
|
|
58
|
+
};
|
|
59
|
+
const ctx: any = {
|
|
60
|
+
app: { jsonLogic: { apply: () => true } },
|
|
61
|
+
model,
|
|
62
|
+
t: (text: string) => text,
|
|
63
|
+
getAction: (name: string) => (name === 'linkageSetMenuItemProps' ? linkageSetMenuItemProps : undefined),
|
|
64
|
+
resolveJsonTemplate: vi.fn(async (value: any) => value),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
await menuLinkageRules.handler(ctx, {
|
|
68
|
+
value: [
|
|
69
|
+
{
|
|
70
|
+
key: 'r1',
|
|
71
|
+
title: 'Hide menu item',
|
|
72
|
+
enable: true,
|
|
73
|
+
condition: { logic: '$and', items: [] },
|
|
74
|
+
actions: [
|
|
75
|
+
{
|
|
76
|
+
key: 'a1',
|
|
77
|
+
name: 'linkageSetMenuItemProps',
|
|
78
|
+
params: {
|
|
79
|
+
value: 'hidden',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(setHidden).toHaveBeenCalledWith(true);
|
|
88
|
+
expect(model.hidden).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -42,9 +42,11 @@ export {
|
|
|
42
42
|
detailsFieldLinkageRules,
|
|
43
43
|
linkageSetDetailsFieldProps,
|
|
44
44
|
actionLinkageRules,
|
|
45
|
+
menuLinkageRules,
|
|
45
46
|
blockLinkageRules,
|
|
46
47
|
linkageSetBlockProps,
|
|
47
48
|
linkageSetActionProps,
|
|
49
|
+
linkageSetMenuItemProps,
|
|
48
50
|
linkageSetFieldProps,
|
|
49
51
|
subFormLinkageSetFieldProps,
|
|
50
52
|
linkageAssignField,
|