@nocobase/app-template-default 1.0.0-beta.71 → 1.0.0-beta.73

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/AGENTS.md CHANGED
@@ -109,7 +109,7 @@ For any UI work, start with [the frontend workflow](.agents/skills/nocobase-app-
109
109
 
110
110
  `client/routing/`, `client/layouts/`, `client/theme/`, the server entry points, the `package.json` scripts that call `nocobase`, and the tsconfigs are the scaffolding the template provides. It is still this application's own source — it shipped to the user and they may change it — but it is the part the template evolves, so an edit there is what a future upgrade has to reconcile.
111
111
 
112
- Prefer the mechanism the system already provides. Declare a page in `client/routes.ts` and add `navigation` when it needs a menu entry. Refine resources add no menu entries and, with no data provider registered, load nothing; pages load data through `useApiClient`. A page uses `authz` to restrict access; a plugin page is customized through an option or an override. Before editing the shell to add a menu, check the route and its `navigation` declaration.
112
+ Prefer the mechanism the system already provides. Declare a page in `client/routes.ts` and add `navigation` when it needs a menu entry; pages load data through `useApiClient`. A page uses `authz` to restrict access; a plugin page is customized through an option or an override. Before editing the shell to add a menu, check the route and its `navigation` declaration.
113
113
 
114
114
  When the built-in mechanism genuinely cannot express what is being asked, changing this structure is a legitimate answer — not a last resort to apologize for. Do it deliberately, and leave the next agent enough to work with:
115
115
 
package/README.MD CHANGED
@@ -1,6 +1,6 @@
1
1
  # NocoBase 3 Application
2
2
 
3
- A full-stack NocoBase 3 application. The server runs on `@nocobase/app-server` and the browser client is React with Refine, shadcn/ui, and Tailwind CSS.
3
+ A full-stack NocoBase 3 application. The server runs on `@nocobase/app-server` and the browser client is React with shadcn/ui and Tailwind CSS.
4
4
 
5
5
  You own everything in this directory. Add pages, API routes, database tables, and business logic directly here — this is your application's source code, not a framework you extend from the outside.
6
6
 
@@ -19,7 +19,6 @@ export class DefaultClientServiceProvider extends ServiceProvider<ClientApplicat
19
19
  typeof configuredTitle === 'string' && configuredTitle.trim()
20
20
  ? configuredTitle.trim()
21
21
  : 'NocoBase';
22
- this.app.refine.setOptions({ title: { text: title } });
23
22
  this.previousDocumentTitle = document.title;
24
23
  document.title = title;
25
24
  return Promise.resolve();
@@ -22,6 +22,26 @@
22
22
  # max: 600
23
23
  # window: 1m
24
24
 
25
+ # Realtime events are transient and local to this process. Clients refetch after subscription acknowledgement.
26
+ # realtime:
27
+ # adapter: inMemory # Use redis for multiple application processes.
28
+ # redis:
29
+ # connection:
30
+ # host: 127.0.0.1
31
+ # port: 6379
32
+ # namespace: my-app # Defaults to the application name; keep applications isolated.
33
+ # authorizationTimeoutMs: 5000
34
+ # sessionCheckIntervalMs: 30000
35
+ # limits:
36
+ # maxConcurrentAuthorizations: 256
37
+ # maxPendingAuthorizations: 2048
38
+ # maxRecentEventIds: 10000
39
+ # maxConnections: 10000
40
+ # maxMessageBytes: 65536
41
+ # maxSubscriptionsPerConnection: 64
42
+ # maxBufferedBytesPerConnection: 1048576
43
+ # maxPendingMessagesPerConnection: 64
44
+
25
45
  # Values under client are public and embedded in the SPA HTML.
26
46
  client:
27
47
  app:
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.71",
4
+ "version": "1.0.0-beta.73",
5
5
  "type": "module",
6
6
  "prettier": "@nocobase/dev-config/prettier",
7
7
  "engines": {
@@ -9,19 +9,14 @@
9
9
  },
10
10
  "nocobase": {
11
11
  "templateKind": "app",
12
- "defaultTemplateVersion": "1.0.0-beta.71"
12
+ "defaultTemplateVersion": "1.0.0-beta.73"
13
13
  },
14
14
  "devDependencies": {
15
- "@ai-sdk/react": "2.0.0",
15
+ "@ai-sdk/react": "4.0.131",
16
16
  "@base-ui/react": "^1.6.0",
17
17
  "@dicebear/core": "^9.4.3",
18
18
  "@hookform/resolvers": "^5.0.1",
19
19
  "@playwright/test": "^1.62.1",
20
- "@refinedev/cli": "^2.16.48",
21
- "@refinedev/core": "^5.0.8",
22
- "@refinedev/react-hook-form": "^5.0.0",
23
- "@refinedev/react-router": "^2.0.0",
24
- "@refinedev/react-table": "^6.0.0",
25
20
  "@silurus/ooxml": "0.85.1",
26
21
  "@tailwindcss/postcss": "^4.1.4",
27
22
  "@tailwindcss/vite": "^4.1.8",
@@ -42,7 +37,7 @@
42
37
  "@types/react-dom": "^19.2.0",
43
38
  "@vitejs/plugin-react": "^4.4.1",
44
39
  "@vitest/coverage-v8": "^4.1.10",
45
- "ai": "5.0.0",
40
+ "ai": "7.0.128",
46
41
  "class-variance-authority": "^0.7.1",
47
42
  "cn": "^0.4.0",
48
43
  "dayjs": "^1.10.7",
@@ -77,12 +72,12 @@
77
72
  "tar": "^7.5.22",
78
73
  "@xyflow/react": "^12.11.5",
79
74
  "elkjs": "^0.10.2",
80
- "@nocobase/app-client": "^3.0.0-beta.2",
75
+ "@nocobase/app-client": "^4.0.0-beta.0",
81
76
  "@nocobase/app-plugin-template-print": "0.0.2-beta.2",
82
- "@nocobase/app-skills": "0.1.0-beta.39",
83
- "@nocobase/app-testing": "0.1.0-beta.4",
84
- "@nocobase/app-websocket": "^0.0.2-beta.0",
85
- "@nocobase/dev-config": "0.1.0-beta.23",
77
+ "@nocobase/app-skills": "0.1.0-beta.41",
78
+ "@nocobase/app-testing": "0.1.0-beta.5",
79
+ "@nocobase/app-websocket": "^0.1.0-beta.1",
80
+ "@nocobase/dev-config": "0.1.0-beta.24",
86
81
  "@nocobase/drive": "^0.1.0-beta.7",
87
82
  "@nocobase/logging": "^0.1.0-beta.7",
88
83
  "@nocobase/session": "^0.1.0-beta.3"
@@ -144,35 +139,37 @@
144
139
  "@oclif/core": "^4.14.0",
145
140
  "better-auth": "1.7.5",
146
141
  "hono": "^4.13.2",
147
- "@nocobase/app-plugin-api-keys": "^1.0.0-beta.17",
148
- "@nocobase/app-plugin-authentication": "^2.0.0-beta.10",
149
- "@nocobase/app-plugin-authorization": "^1.0.0-beta.28",
150
- "@nocobase/app-plugin-database-explorer": "^1.0.0-beta.15",
151
- "@nocobase/app-plugin-file": "^1.0.0-beta.24",
152
- "@nocobase/app-plugin-i18n": "^1.0.0-beta.16",
153
- "@nocobase/app-plugin-mail": "^1.0.0-beta.14",
154
- "@nocobase/app-plugin-notification": "^1.0.0-beta.27",
155
- "@nocobase/app-plugin-notification-in-app": "^1.0.0-beta.25",
156
- "@nocobase/app-plugin-notification-providers": "^0.2.0-beta.12",
157
- "@nocobase/app-plugin-scheduler": "^1.0.0-beta.17",
158
- "@nocobase/app-plugin-users": "^2.0.0-beta.5",
159
- "@nocobase/app-server": "^2.0.0-beta.9",
142
+ "@nocobase/app-plugin-agents": "^1.0.0-beta.15",
143
+ "@nocobase/app-plugin-api-keys": "^1.0.0-beta.18",
144
+ "@nocobase/app-plugin-authentication": "^2.0.0-beta.11",
145
+ "@nocobase/app-plugin-authorization": "^1.0.0-beta.29",
146
+ "@nocobase/app-plugin-database-explorer": "^1.0.0-beta.16",
147
+ "@nocobase/app-plugin-file": "^1.0.0-beta.25",
148
+ "@nocobase/app-plugin-i18n": "^1.0.0-beta.17",
149
+ "@nocobase/app-plugin-mail": "^1.0.0-beta.15",
150
+ "@nocobase/app-plugin-notification": "^1.0.0-beta.28",
151
+ "@nocobase/app-plugin-notification-in-app": "^1.0.0-beta.26",
152
+ "@nocobase/app-plugin-notification-providers": "^0.2.0-beta.13",
153
+ "@nocobase/app-plugin-scheduler": "^1.0.0-beta.18",
154
+ "@nocobase/app-plugin-users": "^2.0.0-beta.6",
155
+ "@nocobase/app-server": "^3.0.0-beta.0",
160
156
  "@nocobase/config": "^0.1.0-beta.3",
161
157
  "@nocobase/db": "^1.0.0-beta.21",
162
158
  "@nocobase/db-sqlite": "^0.1.0-beta.6",
163
159
  "@nocobase/service-provider": "^0.0.2-beta.2",
164
160
  "@nocobase/snowflake": "^1.0.0-beta.4",
165
- "@nocobase/app-plugin-authz-default-access": "^1.0.0-beta.13",
166
- "@nocobase/app-plugin-authz-sharing-rules": "^1.0.0-beta.13",
167
- "@nocobase/app-plugin-authz-restriction-rules": "^1.0.0-beta.12",
161
+ "@nocobase/app-plugin-authz-default-access": "^1.0.0-beta.14",
162
+ "@nocobase/app-plugin-authz-sharing-rules": "^1.0.0-beta.14",
163
+ "@nocobase/app-plugin-authz-restriction-rules": "^1.0.0-beta.13",
168
164
  "@nocobase/authorization": "^1.0.0-beta.14",
169
165
  "@nocobase/queue": "^0.1.0-beta.9",
170
166
  "@nocobase/jobs": "^0.1.0-beta.3",
171
- "@nocobase/lifecycle": "^0.1.0-beta.2",
172
- "@nocobase/i18n": "^1.0.0-beta.7",
167
+ "@nocobase/lifecycle": "^0.1.0-beta.3",
168
+ "@nocobase/i18n": "^2.0.0-beta.0",
173
169
  "@nocobase/caching": "^0.1.0-beta.3",
174
170
  "@nocobase/repository-input": "^0.1.0-beta.3",
175
- "@nocobase/app-cli": "^1.0.0-beta.24"
171
+ "@nocobase/app-cli": "^1.0.0-beta.25",
172
+ "@nocobase/realtime": "^0.1.0-beta.3"
176
173
  },
177
174
  "description": "NocoBase 3 application template with the standard product capabilities.",
178
175
  "imports": {
@@ -17,9 +17,6 @@ describe('application title', () => {
17
17
 
18
18
  await app.start();
19
19
 
20
- expect(app.refineConfig.options?.title).toEqual({
21
- text: 'Configured application',
22
- });
23
20
  expect(document.title).toBe('Configured application');
24
21
 
25
22
  await app.shutdown();
@@ -33,7 +30,6 @@ describe('application title', () => {
33
30
 
34
31
  await app.start();
35
32
 
36
- expect(app.refineConfig.options?.title).toEqual({ text: 'NocoBase' });
37
33
  expect(document.title).toBe('NocoBase');
38
34
 
39
35
  await app.shutdown();
@@ -20,7 +20,11 @@ vi.mock('@nocobase/app-plugin-authentication/client', () => ({
20
20
  vi.mock('@nocobase/app-client', async (importOriginal) => {
21
21
  const actual = await importOriginal<typeof import('@nocobase/app-client')>();
22
22
  const client = { request: mocks.request };
23
- const realtime = { subscribe: () => vi.fn(), onOpen: () => vi.fn() };
23
+ const realtime = {
24
+ watch: () => ({ close: vi.fn() }),
25
+ subscribe: () => vi.fn(),
26
+ onOpen: () => vi.fn(),
27
+ };
24
28
  return {
25
29
  ...actual,
26
30
  useApiClient: () => client,
@@ -2,7 +2,6 @@ import {
2
2
  createAppSidebarPreferenceStore,
3
3
  SidebarPreferenceProvider,
4
4
  } from '../../client/layouts/use-sidebar-preference.js';
5
- import { Refine } from '@refinedev/core';
6
5
  import { createRoot } from 'react-dom/client';
7
6
  import { MemoryRouter } from 'react-router';
8
7
  import { Loading } from '../../client/components/loading';
@@ -28,90 +27,88 @@ export default function Fixture() {
28
27
  return (
29
28
  <MemoryRouter>
30
29
  <AppThemeProvider>
31
- <Refine options={{ disableTelemetry: true }}>
32
- <AppSidebarProvider>
33
- <AppSidebar label='Fixture sidebar'>
34
- <div className='p-2'>
35
- <Input aria-label='Sidebar content' />
36
- </div>
37
- </AppSidebar>
38
- <main className='min-w-0 flex-1 space-y-6 overflow-y-auto p-6'>
39
- <div className='flex flex-wrap gap-2'>
40
- <ThemeSettings />
41
- <AppSidebarToggle />
42
- </div>
43
- <h1 className='text-3xl'>Theme tokens · 主题样式</h1>
44
- <section
45
- className='flex h-32 items-center justify-center rounded-lg border bg-background'
46
- aria-label='Brand loading preview'
47
- >
48
- <Loading label='Loading preview' />
49
- </section>
50
- <p data-testid='body'>Body text · 中文内容与 English text</p>
51
- <p className='font-serif'>Serif text</p>
52
- <code>const theme = 'compact';</code>
53
- <div className='flex flex-wrap gap-2'>
54
- <Button>Default action</Button>
55
- <Button size='sm'>Small action</Button>
56
- <Button size='xs'>Extra small</Button>
57
- <Button variant='destructive'>Delete</Button>
58
- </div>
59
- <Input
60
- aria-label='Example input'
61
- placeholder='输入内容 / Enter text'
62
- />
63
- <Popover>
64
- <PopoverTrigger render={<Button variant='outline' />}>
65
- Open popover
66
- </PopoverTrigger>
67
- <PopoverContent>
68
- <PopoverTitle>Portal heading</PopoverTitle>
69
- <p>Portal body · 浮层内容</p>
70
- </PopoverContent>
71
- </Popover>
72
- <section className='rounded-xl border bg-card p-4 text-card-foreground shadow-md ring-1 ring-ring'>
73
- <h2 className='text-xl'>Card heading</h2>
74
- <p className='text-sm'>
75
- A long description that wraps without clipping when fonts and
76
- spacing change. 长文本应该正常换行,不应被控件裁切。
77
- </p>
78
- </section>
79
- <svg
80
- aria-label='Chart palette'
81
- viewBox='0 0 120 30'
82
- className='h-8'
83
- >
84
- {[1, 2, 3, 4, 5].map((i) => (
85
- <rect
86
- key={i}
87
- x={(i - 1) * 24}
88
- width='20'
89
- height='30'
90
- fill={`var(--chart-${i})`}
91
- />
92
- ))}
93
- </svg>
94
- <div className='flex flex-wrap gap-2'>
95
- <div className='rounded-sm border p-2'>sm</div>
96
- <div className='rounded-md border p-2'>md</div>
97
- <div className='rounded-lg border p-2'>lg</div>
98
- <div className='rounded-xl border p-2'>xl</div>
99
- <div className='rounded-2xl border p-2'>2xl</div>
100
- <div className='rounded-3xl border p-2'>3xl</div>
101
- <div className='rounded-4xl border p-2'>4xl</div>
102
- </div>
103
- <div className='flex flex-wrap gap-4'>
104
- <div className='shadow-2xs p-2'>2xs</div>
105
- <div className='shadow-xs p-2'>xs</div>
106
- <div className='shadow-sm p-2'>sm</div>
107
- <div className='shadow-md p-2'>md</div>
108
- <div className='shadow-lg p-2'>lg</div>
109
- <div className='shadow-xl p-2'>xl</div>
110
- <div className='shadow-2xl p-2'>2xl</div>
111
- </div>
112
- </main>
113
- </AppSidebarProvider>
114
- </Refine>
30
+ <AppSidebarProvider>
31
+ <AppSidebar label='Fixture sidebar'>
32
+ <div className='p-2'>
33
+ <Input aria-label='Sidebar content' />
34
+ </div>
35
+ </AppSidebar>
36
+ <main className='min-w-0 flex-1 space-y-6 overflow-y-auto p-6'>
37
+ <div className='flex flex-wrap gap-2'>
38
+ <ThemeSettings />
39
+ <AppSidebarToggle />
40
+ </div>
41
+ <h1 className='text-3xl'>Theme tokens · 主题样式</h1>
42
+ <section
43
+ className='flex h-32 items-center justify-center rounded-lg border bg-background'
44
+ aria-label='Brand loading preview'
45
+ >
46
+ <Loading label='Loading preview' />
47
+ </section>
48
+ <p data-testid='body'>Body text · 中文内容与 English text</p>
49
+ <p className='font-serif'>Serif text</p>
50
+ <code>const theme = 'compact';</code>
51
+ <div className='flex flex-wrap gap-2'>
52
+ <Button>Default action</Button>
53
+ <Button size='sm'>Small action</Button>
54
+ <Button size='xs'>Extra small</Button>
55
+ <Button variant='destructive'>Delete</Button>
56
+ </div>
57
+ <Input
58
+ aria-label='Example input'
59
+ placeholder='输入内容 / Enter text'
60
+ />
61
+ <Popover>
62
+ <PopoverTrigger render={<Button variant='outline' />}>
63
+ Open popover
64
+ </PopoverTrigger>
65
+ <PopoverContent>
66
+ <PopoverTitle>Portal heading</PopoverTitle>
67
+ <p>Portal body · 浮层内容</p>
68
+ </PopoverContent>
69
+ </Popover>
70
+ <section className='rounded-xl border bg-card p-4 text-card-foreground shadow-md ring-1 ring-ring'>
71
+ <h2 className='text-xl'>Card heading</h2>
72
+ <p className='text-sm'>
73
+ A long description that wraps without clipping when fonts and
74
+ spacing change. 长文本应该正常换行,不应被控件裁切。
75
+ </p>
76
+ </section>
77
+ <svg
78
+ aria-label='Chart palette'
79
+ viewBox='0 0 120 30'
80
+ className='h-8'
81
+ >
82
+ {[1, 2, 3, 4, 5].map((i) => (
83
+ <rect
84
+ key={i}
85
+ x={(i - 1) * 24}
86
+ width='20'
87
+ height='30'
88
+ fill={`var(--chart-${i})`}
89
+ />
90
+ ))}
91
+ </svg>
92
+ <div className='flex flex-wrap gap-2'>
93
+ <div className='rounded-sm border p-2'>sm</div>
94
+ <div className='rounded-md border p-2'>md</div>
95
+ <div className='rounded-lg border p-2'>lg</div>
96
+ <div className='rounded-xl border p-2'>xl</div>
97
+ <div className='rounded-2xl border p-2'>2xl</div>
98
+ <div className='rounded-3xl border p-2'>3xl</div>
99
+ <div className='rounded-4xl border p-2'>4xl</div>
100
+ </div>
101
+ <div className='flex flex-wrap gap-4'>
102
+ <div className='shadow-2xs p-2'>2xs</div>
103
+ <div className='shadow-xs p-2'>xs</div>
104
+ <div className='shadow-sm p-2'>sm</div>
105
+ <div className='shadow-md p-2'>md</div>
106
+ <div className='shadow-lg p-2'>lg</div>
107
+ <div className='shadow-xl p-2'>xl</div>
108
+ <div className='shadow-2xl p-2'>2xl</div>
109
+ </div>
110
+ </main>
111
+ </AppSidebarProvider>
115
112
  </AppThemeProvider>
116
113
  </MemoryRouter>
117
114
  );
@@ -423,12 +423,13 @@ describe('app server', () => {
423
423
  expect(missingEvents).toBeNull();
424
424
  });
425
425
 
426
- it('subscribes, publishes, and unsubscribes realtime messages', () => {
426
+ it('subscribes, publishes, and unsubscribes realtime messages', async () => {
427
427
  const realtime = createRealtimeService();
428
+ realtime.registerTopic(TEST_REALTIME_TOPIC, { audience: 'public' });
428
429
  const websocket = createTestWebSocket();
429
430
  const connection = realtime.connect(websocket);
430
431
 
431
- realtime.handleClientMessage(
432
+ await realtime.handleClientMessage(
432
433
  connection,
433
434
  JSON.stringify({
434
435
  type: 'subscribe',
@@ -445,7 +446,7 @@ describe('app server', () => {
445
446
  subscriptionId: expect.any(String),
446
447
  });
447
448
 
448
- realtime.publish(TEST_REALTIME_TOPIC, 'tick');
449
+ await realtime.publish(TEST_REALTIME_TOPIC, 'tick');
449
450
 
450
451
  expect(websocket.messages[1]).toMatchObject({
451
452
  type: 'event',
@@ -454,7 +455,7 @@ describe('app server', () => {
454
455
  publishedAt: expect.any(String),
455
456
  });
456
457
 
457
- realtime.handleClientMessage(
458
+ await realtime.handleClientMessage(
458
459
  connection,
459
460
  JSON.stringify({
460
461
  type: 'unsubscribe',
@@ -463,7 +464,7 @@ describe('app server', () => {
463
464
  .subscriptionId,
464
465
  }),
465
466
  );
466
- realtime.publish(TEST_REALTIME_TOPIC, 'after unsubscribe');
467
+ await realtime.publish(TEST_REALTIME_TOPIC, 'after unsubscribe');
467
468
 
468
469
  expect(websocket.messages[2]).toMatchObject({
469
470
  type: 'unsubscribed',
@@ -473,7 +474,7 @@ describe('app server', () => {
473
474
  });
474
475
  expect(websocket.messages).toHaveLength(3);
475
476
 
476
- realtime.close();
477
+ await realtime.close();
477
478
  });
478
479
 
479
480
  it('registers embedded app resources with the scope', async () => {
@@ -791,6 +792,9 @@ describe('app server', () => {
791
792
  const app = trackCloseable(
792
793
  await createIsolatedStandaloneServer({ viteDevUrl: false }),
793
794
  );
795
+ app.application.container
796
+ .resolve(realtimeServiceToken)
797
+ .registerTopic(TEST_REALTIME_TOPIC, { audience: 'public' });
794
798
  const serverUrl = await startStandaloneTestServer(app);
795
799
  const websocket = new WebSocket(
796
800
  `${serverUrl}${app.application.publicBasePath}/ws`,
@@ -1,6 +1,6 @@
1
1
  import { createApp } from '../../client/app.js';
2
2
  import { ServiceProvider } from '@nocobase/service-provider';
3
- import { describe, expect, it, vi } from 'vitest';
3
+ import { describe, expect, it } from 'vitest';
4
4
 
5
5
  import { ClientApplication, toasterToken } from '@nocobase/app-client';
6
6
  import {
@@ -47,19 +47,18 @@ describe('app client runtime', () => {
47
47
  expect(calls).toEqual([]);
48
48
  });
49
49
 
50
- it('runs provider lifecycle and exposes Refine config through ClientApplication', async () => {
51
- const authProvider = {
52
- check: vi.fn(),
53
- getIdentity: vi.fn(),
54
- login: vi.fn(),
55
- logout: vi.fn(),
56
- onError: vi.fn(),
57
- };
50
+ it('runs provider lifecycle through ClientApplication', async () => {
51
+ const calls: string[] = [];
58
52
  class Provider extends ServiceProvider<ClientApplicationType> {
59
53
  public readonly name: string = '@example/provider';
60
54
 
61
55
  public override boot(): Promise<void> {
62
- this.app.refine.setAuthProvider(authProvider);
56
+ calls.push('boot');
57
+ return Promise.resolve();
58
+ }
59
+
60
+ public override shutdown(): Promise<void> {
61
+ calls.push('shutdown');
63
62
  return Promise.resolve();
64
63
  }
65
64
  }
@@ -77,8 +76,9 @@ describe('app client runtime', () => {
77
76
  });
78
77
 
79
78
  await app.start();
80
- expect(app.refineConfig.authProvider).toBe(authProvider);
79
+ expect(calls).toEqual(['boot']);
81
80
  await app.shutdown();
81
+ expect(calls).toEqual(['boot', 'shutdown']);
82
82
  });
83
83
 
84
84
  it('uses the default template Application and static plugin declarations', async () => {
@@ -90,16 +90,12 @@ describe('app client runtime', () => {
90
90
  expect(app).toBeInstanceOf(ClientApplication);
91
91
  await expect(app.start()).resolves.toBeUndefined();
92
92
  expect(app.config.get('app.title')).toBe('Configured application');
93
- expect(app.refineConfig.options?.title).toEqual({
94
- text: 'Configured application',
95
- });
96
93
  // Plugins and pages report through the registered toaster, and the mounted Toaster renders what it forwards.
97
94
  // Without either half, toasts disappear.
98
95
  expect(app.services.has(toasterToken)).toBe(true);
99
96
  expect(runtime.reactProviders).toContainEqual(
100
97
  expect.objectContaining({ name: 'toaster', layer: 'application' }),
101
98
  );
102
- expect(app.refineConfig.resources ?? []).toEqual([]);
103
99
  await app.shutdown();
104
100
  });
105
101
 
package/vitest.config.ts CHANGED
@@ -11,12 +11,13 @@ export default createReactVitestConfig({
11
11
  deps: {
12
12
  // Transform the public Client surfaces so vi.mock applies, without duplicating the Server packages' identity tokens.
13
13
  inline: [
14
- // The authentication guards read the location, so they share the test's react-router rather than Node's copy,
15
- // together with every client that resolves the authentication tokens and provider.
16
- /@nocobase\/app-plugin-(?:authentication|api-keys|users)\/(?:dist\/)?client\//u,
17
- /@nocobase\/app-plugin-authorization\/(?:dist\/)?client\//u,
18
- /@nocobase\/app-plugin-(?:lifecycle-example|office-flows-example)\/(?:dist\/)?client\//u,
19
- /@nocobase\/app-plugin-notification-in-app\/(?:dist\/)?client\//u,
14
+ // The published fixture mounts MemoryRouter; transform it with the page to share the router context.
15
+ /@nocobase\/app-testing\/(?:dist\/)?src\/client\//u,
16
+ // AppClientProviders includes the navigation guard and must use that same router context.
17
+ /@nocobase\/app-client\//u,
18
+ // Every client plugin that resolves AppClient tokens must share the transformed runtime.
19
+ // Keep server/shared modules external so their service identities remain unchanged.
20
+ /@nocobase\/app-plugin-[^/]+\/(?:dist\/)?client\//u,
20
21
  ],
21
22
  },
22
23
  },