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