@nocobase/app-template-default 1.0.0-beta.57 → 1.0.0-beta.59

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 (69) hide show
  1. package/AGENTS.md +26 -21
  2. package/README.MD +7 -53
  3. package/cli/plugins.ts +1 -7
  4. package/client/agent-annotations-host.ts +8 -2
  5. package/client/app.ts +0 -2
  6. package/client/components/brand-loading-mark.tsx +46 -0
  7. package/client/components/brand-mark-geometry.ts +7 -0
  8. package/client/components/brand-spinner.tsx +22 -0
  9. package/client/components/brand-watermark.tsx +32 -0
  10. package/client/components/loading.tsx +4 -3
  11. package/client/components/route-overlay.tsx +15 -4
  12. package/client/components/status-badge.tsx +29 -0
  13. package/client/components/ui/alert-dialog.tsx +186 -0
  14. package/client/components/ui/card.tsx +102 -0
  15. package/client/components/ui/dialog.tsx +2 -2
  16. package/client/components/ui/radio-group.tsx +35 -0
  17. package/client/components/ui/spinner.tsx +2 -20
  18. package/client/components/ui/toast.tsx +3 -2
  19. package/client/extensions/nocobase-file-component-ui/components/file-upload-field.tsx +3 -3
  20. package/client/extensions/nocobase-inbox/inbox-item.tsx +4 -3
  21. package/client/layouts/app-layout.tsx +8 -28
  22. package/client/layouts/components/app-sidebar.tsx +4 -4
  23. package/client/layouts/components/header-actions.tsx +3 -36
  24. package/client/layouts/index.ts +0 -1
  25. package/client/lib/toaster.ts +1 -1
  26. package/client/locales/en-US.ts +31 -17
  27. package/client/locales/zh-CN.ts +24 -14
  28. package/client/pages/auth/login.tsx +1 -0
  29. package/client/pages/auth/shared.tsx +9 -9
  30. package/client/pages/home.tsx +80 -7
  31. package/client/plugins.ts +2 -6
  32. package/client/routes.ts +4 -10
  33. package/client/routing/app-router.tsx +5 -55
  34. package/client/routing/route-navigation.ts +1 -1
  35. package/client/styles.css +244 -0
  36. package/client/theme/theme-settings.tsx +22 -20
  37. package/client/theme/themes/compact.css +34 -16
  38. package/client/theme/themes/default.css +34 -16
  39. package/config.example.yml +0 -16
  40. package/eslint.config.js +0 -13
  41. package/index.html +48 -16
  42. package/package.json +24 -30
  43. package/server/config/index.ts +0 -6
  44. package/server/plugins.ts +0 -4
  45. package/tests/components/app-layout-routes.test.tsx +1 -1
  46. package/tests/components/inbox.test.tsx +0 -19
  47. package/tests/components/layout-i18n.test.tsx +5 -92
  48. package/tests/components/primitive-labels.test.tsx +61 -1
  49. package/tests/fixtures/theme-tokens.html +1 -1
  50. package/tests/fixtures/theme-tokens.tsx +7 -0
  51. package/tests/logic/app-server.test.ts +0 -15
  52. package/tests/logic/client-plugin-registry.test.ts +0 -21
  53. package/tests/logic/client-routes.test.ts +2 -6
  54. package/tests/logic/client-shell.test.tsx +11 -107
  55. package/tests/logic/client-theme.test.tsx +17 -1
  56. package/tests/logic/config.test.ts +0 -1
  57. package/tests/logic/plugin-commands.test.ts +1 -1
  58. package/tsconfig.json +1 -4
  59. package/tsconfig.server.json +1 -6
  60. package/client/components/inbox-badge.ts +0 -49
  61. package/client/components/inbox-button.tsx +0 -132
  62. package/client/components/inbox-header-button.tsx +0 -25
  63. package/client/layouts/components/surface-empty.tsx +0 -35
  64. package/client/layouts/settings-layout.tsx +0 -172
  65. package/server/config/ai.ts +0 -27
  66. package/server/config/workflow.ts +0 -24
  67. package/tests/logic/client-settings.test.tsx +0 -841
  68. package/tests/logic/dev-routes-production-build.test.ts +0 -168
  69. package/tests/logic/plugin-page-authz.test.ts +0 -76
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nocobase/app-template-default",
3
3
  "displayName": "Default Template",
