@nocobase/client-v2 2.2.0-beta.9 → 3.0.0-alpha.1
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/APIClient.d.ts +1 -3
- package/es/BaseApplication.d.ts +1 -0
- package/es/acl/ACLProvider.d.ts +10 -0
- package/es/authRedirect.d.ts +5 -0
- package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
- package/es/components/AttachmentUpload.d.ts +51 -0
- package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
- package/es/components/category-tabs/index.d.ts +9 -0
- package/es/components/form/ScanInput/useCodeScanner.d.ts +3 -2
- package/es/components/form/TypedVariableInput.d.ts +31 -5
- package/es/components/form/filter/CollectionFilter.d.ts +2 -0
- package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
- package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
- package/es/components/form/filter/index.d.ts +2 -0
- package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
- package/es/components/index.d.ts +2 -0
- package/es/flow/actions/index.d.ts +1 -1
- package/es/flow/actions/linkageRules.d.ts +3 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/NocoBaseLogo.d.ts +10 -0
- package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
- package/es/flow/components/FieldAssignValueInput.d.ts +9 -0
- package/es/flow/components/FlowRoute.d.ts +2 -2
- package/es/flow/components/RunJSValueEditor.d.ts +1 -0
- package/es/flow/components/filter/FilterGroup.d.ts +1 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
- package/es/flow/index.d.ts +1 -0
- package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
- package/es/flow/models/base/ActionModelCore.d.ts +4 -2
- package/es/flow/models/base/BlockGridModel.d.ts +3 -0
- package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
- package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
- package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
- package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +1 -0
- package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
- package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
- package/es/flow-compat/routeTypes.d.ts +1 -0
- package/es/index.d.ts +8 -1
- package/es/index.mjs +190 -141
- package/es/nocobase-buildin-plugin/currentUserAuthStatus.d.ts +11 -0
- package/es/nocobase-buildin-plugin/index.d.ts +9 -0
- package/es/settings-app/SettingsApplication.d.ts +18 -0
- package/es/settings-app/SettingsBrand.d.ts +16 -0
- package/es/settings-app/SettingsBuildInPlugin.d.ts +9 -0
- package/es/settings-app/SettingsDocumentRedirect.d.ts +10 -0
- package/es/settings-app/SettingsGroupNav.d.ts +17 -0
- package/es/settings-app/SettingsPluginSettingsManager.d.ts +14 -0
- package/es/settings-app/SettingsRouterManager.d.ts +15 -0
- package/es/settings-app/SettingsSearch.d.ts +17 -0
- package/es/settings-app/SettingsShell.d.ts +10 -0
- package/es/settings-app/isSettingsApp.d.ts +18 -0
- package/es/settings-app/runtimePaths.d.ts +18 -0
- package/es/settings-app/settingsDocumentPath.d.ts +14 -0
- package/es/settings-app/settingsTheme.d.ts +87 -0
- package/es/settings-app/useSettingsThemeConfig.d.ts +22 -0
- package/es/settings-center/AdminSettingsLayout.d.ts +2 -2
- package/es/settings-center/groups.d.ts +66 -0
- package/es/settings-center/useSettingsGroups.d.ts +36 -0
- package/es/settings-center/useSettingsSearch.d.ts +35 -0
- package/es/settings-center/utils.d.ts +24 -0
- package/es/theme/index.d.ts +1 -0
- package/es/theme/itemActive.d.ts +24 -0
- package/es/theme/type.d.ts +9 -0
- package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
- package/es/utils/index.d.ts +1 -0
- package/lib/index.js +195 -146
- package/lib/locale/languageCodes.js +3 -1
- package/package.json +9 -7
- package/src/APIClient.ts +1 -10
- package/src/Application.tsx +4 -0
- package/src/BaseApplication.tsx +11 -6
- package/src/__tests__/app.test.tsx +73 -0
- package/src/__tests__/authRedirect.test.ts +137 -0
- package/src/__tests__/browserChecker.test.ts +185 -6
- package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
- package/src/__tests__/mockSettingsApplication.ts +29 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +79 -10
- package/src/__tests__/plugin-manager.test.tsx +7 -6
- package/src/__tests__/settings-application.test.ts +164 -0
- package/src/__tests__/settings-auth-routes.test.ts +71 -0
- package/src/__tests__/settings-center.test.tsx +38 -37
- package/src/__tests__/settings-layout-root.test.tsx +260 -0
- package/src/__tests__/settings-runtime-paths.test.ts +99 -0
- package/src/__tests__/settings-shell.test.tsx +200 -0
- package/src/acl/ACLProvider.tsx +21 -0
- package/src/authRedirect.ts +72 -3
- package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
- package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
- package/src/collection-manager/field-validation.ts +1 -1
- package/src/components/AttachmentUpload.tsx +275 -0
- package/src/components/README.md +7 -1
- package/src/components/README.zh-CN.md +6 -1
- package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
- package/src/components/category-tabs/index.ts +10 -0
- package/src/components/form/JsonTextArea.tsx +4 -0
- package/src/components/form/ScanInput/CodeScanner.tsx +79 -35
- package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +101 -0
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +284 -11
- package/src/components/form/ScanInput/useCodeScanner.ts +332 -24
- package/src/components/form/TypedVariableInput.tsx +452 -101
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
- package/src/components/form/filter/CollectionFilter.tsx +4 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
- package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
- package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
- package/src/components/form/filter/index.ts +2 -0
- package/src/components/form/filter/useFilterActionProps.ts +37 -6
- package/src/components/index.ts +2 -0
- package/src/flow/FlowPage.tsx +9 -1
- package/src/flow/__tests__/FlowPage.test.tsx +50 -3
- package/src/flow/__tests__/FlowRoute.test.tsx +128 -10
- package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
- package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +314 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +102 -0
- package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
- package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
- package/src/flow/actions/index.ts +2 -0
- package/src/flow/actions/linkageRules.tsx +111 -16
- package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +1 -57
- package/src/flow/admin-shell/admin-layout/NocoBaseLogo.tsx +77 -0
- package/src/flow/admin-shell/admin-layout/TopbarActionsBar.tsx +37 -14
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +253 -6
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +77 -13
- package/src/flow/common/Markdown/style.ts +4 -0
- package/src/flow/components/BlockItemCard.tsx +2 -2
- package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
- package/src/flow/components/FieldAssignValueInput.tsx +91 -20
- package/src/flow/components/FlowRoute.tsx +78 -32
- package/src/flow/components/RunJSValueEditor.tsx +9 -1
- package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
- package/src/flow/components/filter/FilterGroup.tsx +33 -5
- package/src/flow/components/filter/VariableFilterItem.tsx +158 -10
- package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +70 -1
- package/src/flow/index.ts +3 -0
- package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
- package/src/flow/models/base/ActionModelCore.tsx +11 -2
- package/src/flow/models/base/BlockGridModel.tsx +26 -0
- package/src/flow/models/base/CollectionBlockModel.tsx +6 -2
- package/src/flow/models/base/PageModel/PageModel.tsx +387 -53
- package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
- package/src/flow/models/base/PageModel/PageTabModel.tsx +249 -18
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +790 -10
- package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
- package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
- package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +1075 -7
- package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +49 -0
- package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +28 -3
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +66 -4
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
- package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
- package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +2 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +4 -2
- package/src/flow/models/blocks/table/TableColumnModel.tsx +30 -1
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +33 -0
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +2 -1
- package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
- package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
- package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
- package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
- package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -0
- package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
- package/src/flow/models/topbar/TopbarActionModel.tsx +52 -180
- package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
- package/src/flow-compat/fieldValidationConstants.ts +1 -1
- package/src/flow-compat/routeTypes.ts +1 -0
- package/src/index.ts +14 -1
- package/src/locale/languageCodes.ts +3 -1
- package/src/nocobase-buildin-plugin/currentUserAuthStatus.ts +58 -0
- package/src/nocobase-buildin-plugin/index.tsx +110 -70
- package/src/settings-app/SettingsApplication.ts +31 -0
- package/src/settings-app/SettingsBrand.tsx +108 -0
- package/src/settings-app/SettingsBuildInPlugin.ts +10 -0
- package/src/settings-app/SettingsDocumentRedirect.tsx +26 -0
- package/src/settings-app/SettingsGroupNav.tsx +124 -0
- package/src/settings-app/SettingsPluginSettingsManager.ts +38 -0
- package/src/settings-app/SettingsRouterManager.ts +92 -0
- package/src/settings-app/SettingsSearch.tsx +238 -0
- package/src/settings-app/SettingsShell.tsx +167 -0
- package/src/settings-app/isSettingsApp.ts +21 -0
- package/src/settings-app/runtimePaths.ts +104 -0
- package/src/settings-app/settingsDocumentPath.ts +66 -0
- package/src/settings-app/settingsTheme.ts +301 -0
- package/src/settings-app/useSettingsThemeConfig.ts +100 -0
- package/src/settings-center/AdminSettingsLayout.tsx +122 -141
- package/src/settings-center/SystemSettingsPage.tsx +4 -170
- package/src/settings-center/groups.ts +108 -0
- package/src/settings-center/useSettingsGroups.ts +105 -0
- package/src/settings-center/useSettingsSearch.ts +173 -0
- package/src/settings-center/utils.tsx +50 -1
- package/src/theme/index.tsx +1 -0
- package/src/theme/itemActive.ts +31 -0
- package/src/theme/type.ts +10 -0
- package/src/utils/getRouteRuntimeVersion.ts +185 -0
- package/src/utils/index.tsx +1 -0
|
@@ -18,24 +18,53 @@ vi.mock('@nocobase/flow-engine', async (importOriginal) => {
|
|
|
18
18
|
return {
|
|
19
19
|
...actual,
|
|
20
20
|
FlowModel: class {
|
|
21
|
+
uid: string;
|
|
22
|
+
parentId?: string;
|
|
21
23
|
props: any;
|
|
22
24
|
context: any;
|
|
23
25
|
stepParams: any;
|
|
26
|
+
flowEngine?: {
|
|
27
|
+
modelRepository?: {
|
|
28
|
+
findOne?: (query: Record<string, unknown>) => Promise<unknown>;
|
|
29
|
+
};
|
|
30
|
+
flowSettings?: {
|
|
31
|
+
open?: (options: Record<string, unknown>) => Promise<unknown> | unknown;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
flowRegistryData: Record<string, unknown>;
|
|
35
|
+
invalidateFlowCache = vi.fn();
|
|
36
|
+
rerender = vi.fn(async () => undefined);
|
|
24
37
|
|
|
25
38
|
constructor(options: any = {}) {
|
|
39
|
+
this.uid = options.uid || 'mock-model';
|
|
40
|
+
this.parentId = options.parentId;
|
|
26
41
|
this.props = options.props || {};
|
|
27
42
|
this.context = options.context || {};
|
|
28
43
|
this.stepParams = options.stepParams || {};
|
|
44
|
+
this.flowEngine = options.flowEngine;
|
|
45
|
+
this.flowRegistryData = options.flowRegistry || {};
|
|
29
46
|
}
|
|
30
47
|
|
|
31
48
|
serialize() {
|
|
32
|
-
return { flowRegistry:
|
|
49
|
+
return { flowRegistry: this.flowRegistryData };
|
|
33
50
|
}
|
|
34
51
|
|
|
35
52
|
setProps(key: string, value: any) {
|
|
36
53
|
this.props[key] = value;
|
|
37
54
|
}
|
|
38
55
|
|
|
56
|
+
setStepParams(flowKey: string, stepKey: string, params: Record<string, unknown>) {
|
|
57
|
+
this.stepParams[flowKey] = this.stepParams[flowKey] || {};
|
|
58
|
+
this.stepParams[flowKey][stepKey] = {
|
|
59
|
+
...(this.stepParams[flowKey][stepKey] || {}),
|
|
60
|
+
...params,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
openFlowSettings(options: Record<string, unknown> = {}) {
|
|
65
|
+
return this.flowEngine?.flowSettings?.open?.({ model: this, ...options });
|
|
66
|
+
}
|
|
67
|
+
|
|
39
68
|
onInit() {}
|
|
40
69
|
static registerFlow(flow: any) {
|
|
41
70
|
registerFlowMock(flow);
|
|
@@ -59,6 +88,51 @@ vi.mock('ahooks', async (importOriginal) => {
|
|
|
59
88
|
};
|
|
60
89
|
});
|
|
61
90
|
|
|
91
|
+
type RootPageTabModelClass = typeof import('../PageTabModel').RootPageTabModel;
|
|
92
|
+
|
|
93
|
+
type TestRoute = {
|
|
94
|
+
schemaUid?: string;
|
|
95
|
+
options?: Record<string, unknown>;
|
|
96
|
+
} & Record<string, unknown>;
|
|
97
|
+
|
|
98
|
+
type RootPageTabModelTestOptions = {
|
|
99
|
+
props?: {
|
|
100
|
+
route?: TestRoute;
|
|
101
|
+
};
|
|
102
|
+
stepParams?: {
|
|
103
|
+
pageTabSettings?: {
|
|
104
|
+
tab?: {
|
|
105
|
+
title?: string;
|
|
106
|
+
icon?: string;
|
|
107
|
+
documentTitle?: string;
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
context: {
|
|
112
|
+
api: {
|
|
113
|
+
request: ReturnType<typeof vi.fn>;
|
|
114
|
+
};
|
|
115
|
+
routeRepository?: {
|
|
116
|
+
getRouteBySchemaUid?: (schemaUid: string) => TestRoute | undefined;
|
|
117
|
+
refreshAccessible?: () => Promise<unknown>;
|
|
118
|
+
};
|
|
119
|
+
refreshDesktopRoutes?: () => Promise<unknown>;
|
|
120
|
+
logger?: {
|
|
121
|
+
warn?: (context: { err: unknown }, message: string) => void;
|
|
122
|
+
};
|
|
123
|
+
t: (value: string) => string;
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
type RootPageTabModelTestConstructor = new (
|
|
128
|
+
options: RootPageTabModelTestOptions,
|
|
129
|
+
) => InstanceType<RootPageTabModelClass>;
|
|
130
|
+
|
|
131
|
+
function createRootPageTabModel(Model: RootPageTabModelClass, options: RootPageTabModelTestOptions) {
|
|
132
|
+
const TestModel = Model as unknown as RootPageTabModelTestConstructor;
|
|
133
|
+
return new TestModel(options);
|
|
134
|
+
}
|
|
135
|
+
|
|
62
136
|
describe('PageTabModel', () => {
|
|
63
137
|
beforeEach(() => {
|
|
64
138
|
vi.resetModules();
|
|
@@ -86,6 +160,13 @@ describe('PageTabModel', () => {
|
|
|
86
160
|
expect(keys.indexOf('documentTitle')).toBe(keys.indexOf('title') + 1);
|
|
87
161
|
});
|
|
88
162
|
|
|
163
|
+
it('should expose tab linkage rules in page tab settings', async () => {
|
|
164
|
+
await import('../PageTabModel');
|
|
165
|
+
const flow = registerFlowMock.mock.calls.find((call) => call[0]?.key === 'pageTabSettings')?.[0];
|
|
166
|
+
|
|
167
|
+
expect(flow?.steps?.linkageRules?.use).toBe('tabLinkageRules');
|
|
168
|
+
});
|
|
169
|
+
|
|
89
170
|
it('should trigger parent page title update in tab settings handler', async () => {
|
|
90
171
|
await import('../PageTabModel');
|
|
91
172
|
const flow = registerFlowMock.mock.calls.find((call) => call[0]?.key === 'pageTabSettings')?.[0];
|
|
@@ -113,7 +194,7 @@ describe('PageTabModel', () => {
|
|
|
113
194
|
it('should persist tab documentTitle to route options on save', async () => {
|
|
114
195
|
const { RootPageTabModel } = await import('../PageTabModel');
|
|
115
196
|
const request = vi.fn().mockResolvedValue({});
|
|
116
|
-
const model =
|
|
197
|
+
const model = createRootPageTabModel(RootPageTabModel, {
|
|
117
198
|
props: {
|
|
118
199
|
route: {
|
|
119
200
|
schemaUid: 'tab-1',
|
|
@@ -132,7 +213,7 @@ describe('PageTabModel', () => {
|
|
|
132
213
|
api: { request },
|
|
133
214
|
t: (value: string) => value,
|
|
134
215
|
},
|
|
135
|
-
}
|
|
216
|
+
});
|
|
136
217
|
|
|
137
218
|
await model.save();
|
|
138
219
|
|
|
@@ -141,6 +222,413 @@ describe('PageTabModel', () => {
|
|
|
141
222
|
expect(call.data.options.documentTitle).toBe('Tab doc title');
|
|
142
223
|
});
|
|
143
224
|
|
|
225
|
+
it('should update a persisted tab route by id with only editable fields and latest options', async () => {
|
|
226
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
227
|
+
const request = vi.fn().mockResolvedValue({});
|
|
228
|
+
const model = createRootPageTabModel(RootPageTabModel, {
|
|
229
|
+
props: {
|
|
230
|
+
route: {
|
|
231
|
+
id: 123,
|
|
232
|
+
parentId: 456,
|
|
233
|
+
schemaUid: 'tab-1',
|
|
234
|
+
type: 'tabs',
|
|
235
|
+
sort: 1,
|
|
236
|
+
hidden: true,
|
|
237
|
+
tabSchemaName: 'tab-name-1',
|
|
238
|
+
hideInMenu: false,
|
|
239
|
+
enableTabs: false,
|
|
240
|
+
roles: [{ name: 'admin', title: 'Admin' }],
|
|
241
|
+
createdAt: '2026-07-16T00:00:00.000Z',
|
|
242
|
+
updatedAt: '2026-07-16T01:00:00.000Z',
|
|
243
|
+
options: {
|
|
244
|
+
badge: {
|
|
245
|
+
showZero: false,
|
|
246
|
+
},
|
|
247
|
+
pluginState: 'stale',
|
|
248
|
+
removedPluginState: 'stale-only',
|
|
249
|
+
flowRegistry: { stale: true },
|
|
250
|
+
documentTitle: 'Stale document title',
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
stepParams: {
|
|
255
|
+
pageTabSettings: {
|
|
256
|
+
tab: {
|
|
257
|
+
title: 'Renamed tab',
|
|
258
|
+
icon: 'TableOutlined',
|
|
259
|
+
documentTitle: 'Current document title',
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
context: {
|
|
264
|
+
api: { request },
|
|
265
|
+
routeRepository: {
|
|
266
|
+
getRouteBySchemaUid: vi.fn(() => ({
|
|
267
|
+
schemaUid: 'tab-1',
|
|
268
|
+
options: {
|
|
269
|
+
badge: {
|
|
270
|
+
showZero: true,
|
|
271
|
+
},
|
|
272
|
+
pluginState: 'fresh',
|
|
273
|
+
flowRegistry: { fresh: true },
|
|
274
|
+
documentTitle: 'Fresh document title',
|
|
275
|
+
},
|
|
276
|
+
})),
|
|
277
|
+
},
|
|
278
|
+
t: (value: string) => value,
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
await model.save();
|
|
283
|
+
|
|
284
|
+
const call = request.mock.calls[0][0];
|
|
285
|
+
expect(call).toEqual({
|
|
286
|
+
method: 'post',
|
|
287
|
+
url: 'desktopRoutes:update?filter[id]=123',
|
|
288
|
+
data: {
|
|
289
|
+
schemaUid: 'tab-1',
|
|
290
|
+
title: 'Renamed tab',
|
|
291
|
+
icon: 'TableOutlined',
|
|
292
|
+
options: {
|
|
293
|
+
badge: {
|
|
294
|
+
showZero: true,
|
|
295
|
+
},
|
|
296
|
+
pluginState: 'fresh',
|
|
297
|
+
flowRegistry: {},
|
|
298
|
+
documentTitle: 'Current document title',
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
expect(Object.keys(call.data).sort()).toEqual(['icon', 'options', 'schemaUid', 'title']);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should fall back to model route options when the current route is unavailable', async () => {
|
|
306
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
307
|
+
const request = vi.fn().mockResolvedValue({});
|
|
308
|
+
const model = createRootPageTabModel(RootPageTabModel, {
|
|
309
|
+
props: {
|
|
310
|
+
route: {
|
|
311
|
+
id: 123,
|
|
312
|
+
parentId: 456,
|
|
313
|
+
schemaUid: 'tab-1',
|
|
314
|
+
type: 'tabs',
|
|
315
|
+
sort: 1,
|
|
316
|
+
hidden: true,
|
|
317
|
+
options: {
|
|
318
|
+
badge: {
|
|
319
|
+
showZero: true,
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
stepParams: {
|
|
325
|
+
pageTabSettings: {
|
|
326
|
+
tab: {
|
|
327
|
+
title: 'Renamed tab',
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
context: {
|
|
332
|
+
api: { request },
|
|
333
|
+
routeRepository: {
|
|
334
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
335
|
+
},
|
|
336
|
+
t: (value: string) => value,
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
await model.save();
|
|
341
|
+
|
|
342
|
+
const call = request.mock.calls[0][0];
|
|
343
|
+
expect(call).toEqual({
|
|
344
|
+
method: 'post',
|
|
345
|
+
url: 'desktopRoutes:update?filter[id]=123',
|
|
346
|
+
data: {
|
|
347
|
+
schemaUid: 'tab-1',
|
|
348
|
+
title: 'Renamed tab',
|
|
349
|
+
icon: undefined,
|
|
350
|
+
options: {
|
|
351
|
+
badge: {
|
|
352
|
+
showZero: true,
|
|
353
|
+
},
|
|
354
|
+
flowRegistry: {},
|
|
355
|
+
documentTitle: undefined,
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should await a route refresh after the request before a subsequent persisted tab save', async () => {
|
|
362
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
363
|
+
const events: string[] = [];
|
|
364
|
+
let resolveFirstRefresh: () => void = () => {};
|
|
365
|
+
const firstRefreshDone = new Promise<void>((resolve) => {
|
|
366
|
+
resolveFirstRefresh = resolve;
|
|
367
|
+
});
|
|
368
|
+
let currentRoute = {
|
|
369
|
+
schemaUid: 'tab-1',
|
|
370
|
+
options: {
|
|
371
|
+
pluginState: 'before-save',
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
let requestCall = 0;
|
|
375
|
+
const request = vi.fn(async () => {
|
|
376
|
+
requestCall += 1;
|
|
377
|
+
events.push(`request:${requestCall}`);
|
|
378
|
+
if (requestCall === 1) {
|
|
379
|
+
return {
|
|
380
|
+
data: {
|
|
381
|
+
data: [
|
|
382
|
+
{
|
|
383
|
+
id: 123,
|
|
384
|
+
schemaUid: 'tab-1',
|
|
385
|
+
options: {
|
|
386
|
+
pluginState: 'after-save',
|
|
387
|
+
serverHookState: true,
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
],
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
return {};
|
|
395
|
+
});
|
|
396
|
+
let refreshCall = 0;
|
|
397
|
+
const refreshAccessible = vi.fn(async () => {
|
|
398
|
+
refreshCall += 1;
|
|
399
|
+
events.push(`refresh:${refreshCall}:start`);
|
|
400
|
+
if (refreshCall === 1) {
|
|
401
|
+
await firstRefreshDone;
|
|
402
|
+
currentRoute = {
|
|
403
|
+
schemaUid: 'tab-1',
|
|
404
|
+
options: {
|
|
405
|
+
pluginState: 'after-save',
|
|
406
|
+
serverHookState: true,
|
|
407
|
+
},
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
events.push(`refresh:${refreshCall}:end`);
|
|
411
|
+
});
|
|
412
|
+
const model = createRootPageTabModel(RootPageTabModel, {
|
|
413
|
+
props: {
|
|
414
|
+
route: {
|
|
415
|
+
id: 123,
|
|
416
|
+
schemaUid: 'tab-1',
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
stepParams: {
|
|
420
|
+
pageTabSettings: {
|
|
421
|
+
tab: {
|
|
422
|
+
title: 'Renamed tab',
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
context: {
|
|
427
|
+
api: { request },
|
|
428
|
+
routeRepository: {
|
|
429
|
+
getRouteBySchemaUid: vi.fn(() => currentRoute),
|
|
430
|
+
refreshAccessible,
|
|
431
|
+
},
|
|
432
|
+
t: (value: string) => value,
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
let firstSaveResolved = false;
|
|
437
|
+
const firstSave = model.save().then(() => {
|
|
438
|
+
firstSaveResolved = true;
|
|
439
|
+
events.push('save:1:end');
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
await vi.waitFor(() => expect(refreshAccessible).toHaveBeenCalledTimes(1));
|
|
443
|
+
expect(events.slice(0, 2)).toEqual(['request:1', 'refresh:1:start']);
|
|
444
|
+
await Promise.resolve();
|
|
445
|
+
expect(firstSaveResolved).toBe(false);
|
|
446
|
+
|
|
447
|
+
resolveFirstRefresh();
|
|
448
|
+
await firstSave;
|
|
449
|
+
expect(events.indexOf('request:1')).toBeLessThan(events.indexOf('refresh:1:start'));
|
|
450
|
+
expect(events.indexOf('refresh:1:end')).toBeLessThan(events.indexOf('save:1:end'));
|
|
451
|
+
|
|
452
|
+
await model.save();
|
|
453
|
+
|
|
454
|
+
expect(refreshAccessible).toHaveBeenCalledTimes(2);
|
|
455
|
+
expect(request.mock.calls[1][0].data.options).toMatchObject({
|
|
456
|
+
pluginState: 'after-save',
|
|
457
|
+
serverHookState: true,
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('should serialize route refreshes when new tabs are saved concurrently', async () => {
|
|
462
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
463
|
+
let releaseFirstRefresh: () => void = () => {};
|
|
464
|
+
const firstRefreshDone = new Promise<void>((resolve) => {
|
|
465
|
+
releaseFirstRefresh = resolve;
|
|
466
|
+
});
|
|
467
|
+
let refreshCall = 0;
|
|
468
|
+
let activeRefreshes = 0;
|
|
469
|
+
let maxActiveRefreshes = 0;
|
|
470
|
+
const refreshAccessible = vi.fn(async () => {
|
|
471
|
+
refreshCall += 1;
|
|
472
|
+
activeRefreshes += 1;
|
|
473
|
+
maxActiveRefreshes = Math.max(maxActiveRefreshes, activeRefreshes);
|
|
474
|
+
if (refreshCall === 1) {
|
|
475
|
+
await firstRefreshDone;
|
|
476
|
+
}
|
|
477
|
+
activeRefreshes -= 1;
|
|
478
|
+
});
|
|
479
|
+
const request = vi
|
|
480
|
+
.fn()
|
|
481
|
+
.mockResolvedValueOnce({
|
|
482
|
+
data: {
|
|
483
|
+
data: {
|
|
484
|
+
id: 101,
|
|
485
|
+
schemaUid: 'tab-1',
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
})
|
|
489
|
+
.mockResolvedValueOnce({
|
|
490
|
+
data: {
|
|
491
|
+
data: {
|
|
492
|
+
id: 102,
|
|
493
|
+
schemaUid: 'tab-2',
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
const routeRepository = {
|
|
498
|
+
refreshAccessible,
|
|
499
|
+
};
|
|
500
|
+
const createModel = (schemaUid: string, title: string) =>
|
|
501
|
+
createRootPageTabModel(RootPageTabModel, {
|
|
502
|
+
props: {
|
|
503
|
+
route: {
|
|
504
|
+
parentId: 999,
|
|
505
|
+
schemaUid,
|
|
506
|
+
tabSchemaName: `${schemaUid}-name`,
|
|
507
|
+
type: 'tabs',
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
stepParams: {
|
|
511
|
+
pageTabSettings: {
|
|
512
|
+
tab: {
|
|
513
|
+
title,
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
context: {
|
|
518
|
+
api: { request },
|
|
519
|
+
routeRepository,
|
|
520
|
+
t: (value: string) => value,
|
|
521
|
+
},
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
const saves = Promise.all([createModel('tab-1', 'Tab 1').save(), createModel('tab-2', 'Tab 2').save()]);
|
|
525
|
+
|
|
526
|
+
await vi.waitFor(() => expect(request).toHaveBeenCalledTimes(2));
|
|
527
|
+
await vi.waitFor(() => expect(refreshAccessible).toHaveBeenCalled());
|
|
528
|
+
await Promise.resolve();
|
|
529
|
+
await Promise.resolve();
|
|
530
|
+
const refreshCallsBeforeRelease = refreshAccessible.mock.calls.length;
|
|
531
|
+
|
|
532
|
+
releaseFirstRefresh();
|
|
533
|
+
await saves;
|
|
534
|
+
|
|
535
|
+
expect(refreshCallsBeforeRelease).toBe(1);
|
|
536
|
+
expect(refreshAccessible).toHaveBeenCalledTimes(2);
|
|
537
|
+
expect(maxActiveRefreshes).toBe(1);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it('should keep a successful route save when refreshing the route cache fails', async () => {
|
|
541
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
542
|
+
const refreshError = new Error('refresh failed');
|
|
543
|
+
const request = vi
|
|
544
|
+
.fn()
|
|
545
|
+
.mockResolvedValueOnce({
|
|
546
|
+
data: {
|
|
547
|
+
data: {
|
|
548
|
+
id: 123,
|
|
549
|
+
schemaUid: 'tab-1',
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
})
|
|
553
|
+
.mockResolvedValueOnce({});
|
|
554
|
+
const refreshAccessible = vi.fn().mockRejectedValueOnce(refreshError).mockResolvedValueOnce([]);
|
|
555
|
+
const warn = vi.fn();
|
|
556
|
+
const model = createRootPageTabModel(RootPageTabModel, {
|
|
557
|
+
props: {
|
|
558
|
+
route: {
|
|
559
|
+
parentId: 999,
|
|
560
|
+
schemaUid: 'tab-1',
|
|
561
|
+
tabSchemaName: 'tab-name-1',
|
|
562
|
+
type: 'tabs',
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
stepParams: {
|
|
566
|
+
pageTabSettings: {
|
|
567
|
+
tab: {
|
|
568
|
+
title: 'Tab title',
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
},
|
|
572
|
+
context: {
|
|
573
|
+
api: { request },
|
|
574
|
+
routeRepository: {
|
|
575
|
+
refreshAccessible,
|
|
576
|
+
},
|
|
577
|
+
logger: { warn },
|
|
578
|
+
t: (value: string) => value,
|
|
579
|
+
},
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
await expect(model.save()).resolves.toBeUndefined();
|
|
583
|
+
|
|
584
|
+
expect(model.props.route.id).toBe(123);
|
|
585
|
+
expect(warn).toHaveBeenCalledWith(
|
|
586
|
+
{ err: refreshError },
|
|
587
|
+
'[client-v2] Failed to refresh desktop routes after saving a page tab',
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
await expect(model.save()).resolves.toBeUndefined();
|
|
591
|
+
expect(refreshAccessible).toHaveBeenCalledTimes(2);
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
it('should still reject when writing the tab route fails', async () => {
|
|
595
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
596
|
+
const writeError = new Error('write failed');
|
|
597
|
+
const request = vi.fn().mockRejectedValue(writeError);
|
|
598
|
+
const refreshAccessible = vi.fn().mockResolvedValue([]);
|
|
599
|
+
const warn = vi.fn();
|
|
600
|
+
const model = createRootPageTabModel(RootPageTabModel, {
|
|
601
|
+
props: {
|
|
602
|
+
route: {
|
|
603
|
+
parentId: 999,
|
|
604
|
+
schemaUid: 'tab-1',
|
|
605
|
+
tabSchemaName: 'tab-name-1',
|
|
606
|
+
type: 'tabs',
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
stepParams: {
|
|
610
|
+
pageTabSettings: {
|
|
611
|
+
tab: {
|
|
612
|
+
title: 'Tab title',
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
context: {
|
|
617
|
+
api: { request },
|
|
618
|
+
routeRepository: {
|
|
619
|
+
refreshAccessible,
|
|
620
|
+
},
|
|
621
|
+
logger: { warn },
|
|
622
|
+
t: (value: string) => value,
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
await expect(model.save()).rejects.toBe(writeError);
|
|
627
|
+
expect(refreshAccessible).not.toHaveBeenCalled();
|
|
628
|
+
expect(warn).not.toHaveBeenCalled();
|
|
629
|
+
expect(model.props.route.id).toBeUndefined();
|
|
630
|
+
});
|
|
631
|
+
|
|
144
632
|
it('should sync persisted route id after save', async () => {
|
|
145
633
|
const { RootPageTabModel } = await import('../PageTabModel');
|
|
146
634
|
const request = vi.fn().mockResolvedValue({
|
|
@@ -152,16 +640,24 @@ describe('PageTabModel', () => {
|
|
|
152
640
|
},
|
|
153
641
|
},
|
|
154
642
|
});
|
|
155
|
-
const model =
|
|
643
|
+
const model = createRootPageTabModel(RootPageTabModel, {
|
|
156
644
|
props: {
|
|
157
645
|
route: {
|
|
646
|
+
parentId: 999,
|
|
158
647
|
schemaUid: 'tab-1',
|
|
648
|
+
tabSchemaName: 'tab-name-1',
|
|
649
|
+
type: 'tabs',
|
|
650
|
+
params: [],
|
|
651
|
+
hideInMenu: false,
|
|
652
|
+
enableTabs: false,
|
|
159
653
|
},
|
|
160
654
|
},
|
|
161
655
|
stepParams: {
|
|
162
656
|
pageTabSettings: {
|
|
163
657
|
tab: {
|
|
164
658
|
title: 'Tab title',
|
|
659
|
+
icon: 'TableOutlined',
|
|
660
|
+
documentTitle: 'Tab document title',
|
|
165
661
|
},
|
|
166
662
|
},
|
|
167
663
|
},
|
|
@@ -169,10 +665,33 @@ describe('PageTabModel', () => {
|
|
|
169
665
|
api: { request },
|
|
170
666
|
t: (value: string) => value,
|
|
171
667
|
},
|
|
172
|
-
}
|
|
668
|
+
});
|
|
173
669
|
|
|
174
670
|
await model.save();
|
|
175
671
|
|
|
672
|
+
const call = request.mock.calls[0][0];
|
|
673
|
+
expect(call).toEqual({
|
|
674
|
+
method: 'post',
|
|
675
|
+
url: 'desktopRoutes:updateOrCreate',
|
|
676
|
+
params: {
|
|
677
|
+
filterKeys: ['schemaUid'],
|
|
678
|
+
},
|
|
679
|
+
data: {
|
|
680
|
+
parentId: 999,
|
|
681
|
+
schemaUid: 'tab-1',
|
|
682
|
+
tabSchemaName: 'tab-name-1',
|
|
683
|
+
type: 'tabs',
|
|
684
|
+
params: [],
|
|
685
|
+
hideInMenu: false,
|
|
686
|
+
enableTabs: false,
|
|
687
|
+
title: 'Tab title',
|
|
688
|
+
icon: 'TableOutlined',
|
|
689
|
+
options: {
|
|
690
|
+
flowRegistry: {},
|
|
691
|
+
documentTitle: 'Tab document title',
|
|
692
|
+
},
|
|
693
|
+
},
|
|
694
|
+
});
|
|
176
695
|
expect(model.props.route.id).toBe(123);
|
|
177
696
|
expect(model.props.route.sort).toBe(9);
|
|
178
697
|
});
|
|
@@ -192,7 +711,7 @@ describe('PageTabModel', () => {
|
|
|
192
711
|
],
|
|
193
712
|
},
|
|
194
713
|
});
|
|
195
|
-
const model =
|
|
714
|
+
const model = createRootPageTabModel(RootPageTabModel, {
|
|
196
715
|
props: {
|
|
197
716
|
route: {
|
|
198
717
|
schemaUid: 'tab-1',
|
|
@@ -212,7 +731,7 @@ describe('PageTabModel', () => {
|
|
|
212
731
|
api: { request },
|
|
213
732
|
t: (value: string) => value,
|
|
214
733
|
},
|
|
215
|
-
}
|
|
734
|
+
});
|
|
216
735
|
|
|
217
736
|
await model.save();
|
|
218
737
|
|
|
@@ -222,4 +741,553 @@ describe('PageTabModel', () => {
|
|
|
222
741
|
documentTitle: 'Server doc title',
|
|
223
742
|
});
|
|
224
743
|
});
|
|
744
|
+
|
|
745
|
+
describe('root tab linkage hydrate', () => {
|
|
746
|
+
it('should hydrate linkage rules before opening settings in configuration mode', async () => {
|
|
747
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
748
|
+
const linkageRules = { value: [{ key: 'rule-1' }] };
|
|
749
|
+
const request = vi.fn().mockResolvedValue({
|
|
750
|
+
data: {
|
|
751
|
+
data: {
|
|
752
|
+
uid: 'tab-1',
|
|
753
|
+
use: 'RouteModel',
|
|
754
|
+
props: { title: 'Stale anchor title' },
|
|
755
|
+
stepParams: {
|
|
756
|
+
pageTabSettings: {
|
|
757
|
+
tab: { title: 'Stale tab title' },
|
|
758
|
+
linkageRules,
|
|
759
|
+
},
|
|
760
|
+
},
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
});
|
|
764
|
+
const open = vi.fn(({ model }: { model: { stepParams: Record<string, unknown> } }) => {
|
|
765
|
+
expect(model.stepParams).toMatchObject({ pageTabSettings: { linkageRules } });
|
|
766
|
+
return 'opened';
|
|
767
|
+
});
|
|
768
|
+
const model = new RootPageTabModel({
|
|
769
|
+
uid: 'tab-1',
|
|
770
|
+
flowEngine: {
|
|
771
|
+
flowSettings: { open },
|
|
772
|
+
},
|
|
773
|
+
props: {
|
|
774
|
+
route: {
|
|
775
|
+
schemaUid: 'tab-1',
|
|
776
|
+
title: 'Current route title',
|
|
777
|
+
options: {},
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
stepParams: {
|
|
781
|
+
pageTabSettings: {
|
|
782
|
+
tab: { title: 'Current tab title' },
|
|
783
|
+
},
|
|
784
|
+
},
|
|
785
|
+
context: {
|
|
786
|
+
api: { request },
|
|
787
|
+
flowSettingsEnabled: true,
|
|
788
|
+
defineProperty: vi.fn(),
|
|
789
|
+
t: (value: string) => value,
|
|
790
|
+
},
|
|
791
|
+
} as any);
|
|
792
|
+
|
|
793
|
+
model.onInit({});
|
|
794
|
+
await model.openFlowSettings({ flowKey: 'pageTabSettings', stepKey: 'linkageRules' });
|
|
795
|
+
|
|
796
|
+
expect(request).toHaveBeenCalledWith({
|
|
797
|
+
url: 'flowModels:findOne',
|
|
798
|
+
params: { uid: 'tab-1' },
|
|
799
|
+
});
|
|
800
|
+
expect(model.props.route.title).toBe('Current route title');
|
|
801
|
+
expect(model.stepParams.pageTabSettings.tab.title).toBe('Current tab title');
|
|
802
|
+
expect(model.stepParams.pageTabSettings.linkageRules).toEqual(linkageRules);
|
|
803
|
+
expect(model.invalidateFlowCache).toHaveBeenCalledWith('beforeRender', true);
|
|
804
|
+
expect(model.rerender).toHaveBeenCalledTimes(1);
|
|
805
|
+
expect(open).toHaveBeenCalledTimes(1);
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
it('should only hydrate marked root tabs at runtime', async () => {
|
|
809
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
810
|
+
const unmarkedRequest = vi.fn().mockResolvedValue({ data: { data: null } });
|
|
811
|
+
const markedRequest = vi.fn().mockResolvedValue({
|
|
812
|
+
data: {
|
|
813
|
+
data: {
|
|
814
|
+
stepParams: {
|
|
815
|
+
pageTabSettings: {
|
|
816
|
+
linkageRules: { value: [] },
|
|
817
|
+
},
|
|
818
|
+
},
|
|
819
|
+
},
|
|
820
|
+
},
|
|
821
|
+
});
|
|
822
|
+
const createModel = (request: typeof markedRequest, marked: boolean) =>
|
|
823
|
+
new RootPageTabModel({
|
|
824
|
+
uid: marked ? 'tab-marked' : 'tab-unmarked',
|
|
825
|
+
props: {
|
|
826
|
+
route: {
|
|
827
|
+
schemaUid: marked ? 'tab-marked' : 'tab-unmarked',
|
|
828
|
+
options: marked ? { hasPersistedPageTabFlowModel: true } : {},
|
|
829
|
+
},
|
|
830
|
+
},
|
|
831
|
+
context: {
|
|
832
|
+
api: { request },
|
|
833
|
+
flowSettingsEnabled: false,
|
|
834
|
+
defineProperty: vi.fn(),
|
|
835
|
+
t: (value: string) => value,
|
|
836
|
+
},
|
|
837
|
+
} as any);
|
|
838
|
+
|
|
839
|
+
const unmarkedModel = createModel(unmarkedRequest, false);
|
|
840
|
+
const markedModel = createModel(markedRequest, true);
|
|
841
|
+
unmarkedModel.onInit({});
|
|
842
|
+
markedModel.onInit({});
|
|
843
|
+
|
|
844
|
+
await vi.waitFor(() => {
|
|
845
|
+
expect(markedRequest).toHaveBeenCalledTimes(1);
|
|
846
|
+
expect(markedModel.stepParams.pageTabSettings.linkageRules).toEqual({ value: [] });
|
|
847
|
+
});
|
|
848
|
+
expect(unmarkedRequest).not.toHaveBeenCalled();
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
it('should ignore the linkage-specific legacy marker at runtime', async () => {
|
|
852
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
853
|
+
const request = vi.fn().mockResolvedValue({ data: { data: null } });
|
|
854
|
+
const legacyMarker = ['hasPersistedPageTab', 'LinkageRules'].join('');
|
|
855
|
+
const model = new RootPageTabModel({
|
|
856
|
+
uid: 'tab-legacy-marker',
|
|
857
|
+
props: {
|
|
858
|
+
route: {
|
|
859
|
+
schemaUid: 'tab-legacy-marker',
|
|
860
|
+
options: { [legacyMarker]: true },
|
|
861
|
+
},
|
|
862
|
+
},
|
|
863
|
+
context: {
|
|
864
|
+
api: { request },
|
|
865
|
+
flowSettingsEnabled: false,
|
|
866
|
+
defineProperty: vi.fn(),
|
|
867
|
+
t: (value: string) => value,
|
|
868
|
+
},
|
|
869
|
+
} as any);
|
|
870
|
+
|
|
871
|
+
model.onInit({});
|
|
872
|
+
|
|
873
|
+
expect(request).not.toHaveBeenCalled();
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
it('should share one in-flight hydrate request across concurrent settings opens', async () => {
|
|
877
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
878
|
+
let resolveAnchor: (value: unknown) => void = () => undefined;
|
|
879
|
+
const request = vi.fn(
|
|
880
|
+
() =>
|
|
881
|
+
new Promise<unknown>((resolve) => {
|
|
882
|
+
resolveAnchor = (value) => resolve({ data: { data: value } });
|
|
883
|
+
}),
|
|
884
|
+
);
|
|
885
|
+
const open = vi.fn().mockResolvedValue(undefined);
|
|
886
|
+
const model = new RootPageTabModel({
|
|
887
|
+
uid: 'tab-1',
|
|
888
|
+
flowEngine: {
|
|
889
|
+
flowSettings: { open },
|
|
890
|
+
},
|
|
891
|
+
props: { route: { schemaUid: 'tab-1', options: {} } },
|
|
892
|
+
context: {
|
|
893
|
+
api: { request },
|
|
894
|
+
flowSettingsEnabled: false,
|
|
895
|
+
t: (value: string) => value,
|
|
896
|
+
},
|
|
897
|
+
} as any);
|
|
898
|
+
|
|
899
|
+
const firstOpen = model.openFlowSettings({ flowKey: 'pageTabSettings', stepKey: 'linkageRules' });
|
|
900
|
+
const secondOpen = model.openFlowSettings({ flowKey: 'pageTabSettings', stepKey: 'linkageRules' });
|
|
901
|
+
|
|
902
|
+
await vi.waitFor(() => expect(request).toHaveBeenCalledTimes(1));
|
|
903
|
+
resolveAnchor({
|
|
904
|
+
stepParams: {
|
|
905
|
+
pageTabSettings: {
|
|
906
|
+
linkageRules: { value: [] },
|
|
907
|
+
},
|
|
908
|
+
},
|
|
909
|
+
});
|
|
910
|
+
await Promise.all([firstOpen, secondOpen]);
|
|
911
|
+
|
|
912
|
+
expect(request).toHaveBeenCalledTimes(1);
|
|
913
|
+
expect(open).toHaveBeenCalledTimes(2);
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
it('should safely handle missing anchors and missing linkage steps', async () => {
|
|
917
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
918
|
+
const request = vi.fn().mockResolvedValue({ data: { data: null } });
|
|
919
|
+
const model = new RootPageTabModel({
|
|
920
|
+
uid: 'tab-1',
|
|
921
|
+
flowEngine: {
|
|
922
|
+
flowSettings: { open: vi.fn().mockResolvedValue(undefined) },
|
|
923
|
+
},
|
|
924
|
+
props: { route: { schemaUid: 'tab-1', options: {} } },
|
|
925
|
+
stepParams: {
|
|
926
|
+
pageTabSettings: {
|
|
927
|
+
tab: { title: 'Current tab title' },
|
|
928
|
+
},
|
|
929
|
+
},
|
|
930
|
+
context: {
|
|
931
|
+
api: { request },
|
|
932
|
+
flowSettingsEnabled: true,
|
|
933
|
+
defineProperty: vi.fn(),
|
|
934
|
+
t: (value: string) => value,
|
|
935
|
+
},
|
|
936
|
+
} as any);
|
|
937
|
+
|
|
938
|
+
model.onInit({});
|
|
939
|
+
await expect(
|
|
940
|
+
model.openFlowSettings({ flowKey: 'pageTabSettings', stepKey: 'linkageRules' }),
|
|
941
|
+
).resolves.toBeUndefined();
|
|
942
|
+
|
|
943
|
+
expect(request).toHaveBeenCalledWith({
|
|
944
|
+
url: 'flowModels:findOne',
|
|
945
|
+
params: { uid: 'tab-1' },
|
|
946
|
+
});
|
|
947
|
+
expect(model.stepParams.pageTabSettings.linkageRules).toBeUndefined();
|
|
948
|
+
expect(model.stepParams.pageTabSettings.tab.title).toBe('Current tab title');
|
|
949
|
+
});
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
describe('root tab linkage persistence', () => {
|
|
953
|
+
it('should not touch the anchor or marker when linkage rules were never loaded', async () => {
|
|
954
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
955
|
+
const request = vi.fn().mockResolvedValue({});
|
|
956
|
+
const updateRoute = vi.fn();
|
|
957
|
+
const model = new RootPageTabModel({
|
|
958
|
+
uid: 'tab-1',
|
|
959
|
+
props: {
|
|
960
|
+
route: {
|
|
961
|
+
id: 10,
|
|
962
|
+
schemaUid: 'tab-1',
|
|
963
|
+
options: { badge: 'new' },
|
|
964
|
+
},
|
|
965
|
+
},
|
|
966
|
+
stepParams: {
|
|
967
|
+
pageTabSettings: {
|
|
968
|
+
tab: { title: 'Tab title' },
|
|
969
|
+
},
|
|
970
|
+
},
|
|
971
|
+
context: {
|
|
972
|
+
api: { request },
|
|
973
|
+
routeRepository: { updateRoute },
|
|
974
|
+
t: (value: string) => value,
|
|
975
|
+
},
|
|
976
|
+
} as any);
|
|
977
|
+
|
|
978
|
+
await model.saveStepParams();
|
|
979
|
+
|
|
980
|
+
expect(request).toHaveBeenCalledTimes(1);
|
|
981
|
+
expect(request.mock.calls[0][0].url).toBe('desktopRoutes:update?filter[id]=10');
|
|
982
|
+
expect(updateRoute).not.toHaveBeenCalled();
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
it('should merge the latest anchor without sending subModels and then set the marker', async () => {
|
|
986
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
987
|
+
const latestAnchor = {
|
|
988
|
+
uid: 'tab-1',
|
|
989
|
+
use: 'CustomRouteModel',
|
|
990
|
+
props: { anchorProp: true },
|
|
991
|
+
decoratorProps: { compact: true },
|
|
992
|
+
stepParams: {
|
|
993
|
+
pageTabSettings: {
|
|
994
|
+
tab: { title: 'Anchor tab title' },
|
|
995
|
+
linkageRules: { value: [{ key: 'old-rule' }] },
|
|
996
|
+
},
|
|
997
|
+
otherFlow: {
|
|
998
|
+
otherStep: { value: 1 },
|
|
999
|
+
},
|
|
1000
|
+
},
|
|
1001
|
+
flowRegistry: { customFlow: { steps: {} } },
|
|
1002
|
+
unknownRoot: { keep: true },
|
|
1003
|
+
subModels: {
|
|
1004
|
+
grid: { uid: 'grid-1' },
|
|
1005
|
+
},
|
|
1006
|
+
};
|
|
1007
|
+
const request = vi.fn(async ({ url }: { url: string }) => {
|
|
1008
|
+
if (url === 'flowModels:findOne') {
|
|
1009
|
+
return { data: { data: latestAnchor } };
|
|
1010
|
+
}
|
|
1011
|
+
return { data: { data: {} } };
|
|
1012
|
+
});
|
|
1013
|
+
const updateRoute = vi.fn().mockResolvedValue(undefined);
|
|
1014
|
+
const currentRoute = {
|
|
1015
|
+
id: 10,
|
|
1016
|
+
schemaUid: 'tab-1',
|
|
1017
|
+
options: {
|
|
1018
|
+
badge: 'new',
|
|
1019
|
+
pluginOption: { keep: true },
|
|
1020
|
+
serverHookState: true,
|
|
1021
|
+
flowRegistry: { routeFlow: { steps: {} } },
|
|
1022
|
+
},
|
|
1023
|
+
};
|
|
1024
|
+
const linkageRules = { value: [{ key: 'new-rule' }] };
|
|
1025
|
+
const model = new RootPageTabModel({
|
|
1026
|
+
uid: 'tab-1',
|
|
1027
|
+
flowRegistry: { routeFlow: { steps: {} } },
|
|
1028
|
+
props: {
|
|
1029
|
+
route: {
|
|
1030
|
+
id: 10,
|
|
1031
|
+
schemaUid: 'tab-1',
|
|
1032
|
+
options: {
|
|
1033
|
+
badge: 'new',
|
|
1034
|
+
pluginOption: { keep: true },
|
|
1035
|
+
flowRegistry: { routeFlow: { steps: {} } },
|
|
1036
|
+
},
|
|
1037
|
+
},
|
|
1038
|
+
},
|
|
1039
|
+
stepParams: {
|
|
1040
|
+
pageTabSettings: {
|
|
1041
|
+
tab: {
|
|
1042
|
+
title: 'Tab title',
|
|
1043
|
+
documentTitle: 'Tab document title',
|
|
1044
|
+
},
|
|
1045
|
+
linkageRules,
|
|
1046
|
+
},
|
|
1047
|
+
},
|
|
1048
|
+
context: {
|
|
1049
|
+
api: { request },
|
|
1050
|
+
routeRepository: {
|
|
1051
|
+
getRouteBySchemaUid: vi.fn(() => currentRoute),
|
|
1052
|
+
refreshAccessible: vi.fn().mockResolvedValue([currentRoute]),
|
|
1053
|
+
updateRoute,
|
|
1054
|
+
},
|
|
1055
|
+
t: (value: string) => value,
|
|
1056
|
+
},
|
|
1057
|
+
} as any);
|
|
1058
|
+
|
|
1059
|
+
await model.saveStepParams();
|
|
1060
|
+
|
|
1061
|
+
const urls = request.mock.calls.map(([config]) => config.url);
|
|
1062
|
+
expect(urls).toEqual(['desktopRoutes:update?filter[id]=10', 'flowModels:findOne', 'flowModels:save']);
|
|
1063
|
+
expect(request.mock.calls[0][0].data.options).toMatchObject({
|
|
1064
|
+
badge: 'new',
|
|
1065
|
+
pluginOption: { keep: true },
|
|
1066
|
+
serverHookState: true,
|
|
1067
|
+
flowRegistry: { routeFlow: { steps: {} } },
|
|
1068
|
+
documentTitle: 'Tab document title',
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
const anchorPayload = request.mock.calls[2][0].data;
|
|
1072
|
+
expect(anchorPayload).toMatchObject({
|
|
1073
|
+
uid: 'tab-1',
|
|
1074
|
+
use: 'CustomRouteModel',
|
|
1075
|
+
props: { anchorProp: true },
|
|
1076
|
+
decoratorProps: { compact: true },
|
|
1077
|
+
stepParams: {
|
|
1078
|
+
pageTabSettings: {
|
|
1079
|
+
tab: { title: 'Anchor tab title' },
|
|
1080
|
+
linkageRules,
|
|
1081
|
+
},
|
|
1082
|
+
otherFlow: {
|
|
1083
|
+
otherStep: { value: 1 },
|
|
1084
|
+
},
|
|
1085
|
+
},
|
|
1086
|
+
flowRegistry: { customFlow: { steps: {} } },
|
|
1087
|
+
unknownRoot: { keep: true },
|
|
1088
|
+
});
|
|
1089
|
+
expect(anchorPayload).not.toHaveProperty('subModels');
|
|
1090
|
+
expect(updateRoute).toHaveBeenCalledWith(
|
|
1091
|
+
10,
|
|
1092
|
+
{
|
|
1093
|
+
options: expect.objectContaining({
|
|
1094
|
+
badge: 'new',
|
|
1095
|
+
pluginOption: { keep: true },
|
|
1096
|
+
serverHookState: true,
|
|
1097
|
+
hasPersistedPageTabFlowModel: true,
|
|
1098
|
+
}),
|
|
1099
|
+
},
|
|
1100
|
+
{ refreshAfterMutation: false },
|
|
1101
|
+
);
|
|
1102
|
+
expect(request.mock.invocationCallOrder[2]).toBeLessThan(updateRoute.mock.invocationCallOrder[0]);
|
|
1103
|
+
expect(model.props.route.options.hasPersistedPageTabFlowModel).toBe(true);
|
|
1104
|
+
expect(model.props.route.options.serverHookState).toBe(true);
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
it('should read the latest anchor again before every linkage save', async () => {
|
|
1108
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
1109
|
+
const request = vi.fn(async ({ url }: { url: string }) => {
|
|
1110
|
+
if (url === 'flowModels:findOne') {
|
|
1111
|
+
return { data: { data: { uid: 'tab-1', use: 'RouteModel' } } };
|
|
1112
|
+
}
|
|
1113
|
+
return {};
|
|
1114
|
+
});
|
|
1115
|
+
const model = new RootPageTabModel({
|
|
1116
|
+
uid: 'tab-1',
|
|
1117
|
+
props: { route: { id: 10, schemaUid: 'tab-1', options: {} } },
|
|
1118
|
+
stepParams: {
|
|
1119
|
+
pageTabSettings: {
|
|
1120
|
+
tab: { title: 'Tab title' },
|
|
1121
|
+
linkageRules: { value: [] },
|
|
1122
|
+
},
|
|
1123
|
+
},
|
|
1124
|
+
context: {
|
|
1125
|
+
api: { request },
|
|
1126
|
+
routeRepository: { updateRoute: vi.fn().mockResolvedValue(undefined) },
|
|
1127
|
+
t: (value: string) => value,
|
|
1128
|
+
},
|
|
1129
|
+
} as any);
|
|
1130
|
+
|
|
1131
|
+
await model.saveStepParams();
|
|
1132
|
+
await model.saveStepParams();
|
|
1133
|
+
|
|
1134
|
+
expect(request.mock.calls.filter(([config]) => config.url === 'flowModels:findOne')).toHaveLength(2);
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
it('should persist empty rules without destroying the anchor and clear the marker', async () => {
|
|
1138
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
1139
|
+
const request = vi.fn(async ({ url }: { url: string }) => {
|
|
1140
|
+
if (url === 'flowModels:findOne') {
|
|
1141
|
+
return {
|
|
1142
|
+
data: {
|
|
1143
|
+
data: {
|
|
1144
|
+
uid: 'tab-1',
|
|
1145
|
+
use: 'RouteModel',
|
|
1146
|
+
subModels: { grid: { uid: 'grid-1' } },
|
|
1147
|
+
},
|
|
1148
|
+
},
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
return {};
|
|
1152
|
+
});
|
|
1153
|
+
const updateRoute = vi.fn().mockResolvedValue(undefined);
|
|
1154
|
+
const model = new RootPageTabModel({
|
|
1155
|
+
uid: 'tab-1',
|
|
1156
|
+
props: {
|
|
1157
|
+
route: {
|
|
1158
|
+
id: 10,
|
|
1159
|
+
schemaUid: 'tab-1',
|
|
1160
|
+
options: {
|
|
1161
|
+
badge: 'new',
|
|
1162
|
+
hasPersistedPageTabFlowModel: true,
|
|
1163
|
+
},
|
|
1164
|
+
},
|
|
1165
|
+
},
|
|
1166
|
+
stepParams: {
|
|
1167
|
+
pageTabSettings: {
|
|
1168
|
+
tab: { title: 'Tab title' },
|
|
1169
|
+
linkageRules: { value: [] },
|
|
1170
|
+
},
|
|
1171
|
+
},
|
|
1172
|
+
context: {
|
|
1173
|
+
api: { request },
|
|
1174
|
+
routeRepository: { updateRoute },
|
|
1175
|
+
t: (value: string) => value,
|
|
1176
|
+
},
|
|
1177
|
+
} as any);
|
|
1178
|
+
|
|
1179
|
+
await model.saveStepParams();
|
|
1180
|
+
|
|
1181
|
+
const urls = request.mock.calls.map(([config]) => config.url);
|
|
1182
|
+
expect(urls).toContain('flowModels:save');
|
|
1183
|
+
expect(urls).not.toContain('flowModels:destroy');
|
|
1184
|
+
const anchorPayload = request.mock.calls.find(([config]) => config.url === 'flowModels:save')?.[0].data;
|
|
1185
|
+
expect(anchorPayload.stepParams.pageTabSettings.linkageRules).toEqual({ value: [] });
|
|
1186
|
+
expect(anchorPayload).not.toHaveProperty('subModels');
|
|
1187
|
+
expect(updateRoute.mock.calls[0][1].options).toMatchObject({ badge: 'new' });
|
|
1188
|
+
expect(updateRoute.mock.calls[0][1].options).not.toHaveProperty('hasPersistedPageTabFlowModel');
|
|
1189
|
+
expect(model.props.route.options).not.toHaveProperty('hasPersistedPageTabFlowModel');
|
|
1190
|
+
});
|
|
1191
|
+
|
|
1192
|
+
it('should create a minimal RouteModel anchor when the anchor is missing', async () => {
|
|
1193
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
1194
|
+
const request = vi.fn(async ({ url }: { url: string }) => {
|
|
1195
|
+
if (url === 'flowModels:findOne') {
|
|
1196
|
+
return { data: { data: null } };
|
|
1197
|
+
}
|
|
1198
|
+
return {};
|
|
1199
|
+
});
|
|
1200
|
+
const model = new RootPageTabModel({
|
|
1201
|
+
uid: 'tab-1',
|
|
1202
|
+
props: { route: { id: 10, schemaUid: 'tab-1', options: {} } },
|
|
1203
|
+
stepParams: {
|
|
1204
|
+
pageTabSettings: {
|
|
1205
|
+
tab: { title: 'Tab title' },
|
|
1206
|
+
linkageRules: { value: [{ key: 'rule-1' }] },
|
|
1207
|
+
},
|
|
1208
|
+
},
|
|
1209
|
+
context: {
|
|
1210
|
+
api: { request },
|
|
1211
|
+
routeRepository: { updateRoute: vi.fn().mockResolvedValue(undefined) },
|
|
1212
|
+
t: (value: string) => value,
|
|
1213
|
+
},
|
|
1214
|
+
} as any);
|
|
1215
|
+
|
|
1216
|
+
await model.saveStepParams();
|
|
1217
|
+
|
|
1218
|
+
const anchorPayload = request.mock.calls.find(([config]) => config.url === 'flowModels:save')?.[0].data;
|
|
1219
|
+
expect(anchorPayload).toMatchObject({
|
|
1220
|
+
uid: 'tab-1',
|
|
1221
|
+
use: 'RouteModel',
|
|
1222
|
+
stepParams: {
|
|
1223
|
+
pageTabSettings: {
|
|
1224
|
+
linkageRules: { value: [{ key: 'rule-1' }] },
|
|
1225
|
+
},
|
|
1226
|
+
},
|
|
1227
|
+
});
|
|
1228
|
+
expect(anchorPayload).not.toHaveProperty('props.route');
|
|
1229
|
+
expect(anchorPayload).not.toHaveProperty('subModels');
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
it('should not update the marker when saving the anchor fails', async () => {
|
|
1233
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
1234
|
+
const error = new Error('anchor save failed');
|
|
1235
|
+
const request = vi.fn(async ({ url }: { url: string }) => {
|
|
1236
|
+
if (url === 'flowModels:findOne') {
|
|
1237
|
+
return { data: { data: { uid: 'tab-1', use: 'RouteModel' } } };
|
|
1238
|
+
}
|
|
1239
|
+
if (url === 'flowModels:save') {
|
|
1240
|
+
throw error;
|
|
1241
|
+
}
|
|
1242
|
+
return {};
|
|
1243
|
+
});
|
|
1244
|
+
const updateRoute = vi.fn();
|
|
1245
|
+
const model = new RootPageTabModel({
|
|
1246
|
+
uid: 'tab-1',
|
|
1247
|
+
props: { route: { id: 10, schemaUid: 'tab-1', options: {} } },
|
|
1248
|
+
stepParams: {
|
|
1249
|
+
pageTabSettings: {
|
|
1250
|
+
linkageRules: { value: [{ key: 'rule-1' }] },
|
|
1251
|
+
},
|
|
1252
|
+
},
|
|
1253
|
+
context: {
|
|
1254
|
+
api: { request },
|
|
1255
|
+
routeRepository: { updateRoute },
|
|
1256
|
+
t: (value: string) => value,
|
|
1257
|
+
},
|
|
1258
|
+
} as any);
|
|
1259
|
+
|
|
1260
|
+
await expect(model.saveStepParams()).rejects.toBe(error);
|
|
1261
|
+
expect(updateRoute).not.toHaveBeenCalled();
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1264
|
+
it('should reject when updating the route marker fails', async () => {
|
|
1265
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
1266
|
+
const error = new Error('marker update failed');
|
|
1267
|
+
const request = vi.fn(async ({ url }: { url: string }) => {
|
|
1268
|
+
if (url === 'flowModels:findOne') {
|
|
1269
|
+
return { data: { data: { uid: 'tab-1', use: 'RouteModel' } } };
|
|
1270
|
+
}
|
|
1271
|
+
return {};
|
|
1272
|
+
});
|
|
1273
|
+
const updateRoute = vi.fn().mockRejectedValue(error);
|
|
1274
|
+
const model = new RootPageTabModel({
|
|
1275
|
+
uid: 'tab-1',
|
|
1276
|
+
props: { route: { id: 10, schemaUid: 'tab-1', options: {} } },
|
|
1277
|
+
stepParams: {
|
|
1278
|
+
pageTabSettings: {
|
|
1279
|
+
linkageRules: { value: [{ key: 'rule-1' }] },
|
|
1280
|
+
},
|
|
1281
|
+
},
|
|
1282
|
+
context: {
|
|
1283
|
+
api: { request },
|
|
1284
|
+
routeRepository: { updateRoute },
|
|
1285
|
+
t: (value: string) => value,
|
|
1286
|
+
},
|
|
1287
|
+
} as any);
|
|
1288
|
+
|
|
1289
|
+
await expect(model.saveStepParams()).rejects.toBe(error);
|
|
1290
|
+
expect(request.mock.calls.some(([config]) => config.url === 'flowModels:save')).toBe(true);
|
|
1291
|
+
});
|
|
1292
|
+
});
|
|
225
1293
|
});
|