@nocobase/client-v2 2.2.0-beta.9 → 3.0.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.
Files changed (208) hide show
  1. package/es/APIClient.d.ts +1 -3
  2. package/es/BaseApplication.d.ts +1 -0
  3. package/es/acl/ACLProvider.d.ts +10 -0
  4. package/es/authRedirect.d.ts +5 -0
  5. package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
  6. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
  7. package/es/components/AttachmentUpload.d.ts +51 -0
  8. package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
  9. package/es/components/category-tabs/index.d.ts +9 -0
  10. package/es/components/form/ScanInput/useCodeScanner.d.ts +3 -2
  11. package/es/components/form/TypedVariableInput.d.ts +31 -5
  12. package/es/components/form/filter/CollectionFilter.d.ts +2 -0
  13. package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
  14. package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
  15. package/es/components/form/filter/index.d.ts +2 -0
  16. package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
  17. package/es/components/index.d.ts +2 -0
  18. package/es/flow/actions/index.d.ts +1 -1
  19. package/es/flow/actions/linkageRules.d.ts +3 -0
  20. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
  21. package/es/flow/admin-shell/admin-layout/NocoBaseLogo.d.ts +10 -0
  22. package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
  23. package/es/flow/components/FieldAssignValueInput.d.ts +9 -0
  24. package/es/flow/components/FlowRoute.d.ts +2 -2
  25. package/es/flow/components/RunJSValueEditor.d.ts +1 -0
  26. package/es/flow/components/filter/FilterGroup.d.ts +1 -0
  27. package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
  28. package/es/flow/index.d.ts +1 -0
  29. package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
  30. package/es/flow/models/base/ActionModelCore.d.ts +4 -2
  31. package/es/flow/models/base/BlockGridModel.d.ts +3 -0
  32. package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
  33. package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
  34. package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
  35. package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
  36. package/es/flow/models/blocks/table/TableColumnModel.d.ts +1 -0
  37. package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
  38. package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
  39. package/es/flow-compat/routeTypes.d.ts +1 -0
  40. package/es/index.d.ts +8 -1
  41. package/es/index.mjs +190 -141
  42. package/es/nocobase-buildin-plugin/currentUserAuthStatus.d.ts +11 -0
  43. package/es/nocobase-buildin-plugin/index.d.ts +9 -0
  44. package/es/settings-app/SettingsApplication.d.ts +18 -0
  45. package/es/settings-app/SettingsBrand.d.ts +16 -0
  46. package/es/settings-app/SettingsBuildInPlugin.d.ts +9 -0
  47. package/es/settings-app/SettingsDocumentRedirect.d.ts +10 -0
  48. package/es/settings-app/SettingsGroupNav.d.ts +17 -0
  49. package/es/settings-app/SettingsPluginSettingsManager.d.ts +14 -0
  50. package/es/settings-app/SettingsRouterManager.d.ts +15 -0
  51. package/es/settings-app/SettingsSearch.d.ts +17 -0
  52. package/es/settings-app/SettingsShell.d.ts +10 -0
  53. package/es/settings-app/isSettingsApp.d.ts +18 -0
  54. package/es/settings-app/runtimePaths.d.ts +18 -0
  55. package/es/settings-app/settingsDocumentPath.d.ts +14 -0
  56. package/es/settings-app/settingsTheme.d.ts +87 -0
  57. package/es/settings-app/useSettingsThemeConfig.d.ts +22 -0
  58. package/es/settings-center/AdminSettingsLayout.d.ts +2 -2
  59. package/es/settings-center/groups.d.ts +66 -0
  60. package/es/settings-center/useSettingsGroups.d.ts +36 -0
  61. package/es/settings-center/useSettingsSearch.d.ts +35 -0
  62. package/es/settings-center/utils.d.ts +24 -0
  63. package/es/theme/index.d.ts +1 -0
  64. package/es/theme/itemActive.d.ts +24 -0
  65. package/es/theme/type.d.ts +9 -0
  66. package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
  67. package/es/utils/index.d.ts +1 -0
  68. package/lib/index.js +195 -146
  69. package/lib/locale/languageCodes.js +3 -1
  70. package/package.json +9 -7
  71. package/src/APIClient.ts +1 -10
  72. package/src/Application.tsx +4 -0
  73. package/src/BaseApplication.tsx +11 -6
  74. package/src/__tests__/app.test.tsx +73 -0
  75. package/src/__tests__/authRedirect.test.ts +137 -0
  76. package/src/__tests__/browserChecker.test.ts +185 -6
  77. package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
  78. package/src/__tests__/mockSettingsApplication.ts +29 -0
  79. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +79 -10
  80. package/src/__tests__/plugin-manager.test.tsx +7 -6
  81. package/src/__tests__/settings-application.test.ts +164 -0
  82. package/src/__tests__/settings-auth-routes.test.ts +71 -0
  83. package/src/__tests__/settings-center.test.tsx +38 -37
  84. package/src/__tests__/settings-layout-root.test.tsx +260 -0
  85. package/src/__tests__/settings-runtime-paths.test.ts +99 -0
  86. package/src/__tests__/settings-shell.test.tsx +200 -0
  87. package/src/acl/ACLProvider.tsx +21 -0
  88. package/src/authRedirect.ts +72 -3
  89. package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
  90. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
  91. package/src/collection-manager/field-validation.ts +1 -1
  92. package/src/components/AttachmentUpload.tsx +275 -0
  93. package/src/components/README.md +7 -1
  94. package/src/components/README.zh-CN.md +6 -1
  95. package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
  96. package/src/components/category-tabs/index.ts +10 -0
  97. package/src/components/form/JsonTextArea.tsx +4 -0
  98. package/src/components/form/ScanInput/CodeScanner.tsx +79 -35
  99. package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +101 -0
  100. package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +284 -11
  101. package/src/components/form/ScanInput/useCodeScanner.ts +332 -24
  102. package/src/components/form/TypedVariableInput.tsx +452 -101
  103. package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
  104. package/src/components/form/filter/CollectionFilter.tsx +4 -0
  105. package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
  106. package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
  107. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
  108. package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
  109. package/src/components/form/filter/index.ts +2 -0
  110. package/src/components/form/filter/useFilterActionProps.ts +37 -6
  111. package/src/components/index.ts +2 -0
  112. package/src/flow/FlowPage.tsx +9 -1
  113. package/src/flow/__tests__/FlowPage.test.tsx +50 -3
  114. package/src/flow/__tests__/FlowRoute.test.tsx +128 -10
  115. package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
  116. package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +314 -0
  117. package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +102 -0
  118. package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
  119. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
  120. package/src/flow/actions/index.ts +2 -0
  121. package/src/flow/actions/linkageRules.tsx +111 -16
  122. package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
  123. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +1 -57
  124. package/src/flow/admin-shell/admin-layout/NocoBaseLogo.tsx +77 -0
  125. package/src/flow/admin-shell/admin-layout/TopbarActionsBar.tsx +37 -14
  126. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +253 -6
  127. package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +77 -13
  128. package/src/flow/common/Markdown/style.ts +4 -0
  129. package/src/flow/components/BlockItemCard.tsx +2 -2
  130. package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
  131. package/src/flow/components/FieldAssignValueInput.tsx +91 -20
  132. package/src/flow/components/FlowRoute.tsx +78 -32
  133. package/src/flow/components/RunJSValueEditor.tsx +9 -1
  134. package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
  135. package/src/flow/components/filter/FilterGroup.tsx +33 -5
  136. package/src/flow/components/filter/VariableFilterItem.tsx +158 -10
  137. package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
  138. package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
  139. package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +70 -1
  140. package/src/flow/index.ts +3 -0
  141. package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
  142. package/src/flow/models/base/ActionModelCore.tsx +11 -2
  143. package/src/flow/models/base/BlockGridModel.tsx +26 -0
  144. package/src/flow/models/base/CollectionBlockModel.tsx +6 -2
  145. package/src/flow/models/base/PageModel/PageModel.tsx +387 -53
  146. package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
  147. package/src/flow/models/base/PageModel/PageTabModel.tsx +249 -18
  148. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +790 -10
  149. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
  150. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
  151. package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +1075 -7
  152. package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +49 -0
  153. package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
  154. package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
  155. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +28 -3
  156. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
  157. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
  158. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +66 -4
  159. package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
  160. package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
  161. package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
  162. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +2 -1
  163. package/src/flow/models/blocks/table/TableBlockModel.tsx +4 -2
  164. package/src/flow/models/blocks/table/TableColumnModel.tsx +30 -1
  165. package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
  166. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +33 -0
  167. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +2 -1
  168. package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
  169. package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
  170. package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
  171. package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
  172. package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -0
  173. package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
  174. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
  175. package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
  176. package/src/flow/models/topbar/TopbarActionModel.tsx +52 -180
  177. package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
  178. package/src/flow-compat/fieldValidationConstants.ts +1 -1
  179. package/src/flow-compat/routeTypes.ts +1 -0
  180. package/src/index.ts +14 -1
  181. package/src/locale/languageCodes.ts +3 -1
  182. package/src/nocobase-buildin-plugin/currentUserAuthStatus.ts +58 -0
  183. package/src/nocobase-buildin-plugin/index.tsx +110 -70
  184. package/src/settings-app/SettingsApplication.ts +31 -0
  185. package/src/settings-app/SettingsBrand.tsx +108 -0
  186. package/src/settings-app/SettingsBuildInPlugin.ts +10 -0
  187. package/src/settings-app/SettingsDocumentRedirect.tsx +26 -0
  188. package/src/settings-app/SettingsGroupNav.tsx +124 -0
  189. package/src/settings-app/SettingsPluginSettingsManager.ts +38 -0
  190. package/src/settings-app/SettingsRouterManager.ts +92 -0
  191. package/src/settings-app/SettingsSearch.tsx +238 -0
  192. package/src/settings-app/SettingsShell.tsx +167 -0
  193. package/src/settings-app/isSettingsApp.ts +21 -0
  194. package/src/settings-app/runtimePaths.ts +104 -0
  195. package/src/settings-app/settingsDocumentPath.ts +66 -0
  196. package/src/settings-app/settingsTheme.ts +301 -0
  197. package/src/settings-app/useSettingsThemeConfig.ts +100 -0
  198. package/src/settings-center/AdminSettingsLayout.tsx +122 -141
  199. package/src/settings-center/SystemSettingsPage.tsx +4 -170
  200. package/src/settings-center/groups.ts +108 -0
  201. package/src/settings-center/useSettingsGroups.ts +105 -0
  202. package/src/settings-center/useSettingsSearch.ts +173 -0
  203. package/src/settings-center/utils.tsx +50 -1
  204. package/src/theme/index.tsx +1 -0
  205. package/src/theme/itemActive.ts +31 -0
  206. package/src/theme/type.ts +10 -0
  207. package/src/utils/getRouteRuntimeVersion.ts +185 -0
  208. package/src/utils/index.tsx +1 -0