4
- "version": "1.0.0-beta.57",
4
+ "version": "1.0.0-beta.59",
5
5
  "type": "module",
6
6
  "prettier": "@nocobase/dev-config/prettier",
7
7
  "engines": {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "nocobase": {
11
11
  "templateKind": "app",
12
- "defaultTemplateVersion": "1.0.0-beta.57"
12
+ "defaultTemplateVersion": "1.0.0-beta.59"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@ai-sdk/react": "2.0.0",
@@ -17,7 +17,6 @@
17
17
  "@dicebear/core": "^9.4.3",
18
18
  "@gchust/agent-annotations": "0.1.9",
19
19
  "@hookform/resolvers": "^5.0.1",
20
- "@nocobase/ai-employee-avatars": "^1.0.2",
21
20
  "@playwright/test": "^1.62.1",
22
21
  "@refinedev/cli": "^2.16.48",
23
22
  "@refinedev/core": "^5.0.8",
@@ -76,14 +75,13 @@
76
75
  "vite": "^6.3.5",
77
76
  "vitest": "^4.1.10",
78
77
  "tar": "^7.5.22",
79
- "@nocobase/app-client": "^2.0.0-beta.2",
80
- "@nocobase/app-plugin-template-print": "0.0.2-beta.0",
81
- "@nocobase/app-skills": "0.1.0-beta.25",
78
+ "@nocobase/app-client": "^3.0.0-beta.0",
79
+ "@nocobase/app-plugin-template-print": "0.0.2-beta.1",
80
+ "@nocobase/app-skills": "0.1.0-beta.27",
81
+ "@nocobase/app-testing": "0.1.0-beta.2",
82
82
  "@nocobase/app-websocket": "^0.0.1",
83
- "@nocobase/dev-config": "0.1.0-beta.19",
83
+ "@nocobase/dev-config": "0.1.0-beta.21",
84
84
  "@nocobase/drive": "^0.1.0-beta.4",
85
- "@nocobase/app-testing": "0.1.0-beta.1",
86
- "@nocobase/hub-cli": "^1.0.0-beta.1",
87
85
  "@nocobase/logging": "^0.1.0-beta.6",
88
86
  "@nocobase/session": "^0.1.0-beta.2"
89
87
  },
@@ -95,7 +93,6 @@
95
93
  "cli",
96
94
  "client",
97
95
  "server",
98
- "workflows",
99
96
  "database/tsconfig.json",
100
97
  "database/*/migrations/**",
101
98
  "database/*/seeds/**",
@@ -124,7 +121,7 @@
124
121
  },
125
122
  "repository": {
126
123
  "type": "git",
127
- "url": "git+https://github.com/nocobase/nocobase3.git",
124
+ "url": "git+https://github.com/nocobase/nocobase.git",
128
125
  "directory": "packages/templates/app-template-default"
129
126
  },
