@nocobase/client-v2 2.3.0-alpha.1 → 3.0.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/authRedirect.d.ts +5 -0
- package/es/components/AttachmentUpload.d.ts +51 -0
- package/es/components/form/ScanInput/useCodeScanner.d.ts +2 -2
- package/es/components/index.d.ts +1 -0
- package/es/flow/actions/linkageRules.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/NocoBaseLogo.d.ts +10 -0
- package/es/flow/components/FlowRoute.d.ts +2 -2
- package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
- package/es/flow/models/base/ActionModelCore.d.ts +4 -2
- package/es/flow/models/base/BlockGridModel.d.ts +3 -0
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +1 -0
- package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
- package/es/index.d.ts +4 -1
- package/es/index.mjs +139 -125
- package/es/nocobase-buildin-plugin/currentUserAuthStatus.d.ts +11 -0
- package/es/nocobase-buildin-plugin/index.d.ts +9 -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 +17 -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 +141 -127
- package/lib/locale/languageCodes.js +1 -0
- package/package.json +8 -7
- package/src/__tests__/authRedirect.test.ts +137 -0
- package/src/__tests__/browserChecker.test.ts +124 -6
- package/src/__tests__/mockSettingsApplication.ts +29 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +51 -10
- 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 +38 -37
- package/src/__tests__/settings-layout-root.test.tsx +260 -0
- package/src/__tests__/settings-runtime-paths.test.ts +99 -0
- package/src/__tests__/settings-shell.test.tsx +200 -0
- package/src/acl/ACLProvider.tsx +21 -0
- package/src/authRedirect.ts +72 -3
- package/src/components/AttachmentUpload.tsx +275 -0
- package/src/components/form/ScanInput/CodeScanner.tsx +61 -34
- package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +101 -0
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +114 -18
- package/src/components/form/ScanInput/useCodeScanner.ts +166 -23
- package/src/components/index.ts +1 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +126 -8
- package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
- package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +314 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +102 -0
- package/src/flow/actions/linkageRules.tsx +26 -6
- 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/common/Markdown/style.ts +4 -0
- package/src/flow/components/BlockItemCard.tsx +2 -2
- package/src/flow/components/FlowRoute.tsx +71 -29
- package/src/flow/index.ts +2 -0
- package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
- package/src/flow/models/base/ActionModelCore.tsx +11 -2
- package/src/flow/models/base/BlockGridModel.tsx +26 -0
- package/src/flow/models/base/CollectionBlockModel.tsx +6 -2
- package/src/flow/models/base/PageModel/PageTabModel.tsx +67 -17
- package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +509 -12
- package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +49 -0
- package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +27 -3
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +32 -4
- package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +3 -1
- package/src/flow/models/blocks/table/TableColumnModel.tsx +30 -1
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +33 -0
- package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
- package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
- package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
- package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -0
- package/src/flow/models/topbar/TopbarActionModel.tsx +52 -180
- package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
- package/src/index.ts +10 -1
- package/src/locale/languageCodes.ts +1 -0
- package/src/nocobase-buildin-plugin/currentUserAuthStatus.ts +58 -0
- package/src/nocobase-buildin-plugin/index.tsx +99 -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 +238 -0
- package/src/settings-app/SettingsShell.tsx +167 -0
- package/src/settings-app/isSettingsApp.ts +21 -0
- package/src/settings-app/runtimePaths.ts +104 -0
- package/src/settings-app/settingsDocumentPath.ts +66 -0
- package/src/settings-app/settingsTheme.ts +301 -0
- package/src/settings-app/useSettingsThemeConfig.ts +100 -0
- package/src/settings-center/AdminSettingsLayout.tsx +122 -141
- package/src/settings-center/SystemSettingsPage.tsx +4 -170
- 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
|
@@ -64,6 +64,7 @@ const languageCodes = {
|
|
|
64
64
|
"ml-IN": { label: "\u0D2E\u0D32\u0D2F\u0D3E\u0D33\u0D02" },
|
|
65
65
|
"mn-MN": { label: "\u041C\u043E\u043D\u0433\u043E\u043B \u0445\u044D\u043B" },
|
|
66
66
|
"ms-MY": { label: "\u0628\u0647\u0627\u0633 \u0645\u0644\u0627\u064A\u0648" },
|
|
67
|
+
"my-MM": { label: "\u1019\u103C\u1014\u103A\u1019\u102C\u1018\u102C\u101E\u102C" },
|
|
67
68
|
"nb-NO": { label: "Norsk bokm\xE5l" },
|
|
68
69
|
"ne-NP": { label: "\u0928\u0947\u092A\u093E\u0932\u0940" },
|
|
69
70
|
"nl-BE": { label: "Vlaams" },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/client-v2",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-alpha.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "es/index.mjs",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"@formily/antd-v5": "1.2.3",
|
|
28
28
|
"@formily/react": "^2.2.27",
|
|
29
29
|
"@formily/shared": "^2.2.27",
|
|
30
|
-
"@nocobase/evaluators": "
|
|
31
|
-
"@nocobase/flow-engine": "
|
|
32
|
-
"@nocobase/sdk": "
|
|
33
|
-
"@nocobase/shared": "
|
|
34
|
-
"@nocobase/utils": "
|
|
30
|
+
"@nocobase/evaluators": "3.0.0-alpha.2",
|
|
31
|
+
"@nocobase/flow-engine": "3.0.0-alpha.2",
|
|
32
|
+
"@nocobase/sdk": "3.0.0-alpha.2",
|
|
33
|
+
"@nocobase/shared": "3.0.0-alpha.2",
|
|
34
|
+
"@nocobase/utils": "3.0.0-alpha.2",
|
|
35
35
|
"ahooks": "^3.7.2",
|
|
36
36
|
"antd": "5.24.2",
|
|
37
37
|
"antd-style": "3.7.1",
|
|
@@ -44,8 +44,9 @@
|
|
|
44
44
|
"json5": "^2.2.3",
|
|
45
45
|
"jsqr": "^1.4.0",
|
|
46
46
|
"lodash": "4.17.21",
|
|
47
|
+
"react-device-detect": "2.2.3",
|
|
47
48
|
"react-i18next": "^11.15.1",
|
|
48
49
|
"react-router-dom": "^6.30.1"
|
|
49
50
|
},
|
|
50
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "a3faea21082b30989301de04591ee1b91fd4e6a5"
|
|
51
52
|
}
|
|
@@ -17,12 +17,18 @@ import {
|
|
|
17
17
|
|
|
18
18
|
describe('auth redirect helpers', () => {
|
|
19
19
|
const originalLocation = globalThis.window.location;
|
|
20
|
+
const originalModernClientPrefix = window.__nocobase_modern_client_prefix__;
|
|
20
21
|
|
|
21
22
|
afterEach(() => {
|
|
22
23
|
Object.defineProperty(globalThis.window, 'location', {
|
|
23
24
|
configurable: true,
|
|
24
25
|
value: originalLocation,
|
|
25
26
|
});
|
|
27
|
+
if (originalModernClientPrefix === undefined) {
|
|
28
|
+
delete window.__nocobase_modern_client_prefix__;
|
|
29
|
+
} else {
|
|
30
|
+
window.__nocobase_modern_client_prefix__ = originalModernClientPrefix;
|
|
31
|
+
}
|
|
26
32
|
vi.restoreAllMocks();
|
|
27
33
|
});
|
|
28
34
|
|
|
@@ -84,6 +90,107 @@ describe('auth redirect helpers', () => {
|
|
|
84
90
|
expect(buildV2SigninHref(app, '/v2/admin/7vu4c2sdk6h')).toBe('/v2/signin?redirect=%2Fv2%2Fadmin%2F7vu4c2sdk6h');
|
|
85
91
|
});
|
|
86
92
|
|
|
93
|
+
it('should keep validated standalone settings redirects outside the v2 basename', () => {
|
|
94
|
+
const app = {
|
|
95
|
+
getPublicPath: () => '/nocobase/v2/',
|
|
96
|
+
router: {
|
|
97
|
+
getBasename: () => '/nocobase/v2',
|
|
98
|
+
},
|
|
99
|
+
} as any;
|
|
100
|
+
|
|
101
|
+
expect(normalizeV2RedirectPath(app, '/nocobase/settings/workflow?tab=list#recent')).toBe(
|
|
102
|
+
'/nocobase/settings/workflow?tab=list#recent',
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should build the standalone settings signin URL for a standalone settings app', () => {
|
|
107
|
+
const app = {
|
|
108
|
+
name: 'main',
|
|
109
|
+
getPublicPath: () => '/nocobase/',
|
|
110
|
+
router: {
|
|
111
|
+
getBasename: () => '/nocobase',
|
|
112
|
+
},
|
|
113
|
+
pluginSettingsManager: {
|
|
114
|
+
getRouteName: () => 'settings.',
|
|
115
|
+
getRoutePath: () => '/settings/',
|
|
116
|
+
},
|
|
117
|
+
} satisfies Parameters<typeof buildV2SigninHref>[0];
|
|
118
|
+
expect(buildV2SigninHref(app, '/nocobase/settings/workflow?tab=list#recent')).toBe(
|
|
119
|
+
'/nocobase/settings/signin?redirect=%2Fnocobase%2Fsettings%2Fworkflow%3Ftab%3Dlist%23recent',
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should keep an apps segment inside the main public path out of the Settings application scope', () => {
|
|
124
|
+
const app = {
|
|
125
|
+
name: 'main',
|
|
126
|
+
getPublicPath: () => '/tenant/apps/root/',
|
|
127
|
+
router: {
|
|
128
|
+
getBasename: () => '/tenant/apps/root/',
|
|
129
|
+
},
|
|
130
|
+
pluginSettingsManager: {
|
|
131
|
+
getRouteName: () => 'settings.',
|
|
132
|
+
getRoutePath: () => '/settings/',
|
|
133
|
+
},
|
|
134
|
+
} satisfies Parameters<typeof buildV2SigninHref>[0];
|
|
135
|
+
|
|
136
|
+
expect(buildV2SigninHref(app, '/tenant/apps/root/settings/workflow')).toBe(
|
|
137
|
+
'/tenant/apps/root/settings/signin?redirect=%2Ftenant%2Fapps%2Froot%2Fsettings%2Fworkflow',
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it.each([
|
|
142
|
+
['/nocobase/settings/apps/test-app/', '/nocobase/settings/apps/test-app/signin'],
|
|
143
|
+
['/nocobase/settings/_app/test-app/', '/nocobase/settings/_app/test-app/signin'],
|
|
144
|
+
])('should keep the standalone settings signin in the current sub-app scope: %s', (basename, signinPath) => {
|
|
145
|
+
const app = {
|
|
146
|
+
name: 'test-app',
|
|
147
|
+
getPublicPath: () => '/nocobase/',
|
|
148
|
+
router: {
|
|
149
|
+
getBasename: () => basename,
|
|
150
|
+
},
|
|
151
|
+
pluginSettingsManager: {
|
|
152
|
+
getRouteName: () => 'settings.',
|
|
153
|
+
getRoutePath: () => '/',
|
|
154
|
+
},
|
|
155
|
+
} as any;
|
|
156
|
+
|
|
157
|
+
expect(buildV2SigninHref(app, `${basename}workflow`)).toBe(
|
|
158
|
+
`${signinPath}?redirect=${encodeURIComponent(`${basename}workflow`)}`,
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should accept the current standalone settings signin URL only', () => {
|
|
163
|
+
Object.defineProperty(globalThis.window, 'location', {
|
|
164
|
+
configurable: true,
|
|
165
|
+
value: {
|
|
166
|
+
...originalLocation,
|
|
167
|
+
origin: 'http://localhost:20000',
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
const app = {
|
|
171
|
+
name: 'test-app',
|
|
172
|
+
getPublicPath: () => '/nocobase/',
|
|
173
|
+
router: {
|
|
174
|
+
getBasename: () => '/nocobase/settings/apps/test-app/',
|
|
175
|
+
},
|
|
176
|
+
pluginSettingsManager: {
|
|
177
|
+
getRouteName: () => 'settings.',
|
|
178
|
+
getRoutePath: () => '/',
|
|
179
|
+
},
|
|
180
|
+
} as any;
|
|
181
|
+
|
|
182
|
+
expect(
|
|
183
|
+
resolveV2SigninRedirect(
|
|
184
|
+
'/nocobase/settings/apps/test-app/signin?redirect=%2Fnocobase%2Fsettings%2Fapps%2Ftest-app',
|
|
185
|
+
app,
|
|
186
|
+
),
|
|
187
|
+
).toBe(
|
|
188
|
+
'http://localhost:20000/nocobase/settings/apps/test-app/signin?redirect=%2Fnocobase%2Fsettings%2Fapps%2Ftest-app',
|
|
189
|
+
);
|
|
190
|
+
expect(resolveV2SigninRedirect('/nocobase/v/apps/test-app/signin', app)).toBeNull();
|
|
191
|
+
expect(resolveV2SigninRedirect('/nocobase/settings/apps/other-app/signin', app)).toBeNull();
|
|
192
|
+
});
|
|
193
|
+
|
|
87
194
|
it('should redirect with window.location.replace by default', () => {
|
|
88
195
|
const replace = vi.fn();
|
|
89
196
|
Object.defineProperty(globalThis.window, 'location', {
|
|
@@ -134,6 +241,36 @@ describe('auth redirect helpers', () => {
|
|
|
134
241
|
});
|
|
135
242
|
|
|
136
243
|
describe('v2 sub-app context (router basename contains /apps/<id>/)', () => {
|
|
244
|
+
it('should keep only the current sub-app standalone settings redirect', () => {
|
|
245
|
+
const app = {
|
|
246
|
+
getPublicPath: () => '/nocobase/v2/',
|
|
247
|
+
router: {
|
|
248
|
+
getBasename: () => '/nocobase/v2/apps/test-app/',
|
|
249
|
+
},
|
|
250
|
+
} as any;
|
|
251
|
+
|
|
252
|
+
expect(normalizeV2RedirectPath(app, '/nocobase/settings/apps/test-app/workflow')).toBe(
|
|
253
|
+
'/nocobase/settings/apps/test-app/workflow',
|
|
254
|
+
);
|
|
255
|
+
expect(normalizeV2RedirectPath(app, '/nocobase/settings/apps/other-app/workflow')).toBe(
|
|
256
|
+
'/nocobase/v2/apps/test-app/admin/',
|
|
257
|
+
);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it.each([
|
|
261
|
+
'/nocobase/settings/apps/test-app/../../other-app/settings',
|
|
262
|
+
'/nocobase/settings/apps/test-app/%2e%2e/%2E%2e/other-app/settings',
|
|
263
|
+
])('should reject a standalone settings redirect that resolves outside the current sub-app: %s', (target) => {
|
|
264
|
+
const app = {
|
|
265
|
+
getPublicPath: () => '/nocobase/v2/',
|
|
266
|
+
router: {
|
|
267
|
+
getBasename: () => '/nocobase/v2/apps/test-app/',
|
|
268
|
+
},
|
|
269
|
+
} as any;
|
|
270
|
+
|
|
271
|
+
expect(normalizeV2RedirectPath(app, target)).toBe('/nocobase/v2/apps/test-app/admin/');
|
|
272
|
+
});
|
|
273
|
+
|
|
137
274
|
it('should normalize signin redirect fallback under the current sub-app basename', () => {
|
|
138
275
|
const app = {
|
|
139
276
|
getPublicPath: () => '/v/',
|
|
@@ -15,8 +15,11 @@ import { fileURLToPath } from 'node:url';
|
|
|
15
15
|
type BrowserCheckerCase = {
|
|
16
16
|
pathname: string;
|
|
17
17
|
publicPath: string;
|
|
18
|
+
search?: string;
|
|
19
|
+
hash?: string;
|
|
18
20
|
modernClientPrefix?: string;
|
|
19
21
|
appClientEntryMode?: string;
|
|
22
|
+
scriptSrc?: string;
|
|
20
23
|
expectedRedirect?: string;
|
|
21
24
|
};
|
|
22
25
|
|
|
@@ -47,8 +50,8 @@ function executeBrowserChecker(scriptPath: string, input: BrowserCheckerCase) {
|
|
|
47
50
|
location: {
|
|
48
51
|
origin: 'http://c.local.nocobase.com',
|
|
49
52
|
pathname: input.pathname,
|
|
50
|
-
search: '',
|
|
51
|
-
hash: '',
|
|
53
|
+
search: input.search || '',
|
|
54
|
+
hash: input.hash || '',
|
|
52
55
|
replace,
|
|
53
56
|
},
|
|
54
57
|
console: consoleMock,
|
|
@@ -58,6 +61,13 @@ function executeBrowserChecker(scriptPath: string, input: BrowserCheckerCase) {
|
|
|
58
61
|
onresize: undefined,
|
|
59
62
|
},
|
|
60
63
|
document: {
|
|
64
|
+
currentScript: {
|
|
65
|
+
src:
|
|
66
|
+
input.scriptSrc ||
|
|
67
|
+
(scriptPath.includes('/app/client-v2/public/')
|
|
68
|
+
? 'http://assets.local.nocobase.com/v/browser-checker.js?v=1'
|
|
69
|
+
: 'http://assets.local.nocobase.com/browser-checker.js?v=1'),
|
|
70
|
+
},
|
|
61
71
|
documentElement: {
|
|
62
72
|
className: '',
|
|
63
73
|
clientWidth: 1280,
|
|
@@ -80,11 +90,11 @@ function executeBrowserChecker(scriptPath: string, input: BrowserCheckerCase) {
|
|
|
80
90
|
describe.each(browserCheckerCases)('$label', ({ scriptPath }) => {
|
|
81
91
|
it('normalizes a relative public path before redirecting to the trailing-slash entry', () => {
|
|
82
92
|
const replace = executeBrowserChecker(scriptPath, {
|
|
83
|
-
pathname: '/nocobase/
|
|
84
|
-
publicPath: 'nocobase/
|
|
93
|
+
pathname: '/nocobase/console',
|
|
94
|
+
publicPath: 'nocobase/console/',
|
|
85
95
|
});
|
|
86
96
|
|
|
87
|
-
expect(replace).toHaveBeenCalledWith('http://c.local.nocobase.com/nocobase/
|
|
97
|
+
expect(replace).toHaveBeenCalledWith('http://c.local.nocobase.com/nocobase/console/');
|
|
88
98
|
});
|
|
89
99
|
|
|
90
100
|
it('prefixes outside paths with a root-relative basename instead of duplicating a relative segment', () => {
|
|
@@ -98,7 +108,7 @@ describe.each(browserCheckerCases)('$label', ({ scriptPath }) => {
|
|
|
98
108
|
|
|
99
109
|
it('does not redirect when the current path is already under the normalized basename', () => {
|
|
100
110
|
const replace = executeBrowserChecker(scriptPath, {
|
|
101
|
-
pathname: '/nocobase/v/',
|
|
111
|
+
pathname: '/nocobase/v/admin',
|
|
102
112
|
publicPath: 'nocobase/v/',
|
|
103
113
|
});
|
|
104
114
|
|
|
@@ -170,4 +180,112 @@ describe.each(browserCheckerCases)('$label', ({ scriptPath }) => {
|
|
|
170
180
|
expect(replace).toHaveBeenCalledWith('http://c.local.nocobase.com/nocobase/v/apps/a_31itq60q4kg/admin');
|
|
171
181
|
});
|
|
172
182
|
}
|
|
183
|
+
|
|
184
|
+
if (scriptPath.includes('/app/client-v2/public/')) {
|
|
185
|
+
it.each(['/v', '/v/'])('redirects the main modern root %s to Settings', (pathname) => {
|
|
186
|
+
const replace = executeBrowserChecker(scriptPath, {
|
|
187
|
+
pathname,
|
|
188
|
+
publicPath: '/v/',
|
|
189
|
+
modernClientPrefix: 'v',
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
expect(replace).toHaveBeenCalledOnce();
|
|
193
|
+
expect(replace).toHaveBeenCalledWith('http://c.local.nocobase.com/settings');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it.each([
|
|
197
|
+
['/v/apps/demo', '/settings/apps/demo'],
|
|
198
|
+
['/v/apps/demo/', '/settings/apps/demo'],
|
|
199
|
+
['/v/_app/demo', '/settings/_app/demo'],
|
|
200
|
+
['/v/_app/demo/', '/settings/_app/demo'],
|
|
201
|
+
])('redirects the scoped modern root %s to %s', (pathname, expectedPathname) => {
|
|
202
|
+
const replace = executeBrowserChecker(scriptPath, {
|
|
203
|
+
pathname,
|
|
204
|
+
publicPath: '/v/',
|
|
205
|
+
modernClientPrefix: 'v',
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
expect(replace).toHaveBeenCalledOnce();
|
|
209
|
+
expect(replace).toHaveBeenCalledWith(`http://c.local.nocobase.com${expectedPathname}`);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it.each([
|
|
213
|
+
['/settings/apps/demo', '/', '/settings/apps/demo/', 'v'],
|
|
214
|
+
['/settings/_app/demo', '/', '/settings/_app/demo/', 'v'],
|
|
215
|
+
['/nocobase/settings/apps/demo', '/nocobase/', '/nocobase/settings/apps/demo/', 'v'],
|
|
216
|
+
['/nocobase/settings/_app/demo', '/nocobase/', '/nocobase/settings/_app/demo/', 'v'],
|
|
217
|
+
['/tenant/apps/root/settings/apps/demo', '/tenant/apps/root/', '/tenant/apps/root/settings/apps/demo/', 'v'],
|
|
218
|
+
['/tenant/_app/root/settings/_app/demo', '/tenant/_app/root/', '/tenant/_app/root/settings/_app/demo/', 'v'],
|
|
219
|
+
['/tenant/v/settings/apps/demo', '/tenant/v/', '/tenant/v/settings/apps/demo/', 'v'],
|
|
220
|
+
['/tenant/modern/settings/_app/demo', '/tenant/modern/', '/tenant/modern/settings/_app/demo/', 'modern'],
|
|
221
|
+
])('normalizes the scoped Settings root %s to %s', (pathname, publicPath, expectedPathname, modernClientPrefix) => {
|
|
222
|
+
const replace = executeBrowserChecker(scriptPath, {
|
|
223
|
+
pathname,
|
|
224
|
+
publicPath,
|
|
225
|
+
modernClientPrefix,
|
|
226
|
+
scriptSrc: 'https://cdn.example.com/ui/settings/browser-checker.js?v=1',
|
|
227
|
+
search: '?tab=overview',
|
|
228
|
+
hash: '#panel',
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
expect(replace).toHaveBeenCalledOnce();
|
|
232
|
+
expect(replace).toHaveBeenCalledWith(`http://c.local.nocobase.com${expectedPathname}?tab=overview#panel`);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it.each([
|
|
236
|
+
['/tenant/apps/root/modern/apps/demo/', '/tenant/apps/root/modern/', '/tenant/apps/root/settings/apps/demo'],
|
|
237
|
+
['/tenant/_app/root/modern/_app/demo/', '/tenant/_app/root/modern/', '/tenant/_app/root/settings/_app/demo'],
|
|
238
|
+
])(
|
|
239
|
+
'preserves public path, query, and hash for %s without treating public-path segments as app scope',
|
|
240
|
+
(pathname, publicPath, expectedPathname) => {
|
|
241
|
+
const replace = executeBrowserChecker(scriptPath, {
|
|
242
|
+
pathname,
|
|
243
|
+
publicPath,
|
|
244
|
+
modernClientPrefix: 'modern',
|
|
245
|
+
search: '?tab=overview',
|
|
246
|
+
hash: '#panel',
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
expect(replace).toHaveBeenCalledOnce();
|
|
250
|
+
expect(replace).toHaveBeenCalledWith(`http://c.local.nocobase.com${expectedPathname}?tab=overview#panel`);
|
|
251
|
+
},
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
it.each([
|
|
255
|
+
['/v/admin', '/v/'],
|
|
256
|
+
['/v/apps/demo/admin', '/v/'],
|
|
257
|
+
['/v/_app/demo/admin', '/v/'],
|
|
258
|
+
['/v/_apps/demo', '/v/'],
|
|
259
|
+
['/v/settings/apps/demo', '/v/'],
|
|
260
|
+
['/nocobase/modern/settings/_app/demo', '/nocobase/modern/', 'modern'],
|
|
261
|
+
])('does not redirect a non-root modern path %s to Settings', (pathname, publicPath, modernClientPrefix = 'v') => {
|
|
262
|
+
const replace = executeBrowserChecker(scriptPath, {
|
|
263
|
+
pathname,
|
|
264
|
+
publicPath,
|
|
265
|
+
modernClientPrefix,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
expect(replace).not.toHaveBeenCalled();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it.each([
|
|
272
|
+
['/settings', '/'],
|
|
273
|
+
['/settings/', '/'],
|
|
274
|
+
['/settings/apps/demo/', '/'],
|
|
275
|
+
['/settings/_app/demo/', '/'],
|
|
276
|
+
['/settings/apps/demo/multi-portal', '/'],
|
|
277
|
+
['/settings/_app/demo/multi-portal', '/'],
|
|
278
|
+
['/settings/_apps/demo', '/'],
|
|
279
|
+
['/nocobase/settings/apps/demo/multi-portal', '/nocobase/'],
|
|
280
|
+
])('does not redirect a non-target Settings path %s', (pathname, publicPath) => {
|
|
281
|
+
const replace = executeBrowserChecker(scriptPath, {
|
|
282
|
+
pathname,
|
|
283
|
+
publicPath,
|
|
284
|
+
modernClientPrefix: 'v',
|
|
285
|
+
scriptSrc: 'https://cdn.example.com/ui/settings/browser-checker.js?v=1',
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
expect(replace).not.toHaveBeenCalled();
|
|
289
|
+
});
|
|
290
|
+
}
|
|
173
291
|
});
|
|
@@ -0,0 +1,29 @@
|
|
|
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 type { ApplicationOptions } from '../Application';
|
|
12
|
+
import { SettingsApplication } from '../settings-app/SettingsApplication';
|
|
13
|
+
|
|
14
|
+
class MockSettingsApplication extends SettingsApplication {
|
|
15
|
+
readonly apiMock: MockAdapter;
|
|
16
|
+
|
|
17
|
+
constructor(options: ApplicationOptions = {}) {
|
|
18
|
+
super({
|
|
19
|
+
router: { type: 'memory', initialEntries: ['/settings'] },
|
|
20
|
+
ws: false,
|
|
21
|
+
...options,
|
|
22
|
+
});
|
|
23
|
+
this.apiMock = new MockAdapter(this.apiClient.axios);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createMockSettingsClient(options?: ApplicationOptions) {
|
|
28
|
+
return new MockSettingsApplication(options);
|
|
29
|
+
}
|
|
@@ -51,6 +51,27 @@ 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
|
+
|
|
54
75
|
describe('nocobase buildin plugin auth redirect', () => {
|
|
55
76
|
const originalLocation = globalThis.window.location;
|
|
56
77
|
|
|
@@ -85,6 +106,16 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
85
106
|
vi.restoreAllMocks();
|
|
86
107
|
});
|
|
87
108
|
|
|
109
|
+
it('should leave Admin portal registration to runtime plugins', async () => {
|
|
110
|
+
const app = createMockClient();
|
|
111
|
+
const plugin = new NocoBaseBuildInPlugin({}, app);
|
|
112
|
+
|
|
113
|
+
await plugin.load();
|
|
114
|
+
|
|
115
|
+
expect(app.layoutManager.hasLayout('admin')).toBe(false);
|
|
116
|
+
expect(app.router.has('root')).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
88
119
|
it('should navigate to v2 signin when /auth:check returns no user', async () => {
|
|
89
120
|
// Aligns with v1: use react-router navigate (virtual) rather than
|
|
90
121
|
// `window.location.replace`, so a `window.location.href` queued elsewhere
|
|
@@ -92,7 +123,7 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
92
123
|
// overridden.
|
|
93
124
|
const app = createMockClient({
|
|
94
125
|
publicPath: '/v2/',
|
|
95
|
-
plugins: [NocoBaseBuildInPlugin as any],
|
|
126
|
+
plugins: [NocoBaseBuildInPlugin as any, TestAdminPortalPlugin as typeof Plugin],
|
|
96
127
|
router: { type: 'memory', initialEntries: ['/v2/admin/7vu4c2sdk6h'] },
|
|
97
128
|
});
|
|
98
129
|
app.apiMock.onGet('app:getLang').reply(200, {
|
|
@@ -115,7 +146,7 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
115
146
|
// and race the 2FA response interceptor with its own signin redirect.
|
|
116
147
|
const app = createMockClient({
|
|
117
148
|
publicPath: '/v2/',
|
|
118
|
-
plugins: [NocoBaseBuildInPlugin as any],
|
|
149
|
+
plugins: [NocoBaseBuildInPlugin as any, TestAdminPortalPlugin as typeof Plugin],
|
|
119
150
|
router: { type: 'memory', initialEntries: ['/v2/admin'] },
|
|
120
151
|
});
|
|
121
152
|
app.apiClient.auth.setToken('test-token');
|
|
@@ -136,29 +167,34 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
136
167
|
expect(app.apiMock.history.post.filter((request) => request.url === 'auth:syncCookies')).toHaveLength(0);
|
|
137
168
|
});
|
|
138
169
|
|
|
139
|
-
it('should redirect unauthenticated
|
|
170
|
+
it('should redirect an unauthenticated Portal root to v2 signin', async () => {
|
|
140
171
|
const app = createMockClient({
|
|
141
172
|
publicPath: '/nocobase/v2/',
|
|
142
|
-
plugins: [NocoBaseBuildInPlugin as any],
|
|
173
|
+
plugins: [NocoBaseBuildInPlugin as any, TestRootPortalPlugin as typeof Plugin],
|
|
143
174
|
router: { type: 'memory', initialEntries: ['/nocobase/v2/'] },
|
|
144
175
|
});
|
|
145
176
|
app.apiMock.onGet('app:getLang').reply(200, {
|
|
146
177
|
data: { lang: 'en-US', resources: { client: {} }, cron: {} },
|
|
147
178
|
});
|
|
179
|
+
app.apiMock.onGet('/auth:check').reply(200, { data: {} });
|
|
148
180
|
|
|
149
181
|
const Root = app.getRootComponent();
|
|
150
182
|
render(<Root />);
|
|
151
183
|
|
|
152
184
|
await waitFor(() => {
|
|
153
185
|
expect(app.router.router.state.location.pathname).toBe('/nocobase/v2/signin');
|
|
154
|
-
expect(app.router.router.state.location.search).toBe('?redirect=%2Fnocobase%2Fv2
|
|
186
|
+
expect(app.router.router.state.location.search).toBe('?redirect=%2Fnocobase%2Fv2');
|
|
155
187
|
});
|
|
156
188
|
});
|
|
157
189
|
|
|
158
190
|
it('should check current user after navigating from skipped route to v2 admin', async () => {
|
|
159
191
|
const app = createMockClient({
|
|
160
192
|
publicPath: '/v2/',
|
|
161
|
-
plugins: [
|
|
193
|
+
plugins: [
|
|
194
|
+
NocoBaseBuildInPlugin as any,
|
|
195
|
+
TestAdminPortalPlugin as typeof Plugin,
|
|
196
|
+
SkippedPublicRoutePlugin as typeof Plugin,
|
|
197
|
+
],
|
|
162
198
|
router: { type: 'memory', initialEntries: ['/v2/public'] },
|
|
163
199
|
});
|
|
164
200
|
app.apiMock.onGet('app:getLang').reply(200, {
|
|
@@ -385,7 +421,7 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
385
421
|
it('should render v2 admin root without redirecting away', async () => {
|
|
386
422
|
const app = createMockClient({
|
|
387
423
|
publicPath: '/v2/',
|
|
388
|
-
plugins: [NocoBaseBuildInPlugin as any],
|
|
424
|
+
plugins: [NocoBaseBuildInPlugin as any, TestAdminPortalPlugin as typeof Plugin],
|
|
389
425
|
router: { type: 'memory', initialEntries: ['/v2/admin'] },
|
|
390
426
|
});
|
|
391
427
|
app.apiMock.onGet('app:getLang').reply(200, {
|
|
@@ -415,11 +451,11 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
415
451
|
});
|
|
416
452
|
|
|
417
453
|
it.each(['/v2/admin/legacy-page/tab/tab-1', '/v2/admin/legacy-page/view/detail'])(
|
|
418
|
-
'should
|
|
454
|
+
'should explain authenticated direct v1-style v2 page access: %s',
|
|
419
455
|
async (pathname) => {
|
|
420
456
|
const app = createMockClient({
|
|
421
457
|
publicPath: '/v2/',
|
|
422
|
-
plugins: [NocoBaseBuildInPlugin as any],
|
|
458
|
+
plugins: [NocoBaseBuildInPlugin as any, TestAdminPortalPlugin as typeof Plugin],
|
|
423
459
|
router: { type: 'memory', initialEntries: [pathname] },
|
|
424
460
|
});
|
|
425
461
|
app.apiMock.onGet('app:getLang').reply(200, {
|
|
@@ -441,7 +477,12 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
441
477
|
const Root = app.getRootComponent();
|
|
442
478
|
render(<Root />);
|
|
443
479
|
|
|
444
|
-
expect(await screen.findByText('
|
|
480
|
+
expect(await screen.findByText('This page is not supported in the /v2/ branch')).toBeInTheDocument();
|
|
481
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
482
|
+
expect(screen.getByRole('link', { name: 'Open from the original entry' })).toHaveAttribute(
|
|
483
|
+
'href',
|
|
484
|
+
pathname.replace(/^\/v2/, ''),
|
|
485
|
+
);
|
|
445
486
|
expect(app.router.router.state.location.pathname).toBe(pathname);
|
|
446
487
|
},
|
|
447
488
|
);
|
|
@@ -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, {
|