@nocobase/client-v2 2.1.0-beta.37 → 2.1.0-beta.38
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/Application.d.ts +1 -0
- package/es/BaseApplication.d.ts +3 -0
- package/es/RouterManager.d.ts +1 -0
- package/es/components/KeepAlive.d.ts +22 -0
- package/es/components/RouterBridge.d.ts +9 -0
- package/es/data-source/ExtendCollectionsProvider.d.ts +28 -2
- package/es/flow/FlowPage.d.ts +2 -1
- package/es/flow/admin-shell/AdminLayoutRouteCoordinator.d.ts +8 -40
- package/es/flow/admin-shell/BaseLayoutModel.d.ts +89 -0
- package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +74 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +12 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutModel.d.ts +7 -92
- package/es/flow/admin-shell/admin-layout/index.d.ts +2 -0
- package/es/flow/admin-shell/useAdminLayoutRoutePage.d.ts +2 -2
- package/es/flow/admin-shell/useLayoutRoutePage.d.ts +23 -0
- package/es/flow/components/FlowRoute.d.ts +10 -1
- package/es/flow/index.d.ts +4 -0
- package/es/flow/models/base/PageModel/PageModel.d.ts +3 -1
- package/es/flow/models/blocks/form/FormActionGroupModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +10 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.d.ts +1 -1
- package/es/index.d.ts +1 -0
- package/es/index.mjs +484 -437
- package/es/layout-manager/LayoutContentRoute.d.ts +14 -0
- package/es/layout-manager/LayoutManager.d.ts +22 -0
- package/es/layout-manager/LayoutRoute.d.ts +14 -0
- package/es/layout-manager/index.d.ts +13 -0
- package/es/layout-manager/types.d.ts +20 -0
- package/es/layout-manager/utils.d.ts +14 -0
- package/es/settings-center/index.d.ts +1 -1
- package/es/settings-center/plugin-manager/BulkEnableButton.d.ts +15 -0
- package/es/settings-center/plugin-manager/PluginCard.d.ts +15 -0
- package/es/settings-center/plugin-manager/PluginDetail.d.ts +16 -0
- package/es/settings-center/{PluginManagerPage.d.ts → plugin-manager/index.d.ts} +1 -7
- package/es/settings-center/plugin-manager/types.d.ts +34 -0
- package/lib/index.js +484 -437
- package/package.json +8 -7
- package/src/Application.tsx +27 -12
- package/src/BaseApplication.tsx +6 -0
- package/src/PluginSettingsManager.ts +1 -1
- package/src/RouterManager.tsx +17 -1
- package/src/__tests__/PluginSettingsManager.test.ts +41 -2
- package/src/__tests__/app.test.tsx +8 -1
- package/src/__tests__/globalDeps.test.ts +1 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +45 -2
- package/src/__tests__/plugin-manager.test.tsx +177 -0
- package/src/__tests__/settings-center.test.tsx +24 -2
- package/src/components/KeepAlive.tsx +131 -0
- package/src/components/RouterBridge.tsx +28 -4
- package/src/components/__tests__/KeepAlive.test.tsx +63 -0
- package/src/components/__tests__/RouterBridge.test.tsx +27 -0
- package/src/data-source/ExtendCollectionsProvider.tsx +94 -20
- package/src/data-source/__tests__/ExtendCollectionsProvider.test.tsx +264 -0
- package/src/flow/FlowPage.tsx +35 -7
- package/src/flow/__tests__/FlowPage.test.tsx +79 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +529 -2
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +191 -0
- package/src/flow/actions/__tests__/openView.subModelKey.test.tsx +33 -0
- package/src/flow/actions/aclCheck.tsx +4 -0
- package/src/flow/actions/aclCheckRefresh.tsx +4 -0
- package/src/flow/actions/dateTimeFormat.tsx +12 -8
- package/src/flow/actions/linkageRules.tsx +122 -0
- package/src/flow/actions/openView.tsx +28 -4
- package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +11 -329
- package/src/flow/admin-shell/BaseLayoutModel.tsx +455 -0
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +502 -0
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +547 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +4 -4
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +160 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +0 -12
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +28 -201
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +11 -2
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +1 -26
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +149 -27
- package/src/flow/admin-shell/admin-layout/index.ts +2 -0
- package/src/flow/admin-shell/useAdminLayoutRoutePage.ts +10 -26
- package/src/flow/admin-shell/useLayoutRoutePage.ts +61 -0
- package/src/flow/components/AdminLayout.tsx +4 -154
- package/src/flow/components/FlowRoute.tsx +105 -15
- package/src/flow/index.ts +4 -0
- package/src/flow/models/base/ActionModel.tsx +8 -1
- package/src/flow/models/base/PageModel/PageModel.tsx +51 -18
- package/src/flow/models/base/PageModel/RootPageModel.tsx +6 -13
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +102 -1
- package/src/flow/models/base/RouteModel.tsx +1 -1
- package/src/flow/models/blocks/form/FormActionGroupModel.tsx +14 -0
- package/src/flow/models/blocks/form/FormItemModel.tsx +8 -1
- package/src/flow/models/blocks/form/__tests__/FormActionGroupModel.test.ts +46 -0
- package/src/flow/models/blocks/form/submitValues.ts +4 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +118 -16
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +114 -0
- package/src/flow/models/fields/AssociationFieldModel/SubFormFieldModel.tsx +7 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +1 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -5
- package/src/flow/models/fields/ClickableFieldModel.tsx +9 -1
- package/src/flow/models/fields/DisplayTimeFieldModel.tsx +1 -1
- package/src/flow/models/fields/TimeFieldModel.tsx +1 -1
- package/src/flow/models/fields/__tests__/TimeFieldModel.test.tsx +61 -0
- package/src/flow/models/fields/mobile-components/MobileDatePicker.tsx +19 -3
- package/src/flow/models/fields/mobile-components/__tests__/MobileDatePicker.test.tsx +94 -0
- package/src/flow/models/topbar/TopbarActionModel.tsx +1 -1
- package/src/flow/utils/__tests__/dateTimeFormat.test.ts +91 -0
- package/src/index.ts +1 -0
- package/src/layout-manager/LayoutContentRoute.tsx +90 -0
- package/src/layout-manager/LayoutManager.tsx +185 -0
- package/src/layout-manager/LayoutRoute.tsx +138 -0
- package/src/layout-manager/__tests__/LayoutManager.test.tsx +335 -0
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +473 -0
- package/src/layout-manager/index.ts +14 -0
- package/src/layout-manager/types.ts +22 -0
- package/src/layout-manager/utils.ts +37 -0
- package/src/nocobase-buildin-plugin/index.tsx +56 -48
- package/src/settings-center/index.ts +1 -1
- package/src/settings-center/plugin-manager/BulkEnableButton.tsx +111 -0
- package/src/settings-center/plugin-manager/PluginCard.tsx +270 -0
- package/src/settings-center/plugin-manager/PluginDetail.tsx +195 -0
- package/src/settings-center/plugin-manager/index.tsx +254 -0
- package/src/settings-center/plugin-manager/types.ts +35 -0
- package/src/settings-center/utils.tsx +8 -1
- package/src/theme/__tests__/globalStyles.test.ts +24 -0
- package/src/theme/globalStyles.ts +10 -0
- package/src/utils/globalDeps.ts +2 -0
- package/src/settings-center/PluginManagerPage.tsx +0 -162
|
@@ -11,7 +11,7 @@ import React from 'react';
|
|
|
11
11
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
12
12
|
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
|
13
13
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
14
|
-
import { FlowEngine, FlowEngineProvider, type FlowModel } from '@nocobase/flow-engine';
|
|
14
|
+
import { FlowContextProvider, FlowEngine, FlowEngineProvider, type FlowModel } from '@nocobase/flow-engine';
|
|
15
15
|
import FlowRoute from '../components/FlowRoute';
|
|
16
16
|
|
|
17
17
|
type MockAdminLayoutModel = FlowModel & {
|
|
@@ -86,7 +86,7 @@ describe('FlowRoute', () => {
|
|
|
86
86
|
expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith(
|
|
87
87
|
'test-page',
|
|
88
88
|
expect.objectContaining({
|
|
89
|
-
active:
|
|
89
|
+
active: true,
|
|
90
90
|
refreshDesktopRoutes: expect.any(Function),
|
|
91
91
|
layoutContentElement: expect.any(HTMLDivElement),
|
|
92
92
|
}),
|
|
@@ -120,6 +120,200 @@ describe('FlowRoute', () => {
|
|
|
120
120
|
expect(adminLayoutModel.unregisterRoutePage).toHaveBeenCalledWith('test-page');
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
+
it('should sync explicit active state to layout route page', async () => {
|
|
124
|
+
const engine = new FlowEngine();
|
|
125
|
+
engine.context.defineProperty('routeRepository', {
|
|
126
|
+
value: {
|
|
127
|
+
refreshAccessible: hookState.refresh,
|
|
128
|
+
isAccessibleLoaded: () => true,
|
|
129
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
130
|
+
getRouteBySchemaUid: vi.fn(() => ({ type: 'flowPage', schemaUid: 'test-page' })),
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
engine.context.defineProperty('app', {
|
|
134
|
+
value: {
|
|
135
|
+
getPublicPath: () => '/v2/',
|
|
136
|
+
router: {
|
|
137
|
+
getBasename: () => '/v2',
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
143
|
+
engine.createModel({
|
|
144
|
+
uid: 'admin-layout-model',
|
|
145
|
+
use: 'FlowModel',
|
|
146
|
+
}),
|
|
147
|
+
{
|
|
148
|
+
registerRoutePage: vi.fn(),
|
|
149
|
+
updateRoutePage: vi.fn(),
|
|
150
|
+
unregisterRoutePage: vi.fn(),
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const result = render(
|
|
155
|
+
<FlowEngineProvider engine={engine}>
|
|
156
|
+
<MemoryRouter initialEntries={['/flow/test-page']}>
|
|
157
|
+
<Routes>
|
|
158
|
+
<Route path="/flow/:name" element={<FlowRoute active={false} />} />
|
|
159
|
+
</Routes>
|
|
160
|
+
</MemoryRouter>
|
|
161
|
+
</FlowEngineProvider>,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
await waitFor(() => {
|
|
165
|
+
expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith(
|
|
166
|
+
'test-page',
|
|
167
|
+
expect.objectContaining({
|
|
168
|
+
active: false,
|
|
169
|
+
}),
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
result.rerender(
|
|
174
|
+
<FlowEngineProvider engine={engine}>
|
|
175
|
+
<MemoryRouter initialEntries={['/flow/test-page']}>
|
|
176
|
+
<Routes>
|
|
177
|
+
<Route path="/flow/:name" element={<FlowRoute active={true} />} />
|
|
178
|
+
</Routes>
|
|
179
|
+
</MemoryRouter>
|
|
180
|
+
</FlowEngineProvider>,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
await waitFor(() => {
|
|
184
|
+
expect(adminLayoutModel.updateRoutePage).toHaveBeenCalledWith(
|
|
185
|
+
'test-page',
|
|
186
|
+
expect.objectContaining({
|
|
187
|
+
active: true,
|
|
188
|
+
}),
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should bridge page lifecycle with explicit pageUid', async () => {
|
|
194
|
+
const engine = new FlowEngine();
|
|
195
|
+
engine.context.defineProperty('routeRepository', {
|
|
196
|
+
value: {
|
|
197
|
+
refreshAccessible: hookState.refresh,
|
|
198
|
+
isAccessibleLoaded: () => true,
|
|
199
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
200
|
+
getRouteBySchemaUid: vi.fn(() => ({ type: 'flowPage', schemaUid: 'test-page' })),
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
engine.context.defineProperty('app', {
|
|
204
|
+
value: {
|
|
205
|
+
getPublicPath: () => '/v2/',
|
|
206
|
+
router: {
|
|
207
|
+
getBasename: () => '/v2',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
213
|
+
engine.createModel({
|
|
214
|
+
uid: 'admin-layout-model',
|
|
215
|
+
use: 'FlowModel',
|
|
216
|
+
}),
|
|
217
|
+
{
|
|
218
|
+
registerRoutePage: vi.fn(),
|
|
219
|
+
updateRoutePage: vi.fn(),
|
|
220
|
+
unregisterRoutePage: vi.fn(),
|
|
221
|
+
},
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
render(
|
|
225
|
+
<FlowEngineProvider engine={engine}>
|
|
226
|
+
<MemoryRouter initialEntries={['/flow']}>
|
|
227
|
+
<Routes>
|
|
228
|
+
<Route path="/flow" element={<FlowRoute pageUid="test-page" />} />
|
|
229
|
+
</Routes>
|
|
230
|
+
</MemoryRouter>
|
|
231
|
+
</FlowEngineProvider>,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
await waitFor(() => {
|
|
235
|
+
expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith('test-page', expect.any(Object));
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should derive layout model from current layout context when rendered from schema', async () => {
|
|
240
|
+
const engine = new FlowEngine();
|
|
241
|
+
const routeRepository = {
|
|
242
|
+
refreshAccessible: hookState.refresh,
|
|
243
|
+
isAccessibleLoaded: () => true,
|
|
244
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
245
|
+
getRouteBySchemaUid: vi.fn(() => ({ type: 'flowPage', schemaUid: 'test-page' })),
|
|
246
|
+
};
|
|
247
|
+
engine.context.defineProperty('route', {
|
|
248
|
+
value: {
|
|
249
|
+
params: { name: 'test-page' },
|
|
250
|
+
pathname: '/embed/test-page',
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
engine.context.defineProperty('routeRepository', {
|
|
254
|
+
value: routeRepository,
|
|
255
|
+
});
|
|
256
|
+
const routeModel = engine.createModel({
|
|
257
|
+
uid: 'route-model',
|
|
258
|
+
use: 'FlowModel',
|
|
259
|
+
});
|
|
260
|
+
routeModel.context.defineProperty('layout', {
|
|
261
|
+
value: {
|
|
262
|
+
routeName: 'embed',
|
|
263
|
+
routePath: '/embed',
|
|
264
|
+
rootRouteName: 'embed',
|
|
265
|
+
uid: 'embed-layout-model',
|
|
266
|
+
layoutModelClass: 'EmbedLayoutModelV2',
|
|
267
|
+
rootPageModelClass: 'RootPageModel',
|
|
268
|
+
childPageModelClass: 'ChildPageModel',
|
|
269
|
+
authCheck: true,
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
engine.context.defineProperty('app', {
|
|
273
|
+
value: {
|
|
274
|
+
getPublicPath: () => '/v2/',
|
|
275
|
+
router: {
|
|
276
|
+
getBasename: () => '/v2',
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const embedLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
282
|
+
engine.createModel({
|
|
283
|
+
uid: 'embed-layout-model',
|
|
284
|
+
use: 'FlowModel',
|
|
285
|
+
}),
|
|
286
|
+
{
|
|
287
|
+
registerRoutePage: vi.fn(),
|
|
288
|
+
updateRoutePage: vi.fn(),
|
|
289
|
+
unregisterRoutePage: vi.fn(),
|
|
290
|
+
},
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
render(
|
|
294
|
+
<FlowEngineProvider engine={engine}>
|
|
295
|
+
<FlowContextProvider context={routeModel.context}>
|
|
296
|
+
<MemoryRouter initialEntries={['/embed/test-page']}>
|
|
297
|
+
<Routes>
|
|
298
|
+
<Route path="/embed/:name" element={<FlowRoute />} />
|
|
299
|
+
</Routes>
|
|
300
|
+
</MemoryRouter>
|
|
301
|
+
</FlowContextProvider>
|
|
302
|
+
</FlowEngineProvider>,
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
await waitFor(() => {
|
|
306
|
+
expect(embedLayoutModel.registerRoutePage).toHaveBeenCalledWith(
|
|
307
|
+
'test-page',
|
|
308
|
+
expect.objectContaining({
|
|
309
|
+
active: true,
|
|
310
|
+
refreshDesktopRoutes: expect.any(Function),
|
|
311
|
+
layoutContentElement: expect.any(HTMLDivElement),
|
|
312
|
+
}),
|
|
313
|
+
);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
123
317
|
it('should fail fast when admin-layout-model is missing', () => {
|
|
124
318
|
const engine = new FlowEngine();
|
|
125
319
|
engine.context.defineProperty('route', {
|
|
@@ -283,6 +477,339 @@ describe('FlowRoute', () => {
|
|
|
283
477
|
}
|
|
284
478
|
});
|
|
285
479
|
|
|
480
|
+
it('should render not found for legacy page when behavior is notFound', async () => {
|
|
481
|
+
const originalLocation = window.location;
|
|
482
|
+
const replace = vi.fn();
|
|
483
|
+
Object.defineProperty(window, 'location', {
|
|
484
|
+
configurable: true,
|
|
485
|
+
value: {
|
|
486
|
+
...originalLocation,
|
|
487
|
+
pathname: '/v2/embed/test-page',
|
|
488
|
+
replace,
|
|
489
|
+
},
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
try {
|
|
493
|
+
const engine = new FlowEngine();
|
|
494
|
+
engine.context.defineProperty('routeRepository', {
|
|
495
|
+
value: {
|
|
496
|
+
refreshAccessible: hookState.refresh,
|
|
497
|
+
isAccessibleLoaded: () => true,
|
|
498
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
499
|
+
getRouteBySchemaUid: vi.fn(() => ({ type: 'page', schemaUid: 'test-page' })),
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
engine.context.defineProperty('app', {
|
|
503
|
+
value: {
|
|
504
|
+
getPublicPath: () => '/v2/',
|
|
505
|
+
router: {
|
|
506
|
+
getBasename: () => '/v2',
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
512
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
513
|
+
{
|
|
514
|
+
registerRoutePage: vi.fn(),
|
|
515
|
+
updateRoutePage: vi.fn(),
|
|
516
|
+
unregisterRoutePage: vi.fn(),
|
|
517
|
+
},
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
const result = render(
|
|
521
|
+
<FlowEngineProvider engine={engine}>
|
|
522
|
+
<MemoryRouter initialEntries={['/embed/test-page']}>
|
|
523
|
+
<Routes>
|
|
524
|
+
<Route path="/embed/:name" element={<FlowRoute legacyPageBehavior="notFound" />} />
|
|
525
|
+
</Routes>
|
|
526
|
+
</MemoryRouter>
|
|
527
|
+
</FlowEngineProvider>,
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
await result.findByText('404');
|
|
531
|
+
expect(replace).not.toHaveBeenCalled();
|
|
532
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
533
|
+
} finally {
|
|
534
|
+
Object.defineProperty(window, 'location', {
|
|
535
|
+
configurable: true,
|
|
536
|
+
value: originalLocation,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it('should bridge existing FlowModel when behavior is notFound and routeRepository has no route', async () => {
|
|
542
|
+
const engine = new FlowEngine();
|
|
543
|
+
engine.setModelRepository({
|
|
544
|
+
findOne: vi.fn().mockResolvedValue({
|
|
545
|
+
uid: 'public-form-1',
|
|
546
|
+
use: 'FlowModel',
|
|
547
|
+
}),
|
|
548
|
+
save: vi.fn(),
|
|
549
|
+
destroy: vi.fn(),
|
|
550
|
+
} as any);
|
|
551
|
+
engine.context.defineProperty('routeRepository', {
|
|
552
|
+
value: {
|
|
553
|
+
refreshAccessible: hookState.refresh,
|
|
554
|
+
isAccessibleLoaded: () => true,
|
|
555
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
556
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
engine.context.defineProperty('app', {
|
|
560
|
+
value: {
|
|
561
|
+
getPublicPath: () => '/v2/',
|
|
562
|
+
router: {
|
|
563
|
+
getBasename: () => '/v2',
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
569
|
+
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
570
|
+
{
|
|
571
|
+
registerRoutePage: vi.fn(),
|
|
572
|
+
updateRoutePage: vi.fn(),
|
|
573
|
+
unregisterRoutePage: vi.fn(),
|
|
574
|
+
},
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
render(
|
|
578
|
+
<FlowEngineProvider engine={engine}>
|
|
579
|
+
<MemoryRouter initialEntries={['/public-forms/public-form-1']}>
|
|
580
|
+
<Routes>
|
|
581
|
+
<Route
|
|
582
|
+
path="/public-forms/:name"
|
|
583
|
+
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel} />}
|
|
584
|
+
/>
|
|
585
|
+
</Routes>
|
|
586
|
+
</MemoryRouter>
|
|
587
|
+
</FlowEngineProvider>,
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
await waitFor(() => {
|
|
591
|
+
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('public-form-1', expect.any(Object));
|
|
592
|
+
});
|
|
593
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('should check model existence without occupying the route model uid', async () => {
|
|
597
|
+
const engine = new FlowEngine();
|
|
598
|
+
const findOne = vi.fn().mockResolvedValue({
|
|
599
|
+
uid: 'public-form-1',
|
|
600
|
+
});
|
|
601
|
+
const request = vi.fn().mockResolvedValue({
|
|
602
|
+
data: {
|
|
603
|
+
data: {
|
|
604
|
+
uid: 'public-form-1',
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
});
|
|
608
|
+
engine.setModelRepository({
|
|
609
|
+
findOne,
|
|
610
|
+
save: vi.fn(),
|
|
611
|
+
destroy: vi.fn(),
|
|
612
|
+
} as any);
|
|
613
|
+
engine.context.defineProperty('routeRepository', {
|
|
614
|
+
value: {
|
|
615
|
+
refreshAccessible: hookState.refresh,
|
|
616
|
+
isAccessibleLoaded: () => true,
|
|
617
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
618
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
619
|
+
},
|
|
620
|
+
});
|
|
621
|
+
engine.context.defineProperty('app', {
|
|
622
|
+
value: {
|
|
623
|
+
apiClient: {
|
|
624
|
+
request,
|
|
625
|
+
},
|
|
626
|
+
getPublicPath: () => '/v2/',
|
|
627
|
+
router: {
|
|
628
|
+
getBasename: () => '/v2',
|
|
629
|
+
},
|
|
630
|
+
},
|
|
631
|
+
});
|
|
632
|
+
const routeModel = engine.createModel({
|
|
633
|
+
uid: 'public-form-route-model',
|
|
634
|
+
use: 'FlowModel',
|
|
635
|
+
});
|
|
636
|
+
routeModel.context.defineProperty('layout', {
|
|
637
|
+
value: {
|
|
638
|
+
routeName: 'public-forms',
|
|
639
|
+
routePath: '/public-forms',
|
|
640
|
+
rootRouteName: 'public-forms',
|
|
641
|
+
uid: 'public-form-layout-model',
|
|
642
|
+
layoutModelClass: 'PublicFormLayoutModel',
|
|
643
|
+
rootPageModelClass: 'PublicFormPageModel',
|
|
644
|
+
childPageModelClass: 'ChildPageModel',
|
|
645
|
+
authCheck: false,
|
|
646
|
+
},
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
650
|
+
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
651
|
+
{
|
|
652
|
+
registerRoutePage: vi.fn(),
|
|
653
|
+
updateRoutePage: vi.fn(),
|
|
654
|
+
unregisterRoutePage: vi.fn(),
|
|
655
|
+
},
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
render(
|
|
659
|
+
<FlowEngineProvider engine={engine}>
|
|
660
|
+
<FlowContextProvider context={routeModel.context}>
|
|
661
|
+
<MemoryRouter initialEntries={['/public-forms/public-form-1']}>
|
|
662
|
+
<Routes>
|
|
663
|
+
<Route path="/public-forms/:name" element={<FlowRoute legacyPageBehavior="notFound" />} />
|
|
664
|
+
</Routes>
|
|
665
|
+
</MemoryRouter>
|
|
666
|
+
</FlowContextProvider>
|
|
667
|
+
</FlowEngineProvider>,
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
await waitFor(() => {
|
|
671
|
+
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('public-form-1', expect.any(Object));
|
|
672
|
+
});
|
|
673
|
+
expect(findOne).toHaveBeenCalledWith({ uid: 'public-form-1' });
|
|
674
|
+
expect(request).not.toHaveBeenCalled();
|
|
675
|
+
expect(engine.getModel('public-form-1')).toBeUndefined();
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
it('should not skip accessible route loading just because layout authCheck is false', async () => {
|
|
679
|
+
const engine = new FlowEngine();
|
|
680
|
+
const ensureAccessibleLoaded = vi.fn().mockRejectedValue(new Error('cannot load accessible routes'));
|
|
681
|
+
const getRouteBySchemaUid = vi.fn();
|
|
682
|
+
engine.setModelRepository({
|
|
683
|
+
findOne: vi.fn().mockResolvedValue({
|
|
684
|
+
uid: 'public-form-1',
|
|
685
|
+
use: 'FlowModel',
|
|
686
|
+
}),
|
|
687
|
+
save: vi.fn(),
|
|
688
|
+
destroy: vi.fn(),
|
|
689
|
+
} as any);
|
|
690
|
+
engine.context.defineProperty('routeRepository', {
|
|
691
|
+
value: {
|
|
692
|
+
refreshAccessible: hookState.refresh,
|
|
693
|
+
isAccessibleLoaded: () => false,
|
|
694
|
+
ensureAccessibleLoaded,
|
|
695
|
+
getRouteBySchemaUid,
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
engine.context.defineProperty('app', {
|
|
699
|
+
value: {
|
|
700
|
+
getPublicPath: () => '/v2/',
|
|
701
|
+
router: {
|
|
702
|
+
getBasename: () => '/v2',
|
|
703
|
+
},
|
|
704
|
+
},
|
|
705
|
+
});
|
|
706
|
+
const routeModel = engine.createModel({
|
|
707
|
+
uid: 'public-form-route-model',
|
|
708
|
+
use: 'FlowModel',
|
|
709
|
+
});
|
|
710
|
+
routeModel.context.defineProperty('layout', {
|
|
711
|
+
value: {
|
|
712
|
+
routeName: 'public-forms',
|
|
713
|
+
routePath: '/public-forms',
|
|
714
|
+
rootRouteName: 'public-forms',
|
|
715
|
+
uid: 'public-form-layout-model',
|
|
716
|
+
layoutModelClass: 'PublicFormLayoutModel',
|
|
717
|
+
rootPageModelClass: 'PublicFormPageModel',
|
|
718
|
+
childPageModelClass: 'ChildPageModel',
|
|
719
|
+
authCheck: false,
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
724
|
+
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
725
|
+
{
|
|
726
|
+
registerRoutePage: vi.fn(),
|
|
727
|
+
updateRoutePage: vi.fn(),
|
|
728
|
+
unregisterRoutePage: vi.fn(),
|
|
729
|
+
},
|
|
730
|
+
);
|
|
731
|
+
|
|
732
|
+
render(
|
|
733
|
+
<FlowEngineProvider engine={engine}>
|
|
734
|
+
<FlowContextProvider context={routeModel.context}>
|
|
735
|
+
<MemoryRouter initialEntries={['/public-forms/public-form-1']}>
|
|
736
|
+
<Routes>
|
|
737
|
+
<Route path="/public-forms/:name" element={<FlowRoute legacyPageBehavior="notFound" />} />
|
|
738
|
+
</Routes>
|
|
739
|
+
</MemoryRouter>
|
|
740
|
+
</FlowContextProvider>
|
|
741
|
+
</FlowEngineProvider>,
|
|
742
|
+
);
|
|
743
|
+
|
|
744
|
+
await waitFor(() => {
|
|
745
|
+
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('public-form-1', expect.any(Object));
|
|
746
|
+
});
|
|
747
|
+
expect(ensureAccessibleLoaded).toHaveBeenCalledTimes(1);
|
|
748
|
+
expect(getRouteBySchemaUid).not.toHaveBeenCalled();
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
it('should bridge legacy page when behavior is bridge', async () => {
|
|
752
|
+
const originalLocation = window.location;
|
|
753
|
+
const replace = vi.fn();
|
|
754
|
+
Object.defineProperty(window, 'location', {
|
|
755
|
+
configurable: true,
|
|
756
|
+
value: {
|
|
757
|
+
...originalLocation,
|
|
758
|
+
pathname: '/v2/admin/test-page',
|
|
759
|
+
replace,
|
|
760
|
+
},
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
try {
|
|
764
|
+
const engine = new FlowEngine();
|
|
765
|
+
engine.context.defineProperty('routeRepository', {
|
|
766
|
+
value: {
|
|
767
|
+
refreshAccessible: hookState.refresh,
|
|
768
|
+
isAccessibleLoaded: () => true,
|
|
769
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
770
|
+
getRouteBySchemaUid: vi.fn(() => ({ type: 'page', schemaUid: 'test-page' })),
|
|
771
|
+
},
|
|
772
|
+
});
|
|
773
|
+
engine.context.defineProperty('app', {
|
|
774
|
+
value: {
|
|
775
|
+
getPublicPath: () => '/v2/',
|
|
776
|
+
router: {
|
|
777
|
+
getBasename: () => '/v2',
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
783
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
784
|
+
{
|
|
785
|
+
registerRoutePage: vi.fn(),
|
|
786
|
+
updateRoutePage: vi.fn(),
|
|
787
|
+
unregisterRoutePage: vi.fn(),
|
|
788
|
+
},
|
|
789
|
+
);
|
|
790
|
+
|
|
791
|
+
render(
|
|
792
|
+
<FlowEngineProvider engine={engine}>
|
|
793
|
+
<MemoryRouter initialEntries={['/flow/test-page']}>
|
|
794
|
+
<Routes>
|
|
795
|
+
<Route path="/flow/:name" element={<FlowRoute legacyPageBehavior="bridge" />} />
|
|
796
|
+
</Routes>
|
|
797
|
+
</MemoryRouter>
|
|
798
|
+
</FlowEngineProvider>,
|
|
799
|
+
);
|
|
800
|
+
|
|
801
|
+
await waitFor(() => {
|
|
802
|
+
expect(adminLayoutModel.registerRoutePage).toHaveBeenCalled();
|
|
803
|
+
});
|
|
804
|
+
expect(replace).not.toHaveBeenCalled();
|
|
805
|
+
} finally {
|
|
806
|
+
Object.defineProperty(window, 'location', {
|
|
807
|
+
configurable: true,
|
|
808
|
+
value: originalLocation,
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
|
|
286
813
|
it('should not redirect when route does not exist', async () => {
|
|
287
814
|
const originalLocation = window.location;
|
|
288
815
|
const replace = vi.fn();
|