@sap-ux/ui5-test-writer 0.7.88 → 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.
@@ -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 { filterBarItems, tableColumns } = await (0, modelUtils_1.getFeatureData)(basePath, editor, log);
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 FeatureData = {
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