@nocobase/client-v2 2.3.0-beta.5 → 2.4.0-alpha.1
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
|
@@ -51,6 +51,42 @@ class AuthBootstrapRoutePlugin extends Plugin {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
class TestAdminPortalPlugin extends Plugin {
|
|
55
|
+
async load() {
|
|
56
|
+
this.app.layoutManager.registerLayout({
|
|
57
|
+
routeName: 'admin',
|
|
58
|
+
routePath: '/admin',
|
|
59
|
+
uid: 'admin-layout-model',
|
|
60
|
+
layoutModelClass: 'AdminLayoutModel',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
class TestRootPortalPlugin extends Plugin {
|
|
66
|
+
async load() {
|
|
67
|
+
this.router.add('root', {
|
|
68
|
+
path: '/',
|
|
69
|
+
authCheck: true,
|
|
70
|
+
Component: () => <div>portal landing</div>,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class TestScopedPortalPlugin extends Plugin {
|
|
76
|
+
async load() {
|
|
77
|
+
this.router.add('customer-portal', {
|
|
78
|
+
path: '/customer/*',
|
|
79
|
+
authCheck: true,
|
|
80
|
+
Component: () => <div>customer portal</div>,
|
|
81
|
+
});
|
|
82
|
+
this.router.add('customer-portal-signin', {
|
|
83
|
+
path: '/customer/signin',
|
|
84
|
+
skipAuthCheck: true,
|
|
85
|
+
Component: () => <div>customer portal signin</div>,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
54
90
|
describe('nocobase buildin plugin auth redirect', () => {
|
|
55
91
|
const originalLocation = globalThis.window.location;
|
|
56
92
|
|
|
@@ -85,6 +121,16 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
85
121
|
vi.restoreAllMocks();
|
|
86
122
|
});
|
|
87
123
|
|
|
124
|
+
it('should leave Admin portal registration to runtime plugins', async () => {
|
|
125
|
+
const app = createMockClient();
|
|
126
|
+
const plugin = new NocoBaseBuildInPlugin({}, app);
|
|
127
|
+
|
|
128
|
+
await plugin.load();
|
|
129
|
+
|
|
130
|
+
expect(app.layoutManager.hasLayout('admin')).toBe(false);
|
|
131
|
+
expect(app.router.has('root')).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
|
|
88
134
|
it('should navigate to v2 signin when /auth:check returns no user', async () => {
|
|
89
135
|
// Aligns with v1: use react-router navigate (virtual) rather than
|
|
90
136
|
// `window.location.replace`, so a `window.location.href` queued elsewhere
|
|
@@ -92,7 +138,7 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
92
138
|
// overridden.
|
|
93
139
|
const app = createMockClient({
|
|
94
140
|
publicPath: '/v2/',
|
|
95
|
-
plugins: [NocoBaseBuildInPlugin as any],
|
|
141
|
+
plugins: [NocoBaseBuildInPlugin as any, TestAdminPortalPlugin as typeof Plugin],
|
|
96
142
|
router: { type: 'memory', initialEntries: ['/v2/admin/7vu4c2sdk6h'] },
|
|
97
143
|
});
|
|
98
144
|
app.apiMock.onGet('app:getLang').reply(200, {
|
|
@@ -115,7 +161,7 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
115
161
|
// and race the 2FA response interceptor with its own signin redirect.
|
|
116
162
|
const app = createMockClient({
|
|
117
163
|
publicPath: '/v2/',
|
|
118
|
-
plugins: [NocoBaseBuildInPlugin as any],
|
|
164
|
+
plugins: [NocoBaseBuildInPlugin as any, TestAdminPortalPlugin as typeof Plugin],
|
|
119
165
|
router: { type: 'memory', initialEntries: ['/v2/admin'] },
|
|
120
166
|
});
|
|
121
167
|
app.apiClient.auth.setToken('test-token');
|
|
@@ -136,29 +182,56 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
136
182
|
expect(app.apiMock.history.post.filter((request) => request.url === 'auth:syncCookies')).toHaveLength(0);
|
|
137
183
|
});
|
|
138
184
|
|
|
139
|
-
it('should redirect unauthenticated
|
|
185
|
+
it('should redirect an unauthenticated Portal root to v2 signin', async () => {
|
|
140
186
|
const app = createMockClient({
|
|
141
187
|
publicPath: '/nocobase/v2/',
|
|
142
|
-
plugins: [NocoBaseBuildInPlugin as any],
|
|
188
|
+
plugins: [NocoBaseBuildInPlugin as any, TestRootPortalPlugin as typeof Plugin],
|
|
143
189
|
router: { type: 'memory', initialEntries: ['/nocobase/v2/'] },
|
|
144
190
|
});
|
|
145
191
|
app.apiMock.onGet('app:getLang').reply(200, {
|
|
146
192
|
data: { lang: 'en-US', resources: { client: {} }, cron: {} },
|
|
147
193
|
});
|
|
194
|
+
app.apiMock.onGet('/auth:check').reply(200, { data: {} });
|
|
148
195
|
|
|
149
196
|
const Root = app.getRootComponent();
|
|
150
197
|
render(<Root />);
|
|
151
198
|
|
|
152
199
|
await waitFor(() => {
|
|
153
200
|
expect(app.router.router.state.location.pathname).toBe('/nocobase/v2/signin');
|
|
154
|
-
expect(app.router.router.state.location.search).toBe('?redirect=%2Fnocobase%2Fv2
|
|
201
|
+
expect(app.router.router.state.location.search).toBe('?redirect=%2Fnocobase%2Fv2');
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should use an explicitly registered Portal signin route when unauthenticated', async () => {
|
|
206
|
+
const app = createMockClient({
|
|
207
|
+
publicPath: '/v2/',
|
|
208
|
+
plugins: [NocoBaseBuildInPlugin as unknown as typeof Plugin, TestScopedPortalPlugin as typeof Plugin],
|
|
209
|
+
router: { type: 'memory', initialEntries: ['/v2/customer/dashboard?tab=overview#panel'] },
|
|
210
|
+
});
|
|
211
|
+
app.apiMock.onGet('app:getLang').reply(200, {
|
|
212
|
+
data: { lang: 'en-US', resources: { client: {} }, cron: {} },
|
|
213
|
+
});
|
|
214
|
+
app.apiMock.onGet('/auth:check').reply(200, { data: {} });
|
|
215
|
+
|
|
216
|
+
const Root = app.getRootComponent();
|
|
217
|
+
render(<Root />);
|
|
218
|
+
|
|
219
|
+
await waitFor(() => {
|
|
220
|
+
expect(app.router.router.state.location.pathname).toBe('/v2/customer/signin');
|
|
221
|
+
expect(app.router.router.state.location.search).toBe(
|
|
222
|
+
'?redirect=%2Fv2%2Fcustomer%2Fdashboard%3Ftab%3Doverview%23panel',
|
|
223
|
+
);
|
|
155
224
|
});
|
|
156
225
|
});
|
|
157
226
|
|
|
158
227
|
it('should check current user after navigating from skipped route to v2 admin', async () => {
|
|
159
228
|
const app = createMockClient({
|
|
160
229
|
publicPath: '/v2/',
|
|
161
|
-
plugins: [
|
|
230
|
+
plugins: [
|
|
231
|
+
NocoBaseBuildInPlugin as any,
|
|
232
|
+
TestAdminPortalPlugin as typeof Plugin,
|
|
233
|
+
SkippedPublicRoutePlugin as typeof Plugin,
|
|
234
|
+
],
|
|
162
235
|
router: { type: 'memory', initialEntries: ['/v2/public'] },
|
|
163
236
|
});
|
|
164
237
|
app.apiMock.onGet('app:getLang').reply(200, {
|
|
@@ -385,7 +458,7 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
385
458
|
it('should render v2 admin root without redirecting away', async () => {
|
|
386
459
|
const app = createMockClient({
|
|
387
460
|
publicPath: '/v2/',
|
|
388
|
-
plugins: [NocoBaseBuildInPlugin as any],
|
|
461
|
+
plugins: [NocoBaseBuildInPlugin as any, TestAdminPortalPlugin as typeof Plugin],
|
|
389
462
|
router: { type: 'memory', initialEntries: ['/v2/admin'] },
|
|
390
463
|
});
|
|
391
464
|
app.apiMock.onGet('app:getLang').reply(200, {
|
|
@@ -419,7 +492,7 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
419
492
|
async (pathname) => {
|
|
420
493
|
const app = createMockClient({
|
|
421
494
|
publicPath: '/v2/',
|
|
422
|
-
plugins: [NocoBaseBuildInPlugin as any],
|
|
495
|
+
plugins: [NocoBaseBuildInPlugin as any, TestAdminPortalPlugin as typeof Plugin],
|
|
423
496
|
router: { type: 'memory', initialEntries: [pathname] },
|
|
424
497
|
});
|
|
425
498
|
app.apiMock.onGet('app:getLang').reply(200, {
|
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { ACLRolesCheckProvider,
|
|
10
|
+
import { ACLRolesCheckProvider, Plugin } from '@nocobase/client-v2';
|
|
11
11
|
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
|
12
12
|
import React from 'react';
|
|
13
|
-
import {
|
|
13
|
+
import { SettingsBuildInPlugin } from '../settings-app/SettingsBuildInPlugin';
|
|
14
|
+
import { createMockSettingsClient } from './mockSettingsApplication';
|
|
14
15
|
|
|
15
16
|
class TestAclPlugin extends Plugin {
|
|
16
17
|
async load() {
|
|
@@ -31,7 +32,7 @@ class TestSettingsLinkPlugin extends Plugin {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
type MockClientApplication = ReturnType<typeof
|
|
35
|
+
type MockClientApplication = ReturnType<typeof createMockSettingsClient>;
|
|
35
36
|
|
|
36
37
|
const renderApp = (app: MockClientApplication) => {
|
|
37
38
|
const Root = app.getRootComponent();
|
|
@@ -49,9 +50,9 @@ const waitForGetRequests = async (app: MockClientApplication, urls: string[]) =>
|
|
|
49
50
|
};
|
|
50
51
|
|
|
51
52
|
const setupApp = (pmList: any[], plugins: Array<typeof Plugin> = []) => {
|
|
52
|
-
const app =
|
|
53
|
-
plugins: [
|
|
54
|
-
router: { type: 'memory', initialEntries: ['/
|
|
53
|
+
const app = createMockSettingsClient({
|
|
54
|
+
plugins: [SettingsBuildInPlugin, TestAclPlugin, ...plugins],
|
|
55
|
+
router: { type: 'memory', initialEntries: ['/settings/plugin-manager'] },
|
|
55
56
|
});
|
|
56
57
|
|
|
57
58
|
app.apiMock.onGet('/auth:check').reply(200, {
|
|
@@ -0,0 +1,164 @@
|
|
|
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 MockAdapter from 'axios-mock-adapter';
|
|
11
|
+
import { describe, expect, it } from 'vitest';
|
|
12
|
+
import { Application } from '../index';
|
|
13
|
+
import { SettingsApplication } from '../settings-app/SettingsApplication';
|
|
14
|
+
import { SettingsBuildInPlugin } from '../settings-app/SettingsBuildInPlugin';
|
|
15
|
+
|
|
16
|
+
describe('SettingsApplication', () => {
|
|
17
|
+
it('uses the standalone settings route namespace', () => {
|
|
18
|
+
const adminApp = new Application({ router: { type: 'memory' }, ws: false });
|
|
19
|
+
const settingsApp = new SettingsApplication({ router: { type: 'memory' }, ws: false });
|
|
20
|
+
|
|
21
|
+
expect(adminApp.pluginSettingsManager.getRouteName('demo.index')).toBe('admin.settings.demo.index');
|
|
22
|
+
expect(adminApp.pluginSettingsManager.getRoutePath('demo.index')).toBe('/admin/settings/demo');
|
|
23
|
+
|
|
24
|
+
expect(settingsApp.pluginSettingsManager.getRouteName('demo.index')).toBe('settings.demo.index');
|
|
25
|
+
expect(settingsApp.pluginSettingsManager.getRoutePath('demo.index')).toBe('/settings/demo');
|
|
26
|
+
expect(settingsApp.pluginSettingsManager.getRoutePath('')).toBe('/settings/');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it.each(['apps', '_app'])('uses basename-relative paths for the %s sub-application Settings runtime', (scope) => {
|
|
30
|
+
const app = new SettingsApplication({
|
|
31
|
+
publicPath: '/nocobase/',
|
|
32
|
+
router: { type: 'memory', basename: `/nocobase/settings/${scope}/demo/` },
|
|
33
|
+
ws: false,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(app.pluginSettingsManager.getRouteName('demo.index')).toBe('settings.demo.index');
|
|
37
|
+
expect(app.pluginSettingsManager.getRoutePath('')).toBe('/');
|
|
38
|
+
expect(app.pluginSettingsManager.getRoutePath('demo.index')).toBe('/demo');
|
|
39
|
+
|
|
40
|
+
app.router.add('settings', { path: '/settings' });
|
|
41
|
+
app.router.add('settingsDetails.workflow.canvas', { path: '/settings/workflow/workflows/:id' });
|
|
42
|
+
app.router.add('settingsDetails.preview', { path: '/settings-preview/:id' });
|
|
43
|
+
app.router.add('auth.signin', { path: '/signin' });
|
|
44
|
+
|
|
45
|
+
expect(app.router.get('settings')).toMatchObject({ path: '/' });
|
|
46
|
+
expect(app.router.get('settingsDetails.workflow.canvas')).toMatchObject({
|
|
47
|
+
path: '/workflow/workflows/:id',
|
|
48
|
+
authCheck: true,
|
|
49
|
+
});
|
|
50
|
+
expect(
|
|
51
|
+
app.router
|
|
52
|
+
.matchRoutes(`/nocobase/settings/${scope}/demo/workflow/workflows/1`)
|
|
53
|
+
?.some((match) => match.route.id === 'settingsDetails.workflow.canvas'),
|
|
54
|
+
).toBe(true);
|
|
55
|
+
expect(
|
|
56
|
+
app.router
|
|
57
|
+
.matchRoutes(`/nocobase/settings/${scope}/demo/settings/workflow/workflows/1`)
|
|
58
|
+
?.some((match) => match.route.id === 'settingsDetails.workflow.canvas'),
|
|
59
|
+
).toBe(false);
|
|
60
|
+
expect(app.router.get('settingsDetails.preview')).toMatchObject({ path: '/settings-preview/:id' });
|
|
61
|
+
expect(app.router.get('auth.signin')).toMatchObject({ path: '/signin' });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('does not treat an apps segment inside the main application public path as a sub-application scope', () => {
|
|
65
|
+
const app = new SettingsApplication({
|
|
66
|
+
router: { type: 'memory', basename: '/tenant/apps/root/' },
|
|
67
|
+
ws: false,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(app.pluginSettingsManager.getRoutePath('')).toBe('/settings/');
|
|
71
|
+
expect(app.pluginSettingsManager.getRoutePath('demo.index')).toBe('/settings/demo');
|
|
72
|
+
|
|
73
|
+
app.router.add('settingsDetails.workflow.canvas', { path: '/settings/workflow/workflows/:id' });
|
|
74
|
+
expect(app.router.get('settingsDetails.workflow.canvas')).toMatchObject({
|
|
75
|
+
path: '/settings/workflow/workflows/:id',
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it.each(['apps', '_app'])(
|
|
80
|
+
'does not treat a scoped-looking %s suffix inside the main public path as a sub-application scope',
|
|
81
|
+
(scope) => {
|
|
82
|
+
const publicPath = `/tenant/settings/${scope}/root/`;
|
|
83
|
+
const app = new SettingsApplication({
|
|
84
|
+
publicPath,
|
|
85
|
+
router: { type: 'memory', basename: publicPath },
|
|
86
|
+
ws: false,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(app.pluginSettingsManager.getRoutePath('')).toBe('/settings/');
|
|
90
|
+
expect(app.pluginSettingsManager.getRoutePath('demo.index')).toBe('/settings/demo');
|
|
91
|
+
|
|
92
|
+
app.router.add('settingsDetails.workflow.canvas', { path: '/settings/workflow/workflows/:id' });
|
|
93
|
+
app.router.add('auth.signin', { path: '/signin' });
|
|
94
|
+
|
|
95
|
+
expect(app.router.get('settingsDetails.workflow.canvas')).toMatchObject({
|
|
96
|
+
path: '/settings/workflow/workflows/:id',
|
|
97
|
+
});
|
|
98
|
+
expect(app.router.get('auth.signin')).toMatchObject({ path: '/settings/signin' });
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
it('keeps only routes owned by the settings runtime', () => {
|
|
103
|
+
const app = new SettingsApplication({ router: { type: 'memory' }, ws: false });
|
|
104
|
+
|
|
105
|
+
app.router.add('settings', { path: '/settings' });
|
|
106
|
+
app.router.add('settings.demo', { path: 'demo' });
|
|
107
|
+
app.router.add('settingsDetails.workflow.canvas', { path: '/settings/workflow/workflows/:id' });
|
|
108
|
+
app.router.add('admin.demo', { path: '/admin/demo' });
|
|
109
|
+
app.router.add('public-forms', { path: '/public-forms/:key' });
|
|
110
|
+
app.router.add('mobile', { path: '/mobile' });
|
|
111
|
+
|
|
112
|
+
expect(app.router.has('settings')).toBe(true);
|
|
113
|
+
expect(app.router.has('settings.demo')).toBe(true);
|
|
114
|
+
expect(app.router.get('settingsDetails.workflow.canvas')).toMatchObject({
|
|
115
|
+
path: '/settings/workflow/workflows/:id',
|
|
116
|
+
authCheck: true,
|
|
117
|
+
});
|
|
118
|
+
expect(app.router.has('admin.demo')).toBe(false);
|
|
119
|
+
expect(app.router.has('public-forms')).toBe(false);
|
|
120
|
+
expect(app.router.has('mobile')).toBe(false);
|
|
121
|
+
expect(app.router.has('not-found')).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('keeps hidden plugin detail pages in the normal Settings route tree', () => {
|
|
125
|
+
const app = new SettingsApplication({ router: { type: 'memory' }, ws: false });
|
|
126
|
+
|
|
127
|
+
app.pluginSettingsManager.addMenuItem({ key: 'public-forms', title: 'Public forms' });
|
|
128
|
+
app.pluginSettingsManager.addPageTabItem({
|
|
129
|
+
menuKey: 'public-forms',
|
|
130
|
+
key: ':name',
|
|
131
|
+
title: false,
|
|
132
|
+
hidden: true,
|
|
133
|
+
Component: () => null,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(app.pluginSettingsManager.get('public-forms.:name')).toMatchObject({
|
|
137
|
+
hidden: true,
|
|
138
|
+
path: '/settings/public-forms/:name',
|
|
139
|
+
});
|
|
140
|
+
expect(app.router.get('settings.public-forms.:name')).toMatchObject({ path: ':name' });
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('registers the authenticated settings shell without the admin layout', async () => {
|
|
144
|
+
const app = new SettingsApplication({ router: { type: 'memory' }, ws: false });
|
|
145
|
+
const apiMock = new MockAdapter(app.apiClient.axios);
|
|
146
|
+
apiMock.onGet('app:getLang').reply(200, { data: { lang: 'en-US', resources: { client: {} }, cron: {} } });
|
|
147
|
+
const plugin = new SettingsBuildInPlugin({ name: 'settings-buildin' }, app);
|
|
148
|
+
|
|
149
|
+
await plugin.afterAdd();
|
|
150
|
+
await plugin.load();
|
|
151
|
+
app.router.add('settingsDetails.workflow.canvas', { path: '/settings/workflow/workflows/:id' });
|
|
152
|
+
|
|
153
|
+
expect(app.router.get('settings')).toMatchObject({ path: '/settings', authCheck: true });
|
|
154
|
+
expect(app.router.get('settingsDetails')).toMatchObject({ path: '/settings', authCheck: true });
|
|
155
|
+
expect(
|
|
156
|
+
app.router.matchRoutes('/settings/workflow/workflows/1')?.some((match) => match.route.authCheck === true),
|
|
157
|
+
).toBe(true);
|
|
158
|
+
expect(app.router.has('admin')).toBe(false);
|
|
159
|
+
expect(app.layoutManager.hasLayout('admin')).toBe(false);
|
|
160
|
+
expect(app.pluginSettingsManager.has('plugin-manager')).toBe(true);
|
|
161
|
+
expect(app.pluginSettingsManager.has('system-settings')).toBe(true);
|
|
162
|
+
expect(app.pluginSettingsManager.has('security')).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
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 { Application } from '../index';
|
|
11
|
+
import { SettingsApplication } from '../settings-app/SettingsApplication';
|
|
12
|
+
|
|
13
|
+
describe('standalone settings authentication routes', () => {
|
|
14
|
+
it('keeps and rebases the auth and 2fa route families', () => {
|
|
15
|
+
const app = new SettingsApplication({ router: { type: 'memory' }, ws: false });
|
|
16
|
+
|
|
17
|
+
app.router.add('auth', { Component: () => null });
|
|
18
|
+
app.router.add('auth.signin', { path: '/signin', skipAuthCheck: true, Component: () => null });
|
|
19
|
+
app.router.add('auth.signup', { path: '/signup', skipAuthCheck: true, Component: () => null });
|
|
20
|
+
app.router.add('auth.forgotPassword', {
|
|
21
|
+
path: '/forgot-password',
|
|
22
|
+
skipAuthCheck: true,
|
|
23
|
+
Component: () => null,
|
|
24
|
+
});
|
|
25
|
+
app.router.add('auth.resetPassword', {
|
|
26
|
+
path: '/reset-password',
|
|
27
|
+
skipAuthCheck: true,
|
|
28
|
+
Component: () => null,
|
|
29
|
+
});
|
|
30
|
+
app.router.add('2fa', { Component: () => null });
|
|
31
|
+
app.router.add('2fa.verify', { path: '/2fa', skipAuthCheck: true, Component: () => null });
|
|
32
|
+
|
|
33
|
+
expect(app.router.get('auth.signin')).toMatchObject({ path: '/settings/signin', skipAuthCheck: true });
|
|
34
|
+
expect(app.router.get('auth.signup')).toMatchObject({ path: '/settings/signup', skipAuthCheck: true });
|
|
35
|
+
expect(app.router.get('auth.forgotPassword')).toMatchObject({
|
|
36
|
+
path: '/settings/forgot-password',
|
|
37
|
+
skipAuthCheck: true,
|
|
38
|
+
});
|
|
39
|
+
expect(app.router.get('auth.resetPassword')).toMatchObject({
|
|
40
|
+
path: '/settings/reset-password',
|
|
41
|
+
skipAuthCheck: true,
|
|
42
|
+
});
|
|
43
|
+
expect(app.router.get('2fa.verify')).toMatchObject({ path: '/settings/2fa', skipAuthCheck: true });
|
|
44
|
+
expect(app.router.matchRoutes('/settings/signin')?.map((match) => match.route.id)).toContain('auth.signin');
|
|
45
|
+
expect(app.router.isSkippedAuthCheckRoute('/settings/reset-password')).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('does not change the default Client V2 authentication routes', () => {
|
|
49
|
+
const app = new Application({ router: { type: 'memory' }, ws: false });
|
|
50
|
+
|
|
51
|
+
app.router.add('auth', { Component: () => null });
|
|
52
|
+
app.router.add('auth.signin', { path: '/signin', skipAuthCheck: true, Component: () => null });
|
|
53
|
+
app.router.add('2fa', { Component: () => null });
|
|
54
|
+
app.router.add('2fa.verify', { path: '/2fa', skipAuthCheck: true, Component: () => null });
|
|
55
|
+
|
|
56
|
+
expect(app.router.get('auth.signin')).toMatchObject({ path: '/signin', skipAuthCheck: true });
|
|
57
|
+
expect(app.router.get('2fa.verify')).toMatchObject({ path: '/2fa', skipAuthCheck: true });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('continues to reject unrelated routes from the shared plugin lane', () => {
|
|
61
|
+
const app = new SettingsApplication({ router: { type: 'memory' }, ws: false });
|
|
62
|
+
|
|
63
|
+
app.router.add('admin.demo', { path: '/admin/demo' });
|
|
64
|
+
app.router.add('public.demo', { path: '/public/demo' });
|
|
65
|
+
app.router.add('mobile.demo', { path: '/mobile/demo' });
|
|
66
|
+
|
|
67
|
+
expect(app.router.has('admin.demo')).toBe(false);
|
|
68
|
+
expect(app.router.has('public.demo')).toBe(false);
|
|
69
|
+
expect(app.router.has('mobile.demo')).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
});
|