@nocobase/client-v2 2.3.0-beta.6 → 2.4.0-alpha.2
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/acl/ACLProvider.d.ts +10 -0
- package/es/acl/aclCheckReadiness.d.ts +11 -0
- package/es/authRedirect.d.ts +11 -2
- package/es/components/AttachmentUpload.d.ts +51 -0
- package/es/components/index.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/NocoBaseLogo.d.ts +10 -0
- package/es/index.d.ts +4 -1
- package/es/index.mjs +129 -118
- package/es/nocobase-buildin-plugin/currentUserAuthStatus.d.ts +11 -0
- package/es/nocobase-buildin-plugin/index.d.ts +10 -0
- package/es/settings-app/SettingsApplication.d.ts +18 -0
- package/es/settings-app/SettingsBrand.d.ts +16 -0
- package/es/settings-app/SettingsBuildInPlugin.d.ts +9 -0
- package/es/settings-app/SettingsDocumentRedirect.d.ts +10 -0
- package/es/settings-app/SettingsGroupNav.d.ts +18 -0
- package/es/settings-app/SettingsPluginSettingsManager.d.ts +14 -0
- package/es/settings-app/SettingsRouterManager.d.ts +15 -0
- package/es/settings-app/SettingsSearch.d.ts +17 -0
- package/es/settings-app/SettingsShell.d.ts +10 -0
- package/es/settings-app/isSettingsApp.d.ts +18 -0
- package/es/settings-app/runtimePaths.d.ts +18 -0
- package/es/settings-app/settingsDocumentPath.d.ts +14 -0
- package/es/settings-app/settingsTheme.d.ts +87 -0
- package/es/settings-app/useSettingsThemeConfig.d.ts +22 -0
- package/es/settings-center/AdminSettingsLayout.d.ts +2 -2
- package/es/settings-center/groups.d.ts +66 -0
- package/es/settings-center/useSettingsGroups.d.ts +36 -0
- package/es/settings-center/useSettingsSearch.d.ts +35 -0
- package/es/settings-center/utils.d.ts +24 -0
- package/es/theme/index.d.ts +1 -0
- package/es/theme/itemActive.d.ts +24 -0
- package/es/theme/type.d.ts +9 -0
- package/lib/index.js +157 -146
- package/package.json +7 -7
- package/src/__tests__/app.test.tsx +122 -3
- package/src/__tests__/authRedirect.test.ts +154 -0
- package/src/__tests__/browserChecker.test.ts +172 -6
- package/src/__tests__/mockSettingsApplication.ts +29 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +81 -8
- package/src/__tests__/plugin-manager.test.tsx +7 -6
- package/src/__tests__/settings-application.test.ts +164 -0
- package/src/__tests__/settings-auth-routes.test.ts +71 -0
- package/src/__tests__/settings-center.test.tsx +168 -130
- package/src/__tests__/settings-layout-root.test.tsx +600 -0
- package/src/__tests__/settings-runtime-paths.test.ts +99 -0
- package/src/__tests__/settings-search-shortcut.test.tsx +321 -0
- package/src/__tests__/settings-shell.test.tsx +261 -0
- package/src/acl/ACLProvider.tsx +53 -3
- package/src/acl/aclCheckReadiness.ts +44 -0
- package/src/authRedirect.ts +85 -8
- package/src/components/AppComponents.tsx +16 -7
- package/src/components/AttachmentUpload.tsx +275 -0
- package/src/components/index.ts +1 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +1 -56
- package/src/flow/admin-shell/admin-layout/NocoBaseLogo.tsx +77 -0
- package/src/flow/admin-shell/admin-layout/TopbarActionsBar.tsx +37 -14
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +77 -13
- package/src/flow/internal/components/Markdown/DisplayMarkdown.tsx +14 -9
- package/src/flow/internal/components/Markdown/__tests__/DisplayMarkdown.test.tsx +46 -0
- package/src/flow/models/fields/DisplayHtmlFieldModel.tsx +2 -1
- package/src/flow/models/fields/RichTextFieldModel/__tests__/RichTextFieldModel.test.tsx +74 -0
- package/src/flow/models/fields/RichTextFieldModel/index.tsx +27 -6
- package/src/flow/models/topbar/TopbarActionModel.tsx +52 -180
- package/src/index.ts +11 -1
- package/src/nocobase-buildin-plugin/currentUserAuthStatus.ts +58 -0
- package/src/nocobase-buildin-plugin/index.tsx +112 -71
- package/src/settings-app/SettingsApplication.ts +31 -0
- package/src/settings-app/SettingsBrand.tsx +108 -0
- package/src/settings-app/SettingsBuildInPlugin.ts +10 -0
- package/src/settings-app/SettingsDocumentRedirect.tsx +26 -0
- package/src/settings-app/SettingsGroupNav.tsx +124 -0
- package/src/settings-app/SettingsPluginSettingsManager.ts +38 -0
- package/src/settings-app/SettingsRouterManager.ts +92 -0
- package/src/settings-app/SettingsSearch.tsx +343 -0
- package/src/settings-app/SettingsShell.tsx +184 -0
- package/src/settings-app/isSettingsApp.ts +21 -0
- package/src/settings-app/runtimePaths.ts +103 -0
- package/src/settings-app/settingsDocumentPath.ts +66 -0
- package/src/settings-app/settingsTheme.ts +369 -0
- package/src/settings-app/useSettingsThemeConfig.ts +100 -0
- package/src/settings-center/AdminSettingsLayout.tsx +154 -169
- package/src/settings-center/SystemSettingsPage.tsx +4 -170
- package/src/settings-center/__tests__/groups.test.ts +25 -0
- package/src/settings-center/groups.ts +108 -0
- package/src/settings-center/useSettingsGroups.ts +105 -0
- package/src/settings-center/useSettingsSearch.ts +173 -0
- package/src/settings-center/utils.tsx +50 -1
- package/src/theme/index.tsx +1 -0
- package/src/theme/itemActive.ts +31 -0
- package/src/theme/type.ts +10 -0
|
@@ -7,21 +7,17 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
createMockClient,
|
|
13
|
-
NocoBaseDesktopRouteType,
|
|
14
|
-
type NocoBaseDesktopRoute,
|
|
15
|
-
Plugin,
|
|
16
|
-
} from '@nocobase/client-v2';
|
|
17
|
-
import { act, render, screen, waitFor, fireEvent } from '@testing-library/react';
|
|
10
|
+
import { ACLRolesCheckProvider, Plugin } from '@nocobase/client-v2';
|
|
11
|
+
import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
|
18
12
|
import React from 'react';
|
|
19
13
|
import { message } from 'antd';
|
|
20
14
|
import { vi } from 'vitest';
|
|
21
15
|
import { AdminSettingsLayoutModel as ClientV2AdminSettingsLayoutModel } from '../settings-center';
|
|
22
16
|
import { AdminSettingsLayoutModel as ClientV1AdminSettingsLayoutModel } from '../../../client/src/pm/AdminSettingsLayoutModel';
|
|
23
|
-
import
|
|
17
|
+
import zhCN from '../../../client/src/locale/zh-CN.json';
|
|
18
|
+
import { SettingsBuildInPlugin } from '../settings-app/SettingsBuildInPlugin';
|
|
24
19
|
import { matchSettingsRoute, sortTopLevelSettings } from '../settings-center/utils';
|
|
20
|
+
import { createMockSettingsClient } from './mockSettingsApplication';
|
|
25
21
|
|
|
26
22
|
class TestAclPlugin extends Plugin {
|
|
27
23
|
async load() {
|
|
@@ -29,7 +25,7 @@ class TestAclPlugin extends Plugin {
|
|
|
29
25
|
}
|
|
30
26
|
}
|
|
31
27
|
|
|
32
|
-
type MockClientApplication = ReturnType<typeof
|
|
28
|
+
type MockClientApplication = ReturnType<typeof createMockSettingsClient>;
|
|
33
29
|
|
|
34
30
|
const renderApp = async (app: MockClientApplication) => {
|
|
35
31
|
const Root = app.getRootComponent();
|
|
@@ -50,17 +46,19 @@ const waitForGetRequests = async (app: MockClientApplication, urls: string[]) =>
|
|
|
50
46
|
const mockAdminRuntime = (
|
|
51
47
|
app: MockClientApplication,
|
|
52
48
|
options: {
|
|
49
|
+
lang?: string;
|
|
53
50
|
snippets?: string[];
|
|
54
51
|
pmList?: any[];
|
|
52
|
+
resources?: Record<string, string>;
|
|
55
53
|
systemSettings?: Record<string, any>;
|
|
56
|
-
desktopRoutes?: NocoBaseDesktopRoute[];
|
|
57
54
|
} = {},
|
|
58
55
|
) => {
|
|
59
56
|
const {
|
|
57
|
+
lang = 'en-US',
|
|
60
58
|
snippets = ['pm', 'pm.system-settings.system-settings'],
|
|
61
59
|
pmList = [],
|
|
60
|
+
resources = {},
|
|
62
61
|
systemSettings = {},
|
|
63
|
-
desktopRoutes = [],
|
|
64
62
|
} = options;
|
|
65
63
|
|
|
66
64
|
app.dataSourceManager.getCollection = ((name: string, collectionName: string) => {
|
|
@@ -86,9 +84,9 @@ const mockAdminRuntime = (
|
|
|
86
84
|
});
|
|
87
85
|
app.apiMock.onGet('app:getLang').reply(200, {
|
|
88
86
|
data: {
|
|
89
|
-
lang
|
|
87
|
+
lang,
|
|
90
88
|
resources: {
|
|
91
|
-
client:
|
|
89
|
+
client: resources,
|
|
92
90
|
},
|
|
93
91
|
cron: {},
|
|
94
92
|
},
|
|
@@ -105,7 +103,7 @@ const mockAdminRuntime = (
|
|
|
105
103
|
},
|
|
106
104
|
});
|
|
107
105
|
app.apiMock.onGet('/desktopRoutes:listAccessible').reply(200, {
|
|
108
|
-
data:
|
|
106
|
+
data: [],
|
|
109
107
|
});
|
|
110
108
|
app.apiMock.onGet('systemSettings:get').reply(200, {
|
|
111
109
|
data: {
|
|
@@ -187,10 +185,10 @@ describe('settings center', () => {
|
|
|
187
185
|
expect(sortTopLevelSettings(settings).map((item) => item.name)).toEqual(['api-keys', 'backups', 'system-settings']);
|
|
188
186
|
});
|
|
189
187
|
|
|
190
|
-
it('should redirect /
|
|
191
|
-
const app =
|
|
192
|
-
plugins: [
|
|
193
|
-
router: { type: 'memory', initialEntries: ['/
|
|
188
|
+
it('should redirect /settings to system-settings by default', async () => {
|
|
189
|
+
const app = createMockSettingsClient({
|
|
190
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin],
|
|
191
|
+
router: { type: 'memory', initialEntries: ['/settings'] },
|
|
194
192
|
});
|
|
195
193
|
mockAdminRuntime(app);
|
|
196
194
|
|
|
@@ -201,9 +199,9 @@ describe('settings center', () => {
|
|
|
201
199
|
});
|
|
202
200
|
|
|
203
201
|
it('should expose current language variable as enabled-language selector', async () => {
|
|
204
|
-
const app =
|
|
205
|
-
plugins: [
|
|
206
|
-
router: { type: 'memory', initialEntries: ['/
|
|
202
|
+
const app = createMockSettingsClient({
|
|
203
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin],
|
|
204
|
+
router: { type: 'memory', initialEntries: ['/settings/system-settings'] },
|
|
207
205
|
});
|
|
208
206
|
mockAdminRuntime(app, {
|
|
209
207
|
systemSettings: {
|
|
@@ -231,9 +229,9 @@ describe('settings center', () => {
|
|
|
231
229
|
});
|
|
232
230
|
|
|
233
231
|
it('should fallback to plugin-manager when system-settings is not allowed', async () => {
|
|
234
|
-
const app =
|
|
235
|
-
plugins: [
|
|
236
|
-
router: { type: 'memory', initialEntries: ['/
|
|
232
|
+
const app = createMockSettingsClient({
|
|
233
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin],
|
|
234
|
+
router: { type: 'memory', initialEntries: ['/settings'] },
|
|
237
235
|
});
|
|
238
236
|
mockAdminRuntime(app, {
|
|
239
237
|
snippets: ['pm', '!pm.system-settings.system-settings'],
|
|
@@ -256,43 +254,54 @@ describe('settings center', () => {
|
|
|
256
254
|
expect(await screen.findByText('Demo plugin')).toBeInTheDocument();
|
|
257
255
|
});
|
|
258
256
|
|
|
259
|
-
it('should redirect to the
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
schemaUid: 'first-accessible-page',
|
|
270
|
-
title: 'First accessible page',
|
|
271
|
-
type: NocoBaseDesktopRouteType.flowPage,
|
|
272
|
-
},
|
|
273
|
-
{
|
|
274
|
-
id: 2,
|
|
275
|
-
schemaUid: 'second-accessible-page',
|
|
276
|
-
title: 'Second accessible page',
|
|
277
|
-
type: NocoBaseDesktopRouteType.flowPage,
|
|
278
|
-
},
|
|
279
|
-
],
|
|
257
|
+
it('should redirect to the admin app when the role cannot access settings', async () => {
|
|
258
|
+
const originalLocation = globalThis.window.location;
|
|
259
|
+
const originalModernClientPrefix = window.__nocobase_modern_client_prefix__;
|
|
260
|
+
const replace = vi.fn();
|
|
261
|
+
Object.defineProperty(globalThis.window, 'location', {
|
|
262
|
+
configurable: true,
|
|
263
|
+
value: {
|
|
264
|
+
...originalLocation,
|
|
265
|
+
replace,
|
|
266
|
+
},
|
|
280
267
|
});
|
|
268
|
+
window.__nocobase_modern_client_prefix__ = 'v';
|
|
281
269
|
|
|
282
|
-
|
|
283
|
-
|
|
270
|
+
try {
|
|
271
|
+
const app = createMockSettingsClient({
|
|
272
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin],
|
|
273
|
+
router: { type: 'memory', initialEntries: ['/settings/system-settings'] },
|
|
274
|
+
});
|
|
275
|
+
mockAdminRuntime(app, {
|
|
276
|
+
snippets: ['!pm', '!pm.system-settings.system-settings'],
|
|
277
|
+
});
|
|
284
278
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
279
|
+
await renderApp(app);
|
|
280
|
+
await waitForGetRequests(app, ['/auth:check', 'roles:check']);
|
|
281
|
+
|
|
282
|
+
await waitFor(() => {
|
|
283
|
+
expect(replace).toHaveBeenCalledWith('/v/admin');
|
|
284
|
+
});
|
|
285
|
+
expect(
|
|
286
|
+
screen.queryByRole('heading', { name: 'Your current role cannot access Settings' }),
|
|
287
|
+
).not.toBeInTheDocument();
|
|
288
|
+
} finally {
|
|
289
|
+
Object.defineProperty(globalThis.window, 'location', {
|
|
290
|
+
configurable: true,
|
|
291
|
+
value: originalLocation,
|
|
292
|
+
});
|
|
293
|
+
if (originalModernClientPrefix === undefined) {
|
|
294
|
+
delete window.__nocobase_modern_client_prefix__;
|
|
295
|
+
} else {
|
|
296
|
+
window.__nocobase_modern_client_prefix__ = originalModernClientPrefix;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
290
299
|
});
|
|
291
300
|
|
|
292
301
|
it('should hide plugin-manager menu item when pm snippet is missing', async () => {
|
|
293
|
-
const app =
|
|
294
|
-
plugins: [
|
|
295
|
-
router: { type: 'memory', initialEntries: ['/
|
|
302
|
+
const app = createMockSettingsClient({
|
|
303
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin],
|
|
304
|
+
router: { type: 'memory', initialEntries: ['/settings/system-settings'] },
|
|
296
305
|
});
|
|
297
306
|
mockAdminRuntime(app, {
|
|
298
307
|
snippets: ['pm.system-settings.system-settings'],
|
|
@@ -306,27 +315,23 @@ describe('settings center', () => {
|
|
|
306
315
|
});
|
|
307
316
|
|
|
308
317
|
it('should show route empty state for unknown settings routes', async () => {
|
|
309
|
-
const app =
|
|
310
|
-
plugins: [
|
|
311
|
-
router: { type: 'memory', initialEntries: ['/
|
|
318
|
+
const app = createMockSettingsClient({
|
|
319
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin],
|
|
320
|
+
router: { type: 'memory', initialEntries: ['/settings/unknown'] },
|
|
312
321
|
});
|
|
313
322
|
mockAdminRuntime(app, {
|
|
314
323
|
snippets: ['!pm', '!pm.system-settings.system-settings'],
|
|
315
|
-
desktopRoutes: [
|
|
316
|
-
{
|
|
317
|
-
id: 1,
|
|
318
|
-
schemaUid: 'first-accessible-page',
|
|
319
|
-
title: 'First accessible page',
|
|
320
|
-
type: NocoBaseDesktopRouteType.flowPage,
|
|
321
|
-
},
|
|
322
|
-
],
|
|
323
324
|
});
|
|
324
325
|
|
|
325
326
|
await renderApp(app);
|
|
326
327
|
await waitForGetRequests(app, ['/auth:check', 'roles:check']);
|
|
327
328
|
|
|
328
|
-
|
|
329
|
-
expect(
|
|
329
|
+
const result = await screen.findByRole('status');
|
|
330
|
+
expect(within(result).getByRole('heading', { name: 'Settings page not found' })).toBeInTheDocument();
|
|
331
|
+
expect(
|
|
332
|
+
within(result).getByText('The settings page you requested does not exist or has been removed.'),
|
|
333
|
+
).toBeInTheDocument();
|
|
334
|
+
expect(app.router.state.location.pathname).toBe('/settings/unknown');
|
|
330
335
|
});
|
|
331
336
|
|
|
332
337
|
it('should allow direct access to hidden page without showing menu entry', async () => {
|
|
@@ -343,27 +348,18 @@ describe('settings center', () => {
|
|
|
343
348
|
}
|
|
344
349
|
}
|
|
345
350
|
|
|
346
|
-
const app =
|
|
347
|
-
plugins: [
|
|
348
|
-
router: { type: 'memory', initialEntries: ['/
|
|
349
|
-
});
|
|
350
|
-
mockAdminRuntime(app, {
|
|
351
|
-
desktopRoutes: [
|
|
352
|
-
{
|
|
353
|
-
id: 1,
|
|
354
|
-
schemaUid: 'first-accessible-page',
|
|
355
|
-
title: 'First accessible page',
|
|
356
|
-
type: NocoBaseDesktopRouteType.flowPage,
|
|
357
|
-
},
|
|
358
|
-
],
|
|
351
|
+
const app = createMockSettingsClient({
|
|
352
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin, HiddenSettingsPlugin],
|
|
353
|
+
router: { type: 'memory', initialEntries: ['/settings/hidden-demo'] },
|
|
359
354
|
});
|
|
355
|
+
mockAdminRuntime(app);
|
|
360
356
|
|
|
361
357
|
await renderApp(app);
|
|
362
358
|
await waitForGetRequests(app, ['/auth:check', 'roles:check']);
|
|
363
359
|
|
|
364
360
|
expect(await screen.findByText('Hidden settings page')).toBeInTheDocument();
|
|
365
361
|
expect(screen.queryByRole('menuitem', { name: 'Hidden demo' })).not.toBeInTheDocument();
|
|
366
|
-
expect(app.router.state.location.pathname).toBe('/
|
|
362
|
+
expect(app.router.state.location.pathname).toBe('/settings/hidden-demo');
|
|
367
363
|
});
|
|
368
364
|
|
|
369
365
|
it('should show route empty state when direct access page has no permission', async () => {
|
|
@@ -380,28 +376,24 @@ describe('settings center', () => {
|
|
|
380
376
|
}
|
|
381
377
|
}
|
|
382
378
|
|
|
383
|
-
const app =
|
|
384
|
-
plugins: [
|
|
385
|
-
router: { type: 'memory', initialEntries: ['/
|
|
379
|
+
const app = createMockSettingsClient({
|
|
380
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin, ProtectedSettingsPlugin],
|
|
381
|
+
router: { type: 'memory', initialEntries: ['/settings/secure-demo'] },
|
|
386
382
|
});
|
|
387
383
|
mockAdminRuntime(app, {
|
|
384
|
+
lang: 'zh-CN',
|
|
385
|
+
resources: zhCN,
|
|
388
386
|
snippets: ['pm', 'pm.system-settings.system-settings', '!pm.secure-demo.index'],
|
|
389
|
-
desktopRoutes: [
|
|
390
|
-
{
|
|
391
|
-
id: 1,
|
|
392
|
-
schemaUid: 'first-accessible-page',
|
|
393
|
-
title: 'First accessible page',
|
|
394
|
-
type: NocoBaseDesktopRouteType.flowPage,
|
|
395
|
-
},
|
|
396
|
-
],
|
|
397
387
|
});
|
|
398
388
|
|
|
399
389
|
await renderApp(app);
|
|
400
390
|
await waitForGetRequests(app, ['/auth:check', 'roles:check']);
|
|
401
391
|
|
|
402
|
-
|
|
392
|
+
const result = await screen.findByRole('status');
|
|
393
|
+
expect(within(result).getByRole('heading', { name: '当前角色无权访问设置中心' })).toBeInTheDocument();
|
|
394
|
+
expect(within(result).getByText('请切换至有权限的角色,或联系管理员获取访问权限。')).toBeInTheDocument();
|
|
403
395
|
expect(screen.queryByText('Secure settings page')).not.toBeInTheDocument();
|
|
404
|
-
expect(app.router.state.location.pathname).toBe('/
|
|
396
|
+
expect(app.router.state.location.pathname).toBe('/settings/secure-demo');
|
|
405
397
|
});
|
|
406
398
|
|
|
407
399
|
it('should redirect a denied settings tab to the first accessible tab', async () => {
|
|
@@ -437,9 +429,9 @@ describe('settings center', () => {
|
|
|
437
429
|
}
|
|
438
430
|
}
|
|
439
431
|
|
|
440
|
-
const app =
|
|
441
|
-
plugins: [
|
|
442
|
-
router: { type: 'memory', initialEntries: ['/
|
|
432
|
+
const app = createMockSettingsClient({
|
|
433
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin, ProtectedSettingsTabsPlugin],
|
|
434
|
+
router: { type: 'memory', initialEntries: ['/settings/protected-tabs/denied'] },
|
|
443
435
|
});
|
|
444
436
|
mockAdminRuntime(app, {
|
|
445
437
|
snippets: ['pm', '!pm.protected-tabs.denied'],
|
|
@@ -449,7 +441,7 @@ describe('settings center', () => {
|
|
|
449
441
|
await waitForGetRequests(app, ['/auth:check', 'roles:check']);
|
|
450
442
|
|
|
451
443
|
await waitFor(() => {
|
|
452
|
-
expect(app.router.router.state.location.pathname).toBe('/
|
|
444
|
+
expect(app.router.router.state.location.pathname).toBe('/settings/protected-tabs/z-first');
|
|
453
445
|
});
|
|
454
446
|
expect(app.router.router.state.historyAction).toBe('REPLACE');
|
|
455
447
|
expect(await screen.findByText('First accessible tab content')).toBeInTheDocument();
|
|
@@ -457,13 +449,54 @@ describe('settings center', () => {
|
|
|
457
449
|
expect(renderDeniedTab).not.toHaveBeenCalled();
|
|
458
450
|
|
|
459
451
|
await act(async () => {
|
|
460
|
-
await app.router.router.navigate('/
|
|
452
|
+
await app.router.router.navigate('/settings/protected-tabs/a-second');
|
|
461
453
|
});
|
|
462
454
|
|
|
463
455
|
expect(await screen.findByText('Second accessible tab content')).toBeInTheDocument();
|
|
464
|
-
expect(app.router.router.state.location.pathname).toBe('/
|
|
456
|
+
expect(app.router.router.state.location.pathname).toBe('/settings/protected-tabs/a-second');
|
|
465
457
|
});
|
|
466
458
|
|
|
459
|
+
it.each(['/settings/fallback-access', '/settings/fallback-access/'])(
|
|
460
|
+
'should redirect a denied index page to an accessible sibling from %s',
|
|
461
|
+
async (initialEntry) => {
|
|
462
|
+
class FallbackAccessSettingsPlugin extends Plugin {
|
|
463
|
+
async load() {
|
|
464
|
+
this.pluginSettingsManager.addMenuItem({ key: 'fallback-access', title: 'Fallback access' });
|
|
465
|
+
this.pluginSettingsManager.addPageTabItem({
|
|
466
|
+
menuKey: 'fallback-access',
|
|
467
|
+
key: 'index',
|
|
468
|
+
title: 'Restricted index settings',
|
|
469
|
+
aclSnippet: 'pm.fallback-access.index',
|
|
470
|
+
Component: () => <div>Restricted index settings page</div>,
|
|
471
|
+
});
|
|
472
|
+
this.pluginSettingsManager.addPageTabItem({
|
|
473
|
+
menuKey: 'fallback-access',
|
|
474
|
+
key: 'allowed',
|
|
475
|
+
title: 'Allowed fallback settings',
|
|
476
|
+
Component: () => <div>Allowed fallback settings page</div>,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const app = createMockSettingsClient({
|
|
482
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin, FallbackAccessSettingsPlugin],
|
|
483
|
+
router: { type: 'memory', initialEntries: [initialEntry] },
|
|
484
|
+
});
|
|
485
|
+
mockAdminRuntime(app, {
|
|
486
|
+
snippets: ['pm', 'pm.system-settings.system-settings', '!pm.fallback-access.index'],
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
await renderApp(app);
|
|
490
|
+
await waitForGetRequests(app, ['/auth:check', 'roles:check']);
|
|
491
|
+
|
|
492
|
+
expect(await screen.findByText('Allowed fallback settings page')).toBeInTheDocument();
|
|
493
|
+
expect(
|
|
494
|
+
screen.queryByRole('heading', { name: 'Your current role cannot access Settings' }),
|
|
495
|
+
).not.toBeInTheDocument();
|
|
496
|
+
expect(screen.queryByText('Restricted index settings page')).not.toBeInTheDocument();
|
|
497
|
+
},
|
|
498
|
+
);
|
|
499
|
+
|
|
467
500
|
it('should skip an accessible dynamic tab when its route params cannot be resolved', async () => {
|
|
468
501
|
class DynamicSettingsTabsPlugin extends Plugin {
|
|
469
502
|
async load() {
|
|
@@ -495,9 +528,9 @@ describe('settings center', () => {
|
|
|
495
528
|
}
|
|
496
529
|
}
|
|
497
530
|
|
|
498
|
-
const app =
|
|
499
|
-
plugins: [
|
|
500
|
-
router: { type: 'memory', initialEntries: ['/
|
|
531
|
+
const app = createMockSettingsClient({
|
|
532
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin, DynamicSettingsTabsPlugin],
|
|
533
|
+
router: { type: 'memory', initialEntries: ['/settings/dynamic-tabs/denied'] },
|
|
501
534
|
});
|
|
502
535
|
mockAdminRuntime(app, {
|
|
503
536
|
snippets: ['pm', '!pm.dynamic-tabs.denied'],
|
|
@@ -507,7 +540,7 @@ describe('settings center', () => {
|
|
|
507
540
|
await waitForGetRequests(app, ['/auth:check', 'roles:check']);
|
|
508
541
|
|
|
509
542
|
await waitFor(() => {
|
|
510
|
-
expect(app.router.router.state.location.pathname).toBe('/
|
|
543
|
+
expect(app.router.router.state.location.pathname).toBe('/settings/dynamic-tabs/fallback');
|
|
511
544
|
});
|
|
512
545
|
expect(await screen.findByText('Static fallback tab content')).toBeInTheDocument();
|
|
513
546
|
});
|
|
@@ -535,9 +568,9 @@ describe('settings center', () => {
|
|
|
535
568
|
}
|
|
536
569
|
}
|
|
537
570
|
|
|
538
|
-
const app =
|
|
539
|
-
plugins: [
|
|
540
|
-
router: { type: 'memory', initialEntries: ['/
|
|
571
|
+
const app = createMockSettingsClient({
|
|
572
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin, DynamicSiblingTabsPlugin],
|
|
573
|
+
router: { type: 'memory', initialEntries: ['/settings/dynamic-siblings/email:primary/logs'] },
|
|
541
574
|
});
|
|
542
575
|
mockAdminRuntime(app, {
|
|
543
576
|
snippets: ['pm', '!pm.dynamic-siblings.logs'],
|
|
@@ -547,7 +580,7 @@ describe('settings center', () => {
|
|
|
547
580
|
await waitForGetRequests(app, ['/auth:check', 'roles:check']);
|
|
548
581
|
|
|
549
582
|
await waitFor(() => {
|
|
550
|
-
expect(app.router.router.state.location.pathname).toBe('/
|
|
583
|
+
expect(app.router.router.state.location.pathname).toBe('/settings/dynamic-siblings/email:primary/channels');
|
|
551
584
|
});
|
|
552
585
|
expect(await screen.findByText('Dynamic channels tab content')).toBeInTheDocument();
|
|
553
586
|
});
|
|
@@ -575,9 +608,9 @@ describe('settings center', () => {
|
|
|
575
608
|
}
|
|
576
609
|
}
|
|
577
610
|
|
|
578
|
-
const app =
|
|
579
|
-
plugins: [
|
|
580
|
-
router: { type: 'memory', initialEntries: ['/
|
|
611
|
+
const app = createMockSettingsClient({
|
|
612
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin, HyphenatedParamTabsPlugin],
|
|
613
|
+
router: { type: 'memory', initialEntries: ['/settings/hyphenated-params/email/logs'] },
|
|
581
614
|
});
|
|
582
615
|
mockAdminRuntime(app, {
|
|
583
616
|
snippets: ['pm', '!pm.hyphenated-params.logs'],
|
|
@@ -587,7 +620,7 @@ describe('settings center', () => {
|
|
|
587
620
|
await waitForGetRequests(app, ['/auth:check', 'roles:check']);
|
|
588
621
|
|
|
589
622
|
await waitFor(() => {
|
|
590
|
-
expect(app.router.router.state.location.pathname).toBe('/
|
|
623
|
+
expect(app.router.router.state.location.pathname).toBe('/settings/hyphenated-params/email/channels');
|
|
591
624
|
});
|
|
592
625
|
expect(await screen.findByText('Hyphenated channels tab content')).toBeInTheDocument();
|
|
593
626
|
});
|
|
@@ -609,9 +642,9 @@ describe('settings center', () => {
|
|
|
609
642
|
}
|
|
610
643
|
}
|
|
611
644
|
|
|
612
|
-
const app =
|
|
613
|
-
plugins: [
|
|
614
|
-
router: { type: 'memory', initialEntries: ['/
|
|
645
|
+
const app = createMockSettingsClient({
|
|
646
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin, MenuAclPlugin],
|
|
647
|
+
router: { type: 'memory', initialEntries: ['/settings/menu-acl-demo'] },
|
|
615
648
|
});
|
|
616
649
|
mockAdminRuntime(app, {
|
|
617
650
|
snippets: ['pm', 'pm.system-settings.system-settings', '!pm.menu-acl-demo.menu'],
|
|
@@ -621,10 +654,13 @@ describe('settings center', () => {
|
|
|
621
654
|
await waitForGetRequests(app, ['/auth:check', 'roles:check']);
|
|
622
655
|
|
|
623
656
|
expect(await screen.findByText('Menu ACL child page')).toBeInTheDocument();
|
|
624
|
-
|
|
657
|
+
// The sidebar is gone; this group is reached from the "other settings" hover
|
|
658
|
+
// dropdown in the top bar. The dropdown only renders on hover, so this just
|
|
659
|
+
// checks the group entry survived (ACL did not drop the whole menu).
|
|
660
|
+
expect(screen.getByRole('menuitem', { name: 'Other settings' })).toBeInTheDocument();
|
|
625
661
|
});
|
|
626
662
|
|
|
627
|
-
it('should
|
|
663
|
+
it('should keep rendering the active page when a group has many settings', async () => {
|
|
628
664
|
class ManySettingsPlugin extends Plugin {
|
|
629
665
|
async load() {
|
|
630
666
|
for (let index = 0; index < 30; index += 1) {
|
|
@@ -642,9 +678,9 @@ describe('settings center', () => {
|
|
|
642
678
|
}
|
|
643
679
|
}
|
|
644
680
|
|
|
645
|
-
const app =
|
|
646
|
-
plugins: [
|
|
647
|
-
router: { type: 'memory', initialEntries: ['/
|
|
681
|
+
const app = createMockSettingsClient({
|
|
682
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin, ManySettingsPlugin],
|
|
683
|
+
router: { type: 'memory', initialEntries: ['/settings/scroll-demo-29'] },
|
|
648
684
|
});
|
|
649
685
|
mockAdminRuntime(app);
|
|
650
686
|
|
|
@@ -653,14 +689,16 @@ describe('settings center', () => {
|
|
|
653
689
|
|
|
654
690
|
expect(await screen.findByText('Scroll demo page 29')).toBeInTheDocument();
|
|
655
691
|
|
|
656
|
-
|
|
657
|
-
|
|
692
|
+
// Without the sidebar there is no separate scroll area: all 30 settings live in
|
|
693
|
+
// the top bar dropdown and the page renders only the active one, so this just
|
|
694
|
+
// checks it still holds up.
|
|
695
|
+
expect(document.querySelector('.ant-layout-sider')).toBeNull();
|
|
658
696
|
});
|
|
659
697
|
|
|
660
698
|
it('should save system settings through systemSettings:put', async () => {
|
|
661
|
-
const app =
|
|
662
|
-
plugins: [
|
|
663
|
-
router: { type: 'memory', initialEntries: ['/
|
|
699
|
+
const app = createMockSettingsClient({
|
|
700
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin],
|
|
701
|
+
router: { type: 'memory', initialEntries: ['/settings/system-settings'] },
|
|
664
702
|
});
|
|
665
703
|
mockAdminRuntime(app);
|
|
666
704
|
|
|
@@ -677,9 +715,9 @@ describe('settings center', () => {
|
|
|
677
715
|
});
|
|
678
716
|
|
|
679
717
|
it('should block invalid logo uploads by storage rules', async () => {
|
|
680
|
-
const app =
|
|
681
|
-
plugins: [
|
|
682
|
-
router: { type: 'memory', initialEntries: ['/
|
|
718
|
+
const app = createMockSettingsClient({
|
|
719
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin],
|
|
720
|
+
router: { type: 'memory', initialEntries: ['/settings/system-settings'] },
|
|
683
721
|
});
|
|
684
722
|
const messageErrorSpy = vi.spyOn(message, 'error').mockImplementation(() => {
|
|
685
723
|
return undefined as any;
|