@sap-ux/ui5-test-writer 0.7.89 → 0.7.90
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/dist/fiori-elements-opa-writer.d.ts +2 -1
- package/dist/fiori-elements-opa-writer.js +38 -9
- package/dist/types.d.ts +102 -1
- package/dist/utils/listReportUtils.d.ts +94 -0
- package/dist/utils/listReportUtils.js +389 -0
- package/dist/utils/modelUtils.d.ts +60 -9
- package/dist/utils/modelUtils.js +69 -41
- package/dist/utils/objectPageUtils.d.ts +21 -0
- package/dist/utils/objectPageUtils.js +218 -0
- package/package.json +4 -1
- package/templates/v4/integration/FPMJourney.js +49 -0
- package/templates/v4/integration/FirstJourney.js +1 -11
- package/templates/v4/integration/ListReportJourney.js +84 -0
- package/templates/v4/integration/ObjectPageJourney.js +69 -0
- package/templates/v4/integration/opaTests.qunit.js +3 -2
|
@@ -18,6 +18,7 @@ export declare function readManifest(fs: Editor, basePath: string): Manifest;
|
|
|
18
18
|
* @param opaConfig.scriptName - the name of the OPA journey file. If not specified, 'FirstJourney' will be used
|
|
19
19
|
* @param opaConfig.htmlTarget - the name of the html that will be used in OPA journey file. If not specified, 'index.html' will be used
|
|
20
20
|
* @param opaConfig.appID - the appID. If not specified, will be read from the manifest in sap.app/id
|
|
21
|
+
* @param metadata - optional metadata for the OPA test generation
|
|
21
22
|
* @param fs - an optional reference to a mem-fs editor
|
|
22
23
|
* @param log - optional logger instance
|
|
23
24
|
* @returns Reference to a mem-fs-editor
|
|
@@ -26,7 +27,7 @@ export declare function generateOPAFiles(basePath: string, opaConfig: {
|
|
|
26
27
|
scriptName?: string;
|
|
27
28
|
appID?: string;
|
|
28
29
|
htmlTarget?: string;
|
|
29
|
-
}, fs?: Editor, log?: Logger): Promise<Editor>;
|
|
30
|
+
}, metadata?: string, fs?: Editor, log?: Logger): Promise<Editor>;
|
|
30
31
|
/**
|
|
31
32
|
* Generate a page object file for a Fiori elements for OData V4 application.
|
|
32
33
|
* Note: this doesn't modify other existing files in the webapp/test folder.
|
|
@@ -214,11 +214,12 @@ function writePageObject(pageConfig, rootTemplateDirPath, testOutDirPath, fs) {
|
|
|
214
214
|
* @param opaConfig.scriptName - the name of the OPA journey file. If not specified, 'FirstJourney' will be used
|
|
215
215
|
* @param opaConfig.htmlTarget - the name of the html that will be used in OPA journey file. If not specified, 'index.html' will be used
|
|
216
216
|
* @param opaConfig.appID - the appID. If not specified, will be read from the manifest in sap.app/id
|
|
217
|
+
* @param metadata - optional metadata for the OPA test generation
|
|
217
218
|
* @param fs - an optional reference to a mem-fs editor
|
|
218
219
|
* @param log - optional logger instance
|
|
219
220
|
* @returns Reference to a mem-fs-editor
|
|
220
221
|
*/
|
|
221
|
-
async function generateOPAFiles(basePath, opaConfig, fs, log) {
|
|
222
|
+
async function generateOPAFiles(basePath, opaConfig, metadata, fs, log) {
|
|
222
223
|
const editor = fs ?? (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
|
|
223
224
|
const manifest = readManifest(editor, basePath);
|
|
224
225
|
const { applicationType, hideFilterBar } = getAppTypeAndHideFilterBarFromManifest(manifest);
|
|
@@ -232,10 +233,6 @@ async function generateOPAFiles(basePath, opaConfig, fs, log) {
|
|
|
232
233
|
{ appId: config.appID }, undefined, {
|
|
233
234
|
globOptions: { dot: true }
|
|
234
235
|
});
|
|
235
|
-
// Integration (OPA) test files - version-specific
|
|
236
|
-
editor.copyTpl((0, node_path_1.join)(rootV4TemplateDirPath, 'integration', 'opaTests.*.*'), (0, node_path_1.join)(testOutDirPath, 'integration'), config, undefined, {
|
|
237
|
-
globOptions: { dot: true }
|
|
238
|
-
});
|
|
239
236
|
// Pages files (one for each page in the app)
|
|
240
237
|
config.pages.forEach((page) => {
|
|
241
238
|
writePageObject(page, rootV4TemplateDirPath, testOutDirPath, editor);
|
|
@@ -244,18 +241,50 @@ async function generateOPAFiles(basePath, opaConfig, fs, log) {
|
|
|
244
241
|
const startPages = config.pages.filter((page) => page.isStartup).map((page) => page.targetKey);
|
|
245
242
|
const LROP = findLROP(config.pages, manifest);
|
|
246
243
|
// Access ux-specification to get feature data for OPA test generation
|
|
247
|
-
const {
|
|
244
|
+
const { listReport, objectPages, fpm } = await (0, modelUtils_1.getAppFeatures)(basePath, editor, log, metadata);
|
|
248
245
|
const journeyParams = {
|
|
249
246
|
startPages,
|
|
250
247
|
startLR: LROP.pageLR?.targetKey,
|
|
251
248
|
navigatedOP: LROP.pageOP?.targetKey,
|
|
252
|
-
hideFilterBar: config.hideFilterBar
|
|
253
|
-
filterBarItems: filterBarItems,
|
|
254
|
-
tableColumns: tableColumns
|
|
249
|
+
hideFilterBar: config.hideFilterBar
|
|
255
250
|
};
|
|
251
|
+
const generatedJourneyPages = [];
|
|
256
252
|
editor.copyTpl((0, node_path_1.join)(rootV4TemplateDirPath, 'integration/FirstJourney.js'), (0, node_path_1.join)(testOutDirPath, `integration/${config.opaJourneyFileName}.js`), journeyParams, undefined, {
|
|
257
253
|
globOptions: { dot: true }
|
|
258
254
|
});
|
|
255
|
+
if (listReport) {
|
|
256
|
+
editor.copyTpl((0, node_path_1.join)(rootV4TemplateDirPath, 'integration/ListReportJourney.js'), (0, node_path_1.join)(testOutDirPath, `integration/${listReport.name}Journey.js`), {
|
|
257
|
+
...journeyParams,
|
|
258
|
+
...listReport
|
|
259
|
+
}, undefined, {
|
|
260
|
+
globOptions: { dot: true }
|
|
261
|
+
});
|
|
262
|
+
generatedJourneyPages.push(listReport.name);
|
|
263
|
+
}
|
|
264
|
+
if (objectPages && objectPages.length > 0) {
|
|
265
|
+
objectPages.forEach((objectPage) => {
|
|
266
|
+
editor.copyTpl((0, node_path_1.join)(rootV4TemplateDirPath, 'integration/ObjectPageJourney.js'), (0, node_path_1.join)(testOutDirPath, `integration/${objectPage.name}Journey.js`), {
|
|
267
|
+
...journeyParams,
|
|
268
|
+
...objectPage
|
|
269
|
+
}, undefined, {
|
|
270
|
+
globOptions: { dot: true }
|
|
271
|
+
});
|
|
272
|
+
generatedJourneyPages.push(objectPage.name);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
if (fpm) {
|
|
276
|
+
editor.copyTpl((0, node_path_1.join)(rootV4TemplateDirPath, 'integration/FPMJourney.js'), (0, node_path_1.join)(testOutDirPath, `integration/${fpm.name}Journey.js`), {
|
|
277
|
+
...journeyParams,
|
|
278
|
+
...fpm
|
|
279
|
+
}, undefined, {
|
|
280
|
+
globOptions: { dot: true }
|
|
281
|
+
});
|
|
282
|
+
generatedJourneyPages.push(fpm.name);
|
|
283
|
+
}
|
|
284
|
+
// Integration (OPA) test files - version-specific
|
|
285
|
+
editor.copyTpl((0, node_path_1.join)(rootV4TemplateDirPath, 'integration', 'opaTests.*.*'), (0, node_path_1.join)(testOutDirPath, 'integration'), { ...config, generatedJourneyPages }, undefined, {
|
|
286
|
+
globOptions: { dot: true }
|
|
287
|
+
});
|
|
259
288
|
// Journey Runner
|
|
260
289
|
editor.copyTpl((0, node_path_1.join)(rootV4TemplateDirPath, 'integration', 'pages', 'JourneyRunner.js'), (0, node_path_1.join)(testOutDirPath, 'integration', 'pages', 'JourneyRunner.js'), config, undefined, {
|
|
261
290
|
globOptions: { dot: true }
|
package/dist/types.d.ts
CHANGED
|
@@ -62,8 +62,109 @@ export interface FFOPAConfig {
|
|
|
62
62
|
ui5Version?: string;
|
|
63
63
|
ui5Theme?: string;
|
|
64
64
|
}
|
|
65
|
-
export type
|
|
65
|
+
export type ObjectPageNavigationParents = {
|
|
66
|
+
parentLRName?: string;
|
|
67
|
+
parentOPName?: string;
|
|
68
|
+
parentOPTableSection?: string;
|
|
69
|
+
};
|
|
70
|
+
export type ObjectPageFeatures = {
|
|
71
|
+
name?: string;
|
|
72
|
+
navigationParents?: ObjectPageNavigationParents;
|
|
73
|
+
headerTitle?: string;
|
|
74
|
+
headerDescription?: string;
|
|
75
|
+
headerSections?: HeaderSectionFeatureData[];
|
|
76
|
+
};
|
|
77
|
+
export type ListReportFeatures = {
|
|
78
|
+
name?: string;
|
|
79
|
+
createButton?: {
|
|
80
|
+
enabled?: boolean | string;
|
|
81
|
+
visible?: boolean;
|
|
82
|
+
dynamicPath?: string;
|
|
83
|
+
};
|
|
84
|
+
deleteButton?: {
|
|
85
|
+
enabled?: boolean | string;
|
|
86
|
+
visible: boolean;
|
|
87
|
+
dynamicPath?: string;
|
|
88
|
+
};
|
|
89
|
+
filterBarItems?: string[];
|
|
90
|
+
tableColumns?: Record<string, Record<string, string | number | boolean>>;
|
|
91
|
+
toolBarActions?: ActionButtonState[];
|
|
92
|
+
};
|
|
93
|
+
export interface ActionButtonState {
|
|
94
|
+
label: string;
|
|
95
|
+
action: string;
|
|
96
|
+
visible: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Indicates whether the action button is enabled.
|
|
99
|
+
* - true: Button is enabled and can be invoked
|
|
100
|
+
* - false: Button is disabled
|
|
101
|
+
* - 'dynamic': The state is controlled by a dynamic path annotation (e.g., Core.OperationAvailable)
|
|
102
|
+
*/
|
|
103
|
+
enabled: boolean | 'dynamic';
|
|
104
|
+
/**
|
|
105
|
+
* If the enabled state is dynamic, this contains the path to the control property.
|
|
106
|
+
* For example: "_it/__OperationControl/deductDiscount"
|
|
107
|
+
*/
|
|
108
|
+
dynamicPath?: string;
|
|
109
|
+
/**
|
|
110
|
+
* The invocation grouping type if specified (e.g., "Isolated", "ChangeSet").
|
|
111
|
+
*/
|
|
112
|
+
invocationGrouping?: string;
|
|
113
|
+
}
|
|
114
|
+
export type FPMFeatures = {
|
|
115
|
+
name?: string;
|
|
66
116
|
filterBarItems?: string[];
|
|
67
117
|
tableColumns?: Record<string, Record<string, string | number | boolean>>;
|
|
68
118
|
};
|
|
119
|
+
export type AppFeatures = {
|
|
120
|
+
listReport?: ListReportFeatures;
|
|
121
|
+
objectPages?: ObjectPageFeatures[];
|
|
122
|
+
fpm?: FPMFeatures;
|
|
123
|
+
};
|
|
124
|
+
export type HeaderSectionFeatureData = {
|
|
125
|
+
facetId?: string;
|
|
126
|
+
title?: string;
|
|
127
|
+
custom?: boolean;
|
|
128
|
+
collection?: boolean;
|
|
129
|
+
microChart?: boolean;
|
|
130
|
+
form?: boolean;
|
|
131
|
+
stashed?: boolean | string;
|
|
132
|
+
fields?: {
|
|
133
|
+
fieldGroupQualifier?: string;
|
|
134
|
+
field?: string;
|
|
135
|
+
}[];
|
|
136
|
+
};
|
|
137
|
+
export interface ButtonState {
|
|
138
|
+
visible: boolean;
|
|
139
|
+
/**
|
|
140
|
+
* - true: Button is enabled and can be clicked
|
|
141
|
+
* - false: Button is disabled
|
|
142
|
+
* - 'dynamic': The state is controlled by a dynamic path annotation (e.g., Path="__EntityControl/Deletable")
|
|
143
|
+
*/
|
|
144
|
+
enabled: boolean | 'dynamic';
|
|
145
|
+
dynamicPath?: string;
|
|
146
|
+
}
|
|
147
|
+
export interface ButtonVisibilityResult {
|
|
148
|
+
/**
|
|
149
|
+
* State of the Create button based on Capabilities.InsertRestrictions annotation.
|
|
150
|
+
*/
|
|
151
|
+
create: ButtonState;
|
|
152
|
+
/**
|
|
153
|
+
* State of the Delete button based on Capabilities.DeleteRestrictions annotation.
|
|
154
|
+
*/
|
|
155
|
+
delete: ButtonState;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Result interface for action button checks.
|
|
159
|
+
*/
|
|
160
|
+
export interface ActionButtonsResult {
|
|
161
|
+
/**
|
|
162
|
+
* List of action buttons found in the UI.LineItem annotation.
|
|
163
|
+
*/
|
|
164
|
+
actions: ActionButtonState[];
|
|
165
|
+
/**
|
|
166
|
+
* The entity type name that these actions belong to.
|
|
167
|
+
*/
|
|
168
|
+
entityType: string;
|
|
169
|
+
}
|
|
69
170
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Logger } from '@sap-ux/logger';
|
|
2
|
+
import type { TreeAggregations, TreeModel } from '@sap/ux-specification/dist/types/src/parser';
|
|
3
|
+
import type { ActionButtonsResult, ActionButtonState, ButtonState, ButtonVisibilityResult, ListReportFeatures } from '../types';
|
|
4
|
+
import type { PageWithModelV4 } from '@sap/ux-specification/dist/types/src/parser/application';
|
|
5
|
+
/**
|
|
6
|
+
* Builds a button state object from button visibility result.
|
|
7
|
+
*
|
|
8
|
+
* @param buttonState - The button state from visibility check
|
|
9
|
+
* @returns Button state object with visible, enabled, and optional dynamicPath properties
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildButtonState(buttonState?: ButtonState): {
|
|
12
|
+
visible: boolean;
|
|
13
|
+
enabled?: boolean | 'dynamic';
|
|
14
|
+
dynamicPath?: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Safely checks button visibility with error handling.
|
|
18
|
+
*
|
|
19
|
+
* @param metadata - The OData metadata XML content
|
|
20
|
+
* @param entitySetName - The name of the entity set
|
|
21
|
+
* @param log - Optional logger instance
|
|
22
|
+
* @returns Button visibility result or undefined if error occurs
|
|
23
|
+
*/
|
|
24
|
+
export declare function safeCheckButtonVisibility(metadata: string, entitySetName: string, log?: Logger): ButtonVisibilityResult | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Safely checks action button states with error handling.
|
|
27
|
+
*
|
|
28
|
+
* @param metadata - The OData metadata XML content
|
|
29
|
+
* @param entitySetName - The name of the entity set
|
|
30
|
+
* @param actionNames - List of action names to check
|
|
31
|
+
* @param log - Optional logger instance
|
|
32
|
+
* @returns Array of action button states or empty array if error occurs
|
|
33
|
+
*/
|
|
34
|
+
export declare function safeCheckActionButtonStates(metadata: string, entitySetName: string, actionNames: string[], log?: Logger): ActionButtonState[];
|
|
35
|
+
/**
|
|
36
|
+
* Gets List Report features from the page model using ux-specification.
|
|
37
|
+
*
|
|
38
|
+
* @param listReportPage - the List Report page containing the tree model with feature definitions
|
|
39
|
+
* @param log - optional logger instance
|
|
40
|
+
* @param metadata - optional metadata for the OPA test generation
|
|
41
|
+
* @returns feature data extracted from the List Report page model
|
|
42
|
+
*/
|
|
43
|
+
export declare function getListReportFeatures(listReportPage: PageWithModelV4, log?: Logger, metadata?: string): ListReportFeatures;
|
|
44
|
+
/**
|
|
45
|
+
* Retrieves toolbar action definitions from the given tree model.
|
|
46
|
+
*
|
|
47
|
+
* @param pageModel - The tree model containing toolbar definitions.
|
|
48
|
+
* @returns The toolbar actions aggregation object.
|
|
49
|
+
*/
|
|
50
|
+
export declare function getToolBarActions(pageModel: TreeModel): TreeAggregations;
|
|
51
|
+
/**
|
|
52
|
+
* Checks the visibility and enabled state of create and delete buttons for a given entity set
|
|
53
|
+
* by analyzing OData Capabilities annotations in the metadata.
|
|
54
|
+
*
|
|
55
|
+
* @param metadataXml The OData metadata XML content as a string
|
|
56
|
+
* @param entitySetName The name of the entity set to check
|
|
57
|
+
* @returns ButtonVisibilityResult containing the state of create and delete buttons
|
|
58
|
+
* @throws {Error} If metadata cannot be parsed or entity set is not found
|
|
59
|
+
*/
|
|
60
|
+
export declare function checkButtonVisibility(metadataXml: string, entitySetName: string): ButtonVisibilityResult;
|
|
61
|
+
/**
|
|
62
|
+
* Retrieves filter field names from the page model using ux-specification.
|
|
63
|
+
*
|
|
64
|
+
* @param pageModel - the tree model containing filter bar definitions
|
|
65
|
+
* @param log - optional logger instance
|
|
66
|
+
* @returns - an array of filter field names
|
|
67
|
+
*/
|
|
68
|
+
export declare function getFilterFieldNames(pageModel: TreeModel, log?: Logger): string[];
|
|
69
|
+
/**
|
|
70
|
+
* Checks the state of action buttons defined in UI.LineItem annotations for a given entity set.
|
|
71
|
+
*
|
|
72
|
+
* @param metadataXml The OData metadata XML content as a string
|
|
73
|
+
* @param entitySetName The name of the entity set to check
|
|
74
|
+
* @param actionNames Optional list of action names to filter (e.g., ['Check', 'deductDiscount']). If not provided, returns all actions.
|
|
75
|
+
* @returns ActionButtonsResult containing the list of action buttons and their states
|
|
76
|
+
* @throws {Error} If metadata cannot be parsed or entity set is not found
|
|
77
|
+
*/
|
|
78
|
+
export declare function checkActionButtonStates(metadataXml: string, entitySetName: string, actionNames?: string[]): ActionButtonsResult;
|
|
79
|
+
/**
|
|
80
|
+
* Retrieves toolbar action names from the page model using ux-specification.
|
|
81
|
+
*
|
|
82
|
+
* @param pageModel - the tree model containing toolbar definitions
|
|
83
|
+
* @param log - optional logger instance
|
|
84
|
+
* @returns - an array of toolbar action names
|
|
85
|
+
*/
|
|
86
|
+
export declare function getToolBarActionNames(pageModel: TreeModel, log?: Logger): string[];
|
|
87
|
+
/**
|
|
88
|
+
* Retrieves toolbar action items from the given toolbar actions aggregation.
|
|
89
|
+
*
|
|
90
|
+
* @param toolBarActionsAgg - The toolbar actions aggregation containing action definitions.
|
|
91
|
+
* @returns An array of toolbar action descriptions.
|
|
92
|
+
*/
|
|
93
|
+
export declare function getToolBarActionItems(toolBarActionsAgg: TreeAggregations): string[];
|
|
94
|
+
//# sourceMappingURL=listReportUtils.d.ts.map
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildButtonState = buildButtonState;
|
|
4
|
+
exports.safeCheckButtonVisibility = safeCheckButtonVisibility;
|
|
5
|
+
exports.safeCheckActionButtonStates = safeCheckActionButtonStates;
|
|
6
|
+
exports.getListReportFeatures = getListReportFeatures;
|
|
7
|
+
exports.getToolBarActions = getToolBarActions;
|
|
8
|
+
exports.checkButtonVisibility = checkButtonVisibility;
|
|
9
|
+
exports.getFilterFieldNames = getFilterFieldNames;
|
|
10
|
+
exports.checkActionButtonStates = checkActionButtonStates;
|
|
11
|
+
exports.getToolBarActionNames = getToolBarActionNames;
|
|
12
|
+
exports.getToolBarActionItems = getToolBarActionItems;
|
|
13
|
+
const modelUtils_1 = require("./modelUtils");
|
|
14
|
+
const edmx_parser_1 = require("@sap-ux/edmx-parser");
|
|
15
|
+
const annotation_converter_1 = require("@sap-ux/annotation-converter");
|
|
16
|
+
/**
|
|
17
|
+
* Builds a button state object from button visibility result.
|
|
18
|
+
*
|
|
19
|
+
* @param buttonState - The button state from visibility check
|
|
20
|
+
* @returns Button state object with visible, enabled, and optional dynamicPath properties
|
|
21
|
+
*/
|
|
22
|
+
function buildButtonState(buttonState) {
|
|
23
|
+
return {
|
|
24
|
+
visible: !!buttonState?.visible,
|
|
25
|
+
enabled: buttonState?.enabled,
|
|
26
|
+
dynamicPath: buttonState?.enabled === 'dynamic' ? buttonState.dynamicPath : undefined
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Safely checks button visibility with error handling.
|
|
31
|
+
*
|
|
32
|
+
* @param metadata - The OData metadata XML content
|
|
33
|
+
* @param entitySetName - The name of the entity set
|
|
34
|
+
* @param log - Optional logger instance
|
|
35
|
+
* @returns Button visibility result or undefined if error occurs
|
|
36
|
+
*/
|
|
37
|
+
function safeCheckButtonVisibility(metadata, entitySetName, log) {
|
|
38
|
+
try {
|
|
39
|
+
return checkButtonVisibility(metadata, entitySetName);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
log?.debug(`Failed to check button visibility: ${error instanceof Error ? error.message : String(error)}`);
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Safely checks action button states with error handling.
|
|
48
|
+
*
|
|
49
|
+
* @param metadata - The OData metadata XML content
|
|
50
|
+
* @param entitySetName - The name of the entity set
|
|
51
|
+
* @param actionNames - List of action names to check
|
|
52
|
+
* @param log - Optional logger instance
|
|
53
|
+
* @returns Array of action button states or empty array if error occurs
|
|
54
|
+
*/
|
|
55
|
+
function safeCheckActionButtonStates(metadata, entitySetName, actionNames, log) {
|
|
56
|
+
try {
|
|
57
|
+
return checkActionButtonStates(metadata, entitySetName, actionNames).actions;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
log?.debug(`Failed to check action button states: ${error instanceof Error ? error.message : String(error)}`);
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Gets List Report features from the page model using ux-specification.
|
|
66
|
+
*
|
|
67
|
+
* @param listReportPage - the List Report page containing the tree model with feature definitions
|
|
68
|
+
* @param log - optional logger instance
|
|
69
|
+
* @param metadata - optional metadata for the OPA test generation
|
|
70
|
+
* @returns feature data extracted from the List Report page model
|
|
71
|
+
*/
|
|
72
|
+
function getListReportFeatures(listReportPage, log, metadata) {
|
|
73
|
+
const buttonVisibility = metadata && listReportPage.entitySet
|
|
74
|
+
? safeCheckButtonVisibility(metadata, listReportPage.entitySet, log)
|
|
75
|
+
: undefined;
|
|
76
|
+
const toolbarActions = getToolBarActionNames(listReportPage.model, log);
|
|
77
|
+
return {
|
|
78
|
+
name: listReportPage.name,
|
|
79
|
+
createButton: buildButtonState(buttonVisibility?.create),
|
|
80
|
+
deleteButton: buildButtonState(buttonVisibility?.delete),
|
|
81
|
+
filterBarItems: getFilterFieldNames(listReportPage.model, log),
|
|
82
|
+
tableColumns: (0, modelUtils_1.getTableColumnData)(listReportPage.model, log),
|
|
83
|
+
toolBarActions: metadata && listReportPage.entitySet
|
|
84
|
+
? safeCheckActionButtonStates(metadata, listReportPage.entitySet, toolbarActions, log)
|
|
85
|
+
: []
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Retrieves toolbar action definitions from the given tree model.
|
|
90
|
+
*
|
|
91
|
+
* @param pageModel - The tree model containing toolbar definitions.
|
|
92
|
+
* @returns The toolbar actions aggregation object.
|
|
93
|
+
*/
|
|
94
|
+
function getToolBarActions(pageModel) {
|
|
95
|
+
const table = (0, modelUtils_1.getAggregations)(pageModel.root)['table'];
|
|
96
|
+
const tableAggregations = (0, modelUtils_1.getAggregations)(table);
|
|
97
|
+
const toolBar = tableAggregations['toolBar'];
|
|
98
|
+
const toolBarAggregations = (0, modelUtils_1.getAggregations)(toolBar);
|
|
99
|
+
const actions = toolBarAggregations['actions'];
|
|
100
|
+
const actionAggregations = (0, modelUtils_1.getAggregations)(actions);
|
|
101
|
+
return actionAggregations;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Checks the visibility and enabled state of create and delete buttons for a given entity set
|
|
105
|
+
* by analyzing OData Capabilities annotations in the metadata.
|
|
106
|
+
*
|
|
107
|
+
* @param metadataXml The OData metadata XML content as a string
|
|
108
|
+
* @param entitySetName The name of the entity set to check
|
|
109
|
+
* @returns ButtonVisibilityResult containing the state of create and delete buttons
|
|
110
|
+
* @throws {Error} If metadata cannot be parsed or entity set is not found
|
|
111
|
+
*/
|
|
112
|
+
function checkButtonVisibility(metadataXml, entitySetName) {
|
|
113
|
+
try {
|
|
114
|
+
const convertedMetadata = (0, annotation_converter_1.convert)((0, edmx_parser_1.parse)(metadataXml));
|
|
115
|
+
const entitySet = convertedMetadata.entitySets.find((es) => es.name === entitySetName);
|
|
116
|
+
if (!entitySet) {
|
|
117
|
+
throw new Error(`Entity set '${entitySetName}' not found in metadata`);
|
|
118
|
+
}
|
|
119
|
+
const insertRestrictions = entitySet.annotations?.Capabilities?.InsertRestrictions;
|
|
120
|
+
const deleteRestrictions = entitySet.annotations?.Capabilities?.DeleteRestrictions;
|
|
121
|
+
return {
|
|
122
|
+
create: analyzeRestriction(insertRestrictions, 'Insertable'),
|
|
123
|
+
delete: analyzeRestriction(deleteRestrictions, 'Deletable')
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
128
|
+
throw new Error(`Failed to analyze button visibility: ${errorMessage}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Retrieves filter field names from the page model using ux-specification.
|
|
133
|
+
*
|
|
134
|
+
* @param pageModel - the tree model containing filter bar definitions
|
|
135
|
+
* @param log - optional logger instance
|
|
136
|
+
* @returns - an array of filter field names
|
|
137
|
+
*/
|
|
138
|
+
function getFilterFieldNames(pageModel, log) {
|
|
139
|
+
let filterBarItems = [];
|
|
140
|
+
try {
|
|
141
|
+
const filterBarAggregations = (0, modelUtils_1.getFilterFields)(pageModel);
|
|
142
|
+
filterBarItems = (0, modelUtils_1.getSelectionFieldItems)(filterBarAggregations);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
log?.debug(error);
|
|
146
|
+
}
|
|
147
|
+
if (!filterBarItems?.length) {
|
|
148
|
+
log?.warn('Unable to extract filter fields from project model using specification. No filter field tests will be generated.');
|
|
149
|
+
}
|
|
150
|
+
return filterBarItems;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Analyzes a capability restriction annotation to determine button state.
|
|
154
|
+
*
|
|
155
|
+
* @param restriction The restriction annotation object (InsertRestrictions or DeleteRestrictions)
|
|
156
|
+
* @param propertyName The property name to check ('Insertable' or 'Deletable')
|
|
157
|
+
* @returns ButtonState for the button
|
|
158
|
+
*/
|
|
159
|
+
function analyzeRestriction(restriction, propertyName) {
|
|
160
|
+
const defaultState = { visible: true, enabled: true };
|
|
161
|
+
if (!restriction) {
|
|
162
|
+
return defaultState;
|
|
163
|
+
}
|
|
164
|
+
const value = restriction[propertyName];
|
|
165
|
+
if (value === undefined || value === null) {
|
|
166
|
+
return defaultState;
|
|
167
|
+
}
|
|
168
|
+
if (typeof value === 'boolean') {
|
|
169
|
+
return { visible: value, enabled: value };
|
|
170
|
+
}
|
|
171
|
+
if (typeof value === 'object' && value !== null) {
|
|
172
|
+
const path = value.$Path ?? value.path;
|
|
173
|
+
if (path) {
|
|
174
|
+
return { visible: true, enabled: 'dynamic', dynamicPath: path };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return defaultState;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Checks the state of action buttons defined in UI.LineItem annotations for a given entity set.
|
|
181
|
+
*
|
|
182
|
+
* @param metadataXml The OData metadata XML content as a string
|
|
183
|
+
* @param entitySetName The name of the entity set to check
|
|
184
|
+
* @param actionNames Optional list of action names to filter (e.g., ['Check', 'deductDiscount']). If not provided, returns all actions.
|
|
185
|
+
* @returns ActionButtonsResult containing the list of action buttons and their states
|
|
186
|
+
* @throws {Error} If metadata cannot be parsed or entity set is not found
|
|
187
|
+
*/
|
|
188
|
+
function checkActionButtonStates(metadataXml, entitySetName, actionNames) {
|
|
189
|
+
try {
|
|
190
|
+
const convertedMetadata = (0, annotation_converter_1.convert)((0, edmx_parser_1.parse)(metadataXml));
|
|
191
|
+
const entitySet = convertedMetadata.entitySets.find((es) => es.name === entitySetName);
|
|
192
|
+
if (!entitySet) {
|
|
193
|
+
throw new Error(`Entity set '${entitySetName}' not found in metadata`);
|
|
194
|
+
}
|
|
195
|
+
const entityType = entitySet.entityType;
|
|
196
|
+
if (!entityType) {
|
|
197
|
+
throw new Error(`Entity type not found for entity set '${entitySetName}'`);
|
|
198
|
+
}
|
|
199
|
+
const lineItemAnnotation = entityType.annotations?.UI?.LineItem;
|
|
200
|
+
if (!lineItemAnnotation || !Array.isArray(lineItemAnnotation)) {
|
|
201
|
+
return { actions: [], entityType: entityType.name };
|
|
202
|
+
}
|
|
203
|
+
const dataFieldForActions = lineItemAnnotation.filter((item) => item.$Type === 'com.sap.vocabularies.UI.v1.DataFieldForAction');
|
|
204
|
+
const actions = actionNames
|
|
205
|
+
? findActionStates(dataFieldForActions, actionNames, convertedMetadata, entityType.name)
|
|
206
|
+
: extractAllActionStates(dataFieldForActions, convertedMetadata, entityType.name);
|
|
207
|
+
return { actions, entityType: entityType.name };
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
211
|
+
throw new Error(`Failed to analyze action button states: ${errorMessage}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Finds action states for a specific list of action names.
|
|
216
|
+
*
|
|
217
|
+
* @param dataFieldForActions List of DataFieldForAction items from UI.LineItem
|
|
218
|
+
* @param actionNames List of action names to find
|
|
219
|
+
* @param metadata The converted metadata
|
|
220
|
+
* @param entityTypeName The entity type name
|
|
221
|
+
* @returns List of action button states for the specified actions
|
|
222
|
+
*/
|
|
223
|
+
function findActionStates(dataFieldForActions, actionNames, metadata, entityTypeName) {
|
|
224
|
+
const actionStates = [];
|
|
225
|
+
for (const actionName of actionNames) {
|
|
226
|
+
const item = dataFieldForActions.find((dfa) => {
|
|
227
|
+
const actionMethod = extractActionMethodName(dfa.Action || '');
|
|
228
|
+
return actionMethod === actionName || dfa.Label === actionName;
|
|
229
|
+
});
|
|
230
|
+
if (item) {
|
|
231
|
+
actionStates.push(buildActionButtonState(item, metadata, entityTypeName));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return actionStates;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Extracts action states for all DataFieldForAction items.
|
|
238
|
+
*
|
|
239
|
+
* @param dataFieldForActions List of DataFieldForAction items from UI.LineItem
|
|
240
|
+
* @param metadata The converted metadata
|
|
241
|
+
* @param entityTypeName The entity type name
|
|
242
|
+
* @returns List of all action button states
|
|
243
|
+
*/
|
|
244
|
+
function extractAllActionStates(dataFieldForActions, metadata, entityTypeName) {
|
|
245
|
+
return dataFieldForActions.map((item) => buildActionButtonState(item, metadata, entityTypeName));
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Builds an ActionButtonState object from a DataFieldForAction item.
|
|
249
|
+
*
|
|
250
|
+
* @param item The DataFieldForAction item
|
|
251
|
+
* @param metadata The converted metadata
|
|
252
|
+
* @param entityTypeName The entity type name
|
|
253
|
+
* @returns ActionButtonState for the action
|
|
254
|
+
*/
|
|
255
|
+
function buildActionButtonState(item, metadata, entityTypeName) {
|
|
256
|
+
const actionMethod = extractActionMethodName(item.Action || '');
|
|
257
|
+
const operationAvailable = findOperationAvailableAnnotation(metadata, entityTypeName, actionMethod);
|
|
258
|
+
const { enabled, dynamicPath } = analyzeOperationAvailability(operationAvailable);
|
|
259
|
+
return {
|
|
260
|
+
label: item.Label || '',
|
|
261
|
+
action: item.Action || '',
|
|
262
|
+
visible: true,
|
|
263
|
+
enabled,
|
|
264
|
+
dynamicPath,
|
|
265
|
+
invocationGrouping: item.InvocationGrouping ? extractEnumMemberValue(item.InvocationGrouping) : undefined
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Analyzes Core.OperationAvailable annotation to determine action availability.
|
|
270
|
+
*
|
|
271
|
+
* @param operationAvailable The OperationAvailable annotation value
|
|
272
|
+
* @returns Object containing enabled state and optional dynamic path
|
|
273
|
+
*/
|
|
274
|
+
function analyzeOperationAvailability(operationAvailable) {
|
|
275
|
+
if (operationAvailable === undefined) {
|
|
276
|
+
return { enabled: true };
|
|
277
|
+
}
|
|
278
|
+
if (typeof operationAvailable === 'boolean') {
|
|
279
|
+
return { enabled: operationAvailable };
|
|
280
|
+
}
|
|
281
|
+
if (typeof operationAvailable === 'object' && operationAvailable !== null) {
|
|
282
|
+
const path = operationAvailable.$Path ?? operationAvailable.path;
|
|
283
|
+
if (path) {
|
|
284
|
+
return { enabled: 'dynamic', dynamicPath: path };
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return { enabled: true };
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Extracts the action method name from a fully qualified action string.
|
|
291
|
+
*
|
|
292
|
+
* @param actionName The fully qualified action name
|
|
293
|
+
* @returns The action method name
|
|
294
|
+
*/
|
|
295
|
+
function extractActionMethodName(actionName) {
|
|
296
|
+
const match = /\.([^.()]+)\(/.exec(actionName);
|
|
297
|
+
if (match?.[1]) {
|
|
298
|
+
return match[1];
|
|
299
|
+
}
|
|
300
|
+
const lastDotIndex = actionName.lastIndexOf('.');
|
|
301
|
+
const parenIndex = actionName.indexOf('(');
|
|
302
|
+
if (lastDotIndex !== -1 && parenIndex !== -1) {
|
|
303
|
+
return actionName.substring(lastDotIndex + 1, parenIndex);
|
|
304
|
+
}
|
|
305
|
+
return actionName;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Finds the Core.OperationAvailable annotation for a specific action.
|
|
309
|
+
*
|
|
310
|
+
* @param metadata The converted metadata
|
|
311
|
+
* @param entityTypeName The entity type name
|
|
312
|
+
* @param actionMethodName The action method name
|
|
313
|
+
* @returns The OperationAvailable annotation value or undefined if not found
|
|
314
|
+
*/
|
|
315
|
+
function findOperationAvailableAnnotation(metadata, entityTypeName, actionMethodName) {
|
|
316
|
+
if (metadata.actions) {
|
|
317
|
+
const action = metadata.actions.find((a) => a.name === actionMethodName || a.fullyQualifiedName?.includes(`.${actionMethodName}(`));
|
|
318
|
+
if (action?.annotations?.Core?.OperationAvailable !== undefined) {
|
|
319
|
+
return action.annotations.Core.OperationAvailable;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (metadata.entityContainer?.annotations) {
|
|
323
|
+
const annotations = metadata.entityContainer.annotations;
|
|
324
|
+
const matchingKey = Object.keys(annotations).find((key) => key.includes(actionMethodName));
|
|
325
|
+
if (matchingKey && annotations[matchingKey]?.Core?.OperationAvailable !== undefined) {
|
|
326
|
+
return annotations[matchingKey].Core.OperationAvailable;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Extracts the enum member value from an annotation.
|
|
333
|
+
*
|
|
334
|
+
* @param enumValue The enum value object
|
|
335
|
+
* @returns The extracted enum value string
|
|
336
|
+
*/
|
|
337
|
+
function extractEnumMemberValue(enumValue) {
|
|
338
|
+
if (typeof enumValue === 'string') {
|
|
339
|
+
return enumValue;
|
|
340
|
+
}
|
|
341
|
+
if (enumValue?.$EnumMember) {
|
|
342
|
+
const parts = enumValue.$EnumMember.split('/');
|
|
343
|
+
return parts[1] ?? enumValue.$EnumMember;
|
|
344
|
+
}
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Retrieves toolbar action names from the page model using ux-specification.
|
|
349
|
+
*
|
|
350
|
+
* @param pageModel - the tree model containing toolbar definitions
|
|
351
|
+
* @param log - optional logger instance
|
|
352
|
+
* @returns - an array of toolbar action names
|
|
353
|
+
*/
|
|
354
|
+
function getToolBarActionNames(pageModel, log) {
|
|
355
|
+
let toolBarActions = [];
|
|
356
|
+
try {
|
|
357
|
+
const toolbarActions = getToolBarActions(pageModel);
|
|
358
|
+
toolBarActions = getToolBarActionItems(toolbarActions);
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
log?.debug(error);
|
|
362
|
+
}
|
|
363
|
+
if (!toolBarActions?.length) {
|
|
364
|
+
log?.warn('Unable to extract toolbar actions from project model using specification. No toolbar action tests will be generated.');
|
|
365
|
+
}
|
|
366
|
+
return toolBarActions;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Retrieves toolbar action items from the given toolbar actions aggregation.
|
|
370
|
+
*
|
|
371
|
+
* @param toolBarActionsAgg - The toolbar actions aggregation containing action definitions.
|
|
372
|
+
* @returns An array of toolbar action descriptions.
|
|
373
|
+
*/
|
|
374
|
+
function getToolBarActionItems(toolBarActionsAgg) {
|
|
375
|
+
return extractItemDescriptions(toolBarActionsAgg);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Extracts item descriptions from tree aggregations.
|
|
379
|
+
*
|
|
380
|
+
* @param aggregations - The tree aggregations containing item definitions
|
|
381
|
+
* @returns An array of item descriptions
|
|
382
|
+
*/
|
|
383
|
+
function extractItemDescriptions(aggregations) {
|
|
384
|
+
if (aggregations && typeof aggregations === 'object') {
|
|
385
|
+
return Object.keys(aggregations).map((key) => aggregations[key].description);
|
|
386
|
+
}
|
|
387
|
+
return [];
|
|
388
|
+
}
|
|
389
|
+
//# sourceMappingURL=listReportUtils.js.map
|