130
127
  "browserslist": {
@@ -144,36 +141,33 @@
144
141
  "@oclif/core": "^4.14.0",
145
142
  "better-auth": "1.7.5",
146
143
  "hono": "^4.13.2",
147
- "@nocobase/app-plugin-ai-employee": "^3.0.0-beta.1",
148
- "@nocobase/app-plugin-api-keys": "^1.0.0-beta.13",
149
- "@nocobase/app-plugin-authentication": "^2.0.0-beta.2",
150
- "@nocobase/app-plugin-authorization": "^1.0.0-beta.25",
151
- "@nocobase/app-plugin-database-explorer": "^1.0.0-beta.11",
152
- "@nocobase/app-plugin-file": "^1.0.0-beta.20",
153
- "@nocobase/app-plugin-i18n": "^1.0.0-beta.13",
154
- "@nocobase/app-plugin-notification": "^1.0.0-beta.24",
155
- "@nocobase/app-plugin-notification-in-app": "^1.0.0-beta.22",
144
+ "@nocobase/app-plugin-api-keys": "^1.0.0-beta.14",
145
+ "@nocobase/app-plugin-authentication": "^2.0.0-beta.3",
146
+ "@nocobase/app-plugin-authorization": "^1.0.0-beta.26",
147
+ "@nocobase/app-plugin-database-explorer": "^1.0.0-beta.12",
148
+ "@nocobase/app-plugin-file": "^1.0.0-beta.22",
149
+ "@nocobase/app-plugin-i18n": "^1.0.0-beta.14",
150
+ "@nocobase/app-plugin-notification": "^1.0.0-beta.25",
151
+ "@nocobase/app-plugin-notification-in-app": "^1.0.0-beta.23",
156
152
  "@nocobase/app-plugin-notification-providers": "^0.2.0-beta.10",
157
- "@nocobase/app-plugin-scheduler": "^1.0.0-beta.14",
158
- "@nocobase/app-plugin-users": "^2.0.0-beta.1",
159
- "@nocobase/app-server": "^2.0.0-beta.1",
153
+ "@nocobase/app-plugin-scheduler": "^1.0.0-beta.16",
154
+ "@nocobase/app-plugin-users": "^2.0.0-beta.2",
155
+ "@nocobase/app-server": "^2.0.0-beta.3",
160
156
  "@nocobase/config": "^0.1.0-beta.2",
161
157
  "@nocobase/db": "^1.0.0-beta.18",
162
- "@nocobase/app-plugin-workflow": "^2.0.0-beta.1",
163
158
  "@nocobase/db-sqlite": "^0.1.0-beta.4",
164
159
  "@nocobase/service-provider": "^0.0.2-beta.1",
165
160
  "@nocobase/snowflake": "^1.0.0-beta.3",
166
- "@nocobase/app-plugin-authz-default-access": "^1.0.0-beta.10",
167
- "@nocobase/app-plugin-authz-sharing-rules": "^1.0.0-beta.10",
168
- "@nocobase/app-plugin-authz-restriction-rules": "^1.0.0-beta.9",
161
+ "@nocobase/app-plugin-authz-default-access": "^1.0.0-beta.11",
162
+ "@nocobase/app-plugin-authz-sharing-rules": "^1.0.0-beta.11",
163
+ "@nocobase/app-plugin-authz-restriction-rules": "^1.0.0-beta.10",
169
164
  "@nocobase/authorization": "^1.0.0-beta.12",
170
165
  "@nocobase/queue": "^0.1.0-beta.8",
171
166
  "@nocobase/jobs": "^0.1.0-beta.2",
172
- "@nocobase/i18n": "^1.0.0-beta.5",
167
+ "@nocobase/i18n": "^1.0.0-beta.6",
173
168
  "@nocobase/caching": "^0.1.0-beta.2",
174
- "@nocobase/ai-employee": "^0.2.0-beta.8",
175
169
  "@nocobase/repository-input": "^0.1.0-beta.2",
176
- "@nocobase/app-cli": "^1.0.0-beta.14"
170
+ "@nocobase/app-cli": "^1.0.0-beta.17"
177
171
  },
178
172
  "description": "NocoBase 3 application template with the standard product capabilities.",