@@ -7,14 +7,15 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import { ACLRolesCheckProvider, createMockClient, Plugin } from '@nocobase/client-v2';
10
+ import { ACLRolesCheckProvider, Plugin } from '@nocobase/client-v2';
11
11
  import { render, screen, waitFor, fireEvent } from '@testing-library/react';
12
12
  import React from 'react';
13
13
  import { message } from 'antd';
14
14
  import { AdminSettingsLayoutModel as ClientV2AdminSettingsLayoutModel } from '../settings-center';
15
15
  import { AdminSettingsLayoutModel as ClientV1AdminSettingsLayoutModel } from '../../../client/src/pm/AdminSettingsLayoutModel';
16
- import { NocoBaseBuildInPlugin } from '../nocobase-buildin-plugin';
16
+ import { SettingsBuildInPlugin } from '../settings-app/SettingsBuildInPlugin';
17
17
  import { matchSettingsRoute, sortTopLevelSettings } from '../settings-center/utils';
18
+ import { createMockSettingsClient } from './mockSettingsApplication';
18
19
 
19
20
  class TestAclPlugin extends Plugin {
20
21
  async load() {
@@ -22,7 +23,7 @@ class TestAclPlugin extends Plugin {
22
23
  }
23
24
  }
24
25
 
25
- type MockClientApplication = ReturnType<typeof createMockClient>;
26
+ type MockClientApplication = ReturnType<typeof createMockSettingsClient>;
26
27
 
27
28
  const renderApp = async (app: MockClientApplication) => {
28
29
  const Root = app.getRootComponent();
@@ -174,10 +175,10 @@ describe('settings center', () => {
174
175
  expect(sortTopLevelSettings(settings).map((item) => item.name)).toEqual(['api-keys', 'backups', 'system-settings']);
175
176
  });
176
177
 
177
- it('should redirect /admin/settings to system-settings by default', async () => {
178
- const app = createMockClient({
179
- plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
180
- router: { type: 'memory', initialEntries: ['/admin/settings'] },
178
+ it('should redirect /settings to system-settings by default', async () => {
179
+ const app = createMockSettingsClient({
180
+ plugins: [SettingsBuildInPlugin, TestAclPlugin],
181
+ router: { type: 'memory', initialEntries: ['/settings'] },
181
182
  });
182
183
  mockAdminRuntime(app);
183
184
 
@@ -188,9 +189,9 @@ describe('settings center', () => {
188
189
  });
189
190
 
190
191
  it('should expose current language variable as enabled-language selector', async () => {
191
- const app = createMockClient({
192
- plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
193
- router: { type: 'memory', initialEntries: ['/admin/settings/system-settings'] },
192
+ const app = createMockSettingsClient({
193
+ plugins: [SettingsBuildInPlugin, TestAclPlugin],
194
+ router: { type: 'memory', initialEntries: ['/settings/system-settings'] },
194
195
  });
195
196
  mockAdminRuntime(app, {
196
197
  systemSettings: {
@@ -218,9 +219,9 @@ describe('settings center', () => {
218
219
  });
219
220
 
220
221
  it('should fallback to plugin-manager when system-settings is not allowed', async () => {
221
- const app = createMockClient({
222
- plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
223
- router: { type: 'memory', initialEntries: ['/admin/settings'] },
222
+ const app = createMockSettingsClient({
223
+ plugins: [SettingsBuildInPlugin, TestAclPlugin],
224
+ router: { type: 'memory', initialEntries: ['/settings'] },
224
225
  });
225
226
  mockAdminRuntime(app, {
226
227
  snippets: ['pm', '!pm.system-settings.system-settings'],
@@ -244,9 +245,9 @@ describe('settings center', () => {
244
245
  });
245
246
 
246
247
  it('should hide plugin-manager menu item when pm snippet is missing', async () => {
247
- const app = createMockClient({
248
- plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
249
- router: { type: 'memory', initialEntries: ['/admin/settings/system-settings'] },
248
+ const app = createMockSettingsClient({
249
+ plugins: [SettingsBuildInPlugin, TestAclPlugin],
250
+ router: { type: 'memory', initialEntries: ['/settings/system-settings'] },
250
251
  });
251
252
  mockAdminRuntime(app, {
252
253
  snippets: ['pm.system-settings.system-settings'],
@@ -260,9 +261,9 @@ describe('settings center', () => {
260
261
  });
261
262
 
262
263
  it('should show route empty state for unknown settings routes', async () => {
263
- const app = createMockClient({
264
- plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
265
- router: { type: 'memory', initialEntries: ['/admin/settings/unknown'] },
264
+ const app = createMockSettingsClient({
265
+ plugins: [SettingsBuildInPlugin, TestAclPlugin],
266
+ router: { type: 'memory', initialEntries: ['/settings/unknown'] },
266
267
  });
267
268
  mockAdminRuntime(app);
268
269
 
@@ -286,9 +287,9 @@ describe('settings center', () => {
286
287
  }
287
288
  }
288
289
 
289
- const app = createMockClient({
290
- plugins: [NocoBaseBuildInPlugin, TestAclPlugin, HiddenSettingsPlugin],
291
- router: { type: 'memory', initialEntries: ['/admin/settings/hidden-demo'] },
290
+ const app = createMockSettingsClient({
291
+ plugins: [SettingsBuildInPlugin, TestAclPlugin, HiddenSettingsPlugin],
292
+ router: { type: 'memory', initialEntries: ['/settings/hidden-demo'] },
292
293
  });
293
294
  mockAdminRuntime(app);
294
295
 
@@ -313,9 +314,9 @@ describe('settings center', () => {
313
314
  }
314
315
  }
315
316
 
316
- const app = createMockClient({
317
- plugins: [NocoBaseBuildInPlugin, TestAclPlugin, ProtectedSettingsPlugin],
318
- router: { type: 'memory', initialEntries: ['/admin/settings/secure-demo'] },
317
+ const app = createMockSettingsClient({
318
+ plugins: [SettingsBuildInPlugin, TestAclPlugin, ProtectedSettingsPlugin],
319
+ router: { type: 'memory', initialEntries: ['/settings/secure-demo'] },
319
320
  });
320
321
  mockAdminRuntime(app, {
321
322
  snippets: ['pm', 'pm.system-settings.system-settings', '!pm.secure-demo.index'],
@@ -345,9 +346,9 @@ describe('settings center', () => {
345
346
  }
346
347
  }
347
348
 
348
- const app = createMockClient({
349
- plugins: [NocoBaseBuildInPlugin, TestAclPlugin, MenuAclPlugin],
350
- router: { type: 'memory', initialEntries: ['/admin/settings/menu-acl-demo'] },
349
+ const app = createMockSettingsClient({
350
+ plugins: [SettingsBuildInPlugin, TestAclPlugin, MenuAclPlugin],
351
+ router: { type: 'memory', initialEntries: ['/settings/menu-acl-demo'] },
351
352
  });
352
353
  mockAdminRuntime(app, {
353
354
  snippets: ['pm', 'pm.system-settings.system-settings', '!pm.menu-acl-demo.menu'],
@@ -378,9 +379,9 @@ describe('settings center', () => {
378
379
  }
379
380
  }
380
381
 
381
- const app = createMockClient({
382
- plugins: [NocoBaseBuildInPlugin, TestAclPlugin, ManySettingsPlugin],
383
- router: { type: 'memory', initialEntries: ['/admin/settings/scroll-demo-29'] },
382
+ const app = createMockSettingsClient({
383
+ plugins: [SettingsBuildInPlugin, TestAclPlugin, ManySettingsPlugin],
384
+ router: { type: 'memory', initialEntries: ['/settings/scroll-demo-29'] },
384
385
  });
385
386
  mockAdminRuntime(app);
386
387
 
@@ -394,9 +395,9 @@ describe('settings center', () => {
394
395
  });
395
396
 
396
397
  it('should save system settings through systemSettings:put', async () => {
397
- const app = createMockClient({
398
- plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
399
- router: { type: 'memory', initialEntries: ['/admin/settings/system-settings'] },
398
+ const app = createMockSettingsClient({
399
+ plugins: [SettingsBuildInPlugin, TestAclPlugin],
400
+ router: { type: 'memory', initialEntries: ['/settings/system-settings'] },
400
401
  });
401
402
  mockAdminRuntime(app);
402
403
 
@@ -413,9 +414,9 @@ describe('settings center', () => {
413
414
  });
414
415
 
415
416
  it('should block invalid logo uploads by storage rules', async () => {
416
- const app = createMockClient({
417
- plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
418
- router: { type: 'memory', initialEntries: ['/admin/settings/system-settings'] },
417
+ const app = createMockSettingsClient({
418
+ plugins: [SettingsBuildInPlugin, TestAclPlugin],
419
+ router: { type: 'memory', initialEntries: ['/settings/system-settings'] },
419
420
  });
420
421
  const messageErrorSpy = vi.spyOn(message, 'error').mockImplementation(() => {
421
422
  return undefined as any;
@@ -0,0 +1,260 @@
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 { render, screen, waitFor } from '@testing-library/react';
11
+ import MockAdapter from 'axios-mock-adapter';
12
+ import React from 'react';
13
+ import { afterEach, describe, expect, it, vi } from 'vitest';
14
+ import { ACLRolesCheckProvider } from '../acl';
15
+ import { Plugin } from '../Plugin';
16
+ import { SettingsApplication } from '../settings-app/SettingsApplication';
17
+ import { SettingsBuildInPlugin } from '../settings-app/SettingsBuildInPlugin';
18
+
19
+ class TestAclPlugin extends Plugin {
20
+ async load() {
21
+ this.app.use(ACLRolesCheckProvider);
22
+ }
23
+ }
24
+
25
+ class StandaloneSettingsPlugin extends Plugin {
26
+ async load() {
27
+ this.pluginSettingsManager.addMenuItem({ key: 'standalone', title: 'Standalone' });
28
+ this.pluginSettingsManager.addPageTabItem({
29
+ menuKey: 'standalone',
30
+ key: 'index',
31
+ title: 'Standalone',
32
+ Component: () => <div>Standalone settings page</div>,
33
+ sort: -1000,
34
+ });
35
+ }
36
+ }
37
+
38
+ class MultiPortalSettingsPlugin extends Plugin {
39
+ async load() {
40
+ this.pluginSettingsManager.addMenuItem({
41
+ key: 'multi-portal',
42
+ title: 'Portal manager',
43
+ aclSnippet: 'pm.multi-portal',
44
+ sort: -300,
45
+ });
46
+ this.pluginSettingsManager.addPageTabItem({
47
+ menuKey: 'multi-portal',
48
+ key: 'index',
49
+ title: 'Portal manager',
50
+ aclSnippet: 'pm.multi-portal',
51
+ Component: () => <div>Portal manager page</div>,
52
+ });
53
+ }
54
+ }
55
+
56
+ class PrimarySettingsPlugin extends Plugin {
57
+ async load() {
58
+ this.pluginSettingsManager.addMenuItem({
59
+ key: 'portal-manager',
60
+ title: 'Portal manager',
61
+ sort: -300,
62
+ });
63
+ this.pluginSettingsManager.addPageTabItem({
64
+ menuKey: 'portal-manager',
65
+ key: 'index',
66
+ title: 'Portal manager',
67
+ Component: () => <div>Portal manager page</div>,
68
+ });
69
+ }
70
+ }
71
+
72
+ describe('standalone settings layout root', () => {
73
+ const originalLocation = window.location;
74
+ const originalModernClientPrefix = window.__nocobase_modern_client_prefix__;
75
+
76
+ afterEach(() => {
77
+ Object.defineProperty(window, 'location', { configurable: true, value: originalLocation });
78
+ if (originalModernClientPrefix === undefined) {
79
+ delete window.__nocobase_modern_client_prefix__;
80
+ } else {
81
+ window.__nocobase_modern_client_prefix__ = originalModernClientPrefix;
82
+ }
83
+ });
84
+
85
+ it.each(['/settings', '/settings/', '/settings/index'])(
86
+ 'redirects %s to multi-portal when it is accessible',
87
+ async (initialEntry) => {
88
+ const app = new SettingsApplication({
89
+ plugins: [SettingsBuildInPlugin, MultiPortalSettingsPlugin],
90
+ router: { type: 'memory', initialEntries: [initialEntry] },
91
+ ws: false,
92
+ });
93
+ const apiMock = new MockAdapter(app.apiClient.axios);
94
+ app.dataSourceManager.ensureLoaded = async () => {};
95
+ apiMock.onGet('app:getLang').reply(200, {
96
+ data: { lang: 'en-US', resources: { client: {} }, cron: {} },
97
+ });
98
+ apiMock.onGet('/auth:check').reply(200, { data: { id: 1, nickname: 'Admin' } });
99
+ apiMock.onGet('app:getInfo').reply(200, { data: { id: 'mock-app', version: 'test' } });
100
+ apiMock.onGet('systemSettings:get').reply(200, {
101
+ data: { id: 1, title: 'NocoBase', raw_title: 'NocoBase', logo: null },
102
+ });
103
+
104
+ const Root = app.getRootComponent();
105
+ render(<Root />);
106
+
107
+ await waitFor(() => {
108
+ expect(app.router.state.location.pathname).toBe('/settings/multi-portal');
109
+ });
110
+ },
111
+ );
112
+
113
+ it('falls back to system-settings when multi-portal is not registered', async () => {
114
+ const app = new SettingsApplication({
115
+ plugins: [SettingsBuildInPlugin, StandaloneSettingsPlugin],
116
+ router: { type: 'memory', initialEntries: ['/settings'] },
117
+ ws: false,
118
+ });
119
+ const apiMock = new MockAdapter(app.apiClient.axios);
120
+ app.dataSourceManager.ensureLoaded = async () => {};
121
+ apiMock.onGet('app:getLang').reply(200, {
122
+ data: { lang: 'en-US', resources: { client: {} }, cron: {} },
123
+ });
124
+ apiMock.onGet('/auth:check').reply(200, { data: { id: 1, nickname: 'Admin' } });
125
+ apiMock.onGet('app:getInfo').reply(200, { data: { id: 'mock-app', version: 'test' } });
126
+ apiMock.onGet('systemSettings:get').reply(200, {
127
+ data: { id: 1, title: 'NocoBase', raw_title: 'NocoBase', logo: null },
128
+ });
129
+
130
+ const Root = app.getRootComponent();
131
+ render(<Root />);
132
+
133
+ await waitFor(() => {
134
+ expect(app.router.state.location.pathname).toBe('/settings/system-settings');
135
+ });
136
+ });
137
+
138
+ it('falls back to system-settings when multi-portal is not accessible', async () => {
139
+ const app = new SettingsApplication({
140
+ plugins: [SettingsBuildInPlugin, TestAclPlugin, MultiPortalSettingsPlugin],
141
+ router: { type: 'memory', initialEntries: ['/settings'] },
142
+ ws: false,
143
+ });
144
+ const apiMock = new MockAdapter(app.apiClient.axios);
145
+ app.dataSourceManager.ensureLoaded = async () => {};
146
+ apiMock.onGet('app:getLang').reply(200, {
147
+ data: { lang: 'en-US', resources: { client: {} }, cron: {} },
148
+ });
149
+ apiMock.onGet('/auth:check').reply(200, { data: { id: 1, nickname: 'Admin' } });
150
+ apiMock.onGet('app:getInfo').reply(200, { data: { id: 'mock-app', version: 'test' } });
151
+ apiMock.onGet('roles:check').reply(200, {
152
+ data: { role: 'member', snippets: ['!pm.multi-portal'] },
153
+ });
154
+ apiMock.onGet('systemSettings:get').reply(200, {
155
+ data: { id: 1, title: 'NocoBase', raw_title: 'NocoBase', logo: null },
156
+ });
157
+
158
+ const Root = app.getRootComponent();
159
+ render(<Root />);
160
+
161
+ await waitFor(() => {
162
+ expect(app.router.state.location.pathname).toBe('/settings/system-settings');
163
+ });
164
+ });
165
+
166
+ it('keeps ordinary negative-sort settings in the active Settings group sidebar', async () => {
167
+ const app = new SettingsApplication({
168
+ plugins: [SettingsBuildInPlugin, TestAclPlugin, PrimarySettingsPlugin],
169
+ router: { type: 'memory', initialEntries: ['/settings/portal-manager'] },
170
+ ws: false,
171
+ });
172
+ const apiMock = new MockAdapter(app.apiClient.axios);
173
+ app.dataSourceManager.ensureLoaded = async () => {};
174
+ apiMock.onGet('app:getLang').reply(200, {
175
+ data: { lang: 'en-US', resources: { client: {} }, cron: {} },
176
+ });
177
+ apiMock.onGet('/auth:check').reply(200, { data: { id: 1, nickname: 'Admin' } });
178
+ apiMock.onGet('app:getInfo').reply(200, { data: { id: 'mock-app', version: 'test' } });
179
+ apiMock.onGet('roles:check').reply(200, {
180
+ data: { role: 'root', snippets: ['pm'] },
181
+ });
182
+ apiMock.onGet('systemSettings:get').reply(200, {
183
+ data: { id: 1, title: 'NocoBase', raw_title: 'NocoBase', logo: null },
184
+ });
185
+
186
+ const Root = app.getRootComponent();
187
+ render(<Root />);
188
+
189
+ expect(await screen.findByText('Portal manager page')).toBeInTheDocument();
190
+
191
+ const portalManagerItem = screen.getByRole('menuitem', { name: 'Portal manager' });
192
+ expect(portalManagerItem.closest('aside')).toBeInTheDocument();
193
+ expect(screen.queryByRole('menuitem', { name: /Plugin manager$/ })).not.toBeInTheDocument();
194
+ });
195
+
196
+ it('uses document navigation to the standalone Settings signin page when unauthenticated', async () => {
197
+ const replace = vi.fn();
198
+ Object.defineProperty(window, 'location', {
199
+ configurable: true,
200
+ value: { ...originalLocation, replace },
201
+ });
202
+ window.__nocobase_modern_client_prefix__ = 'v';
203
+ const app = new SettingsApplication({
204
+ plugins: [SettingsBuildInPlugin],
205
+ router: { type: 'memory', initialEntries: ['/settings/workflow?tab=list#recent'] },
206
+ ws: false,
207
+ });
208
+ const apiMock = new MockAdapter(app.apiClient.axios);
209
+ apiMock.onGet('app:getLang').reply(200, {
210
+ data: { lang: 'en-US', resources: { client: {} }, cron: {} },
211
+ });
212
+ apiMock.onGet('app:getInfo').reply(200, { data: { id: 'mock-app', version: 'test' } });
213
+ apiMock.onGet('systemSettings:get').reply(200, {
214
+ data: { id: 1, title: 'NocoBase', raw_title: 'NocoBase', logo: null },
215
+ });
216
+ apiMock.onGet('/auth:check').reply(200, { data: {} });
217
+
218
+ const Root = app.getRootComponent();
219
+ render(<Root />);
220
+
221
+ await waitFor(() => {
222
+ expect(replace).toHaveBeenCalledWith('/settings/signin?redirect=%2Fsettings%2Fworkflow%3Ftab%3Dlist%23recent');
223
+ });
224
+ });
225
+
226
+ it('does not render the Settings header before the initial auth check completes', async () => {
227
+ let resolveAuthCheck: (response: [number, { data: { id: number } }]) => void = () => {
228
+ throw new Error('Auth check resolver is not initialized');
229
+ };
230
+ const authCheckResponse = new Promise<[number, { data: { id: number } }]>((resolve) => {
231
+ resolveAuthCheck = resolve;
232
+ });
233
+ const app = new SettingsApplication({
234
+ plugins: [SettingsBuildInPlugin],
235
+ router: { type: 'memory', initialEntries: ['/settings'] },
236
+ ws: false,
237
+ });
238
+ const apiMock = new MockAdapter(app.apiClient.axios);
239
+ app.dataSourceManager.ensureLoaded = async () => {};
240
+ apiMock.onGet('app:getLang').reply(200, {
241
+ data: { lang: 'en-US', resources: { client: {} }, cron: {} },
242
+ });
243
+ apiMock.onGet('/auth:check').reply(() => authCheckResponse);
244
+ apiMock.onGet('app:getInfo').reply(200, { data: { id: 'mock-app', version: 'test' } });
245
+ apiMock.onGet('systemSettings:get').reply(200, {
246
+ data: { id: 1, title: 'NocoBase', raw_title: 'NocoBase', logo: null },
247
+ });
248
+
249
+ const Root = app.getRootComponent();
250
+ render(<Root />);
251
+
252
+ await waitFor(() => {
253
+ expect(apiMock.history.get.some((request) => request.url === '/auth:check')).toBe(true);
254
+ });
255
+ expect(screen.queryByRole('banner')).not.toBeInTheDocument();
256
+
257
+ resolveAuthCheck([200, { data: { id: 1 } }]);
258
+ expect(await screen.findByRole('banner')).toBeInTheDocument();
259
+ });
260
+ });
@@ -0,0 +1,99 @@
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 { afterEach, describe, expect, it } from 'vitest';
11
+ import { resolveStandaloneSettingsPath } from '../settings-app/runtimePaths';
12
+
13
+ const originalModernPrefix = window.__nocobase_modern_client_prefix__;
14
+
15
+ const createApp = (publicPath: string, basename: string, name = 'main') =>
16
+ ({
17
+ name,
18
+ getPublicPath: () => publicPath,
19
+ router: {
20
+ getBasename: () => basename,
21
+ },
22
+ }) as any;
23
+
24
+ describe('standalone settings runtime paths', () => {
25
+ afterEach(() => {
26
+ window.__nocobase_modern_client_prefix__ = originalModernPrefix;
27
+ });
28
+
29
+ it('maps legacy v2 settings paths to the root-public standalone SPA', () => {
30
+ const app = createApp('/nocobase/v/', '/nocobase/v');
31
+
32
+ expect(resolveStandaloneSettingsPath(app, '/nocobase/v/admin/settings/system-settings?tab=mail#smtp')).toBe(
33
+ '/nocobase/settings/system-settings?tab=mail#smtp',
34
+ );
35
+ expect(resolveStandaloneSettingsPath(app, '/nocobase/v/admin/workflow/workflows/42?tab=nodes#canvas')).toBe(
36
+ '/nocobase/settings/workflow/workflows/42?tab=nodes#canvas',
37
+ );
38
+ expect(resolveStandaloneSettingsPath(app, '/nocobase/v/admin/workflow/executions/88')).toBe(
39
+ '/nocobase/settings/workflow/executions/88',
40
+ );
41
+ expect(resolveStandaloneSettingsPath(app, '/nocobase/v/admin/ai/knowledge-base/detail/k1/documents')).toBe(
42
+ '/nocobase/settings/ai/knowledge-base/detail/k1/documents',
43
+ );
44
+ });
45
+
46
+ it('preserves both supported sub-application scopes', () => {
47
+ const appsApp = createApp('/nocobase/v/', '/nocobase/v/apps/demo');
48
+ const newApp = createApp('/nocobase/v/', '/nocobase/v/_app/demo');
49
+
50
+ expect(resolveStandaloneSettingsPath(appsApp, '/nocobase/v/apps/demo/admin/settings/routes')).toBe(
51
+ '/nocobase/settings/apps/demo/routes',
52
+ );
53
+ expect(resolveStandaloneSettingsPath(newApp, '/nocobase/v/_app/demo/admin/settings/routes')).toBe(
54
+ '/nocobase/settings/_app/demo/routes',
55
+ );
56
+ });
57
+
58
+ it('supports a custom modern prefix without leaking it into settings URLs', () => {
59
+ window.__nocobase_modern_client_prefix__ = 'modern';
60
+ const app = createApp('/base/modern/', '/base/modern');
61
+
62
+ expect(resolveStandaloneSettingsPath(app, '/base/modern/admin/settings/security')).toBe('/base/settings/security');
63
+ });
64
+
65
+ it('does not treat an apps segment inside the main application public path as a sub-application scope', () => {
66
+ window.__nocobase_modern_client_prefix__ = 'modern';
67
+ const app = createApp('/tenant/apps/root/modern/', '/tenant/apps/root/modern');
68
+
69
+ expect(resolveStandaloneSettingsPath(app, '/tenant/apps/root/modern/admin/settings/security')).toBe(
70
+ '/tenant/apps/root/settings/security',
71
+ );
72
+ });
73
+
74
+ it.each(['apps', '_app'])(
75
+ 'does not remove a matching %s segment from the root public path for a real sub-application',
76
+ (scope) => {
77
+ window.__nocobase_modern_client_prefix__ = 'modern';
78
+ const app = createApp(`/tenant/${scope}/demo/modern/`, `/tenant/${scope}/demo/modern/${scope}/demo`, 'demo');
79
+
80
+ expect(
81
+ resolveStandaloneSettingsPath(app, `/tenant/${scope}/demo/modern/${scope}/demo/admin/settings/security`),
82
+ ).toBe(`/tenant/${scope}/demo/settings/${scope}/demo/security`);
83
+ expect(
84
+ resolveStandaloneSettingsPath(
85
+ app,
86
+ `/tenant/${scope}/demo/modern/${scope}/demo/admin/settings/mail/oauth2?code=abc#done`,
87
+ ),
88
+ ).toBe(`/tenant/${scope}/demo/${scope}/demo/admin/settings/mail/oauth2?code=abc#done`);
89
+ },
90
+ );
91
+
92
+ it('returns the legacy V1 callback for Email OAuth', () => {
93
+ const app = createApp('/nocobase/v/', '/nocobase/v/apps/demo');
94
+
95
+ expect(resolveStandaloneSettingsPath(app, '/nocobase/v/apps/demo/admin/settings/mail/oauth2?code=abc#done')).toBe(
96
+ '/nocobase/apps/demo/admin/settings/mail/oauth2?code=abc#done',
97
+ );
98
+ });
99
+ });