@nocobase/client-v2 2.1.15 → 2.1.18
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/flow/admin-shell/BaseLayoutModel.d.ts +4 -0
- package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +6 -0
- package/es/index.mjs +91 -91
- package/lib/index.js +118 -118
- package/package.json +7 -7
- package/src/flow/FlowPage.tsx +29 -2
- package/src/flow/__tests__/FlowPage.test.tsx +152 -25
- package/src/flow/admin-shell/BaseLayoutModel.tsx +106 -0
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +160 -34
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +896 -119
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +29 -2
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +311 -2
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { FlowContext, FlowEngine } from '@nocobase/flow-engine';
|
|
10
|
+
import { FlowContext, FlowEngine, type FlowModel } from '@nocobase/flow-engine';
|
|
11
|
+
import type React from 'react';
|
|
11
12
|
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
12
13
|
import { getViewDiffAndUpdateHidden } from '../../getViewDiffAndUpdateHidden';
|
|
13
14
|
import { getOpenViewStepParams } from '../../flows/openViewFlow';
|
|
14
|
-
import { resolveViewParamsToViewList, updateViewListHidden } from '../../resolveViewParamsToViewList';
|
|
15
|
+
import { resolveViewParamsToViewList, updateViewListHidden, type ViewItem } from '../../resolveViewParamsToViewList';
|
|
15
16
|
import { AdminLayoutRouteCoordinator } from '../AdminLayoutRouteCoordinator';
|
|
16
17
|
import { BaseLayoutRouteCoordinator, toViewStack } from '../BaseLayoutRouteCoordinator';
|
|
17
18
|
import { RouteModel } from '../../models/base/RouteModel';
|
|
@@ -27,9 +28,9 @@ vi.mock('../../getViewDiffAndUpdateHidden', () => ({
|
|
|
27
28
|
}));
|
|
28
29
|
|
|
29
30
|
vi.mock('../../flows/openViewFlow', async (importOriginal) => {
|
|
30
|
-
const actual = await importOriginal();
|
|
31
|
+
const actual = await importOriginal<typeof import('../../flows/openViewFlow')>();
|
|
31
32
|
return {
|
|
32
|
-
...
|
|
33
|
+
...actual,
|
|
33
34
|
getOpenViewStepParams: vi.fn(),
|
|
34
35
|
};
|
|
35
36
|
});
|
|
@@ -39,7 +40,48 @@ const mockGetViewDiffAndUpdateHidden = vi.mocked(getViewDiffAndUpdateHidden);
|
|
|
39
40
|
const mockGetOpenViewStepParams = vi.mocked(getOpenViewStepParams);
|
|
40
41
|
const mockUpdateViewListHidden = vi.mocked(updateViewListHidden);
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
type RouteOpenPayload = {
|
|
44
|
+
target?: HTMLElement | null;
|
|
45
|
+
destroyRef: React.RefObject<(result?: unknown, force?: boolean) => void>;
|
|
46
|
+
updateRef: React.RefObject<(value: unknown) => void>;
|
|
47
|
+
activateRef: React.RefObject<(forceRefresh?: boolean) => void>;
|
|
48
|
+
deactivateRef: React.RefObject<() => void>;
|
|
49
|
+
onOpen: () => void;
|
|
50
|
+
pageActive: boolean;
|
|
51
|
+
activationControlledByLayout: boolean;
|
|
52
|
+
hidden: { value: boolean };
|
|
53
|
+
triggerByRouter: boolean;
|
|
54
|
+
[key: string]: unknown;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type DispatchEventMock = ReturnType<typeof createDispatchEventMock>;
|
|
58
|
+
|
|
59
|
+
const createDispatchEventMock = (
|
|
60
|
+
implementation?: (eventName: string, payload: RouteOpenPayload) => Promise<unknown[] | void>,
|
|
61
|
+
) => vi.fn((eventName: string, payload: RouteOpenPayload) => implementation?.(eventName, payload) ?? Promise.resolve());
|
|
62
|
+
|
|
63
|
+
const createViewModel = (uid: string, dispatchEvent: DispatchEventMock) =>
|
|
64
|
+
({
|
|
65
|
+
uid,
|
|
66
|
+
dispatchEvent,
|
|
67
|
+
}) as unknown as FlowModel;
|
|
68
|
+
|
|
69
|
+
const createViewItem = (
|
|
70
|
+
uid: string,
|
|
71
|
+
dispatchEvent: DispatchEventMock,
|
|
72
|
+
index = 0,
|
|
73
|
+
params: ViewItem['params'] = { viewUid: uid },
|
|
74
|
+
): ViewItem => ({
|
|
75
|
+
params,
|
|
76
|
+
modelUid: uid,
|
|
77
|
+
model: createViewModel(uid, dispatchEvent),
|
|
78
|
+
hidden: { value: false },
|
|
79
|
+
index,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const nextTick = () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
83
|
+
|
|
84
|
+
function setupRouteReplay(viewParams: Record<string, unknown>) {
|
|
43
85
|
const engine = new FlowEngine();
|
|
44
86
|
engine.registerModels({ RouteModel });
|
|
45
87
|
engine.context.defineProperty('route', {
|
|
@@ -54,87 +96,713 @@ function setupRouteReplay(viewParams: Record<string, any>) {
|
|
|
54
96
|
},
|
|
55
97
|
});
|
|
56
98
|
|
|
57
|
-
const dispatchEvent =
|
|
58
|
-
const viewItem = {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
};
|
|
99
|
+
const dispatchEvent = createDispatchEventMock();
|
|
100
|
+
const viewItem = createViewItem('popup', dispatchEvent, 0, {
|
|
101
|
+
viewUid: 'popup',
|
|
102
|
+
filterByTk: 'member',
|
|
103
|
+
...viewParams,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
mockResolveViewParamsToViewList.mockReturnValue([viewItem]);
|
|
107
|
+
mockGetViewDiffAndUpdateHidden.mockReturnValue({
|
|
108
|
+
viewsToClose: [],
|
|
109
|
+
viewsToOpen: [viewItem],
|
|
110
|
+
});
|
|
111
|
+
mockGetOpenViewStepParams.mockReturnValue({
|
|
112
|
+
collectionName: 'roles',
|
|
113
|
+
associationName: 'users.roles',
|
|
114
|
+
dataSourceKey: 'main',
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const coordinator = new AdminLayoutRouteCoordinator(engine);
|
|
118
|
+
coordinator.registerPage('test-route', {
|
|
119
|
+
active: true,
|
|
120
|
+
layoutContentElement: document.createElement('div'),
|
|
121
|
+
});
|
|
122
|
+
coordinator.syncRoute({
|
|
123
|
+
params: { name: 'test-route' },
|
|
124
|
+
pathname: '/admin/popup/filterbytk/member',
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return { dispatchEvent };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
describe('AdminLayoutRouteCoordinator', () => {
|
|
131
|
+
beforeEach(() => {
|
|
132
|
+
vi.clearAllMocks();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('drops configured associationName during route replay when sourceId is absent', () => {
|
|
136
|
+
const { dispatchEvent } = setupRouteReplay({});
|
|
137
|
+
|
|
138
|
+
expect(dispatchEvent.mock.calls[0][1]).toMatchObject({
|
|
139
|
+
collectionName: 'roles',
|
|
140
|
+
associationName: null,
|
|
141
|
+
dataSourceKey: 'main',
|
|
142
|
+
filterByTk: 'member',
|
|
143
|
+
triggerByRouter: true,
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('keeps configured associationName during route replay when sourceId is present', () => {
|
|
148
|
+
const { dispatchEvent } = setupRouteReplay({ sourceId: '1' });
|
|
149
|
+
|
|
150
|
+
expect(dispatchEvent.mock.calls[0][1]).toMatchObject({
|
|
151
|
+
collectionName: 'roles',
|
|
152
|
+
associationName: 'users.roles',
|
|
153
|
+
dataSourceKey: 'main',
|
|
154
|
+
filterByTk: 'member',
|
|
155
|
+
sourceId: '1',
|
|
156
|
+
triggerByRouter: true,
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('passes RunJS openView route state as runtime mode and size during route replay', () => {
|
|
161
|
+
const { dispatchEvent } = setupRouteReplay({
|
|
162
|
+
openViewRouteState: { mode: 'dialog', size: 'large' },
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(dispatchEvent.mock.calls[0][1]).toMatchObject({
|
|
166
|
+
mode: 'dialog',
|
|
167
|
+
size: 'large',
|
|
168
|
+
openViewRouteState: { mode: 'dialog', size: 'large' },
|
|
169
|
+
triggerByRouter: true,
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('uses layout content element before route page placeholder as view target', () => {
|
|
174
|
+
const engine = new FlowEngine();
|
|
175
|
+
engine.registerModels({ RouteModel });
|
|
176
|
+
engine.context.defineProperty('route', {
|
|
177
|
+
value: {},
|
|
178
|
+
});
|
|
179
|
+
engine.context.defineProperty('routeRepository', {
|
|
180
|
+
value: {
|
|
181
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const layoutContentElement = document.createElement('div');
|
|
186
|
+
const routePageElement = document.createElement('div');
|
|
187
|
+
const dispatchEvent = createDispatchEventMock();
|
|
188
|
+
const viewItem = createViewItem('test-route', dispatchEvent);
|
|
189
|
+
mockResolveViewParamsToViewList.mockReturnValue([viewItem]);
|
|
190
|
+
mockGetViewDiffAndUpdateHidden.mockReturnValue({
|
|
191
|
+
viewsToClose: [],
|
|
192
|
+
viewsToOpen: [viewItem],
|
|
193
|
+
});
|
|
194
|
+
mockGetOpenViewStepParams.mockReturnValue({
|
|
195
|
+
collectionName: '',
|
|
196
|
+
dataSourceKey: '',
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
200
|
+
coordinator.setLayoutContentElement(layoutContentElement);
|
|
201
|
+
coordinator.registerPage('test-route', {
|
|
202
|
+
active: true,
|
|
203
|
+
layoutContentElement: routePageElement,
|
|
204
|
+
});
|
|
205
|
+
coordinator.syncRoute({
|
|
206
|
+
pageUid: 'test-route',
|
|
207
|
+
pathname: '/admin/test-route',
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
expect(dispatchEvent.mock.calls[0][1].target).toBe(layoutContentElement);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('replays the active route view when the layout content element changes', () => {
|
|
214
|
+
const engine = new FlowEngine();
|
|
215
|
+
engine.registerModels({ RouteModel });
|
|
216
|
+
engine.context.defineProperty('route', {
|
|
217
|
+
value: {},
|
|
218
|
+
});
|
|
219
|
+
engine.context.defineProperty('routeRepository', {
|
|
220
|
+
value: {
|
|
221
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const firstDestroy = vi.fn();
|
|
226
|
+
const dispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
227
|
+
if (dispatchEvent.mock.calls.length === 1) {
|
|
228
|
+
payload.destroyRef.current = firstDestroy;
|
|
229
|
+
}
|
|
230
|
+
return Promise.resolve();
|
|
231
|
+
});
|
|
232
|
+
const viewItem = createViewItem('test-route', dispatchEvent);
|
|
233
|
+
mockResolveViewParamsToViewList.mockReturnValue([viewItem]);
|
|
234
|
+
mockGetViewDiffAndUpdateHidden.mockReturnValue({
|
|
235
|
+
viewsToClose: [],
|
|
236
|
+
viewsToOpen: [viewItem],
|
|
237
|
+
});
|
|
238
|
+
mockGetOpenViewStepParams.mockReturnValue({
|
|
239
|
+
collectionName: '',
|
|
240
|
+
dataSourceKey: '',
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const firstLayoutContentElement = document.createElement('div');
|
|
244
|
+
const secondLayoutContentElement = document.createElement('div');
|
|
245
|
+
const routePageElement = document.createElement('div');
|
|
246
|
+
|
|
247
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
248
|
+
coordinator.setLayoutContentElement(firstLayoutContentElement);
|
|
249
|
+
const routeModel = coordinator.registerPage('test-route', {
|
|
250
|
+
active: true,
|
|
251
|
+
layoutContentElement: routePageElement,
|
|
252
|
+
});
|
|
253
|
+
coordinator.syncRoute({
|
|
254
|
+
pageUid: 'test-route',
|
|
255
|
+
pathname: '/admin/test-route',
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
expect(dispatchEvent).toHaveBeenCalledTimes(1);
|
|
259
|
+
expect(dispatchEvent.mock.calls[0][1].target).toBe(firstLayoutContentElement);
|
|
260
|
+
|
|
261
|
+
coordinator.setLayoutContentElement(null);
|
|
262
|
+
expect(dispatchEvent).toHaveBeenCalledTimes(1);
|
|
263
|
+
|
|
264
|
+
coordinator.setLayoutContentElement(secondLayoutContentElement);
|
|
265
|
+
|
|
266
|
+
expect(firstDestroy).toHaveBeenCalledTimes(1);
|
|
267
|
+
expect(dispatchEvent).toHaveBeenCalledTimes(2);
|
|
268
|
+
expect(dispatchEvent.mock.calls[1][1].target).toBe(secondLayoutContentElement);
|
|
269
|
+
expect(engine.getModel('test-route')).toBe(routeModel);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('reopens an inactive cached route view on the new layout content element when it becomes active again', () => {
|
|
273
|
+
const engine = new FlowEngine();
|
|
274
|
+
engine.registerModels({ RouteModel });
|
|
275
|
+
engine.context.defineProperty('route', {
|
|
276
|
+
value: {},
|
|
277
|
+
});
|
|
278
|
+
engine.context.defineProperty('routeRepository', {
|
|
279
|
+
value: {
|
|
280
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const dispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
285
|
+
payload.destroyRef.current = vi.fn();
|
|
286
|
+
payload.activateRef.current = vi.fn();
|
|
287
|
+
payload.deactivateRef.current = vi.fn();
|
|
288
|
+
return Promise.resolve();
|
|
289
|
+
});
|
|
290
|
+
const viewItem = createViewItem('test-route', dispatchEvent);
|
|
291
|
+
mockResolveViewParamsToViewList.mockReturnValue([viewItem]);
|
|
292
|
+
mockGetViewDiffAndUpdateHidden.mockImplementation((prevViewList, currentViewList) => ({
|
|
293
|
+
viewsToClose: prevViewList.filter((prevViewItem) => !currentViewList.includes(prevViewItem)),
|
|
294
|
+
viewsToOpen: currentViewList.filter((currentViewItem) => !prevViewList.includes(currentViewItem)),
|
|
295
|
+
}));
|
|
296
|
+
mockGetOpenViewStepParams.mockReturnValue({
|
|
297
|
+
collectionName: '',
|
|
298
|
+
dataSourceKey: '',
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const firstLayoutContentElement = document.createElement('div');
|
|
302
|
+
const secondLayoutContentElement = document.createElement('div');
|
|
303
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
304
|
+
coordinator.setLayoutContentElement(firstLayoutContentElement);
|
|
305
|
+
coordinator.registerPage('test-route', {
|
|
306
|
+
active: true,
|
|
307
|
+
layoutContentElement: document.createElement('div'),
|
|
308
|
+
});
|
|
309
|
+
coordinator.syncRoute({
|
|
310
|
+
pageUid: 'test-route',
|
|
311
|
+
pathname: '/admin/test-route',
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
coordinator.syncRoute({
|
|
315
|
+
pageUid: 'other-route',
|
|
316
|
+
pathname: '/admin/other-route',
|
|
317
|
+
});
|
|
318
|
+
coordinator.setLayoutContentElement(secondLayoutContentElement);
|
|
319
|
+
coordinator.syncRoute({
|
|
320
|
+
pageUid: 'test-route',
|
|
321
|
+
pathname: '/admin/test-route',
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
expect(dispatchEvent).toHaveBeenCalledTimes(2);
|
|
325
|
+
expect(dispatchEvent.mock.calls[1][1].target).toBe(secondLayoutContentElement);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('ignores stale view onOpen callbacks after the layout content element changes', () => {
|
|
329
|
+
const engine = new FlowEngine();
|
|
330
|
+
engine.registerModels({ RouteModel });
|
|
331
|
+
engine.context.defineProperty('route', {
|
|
332
|
+
value: {},
|
|
333
|
+
});
|
|
334
|
+
engine.context.defineProperty('routeRepository', {
|
|
335
|
+
value: {
|
|
336
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const rootDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
341
|
+
payload.destroyRef.current = vi.fn();
|
|
342
|
+
return Promise.resolve();
|
|
343
|
+
});
|
|
344
|
+
let stalePopupOnOpen: (() => void) | undefined;
|
|
345
|
+
const popupDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
346
|
+
stalePopupOnOpen = payload.onOpen;
|
|
347
|
+
payload.destroyRef.current = vi.fn();
|
|
348
|
+
return Promise.resolve();
|
|
349
|
+
});
|
|
350
|
+
const detailDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
351
|
+
payload.destroyRef.current = vi.fn();
|
|
352
|
+
return Promise.resolve();
|
|
353
|
+
});
|
|
354
|
+
const rootViewItem = createViewItem('test-route', rootDispatchEvent);
|
|
355
|
+
const popupViewItem = createViewItem('popup', popupDispatchEvent, 1);
|
|
356
|
+
const detailViewItem = createViewItem('detail', detailDispatchEvent, 2);
|
|
357
|
+
let resolvedViewList: ViewItem[] = [rootViewItem];
|
|
358
|
+
mockResolveViewParamsToViewList.mockImplementation(() => resolvedViewList);
|
|
359
|
+
mockGetViewDiffAndUpdateHidden.mockImplementation((prevViewList, currentViewList) => ({
|
|
360
|
+
viewsToClose: prevViewList.filter((prevViewItem) => !currentViewList.includes(prevViewItem)),
|
|
361
|
+
viewsToOpen: currentViewList.filter((currentViewItem) => !prevViewList.includes(currentViewItem)),
|
|
362
|
+
}));
|
|
363
|
+
mockGetOpenViewStepParams.mockReturnValue({
|
|
364
|
+
collectionName: '',
|
|
365
|
+
dataSourceKey: '',
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
369
|
+
coordinator.setLayoutContentElement(document.createElement('div'));
|
|
370
|
+
coordinator.registerPage('test-route', {
|
|
371
|
+
active: true,
|
|
372
|
+
layoutContentElement: document.createElement('div'),
|
|
373
|
+
});
|
|
374
|
+
coordinator.syncRoute({
|
|
375
|
+
pageUid: 'test-route',
|
|
376
|
+
pathname: '/admin/test-route',
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
resolvedViewList = [rootViewItem, popupViewItem, detailViewItem];
|
|
380
|
+
coordinator.syncRoute({
|
|
381
|
+
pageUid: 'test-route',
|
|
382
|
+
pathname: '/admin/test-route',
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
expect(popupDispatchEvent).toHaveBeenCalledTimes(1);
|
|
386
|
+
expect(detailDispatchEvent).not.toHaveBeenCalled();
|
|
387
|
+
expect(stalePopupOnOpen).toBeDefined();
|
|
388
|
+
|
|
389
|
+
resolvedViewList = [rootViewItem];
|
|
390
|
+
coordinator.setLayoutContentElement(document.createElement('div'));
|
|
391
|
+
stalePopupOnOpen?.();
|
|
392
|
+
|
|
393
|
+
expect(detailDispatchEvent).not.toHaveBeenCalled();
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('ignores stale async model loads after the layout content element changes', async () => {
|
|
397
|
+
const engine = new FlowEngine();
|
|
398
|
+
engine.registerModels({ RouteModel });
|
|
399
|
+
engine.context.defineProperty('route', {
|
|
400
|
+
value: {},
|
|
401
|
+
});
|
|
402
|
+
engine.context.defineProperty('routeRepository', {
|
|
403
|
+
value: {
|
|
404
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
const rootDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
409
|
+
payload.destroyRef.current = vi.fn();
|
|
410
|
+
return Promise.resolve();
|
|
411
|
+
});
|
|
412
|
+
const popupDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
413
|
+
payload.destroyRef.current = vi.fn();
|
|
414
|
+
return Promise.resolve();
|
|
415
|
+
});
|
|
416
|
+
const rootViewItem = createViewItem('test-route', rootDispatchEvent);
|
|
417
|
+
const popupViewItem: ViewItem = {
|
|
418
|
+
params: { viewUid: 'popup' },
|
|
419
|
+
modelUid: 'popup',
|
|
420
|
+
model: undefined,
|
|
421
|
+
hidden: { value: false },
|
|
422
|
+
index: 1,
|
|
423
|
+
};
|
|
424
|
+
let resolvePopupModel: (model: FlowModel) => void = () => {};
|
|
425
|
+
vi.spyOn(engine, 'loadModel').mockImplementation(
|
|
426
|
+
() =>
|
|
427
|
+
new Promise((resolve) => {
|
|
428
|
+
resolvePopupModel = resolve;
|
|
429
|
+
}),
|
|
430
|
+
);
|
|
431
|
+
let resolvedViewList: ViewItem[] = [rootViewItem];
|
|
432
|
+
mockResolveViewParamsToViewList.mockImplementation(() => resolvedViewList);
|
|
433
|
+
mockGetViewDiffAndUpdateHidden.mockImplementation((prevViewList, currentViewList) => ({
|
|
434
|
+
viewsToClose: prevViewList.filter((prevViewItem) => !currentViewList.includes(prevViewItem)),
|
|
435
|
+
viewsToOpen: currentViewList.filter((currentViewItem) => !prevViewList.includes(currentViewItem)),
|
|
436
|
+
}));
|
|
437
|
+
mockGetOpenViewStepParams.mockReturnValue({
|
|
438
|
+
collectionName: '',
|
|
439
|
+
dataSourceKey: '',
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
443
|
+
coordinator.setLayoutContentElement(document.createElement('div'));
|
|
444
|
+
coordinator.registerPage('test-route', {
|
|
445
|
+
active: true,
|
|
446
|
+
layoutContentElement: document.createElement('div'),
|
|
447
|
+
});
|
|
448
|
+
coordinator.syncRoute({
|
|
449
|
+
pageUid: 'test-route',
|
|
450
|
+
pathname: '/admin/test-route',
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
resolvedViewList = [rootViewItem, popupViewItem];
|
|
454
|
+
coordinator.syncRoute({
|
|
455
|
+
pageUid: 'test-route',
|
|
456
|
+
pathname: '/admin/test-route',
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
resolvedViewList = [rootViewItem];
|
|
460
|
+
coordinator.setLayoutContentElement(document.createElement('div'));
|
|
461
|
+
resolvePopupModel(createViewModel('popup', popupDispatchEvent));
|
|
462
|
+
await nextTick();
|
|
463
|
+
|
|
464
|
+
expect(popupDispatchEvent.mock.calls.length).toBe(0);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('ignores stale async view opens after the pathname changes', async () => {
|
|
468
|
+
const engine = new FlowEngine();
|
|
469
|
+
engine.registerModels({ RouteModel });
|
|
470
|
+
engine.context.defineProperty('route', {
|
|
471
|
+
value: {},
|
|
472
|
+
});
|
|
473
|
+
engine.context.defineProperty('routeRepository', {
|
|
474
|
+
value: {
|
|
475
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const rootDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
480
|
+
payload.destroyRef.current = vi.fn();
|
|
481
|
+
return Promise.resolve();
|
|
482
|
+
});
|
|
483
|
+
const popupDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
484
|
+
payload.destroyRef.current = vi.fn();
|
|
485
|
+
return Promise.resolve();
|
|
486
|
+
});
|
|
487
|
+
const rootViewItem = createViewItem('test-route', rootDispatchEvent);
|
|
488
|
+
const popupViewItem: ViewItem = {
|
|
489
|
+
params: { viewUid: 'popup' },
|
|
490
|
+
modelUid: 'popup',
|
|
491
|
+
model: undefined,
|
|
492
|
+
hidden: { value: false },
|
|
493
|
+
index: 1,
|
|
494
|
+
};
|
|
495
|
+
const resolvePopupModels: Array<(model: FlowModel) => void> = [];
|
|
496
|
+
vi.spyOn(engine, 'loadModel').mockImplementation(
|
|
497
|
+
() =>
|
|
498
|
+
new Promise((resolve) => {
|
|
499
|
+
resolvePopupModels.push(resolve);
|
|
500
|
+
}),
|
|
501
|
+
);
|
|
502
|
+
let resolvedViewList: ViewItem[] = [rootViewItem];
|
|
503
|
+
mockResolveViewParamsToViewList.mockImplementation(() => resolvedViewList);
|
|
504
|
+
mockGetViewDiffAndUpdateHidden.mockImplementation((prevViewList, currentViewList) => ({
|
|
505
|
+
viewsToClose: prevViewList.filter((prevViewItem) => !currentViewList.includes(prevViewItem)),
|
|
506
|
+
viewsToOpen: currentViewList.filter((currentViewItem) => !prevViewList.includes(currentViewItem)),
|
|
507
|
+
}));
|
|
508
|
+
mockGetOpenViewStepParams.mockReturnValue({
|
|
509
|
+
collectionName: '',
|
|
510
|
+
dataSourceKey: '',
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
514
|
+
coordinator.setLayoutContentElement(document.createElement('div'));
|
|
515
|
+
coordinator.registerPage('test-route', {
|
|
516
|
+
active: true,
|
|
517
|
+
layoutContentElement: document.createElement('div'),
|
|
518
|
+
});
|
|
519
|
+
coordinator.syncRoute({
|
|
520
|
+
pageUid: 'test-route',
|
|
521
|
+
pathname: '/admin/test-route',
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
resolvedViewList = [rootViewItem, popupViewItem];
|
|
525
|
+
coordinator.syncRoute({
|
|
526
|
+
pageUid: 'test-route',
|
|
527
|
+
pathname: '/admin/test-route/view/popup',
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
resolvedViewList = [rootViewItem];
|
|
531
|
+
coordinator.syncRoute({
|
|
532
|
+
pageUid: 'test-route',
|
|
533
|
+
pathname: '/admin/test-route',
|
|
534
|
+
});
|
|
535
|
+
resolvePopupModels.forEach((resolvePopupModel) => {
|
|
536
|
+
resolvePopupModel(createViewModel('popup', popupDispatchEvent));
|
|
537
|
+
});
|
|
538
|
+
await nextTick();
|
|
539
|
+
|
|
540
|
+
expect(popupDispatchEvent.mock.calls.length).toBe(0);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it('does not duplicate pending async view opens when the same pathname syncs again', async () => {
|
|
544
|
+
const engine = new FlowEngine();
|
|
545
|
+
engine.registerModels({ RouteModel });
|
|
546
|
+
engine.context.defineProperty('route', {
|
|
547
|
+
value: {},
|
|
548
|
+
});
|
|
549
|
+
engine.context.defineProperty('routeRepository', {
|
|
550
|
+
value: {
|
|
551
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
552
|
+
},
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
const rootDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
556
|
+
payload.destroyRef.current = vi.fn();
|
|
557
|
+
return Promise.resolve();
|
|
558
|
+
});
|
|
559
|
+
const popupDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
560
|
+
payload.destroyRef.current = vi.fn();
|
|
561
|
+
return Promise.resolve();
|
|
562
|
+
});
|
|
563
|
+
const rootViewItem = createViewItem('test-route', rootDispatchEvent);
|
|
564
|
+
const popupViewItem: ViewItem = {
|
|
565
|
+
params: { viewUid: 'popup' },
|
|
566
|
+
modelUid: 'popup',
|
|
567
|
+
model: undefined,
|
|
568
|
+
hidden: { value: false },
|
|
569
|
+
index: 1,
|
|
570
|
+
};
|
|
571
|
+
const resolvePopupModels: Array<(model: FlowModel) => void> = [];
|
|
572
|
+
vi.spyOn(engine, 'loadModel').mockImplementation(
|
|
573
|
+
() =>
|
|
574
|
+
new Promise((resolve) => {
|
|
575
|
+
resolvePopupModels.push(resolve);
|
|
576
|
+
}),
|
|
577
|
+
);
|
|
578
|
+
let resolvedViewList: ViewItem[] = [rootViewItem];
|
|
579
|
+
mockResolveViewParamsToViewList.mockImplementation(() => resolvedViewList);
|
|
580
|
+
mockGetViewDiffAndUpdateHidden.mockImplementation((prevViewList, currentViewList) => ({
|
|
581
|
+
viewsToClose: prevViewList.filter((prevViewItem) => !currentViewList.includes(prevViewItem)),
|
|
582
|
+
viewsToOpen: currentViewList.filter((currentViewItem) => !prevViewList.includes(currentViewItem)),
|
|
583
|
+
}));
|
|
584
|
+
mockGetOpenViewStepParams.mockReturnValue({
|
|
585
|
+
collectionName: '',
|
|
586
|
+
dataSourceKey: '',
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
590
|
+
coordinator.setLayoutContentElement(document.createElement('div'));
|
|
591
|
+
coordinator.registerPage('test-route', {
|
|
592
|
+
active: true,
|
|
593
|
+
layoutContentElement: document.createElement('div'),
|
|
594
|
+
});
|
|
595
|
+
coordinator.syncRoute({
|
|
596
|
+
pageUid: 'test-route',
|
|
597
|
+
pathname: '/admin/test-route',
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
resolvedViewList = [rootViewItem, popupViewItem];
|
|
601
|
+
coordinator.syncRoute({
|
|
602
|
+
pageUid: 'test-route',
|
|
603
|
+
pathname: '/admin/test-route/view/popup',
|
|
604
|
+
});
|
|
605
|
+
coordinator.syncRoute({
|
|
606
|
+
pageUid: 'test-route',
|
|
607
|
+
pathname: '/admin/test-route/view/popup',
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
resolvePopupModels.forEach((resolvePopupModel) => {
|
|
611
|
+
resolvePopupModel(createViewModel('popup', popupDispatchEvent));
|
|
612
|
+
});
|
|
613
|
+
await nextTick();
|
|
614
|
+
|
|
615
|
+
expect(popupDispatchEvent).toHaveBeenCalledTimes(1);
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it('clears pending route view opens when dispatchEvent throws synchronously', async () => {
|
|
619
|
+
const engine = new FlowEngine();
|
|
620
|
+
engine.registerModels({ RouteModel });
|
|
621
|
+
engine.context.defineProperty('route', {
|
|
622
|
+
value: {},
|
|
623
|
+
});
|
|
624
|
+
engine.context.defineProperty('routeRepository', {
|
|
625
|
+
value: {
|
|
626
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
const dispatchError = new Error('open failed');
|
|
631
|
+
const dispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
632
|
+
if (dispatchEvent.mock.calls.length === 1) {
|
|
633
|
+
throw dispatchError;
|
|
634
|
+
}
|
|
69
635
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
636
|
+
payload.destroyRef.current = vi.fn();
|
|
637
|
+
return Promise.resolve();
|
|
638
|
+
});
|
|
639
|
+
const viewItem = createViewItem('test-route', dispatchEvent);
|
|
640
|
+
mockResolveViewParamsToViewList.mockReturnValue([viewItem]);
|
|
641
|
+
mockGetViewDiffAndUpdateHidden.mockImplementation((prevViewList, currentViewList) => ({
|
|
642
|
+
viewsToClose: prevViewList.filter((prevViewItem) => !currentViewList.includes(prevViewItem)),
|
|
643
|
+
viewsToOpen: currentViewList.filter((currentViewItem) => !prevViewList.includes(currentViewItem)),
|
|
644
|
+
}));
|
|
645
|
+
mockGetOpenViewStepParams.mockReturnValue({
|
|
646
|
+
collectionName: '',
|
|
647
|
+
dataSourceKey: '',
|
|
648
|
+
});
|
|
649
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
80
650
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
651
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
652
|
+
coordinator.setLayoutContentElement(document.createElement('div'));
|
|
653
|
+
coordinator.registerPage('test-route', {
|
|
654
|
+
active: true,
|
|
655
|
+
layoutContentElement: document.createElement('div'),
|
|
656
|
+
});
|
|
657
|
+
coordinator.syncRoute({
|
|
658
|
+
pageUid: 'test-route',
|
|
659
|
+
pathname: '/admin/test-route',
|
|
660
|
+
});
|
|
661
|
+
await nextTick();
|
|
90
662
|
|
|
91
|
-
|
|
92
|
-
|
|
663
|
+
coordinator.syncRoute({
|
|
664
|
+
pageUid: 'test-route',
|
|
665
|
+
pathname: '/admin/test-route',
|
|
666
|
+
});
|
|
93
667
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
vi.clearAllMocks();
|
|
668
|
+
expect(dispatchEvent).toHaveBeenCalledTimes(2);
|
|
669
|
+
consoleErrorSpy.mockRestore();
|
|
97
670
|
});
|
|
98
671
|
|
|
99
|
-
it('
|
|
100
|
-
const
|
|
672
|
+
it('ignores stale initial deep-link replay after the pathname changes before a view opens', async () => {
|
|
673
|
+
const engine = new FlowEngine();
|
|
674
|
+
engine.registerModels({ RouteModel });
|
|
675
|
+
engine.context.defineProperty('router', {
|
|
676
|
+
value: {
|
|
677
|
+
navigate: vi.fn(),
|
|
678
|
+
},
|
|
679
|
+
});
|
|
680
|
+
engine.context.defineProperty('route', {
|
|
681
|
+
value: {},
|
|
682
|
+
});
|
|
683
|
+
engine.context.defineProperty('routeRepository', {
|
|
684
|
+
value: {
|
|
685
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
686
|
+
},
|
|
687
|
+
});
|
|
101
688
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
689
|
+
const rootDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
690
|
+
payload.destroyRef.current = vi.fn();
|
|
691
|
+
payload.onOpen();
|
|
692
|
+
return Promise.resolve();
|
|
693
|
+
});
|
|
694
|
+
const popupDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
695
|
+
payload.destroyRef.current = vi.fn();
|
|
696
|
+
return Promise.resolve();
|
|
697
|
+
});
|
|
698
|
+
const rootViewItem = createViewItem('test-route', rootDispatchEvent);
|
|
699
|
+
const popupViewItem = createViewItem('popup', popupDispatchEvent, 1);
|
|
700
|
+
mockResolveViewParamsToViewList.mockImplementation((_engine, viewParams) => {
|
|
701
|
+
if (viewParams[1]?.viewUid === 'popup') {
|
|
702
|
+
return [rootViewItem, popupViewItem];
|
|
703
|
+
}
|
|
704
|
+
return [];
|
|
705
|
+
});
|
|
706
|
+
mockGetViewDiffAndUpdateHidden.mockImplementation((prevViewList, currentViewList) => ({
|
|
707
|
+
viewsToClose: prevViewList.filter((prevViewItem) => !currentViewList.includes(prevViewItem)),
|
|
708
|
+
viewsToOpen: currentViewList.filter((currentViewItem) => !prevViewList.includes(currentViewItem)),
|
|
709
|
+
}));
|
|
710
|
+
mockGetOpenViewStepParams.mockReturnValue({
|
|
711
|
+
collectionName: '',
|
|
712
|
+
dataSourceKey: '',
|
|
108
713
|
});
|
|
109
|
-
});
|
|
110
714
|
|
|
111
|
-
|
|
112
|
-
|
|
715
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
716
|
+
coordinator.setLayoutContentElement(document.createElement('div'));
|
|
717
|
+
coordinator.registerPage('test-route', {
|
|
718
|
+
active: true,
|
|
719
|
+
layoutContentElement: document.createElement('div'),
|
|
720
|
+
});
|
|
721
|
+
coordinator.syncRoute({
|
|
722
|
+
pageUid: 'test-route',
|
|
723
|
+
pathname: '/admin/test-route/view/popup',
|
|
724
|
+
});
|
|
113
725
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
dataSourceKey: 'main',
|
|
118
|
-
filterByTk: 'member',
|
|
119
|
-
sourceId: '1',
|
|
120
|
-
triggerByRouter: true,
|
|
726
|
+
coordinator.syncRoute({
|
|
727
|
+
pageUid: 'test-route',
|
|
728
|
+
pathname: '/admin/test-route',
|
|
121
729
|
});
|
|
730
|
+
await nextTick();
|
|
731
|
+
|
|
732
|
+
expect(popupDispatchEvent.mock.calls.length).toBe(0);
|
|
122
733
|
});
|
|
123
734
|
|
|
124
|
-
it('
|
|
125
|
-
const
|
|
126
|
-
|
|
735
|
+
it('ignores stale async view opens after the route is cleared', async () => {
|
|
736
|
+
const engine = new FlowEngine();
|
|
737
|
+
engine.registerModels({ RouteModel });
|
|
738
|
+
engine.context.defineProperty('route', {
|
|
739
|
+
value: {},
|
|
740
|
+
});
|
|
741
|
+
engine.context.defineProperty('routeRepository', {
|
|
742
|
+
value: {
|
|
743
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
744
|
+
},
|
|
127
745
|
});
|
|
128
746
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
747
|
+
const rootDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
748
|
+
payload.destroyRef.current = vi.fn();
|
|
749
|
+
return Promise.resolve();
|
|
750
|
+
});
|
|
751
|
+
const popupDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
752
|
+
payload.destroyRef.current = vi.fn();
|
|
753
|
+
return Promise.resolve();
|
|
754
|
+
});
|
|
755
|
+
const rootViewItem = createViewItem('test-route', rootDispatchEvent);
|
|
756
|
+
const popupViewItem: ViewItem = {
|
|
757
|
+
params: { viewUid: 'popup' },
|
|
758
|
+
modelUid: 'popup',
|
|
759
|
+
model: undefined,
|
|
760
|
+
hidden: { value: false },
|
|
761
|
+
index: 1,
|
|
762
|
+
};
|
|
763
|
+
let resolvePopupModel: (model: FlowModel) => void = () => {};
|
|
764
|
+
vi.spyOn(engine, 'loadModel').mockImplementation(
|
|
765
|
+
() =>
|
|
766
|
+
new Promise((resolve) => {
|
|
767
|
+
resolvePopupModel = resolve;
|
|
768
|
+
}),
|
|
769
|
+
);
|
|
770
|
+
let resolvedViewList: ViewItem[] = [rootViewItem];
|
|
771
|
+
mockResolveViewParamsToViewList.mockImplementation(() => resolvedViewList);
|
|
772
|
+
mockGetViewDiffAndUpdateHidden.mockImplementation((prevViewList, currentViewList) => ({
|
|
773
|
+
viewsToClose: prevViewList.filter((prevViewItem) => !currentViewList.includes(prevViewItem)),
|
|
774
|
+
viewsToOpen: currentViewList.filter((currentViewItem) => !prevViewList.includes(currentViewItem)),
|
|
775
|
+
}));
|
|
776
|
+
mockGetOpenViewStepParams.mockReturnValue({
|
|
777
|
+
collectionName: '',
|
|
778
|
+
dataSourceKey: '',
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
782
|
+
coordinator.setLayoutContentElement(document.createElement('div'));
|
|
783
|
+
coordinator.registerPage('test-route', {
|
|
784
|
+
active: true,
|
|
785
|
+
layoutContentElement: document.createElement('div'),
|
|
786
|
+
});
|
|
787
|
+
coordinator.syncRoute({
|
|
788
|
+
pageUid: 'test-route',
|
|
789
|
+
pathname: '/admin/test-route',
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
resolvedViewList = [rootViewItem, popupViewItem];
|
|
793
|
+
coordinator.syncRoute({
|
|
794
|
+
pageUid: 'test-route',
|
|
795
|
+
pathname: '/admin/test-route/view/popup',
|
|
134
796
|
});
|
|
797
|
+
|
|
798
|
+
coordinator.syncRoute({});
|
|
799
|
+
resolvePopupModel(createViewModel('popup', popupDispatchEvent));
|
|
800
|
+
await nextTick();
|
|
801
|
+
|
|
802
|
+
expect(popupDispatchEvent).not.toHaveBeenCalled();
|
|
135
803
|
});
|
|
136
804
|
|
|
137
|
-
it('
|
|
805
|
+
it('ignores stale async view opens after switching away and back to the same pathname', async () => {
|
|
138
806
|
const engine = new FlowEngine();
|
|
139
807
|
engine.registerModels({ RouteModel });
|
|
140
808
|
engine.context.defineProperty('route', {
|
|
@@ -146,35 +814,161 @@ describe('AdminLayoutRouteCoordinator', () => {
|
|
|
146
814
|
},
|
|
147
815
|
});
|
|
148
816
|
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
817
|
+
const pageRootDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
818
|
+
payload.destroyRef.current = vi.fn();
|
|
819
|
+
return Promise.resolve();
|
|
820
|
+
});
|
|
821
|
+
const otherRootDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
822
|
+
payload.destroyRef.current = vi.fn();
|
|
823
|
+
return Promise.resolve();
|
|
824
|
+
});
|
|
825
|
+
const stalePopupDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
826
|
+
payload.destroyRef.current = vi.fn();
|
|
827
|
+
return Promise.resolve();
|
|
828
|
+
});
|
|
829
|
+
const currentPopupDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
830
|
+
payload.destroyRef.current = vi.fn();
|
|
831
|
+
return Promise.resolve();
|
|
832
|
+
});
|
|
833
|
+
const pageRootViewItem = createViewItem('test-route', pageRootDispatchEvent);
|
|
834
|
+
const otherRootViewItem = createViewItem('other-route', otherRootDispatchEvent);
|
|
835
|
+
const firstPopupViewItem: ViewItem = {
|
|
836
|
+
params: { viewUid: 'popup' },
|
|
837
|
+
modelUid: 'popup',
|
|
838
|
+
model: undefined,
|
|
156
839
|
hidden: { value: false },
|
|
157
|
-
index:
|
|
840
|
+
index: 1,
|
|
158
841
|
};
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
842
|
+
const secondPopupViewItem: ViewItem = {
|
|
843
|
+
params: { viewUid: 'popup' },
|
|
844
|
+
modelUid: 'popup',
|
|
845
|
+
model: undefined,
|
|
846
|
+
hidden: { value: false },
|
|
847
|
+
index: 1,
|
|
848
|
+
};
|
|
849
|
+
const resolvePopupModels: Array<(model: FlowModel) => void> = [];
|
|
850
|
+
vi.spyOn(engine, 'loadModel').mockImplementation(
|
|
851
|
+
() =>
|
|
852
|
+
new Promise((resolve) => {
|
|
853
|
+
resolvePopupModels.push(resolve);
|
|
854
|
+
}),
|
|
855
|
+
);
|
|
856
|
+
let resolvedViewList: ViewItem[] = [pageRootViewItem];
|
|
857
|
+
mockResolveViewParamsToViewList.mockImplementation(() => resolvedViewList);
|
|
858
|
+
mockGetViewDiffAndUpdateHidden.mockImplementation((prevViewList, currentViewList) => ({
|
|
859
|
+
viewsToClose: prevViewList.filter((prevViewItem) => !currentViewList.includes(prevViewItem)),
|
|
860
|
+
viewsToOpen: currentViewList.filter((currentViewItem) => !prevViewList.includes(currentViewItem)),
|
|
861
|
+
}));
|
|
862
|
+
mockGetOpenViewStepParams.mockReturnValue({
|
|
863
|
+
collectionName: '',
|
|
864
|
+
dataSourceKey: '',
|
|
163
865
|
});
|
|
164
|
-
mockGetOpenViewStepParams.mockReturnValue({} as any);
|
|
165
866
|
|
|
166
867
|
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
167
|
-
coordinator.setLayoutContentElement(
|
|
868
|
+
coordinator.setLayoutContentElement(document.createElement('div'));
|
|
168
869
|
coordinator.registerPage('test-route', {
|
|
169
870
|
active: true,
|
|
170
|
-
layoutContentElement:
|
|
871
|
+
layoutContentElement: document.createElement('div'),
|
|
872
|
+
});
|
|
873
|
+
coordinator.registerPage('other-route', {
|
|
874
|
+
active: false,
|
|
875
|
+
layoutContentElement: document.createElement('div'),
|
|
171
876
|
});
|
|
172
877
|
coordinator.syncRoute({
|
|
173
878
|
pageUid: 'test-route',
|
|
174
879
|
pathname: '/admin/test-route',
|
|
175
880
|
});
|
|
176
881
|
|
|
177
|
-
|
|
882
|
+
resolvedViewList = [pageRootViewItem, firstPopupViewItem];
|
|
883
|
+
coordinator.syncRoute({
|
|
884
|
+
pageUid: 'test-route',
|
|
885
|
+
pathname: '/admin/test-route/view/popup',
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
resolvedViewList = [otherRootViewItem];
|
|
889
|
+
coordinator.syncRoute({
|
|
890
|
+
pageUid: 'other-route',
|
|
891
|
+
pathname: '/admin/other-route',
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
resolvedViewList = [pageRootViewItem, secondPopupViewItem];
|
|
895
|
+
coordinator.syncRoute({
|
|
896
|
+
pageUid: 'test-route',
|
|
897
|
+
pathname: '/admin/test-route/view/popup',
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
resolvePopupModels[0]?.(createViewModel('popup', stalePopupDispatchEvent));
|
|
901
|
+
resolvePopupModels[1]?.(createViewModel('popup', currentPopupDispatchEvent));
|
|
902
|
+
await nextTick();
|
|
903
|
+
|
|
904
|
+
expect(stalePopupDispatchEvent).not.toHaveBeenCalled();
|
|
905
|
+
expect(currentPopupDispatchEvent).toHaveBeenCalledTimes(1);
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
it('does not run initial deep-link step navigation while replaying after layout content element changes', () => {
|
|
909
|
+
const engine = new FlowEngine();
|
|
910
|
+
engine.registerModels({ RouteModel });
|
|
911
|
+
const navigate = vi.fn();
|
|
912
|
+
engine.context.defineProperty('router', {
|
|
913
|
+
value: {
|
|
914
|
+
navigate,
|
|
915
|
+
},
|
|
916
|
+
});
|
|
917
|
+
engine.context.defineProperty('route', {
|
|
918
|
+
value: {},
|
|
919
|
+
});
|
|
920
|
+
engine.context.defineProperty('routeRepository', {
|
|
921
|
+
value: {
|
|
922
|
+
getRouteBySchemaUid: vi.fn(() => ({})),
|
|
923
|
+
},
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
const rootDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
927
|
+
payload.destroyRef.current = vi.fn();
|
|
928
|
+
payload.onOpen();
|
|
929
|
+
return Promise.resolve();
|
|
930
|
+
});
|
|
931
|
+
const popupDispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
932
|
+
payload.destroyRef.current = vi.fn();
|
|
933
|
+
return Promise.resolve();
|
|
934
|
+
});
|
|
935
|
+
const rootViewItem = createViewItem('test-route', rootDispatchEvent);
|
|
936
|
+
const popupViewItem = createViewItem('popup', popupDispatchEvent, 1);
|
|
937
|
+
let resolvedViewList: ViewItem[] = [rootViewItem];
|
|
938
|
+
mockResolveViewParamsToViewList.mockImplementation(() => resolvedViewList);
|
|
939
|
+
mockGetViewDiffAndUpdateHidden.mockImplementation((prevViewList, currentViewList) => ({
|
|
940
|
+
viewsToClose: prevViewList.filter((prevViewItem) => !currentViewList.includes(prevViewItem)),
|
|
941
|
+
viewsToOpen: currentViewList.filter((currentViewItem) => !prevViewList.includes(currentViewItem)),
|
|
942
|
+
}));
|
|
943
|
+
mockGetOpenViewStepParams.mockReturnValue({
|
|
944
|
+
collectionName: '',
|
|
945
|
+
dataSourceKey: '',
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
const coordinator = new BaseLayoutRouteCoordinator(engine, { basePathname: '/admin' });
|
|
949
|
+
coordinator.setLayoutContentElement(document.createElement('div'));
|
|
950
|
+
coordinator.registerPage('test-route', {
|
|
951
|
+
active: true,
|
|
952
|
+
layoutContentElement: document.createElement('div'),
|
|
953
|
+
});
|
|
954
|
+
coordinator.syncRoute({
|
|
955
|
+
pageUid: 'test-route',
|
|
956
|
+
pathname: '/admin/test-route',
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
resolvedViewList = [rootViewItem, popupViewItem];
|
|
960
|
+
coordinator.syncRoute({
|
|
961
|
+
pageUid: 'test-route',
|
|
962
|
+
pathname: '/admin/test-route/view/popup',
|
|
963
|
+
});
|
|
964
|
+
navigate.mockClear();
|
|
965
|
+
|
|
966
|
+
const nextLayoutContentElement = document.createElement('div');
|
|
967
|
+
coordinator.setLayoutContentElement(nextLayoutContentElement);
|
|
968
|
+
|
|
969
|
+
expect(navigate).not.toHaveBeenCalled();
|
|
970
|
+
expect(popupDispatchEvent).toHaveBeenCalledTimes(2);
|
|
971
|
+
expect(popupDispatchEvent.mock.calls[1][1].target).toBe(nextLayoutContentElement);
|
|
178
972
|
});
|
|
179
973
|
|
|
180
974
|
it('replaces stale non-route model before registering route page', () => {
|
|
@@ -281,18 +1075,12 @@ describe('AdminLayoutRouteCoordinator', () => {
|
|
|
281
1075
|
},
|
|
282
1076
|
});
|
|
283
1077
|
|
|
284
|
-
const dispatchEvent =
|
|
1078
|
+
const dispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
285
1079
|
payload.activateRef.current = vi.fn();
|
|
286
1080
|
payload.deactivateRef.current = vi.fn();
|
|
287
1081
|
return Promise.resolve();
|
|
288
1082
|
});
|
|
289
|
-
const viewItem =
|
|
290
|
-
params: { viewUid: 'test-route' },
|
|
291
|
-
modelUid: 'test-route',
|
|
292
|
-
model: { uid: 'test-route', dispatchEvent } as any,
|
|
293
|
-
hidden: { value: false },
|
|
294
|
-
index: 0,
|
|
295
|
-
};
|
|
1083
|
+
const viewItem = createViewItem('test-route', dispatchEvent);
|
|
296
1084
|
mockResolveViewParamsToViewList.mockReturnValue([viewItem]);
|
|
297
1085
|
mockGetViewDiffAndUpdateHidden.mockReturnValueOnce({
|
|
298
1086
|
viewsToClose: [],
|
|
@@ -342,28 +1130,26 @@ describe('AdminLayoutRouteCoordinator', () => {
|
|
|
342
1130
|
},
|
|
343
1131
|
});
|
|
344
1132
|
|
|
345
|
-
const viewItemsByPageUid = new Map<string,
|
|
346
|
-
const
|
|
347
|
-
const dispatchEvent =
|
|
1133
|
+
const viewItemsByPageUid = new Map<string, ViewItem>();
|
|
1134
|
+
const createCachedViewItem = (pageUid: string) => {
|
|
1135
|
+
const dispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
348
1136
|
payload.activateRef.current = vi.fn();
|
|
349
1137
|
payload.deactivateRef.current = vi.fn();
|
|
350
1138
|
return Promise.resolve();
|
|
351
1139
|
});
|
|
352
|
-
return
|
|
353
|
-
params: { viewUid: pageUid },
|
|
354
|
-
modelUid: pageUid,
|
|
355
|
-
model: { uid: pageUid, dispatchEvent } as any,
|
|
356
|
-
hidden: { value: false },
|
|
357
|
-
index: 0,
|
|
358
|
-
};
|
|
1140
|
+
return createViewItem(pageUid, dispatchEvent);
|
|
359
1141
|
};
|
|
360
1142
|
|
|
361
1143
|
mockResolveViewParamsToViewList.mockImplementation((_engine, viewParams) => {
|
|
362
1144
|
const pageUid = viewParams[0].viewUid;
|
|
363
1145
|
if (!viewItemsByPageUid.has(pageUid)) {
|
|
364
|
-
viewItemsByPageUid.set(pageUid,
|
|
1146
|
+
viewItemsByPageUid.set(pageUid, createCachedViewItem(pageUid));
|
|
1147
|
+
}
|
|
1148
|
+
const viewItem = viewItemsByPageUid.get(pageUid);
|
|
1149
|
+
if (!viewItem) {
|
|
1150
|
+
throw new Error(`Expected cached view item for ${pageUid}.`);
|
|
365
1151
|
}
|
|
366
|
-
return [
|
|
1152
|
+
return [viewItem];
|
|
367
1153
|
});
|
|
368
1154
|
mockGetViewDiffAndUpdateHidden.mockImplementation((prevViewList, currentViewList) => ({
|
|
369
1155
|
viewsToClose: prevViewList.filter((prevViewItem) => !currentViewList.includes(prevViewItem)),
|
|
@@ -420,18 +1206,12 @@ describe('AdminLayoutRouteCoordinator', () => {
|
|
|
420
1206
|
},
|
|
421
1207
|
});
|
|
422
1208
|
|
|
423
|
-
const dispatchEvent =
|
|
1209
|
+
const dispatchEvent = createDispatchEventMock((_eventName, payload) => {
|
|
424
1210
|
payload.activateRef.current = vi.fn();
|
|
425
1211
|
payload.deactivateRef.current = vi.fn();
|
|
426
1212
|
return Promise.resolve();
|
|
427
1213
|
});
|
|
428
|
-
const viewItem =
|
|
429
|
-
params: { viewUid: 'test-route' },
|
|
430
|
-
modelUid: 'test-route',
|
|
431
|
-
model: { uid: 'test-route', dispatchEvent } as any,
|
|
432
|
-
hidden: { value: false },
|
|
433
|
-
index: 0,
|
|
434
|
-
};
|
|
1214
|
+
const viewItem = createViewItem('test-route', dispatchEvent);
|
|
435
1215
|
mockResolveViewParamsToViewList.mockReturnValue([viewItem]);
|
|
436
1216
|
mockGetViewDiffAndUpdateHidden.mockReturnValueOnce({
|
|
437
1217
|
viewsToClose: [],
|
|
@@ -479,13 +1259,10 @@ describe('AdminLayoutRouteCoordinator', () => {
|
|
|
479
1259
|
},
|
|
480
1260
|
});
|
|
481
1261
|
|
|
482
|
-
const popupViewItem = {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
hidden: { value: false },
|
|
487
|
-
index: 1,
|
|
488
|
-
};
|
|
1262
|
+
const popupViewItem = createViewItem('popup', createDispatchEventMock(), 1, {
|
|
1263
|
+
viewUid: 'popup',
|
|
1264
|
+
filterByTk: '1',
|
|
1265
|
+
});
|
|
489
1266
|
mockResolveViewParamsToViewList.mockImplementation((_engine, viewParams, routeModel) => [
|
|
490
1267
|
{
|
|
491
1268
|
params: viewParams[0],
|