179
173
  "imports": {
@@ -20,8 +20,6 @@ import app from './app.js';
20
20
  import api from './api.js';
21
21
  import database from './database.js';
22
22
  import snowflake from './snowflake.js';
23
- import ai from './ai.js';
24
- import workflow from './workflow.js';
25
23
  import users from './users.js';
26
24
 
27
25
  const defaultConfigs: AppConfigFactory<{
@@ -43,8 +41,6 @@ const defaultConfigs: AppConfigFactory<{
43
41
  api: ReturnType<typeof api>;
44
42
  database: ReturnType<typeof database>;
45
43
  snowflake: ReturnType<typeof snowflake>;
46
- ai: ReturnType<typeof ai>;
47
- workflow: ReturnType<typeof workflow>;
48
44
  users: ReturnType<typeof users>;
49
45
  }> = defaultAppConfigs({
50
46
  auth,
@@ -65,8 +61,6 @@ const defaultConfigs: AppConfigFactory<{
65
61
  api,
66
62
  database,
67
63
  snowflake,
68
- ai,
69
- workflow,
70
64
  users,
71
65
  });
72
66
 
package/server/plugins.ts CHANGED
@@ -3,7 +3,6 @@ import sharingRules from '@nocobase/app-plugin-authz-sharing-rules/server';
3
3
  import restrictionRules from '@nocobase/app-plugin-authz-restriction-rules/server';
4
4
  import apiKeys from '@nocobase/app-plugin-api-keys/server';
5
5
  import authentication from '@nocobase/app-plugin-authentication/server';
6
- import aiEmployee from '@nocobase/app-plugin-ai-employee/server/plugin';
7
6
  import authorization from '@nocobase/app-plugin-authorization/server';
8
7
  import databaseExplorer from '@nocobase/app-plugin-database-explorer/server';
9
8
  import users from '@nocobase/app-plugin-users/server';
@@ -11,7 +10,6 @@ import i18n from '@nocobase/app-plugin-i18n/server';
11
10
  import notification from '@nocobase/app-plugin-notification/server';
12
11
  import notificationInApp from '@nocobase/app-plugin-notification-in-app/server';
13
12
  import notificationProviders from '@nocobase/app-plugin-notification-providers/server';
14
- import workflow from '@nocobase/app-plugin-workflow/server';
15
13
  import {
16
14
  defineServerPlugins,
17
15
  type AppServerPlugins,
@@ -21,7 +19,6 @@ import file from '@nocobase/app-plugin-file/server';
21
19
 
22
20
  const serverPlugins: AppServerPlugins = defineServerPlugins([
23
21
  authentication,
24
- aiEmployee,
25
22
  authorization,
26
23
  defaultAccess,
27
24
  sharingRules,
@@ -33,7 +30,6 @@ const serverPlugins: AppServerPlugins = defineServerPlugins([
33
30
  notification,
34
31
  notificationInApp,
35
32
  notificationProviders,
36
- workflow,
37
33
  file,
38
34
  scheduler,
39
35
  ]);
@@ -31,7 +31,7 @@ vi.mock('@nocobase/app-plugin-i18n/client', async (importOriginal) => ({
31
31
  }));
32
32
  vi.mock('@nocobase/app-client', async (original) => ({
33
33
  ...(await original<typeof import('@nocobase/app-client')>()),
34
- useClientApplication: () => ({ runtime: { settingsRouteTree: [] } }),
34
+ useClientApplication: () => ({ runtime: {} }),
35
35
  }));
36
36
  vi.mock('../../client/routing/route-navigation.js', async (original) => ({
37
37
  ...(await original<
@@ -29,7 +29,6 @@ vi.mock('@nocobase/app-client', async (importOriginal) => {
29
29
  };
30
30
  });
31
31
 
32
- import { InboxHeaderButton } from '../../client/components/inbox-header-button.tsx';
33
32
  import InboxPage from '../../client/pages/inbox.tsx';
34
33
 
35
34
  const messages = [
@@ -97,24 +96,6 @@ function Frame({
97
96
  );
98
97
  }
99
98
 
100
- it('links the header to the inbox with the unread count, for a signed-in person only', async () => {
101
- const view = render(
102
- <Frame path='/'>
103
- <InboxHeaderButton />
104
- </Frame>,
105
- );
106
- const link = await screen.findByRole('link', { name: 'Inbox, 2 unread' });
107
- expect(link).toHaveAttribute('href', '/inbox');
108
- expect(screen.getByText('2')).toBeInTheDocument();
109
- mocks.session = null;
110
- view.rerender(
111
- <Frame path='/'>
112
- <InboxHeaderButton />
113
- </Frame>,
114
- );
115
- expect(screen.queryByRole('link')).toBeNull();
116
- });
117
-
118
99
  it("lists the plugin's messages as notifications and marks one read when it is opened", async () => {
119
100
  render(
120
101
  <Frame path='/inbox'>
@@ -6,10 +6,9 @@ import {
6
6
  import type { AppClientRegisteredRoute } from '@nocobase/app-client/plugins';
7
7
  import { I18nRuntime } from '@nocobase/i18n';
8
8
  import { I18nProvider } from '@nocobase/i18n/client';
9
- import userEvent from '@testing-library/user-event';
10
9
  import { act, render, screen } from '@testing-library/react';
11
10
  import type { ReactNode } from 'react';
12
- import { MemoryRouter, Route, Routes } from 'react-router';
11
+ import { MemoryRouter } from 'react-router';
13
12
  import { describe, expect, it, vi } from 'vitest';
14
13
 
15
14
  import locales from '../../client/locales/index.js';
@@ -17,17 +16,7 @@ import { AppLayout } from '../../client/layouts/app-layout.js';
17
16
  vi.mock('@nocobase/app-plugin-i18n/client', () => ({
18
17
  useSyncServerLocale: () => {},
19
18
  }));
20
- import { SettingsLayout } from '../../client/layouts/settings-layout.js';
21
19
 
22
- // The header's inbox button reads the in-app notification plugin's API, which these tests do not serve; it is
23
- // covered by tests/components/inbox.test.tsx.
24
- vi.mock('../../client/components/inbox-header-button', () => ({
25
- InboxHeaderButton: () => null,
26
- }));
27
-
28
- vi.mock('../../client/routing/client-route.js', () => ({
29
- ClientRoute: () => <p>Preferences content</p>,
30
- }));
31
20
  vi.mock('../../client/theme/index.js', () => ({ ThemeSettings: () => null }));
32
21
  vi.mock('../../client/layouts/components/user-menu.js', () => ({
33
22
  UserMenu: () => null,
@@ -43,17 +32,6 @@ vi.mock('../../client/routing/route-navigation.js', async (importOriginal) => ({
43
32
  }),
44
33
  }));
45
34
 
46
- const route: AppClientRegisteredRoute = {
47
- id: 'preferences',
48
- name: 'preferences',
49
- path: '/settings/preferences',
50
- packageName: 'test',
51
- source: 'application',
52
- auth: 'optional',
53
- navigation: { title: 'Preferences' },
54
- componentLoader: async () => ({ default: () => <p>Preferences content</p> }),
55
- };
56
-
57
35
  /** The page the shell opens on: the header names it. */
58
36
  const overview: AppClientRegisteredRoute = {
59
37
  id: 'overview',
@@ -66,7 +44,7 @@ const overview: AppClientRegisteredRoute = {
66
44
  componentLoader: async () => ({ default: () => null }),
67
45
  };
68
46
 
69
- async function setup(children: ReactNode, path = '/') {
47
+ async function setup(children: ReactNode) {
70
48
  vi.stubGlobal('matchMedia', () => ({
71
49
  matches: true,
72
50
  addEventListener: vi.fn(),
@@ -81,12 +59,12 @@ async function setup(children: ReactNode, path = '/') {
81
59
  await runtime.init('en-US');
82
60
  const app = {
83
61
  config: createAppClientConfig({ rawConfig: {} }),
84
- runtime: { settingsRouteTree: [route] },
62
+ runtime: {},
85
63
  } as unknown as ClientApplication;
86
64
  render(
87
65
  <I18nProvider runtime={runtime}>
88
66
  <ClientApplicationContext.Provider value={app}>
89
- <MemoryRouter initialEntries={[path]}>{children}</MemoryRouter>
67
+ <MemoryRouter initialEntries={['/']}>{children}</MemoryRouter>
90
68
  </ClientApplicationContext.Provider>
91
69
  </I18nProvider>,
92
70
  );
@@ -94,7 +72,7 @@ async function setup(children: ReactNode, path = '/') {
94
72
  }
95
73
 
96
74
  describe('shell translations', () => {
97
- it('updates header, footer, tooltips and accessible labels without remounting', async () => {
75
+ it('updates header, footer and accessible labels without remounting', async () => {
98
76
  const runtime = await setup(<AppLayout routes={[overview]} />);
99
77
  // The header's trail, where a tagline used to be.
100
78
  expect(
@@ -114,74 +92,9 @@ describe('shell translations', () => {
114
92
  'href',
115
93
  'https://www.nocobase.com',
116
94
  );
117
- const user = userEvent.setup();
118
- const settings = screen.getByRole('link', { name: '设置' });
119
- expect(settings).not.toHaveAttribute('title');
120
- await user.hover(settings);
121
- expect(
122
- await screen.findByText('设置', {
123
- selector: '[data-slot=tooltip-content]',
124
- }),
125
- ).toBeVisible();
126
- await user.unhover(settings);
127
95
  await act(() => runtime.changeLanguage('en-US'));
128
96
  expect(
129
97
  screen.getByRole('navigation', { name: 'Breadcrumb' }),
130
98
  ).toBeVisible();
131
- act(() => settings.focus());
132
- expect(
133
- await screen.findByText('Settings', {
134
- selector: '[data-slot=tooltip-content]',
135
- }),
136
- ).toBeVisible();
137
99
  });
138
-
139
- it.each([
140
- [
141
- 'settings',
142
- SettingsLayout,
143
- '暂无可用设置',
144
- '没有已启用的插件提供你有权访问的设置页面。',
145
- ],
146
- ] as const)(
147
- 'translates the %s empty state',
148
- async (surface, Layout, title, description) => {
149
- const runtime = await setup(<Layout routeTree={[]} />, `/${surface}`);
150
- await act(() => runtime.changeLanguage('zh-CN'));
151
- expect(screen.getByRole('heading', { name: title })).toBeVisible();
152
- expect(screen.getByText(description)).toBeVisible();
153
- expect(screen.getByRole('link', { name: '返回应用' })).toHaveAttribute(
154
- 'href',
155
- '/',
156
- );
157
- },
158
- );
159
-
160
- it.each(['settings'] as const)(
161
- 'translates the %s page header',
162
- async (surface) => {
163
- const Layout = SettingsLayout;
164
- const page = { ...route, path: `/${surface}/preferences` };
165
- const runtime = await setup(
166
- <Routes>
167
- <Route
168
- path={`/${surface}/*`}
169
- element={<Layout routeTree={[page]} />}
170
- />
171
- </Routes>,
172
- page.path,
173
- );
174
- await screen.findByText('Preferences content');
175
- await act(() => runtime.changeLanguage('zh-CN'));
176
- expect(screen.getByRole('link', { name: '返回应用' })).toHaveAttribute(
177
- 'href',
178
- '/',
179
- );
180
- expect(
181
- screen.getByRole('navigation', {
182
- name: '设置',
183
- }),
184
- ).toBeVisible();
185
- },
186
- );
187
100
  });
@@ -6,9 +6,21 @@ import { act, render, screen } from '@testing-library/react';
6
6
  import type { ReactElement, ReactNode } from 'react';
7
7
  import { describe, expect, it } from 'vitest';
8
8
 
9
- import { Dialog, DialogContent, DialogTitle } from '#components/ui/dialog';
9
+ import {
10
+ Dialog,
11
+ DialogContent,
12
+ DialogFooter,
13
+ DialogTitle,
14
+ } from '#components/ui/dialog';
15
+ import {
16
+ AlertDialog,
17
+ AlertDialogContent,
18
+ AlertDialogTitle,
19
+ } from '#components/ui/alert-dialog';
10
20
  import { Sheet, SheetContent, SheetTitle } from '#components/ui/sheet';
11
21
  import { Spinner } from '#components/ui/spinner';
22
+ import { BrandSpinner } from '#components/brand-spinner';
23
+ import { Loading } from '#components/loading';
12
24
  import { createToastManager, Toaster } from '#components/ui/toast';
13
25
 
14
26
  import zhCN from '../../client/locales/zh-CN.js';
@@ -70,6 +82,19 @@ describe('labels built into the shipped primitives', () => {
70
82
  expect(close).toHaveAttribute('data-slot', 'toast-close');
71
83
  });
72
84
 
85
+ it('labels brand loading and allows decorative inline indicators', () => {
86
+ const { rerender } = render(<BrandSpinner />, { wrapper: I18n });
87
+ expect(
88
+ screen.getByRole('status', { name: zhCN['status.loading'] }),
89
+ ).toBeVisible();
90
+ rerender(<BrandSpinner aria-hidden='true' />);
91
+ expect(screen.queryByRole('status')).not.toBeInTheDocument();
92
+ rerender(<Loading label='正在加载客户' />);
93
+ expect(
94
+ screen.getByRole('status', { name: '正在加载客户' }),
95
+ ).toHaveTextContent('正在加载客户');
96
+ });
97
+
73
98
  it('labels the Spinner through status.loading', () => {
74
99
  render(<Spinner />, { wrapper: I18n });
75
100
 
@@ -78,3 +103,38 @@ describe('labels built into the shipped primitives', () => {
78
103
  ).toBeInTheDocument();
79
104
  });
80
105
  });
106
+
107
+ describe('application-owned dialog layout overrides', () => {
108
+ it('allows a form dialog to replace default width, padding and spacing', () => {
109
+ render(
110
+ <Dialog open>
111
+ <DialogContent className='sm:max-w-3xl p-8 gap-2'>
112
+ <DialogTitle>Custom form</DialogTitle>
113
+ <DialogFooter className='mx-0 mb-0 px-8 py-2 gap-1'>
114
+ Actions
115
+ </DialogFooter>
116
+ </DialogContent>
117
+ </Dialog>,
118
+ { wrapper: I18n },
119
+ );
120
+ const content = screen.getByRole('dialog');
121
+ expect(content).toHaveClass('sm:max-w-3xl', 'p-8', 'gap-2');
122
+ expect(content).not.toHaveClass('sm:max-w-lg', 'p-6', 'gap-5');
123
+ const footer = screen.getByText('Actions');
124
+ expect(footer).toHaveClass('mx-0', 'mb-0', 'px-8', 'py-2', 'gap-1');
125
+ expect(footer).not.toHaveClass('-mx-6', '-mb-6', 'px-6', 'py-4', 'gap-3');
126
+ });
127
+ it('allows a compact confirmation to replace its size default', () => {
128
+ render(
129
+ <AlertDialog open>
130
+ <AlertDialogContent size='sm' className='sm:max-w-xl p-8 gap-2'>
131
+ <AlertDialogTitle>Custom confirmation</AlertDialogTitle>
132
+ </AlertDialogContent>
133
+ </AlertDialog>,
134
+ { wrapper: I18n },
135
+ );
136
+ const content = screen.getByRole('alertdialog');
137
+ expect(content).toHaveClass('sm:max-w-xl', 'p-8', 'gap-2');
138
+ expect(content).not.toHaveClass('sm:max-w-sm', 'p-6', 'gap-5');
139
+ });
140
+ });
@@ -7,6 +7,6 @@
7
7
  </head>
8
8
  <body>
9
9
  <div id="root"></div>
10
- <script type="module" src="./theme-tokens.tsx"></script>
10
+ <script type="module" src="./tests/fixtures/theme-tokens.tsx"></script>
11
11
  </body>
12
12
  </html>
@@ -1,6 +1,7 @@
1
1
  import { Refine } from '@refinedev/core';
2
2
  import { createRoot } from 'react-dom/client';
3
3
  import { MemoryRouter } from 'react-router';
4
+ import { Loading } from '../../client/components/loading';
4
5
  import { Button } from '../../client/components/ui/button';
5
6
  import { Input } from '../../client/components/ui/input';
6
7
  import {
@@ -36,6 +37,12 @@ export default function Fixture() {
36
37
  <AppSidebarToggle />
37
38
  </div>
38
39
  <h1 className='text-3xl'>Theme tokens · 主题样式</h1>
40
+ <section
41
+ className='flex h-32 items-center justify-center rounded-lg border bg-background'
42
+ aria-label='Brand loading preview'
43
+ >
44
+ <Loading label='Loading preview' />
45
+ </section>
39
46
  <p data-testid='body'>Body text · 中文内容与 English text</p>
40
47
  <p className='font-serif'>Serif text</p>
41
48
  <code>const theme = 'compact';</code>
@@ -276,14 +276,6 @@ describe('app server', () => {
276
276
  }
277
277
  });
278
278
 
279
- it('starts without optional plugins or workflow routes', async () => {
280
- const app = createTestApp();
281
-
282
- expect(
283
- app.router.routes.some((route) => route.path.includes('/workflows')),
284
- ).toBe(false);
285
- });
286
-
287
279
  it('creates embedded apps from a scope', async () => {
288
280
  const app = await createEmbeddedServer(
289
281
  await createEmbeddedTestScope({
@@ -1230,12 +1222,6 @@ function createTestApp(options: CreateTestAppOptions = {}): TestApp {
1230
1222
  logging: createSilentLoggingConfig(),
1231
1223
  queue: options.queue ?? createTestQueueConfig(),
1232
1224
  session: createNullSessionConfig(),
1233
- workflow: {
1234
- sourceRoot: path.resolve(process.cwd(), 'workflows'),
1235
- distRoot: path.resolve(process.cwd(), 'dist/workflows'),
1236
- artifactDisk: 'local',
1237
- production: false,
1238
- },
1239
1225
  snowflake: {
1240
1226
  workerId: 0,
1241
1227
  },
@@ -1566,7 +1552,6 @@ async function writeRuntimeTestConfig(
1566
1552
  main: { seeds: { autoRun: env.DB_SEEDS_AUTO_RUN === 'true' } },
1567
1553
  },
1568
1554
  },
1569
- hub: { host: { enabled: false } },
1570
1555
  },
1571
1556
  });
1572
1557
  testConfigs.push(config);
@@ -34,27 +34,6 @@ describe('client plugin registry consistency', () => {
34
34
  expect(undeclared).toEqual([]);
35
35
  });
36
36
 
37
- it('mounts the standard user and API key pages under Settings', () => {
38
- for (const [packageName, routePath] of [
39
- ['@nocobase/app-plugin-users', '/users'],
40
- ['@nocobase/app-plugin-api-keys', '/api-keys'],
41
- ]) {
42
- const plugin = clientPlugins.plugins.find(
43
- (entry) => entry.packageName === packageName,
44
- );
45
- expect(plugin?.routes).toEqual(
46
- expect.arrayContaining([
47
- expect.objectContaining({
48
- parent: 'settings',
49
- routes: expect.arrayContaining([
50
- expect.objectContaining({ path: routePath }),
51
- ]),
52
- }),
53
- ]),
54
- );
55
- }
56
- });
57
-
58
37
  it('registers no package twice', () => {
59
38
  expect(new Set(registeredClientPackages).size).toBe(
60
39
  registeredClientPackages.length,
@@ -29,12 +29,8 @@ describe('app client routes', () => {
29
29
 
30
30
  it('loads every page component', async () => {
31
31
  const resolved = resolveRoutes();
32
- const loaders = [
33
- ...componentLoadersIn(resolved.routes),
34
- ...componentLoadersIn(resolved.settingsRouteTree),
35
- ...componentLoadersIn(resolved.devRouteTree),
36
- ];
37
- // The trees above are filtered by loader, so an empty list would make the loop below pass without loading
32
+ const loaders = componentLoadersIn(resolved.routes);
33
+ // The tree above is filtered by loader, so an empty list would make the loop below pass without loading
38
34
  // anything at all.
39
35
  expect(loaders).not.toHaveLength(0);
40
36