UNPKG

@sap/generator-fiori

Version:

Create an SAPUI5 application using SAP Fiori elements or a freestyle approach

1,143 lines (1,122 loc) 119 kB
"use strict"; exports.id = 2944; exports.ids = [2944]; exports.modules = { /***/ 6435 (__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) { // EXPORTS __webpack_require__.d(__webpack_exports__, { Vo: () => (/* reexport */ addVirtualTestConfig), kJ: () => (/* reexport */ generateFreestyleOPAFiles), fY: () => (/* reexport */ generateOPAFiles) }); // EXTERNAL MODULE: external "node:path" var external_node_path_ = __webpack_require__(76760); // EXTERNAL MODULE: external "node:url" var external_node_url_ = __webpack_require__(73136); // EXTERNAL MODULE: external "node:fs" var external_node_fs_ = __webpack_require__(73024); // EXTERNAL MODULE: ../../../node_modules/mem-fs/index.js var mem_fs = __webpack_require__(64812); // EXTERNAL MODULE: ../../../node_modules/mem-fs-editor/lib/index.js var lib = __webpack_require__(90718); ;// ../../../node_modules/@sap-ux/ui5-test-writer/dist/types.js const DotFileExtension = { JS: '.js', TS: '.ts' }; const SupportedPageTypes = { 'sap.fe.templates.ListReport': 'ListReport', 'sap.fe.templates.ObjectPage': 'ObjectPage', 'sap.fe.core.fpm': 'FPM' }; /** * General validation error thrown if app config options contain invalid combinations */ class ValidationError extends Error { /** * ValidationError constructor. * * @param message - the error message */ constructor(message) { super(`Validation error: ${message}`); this.name = this.constructor.name; } } //# sourceMappingURL=types.js.map // EXTERNAL MODULE: ../../../node_modules/i18next/dist/cjs/i18next.js var i18next = __webpack_require__(68801); ;// ../../../node_modules/@sap-ux/ui5-test-writer/dist/translations/ui5-test-writer.i18n.json const ui5_test_writer_i18n_namespaceObject = /*#__PURE__*/JSON.parse('{"error":{"cannotReadManifest":"Cannot read the `manifest.json` file: {{ filePath }}.","cannotReadAppID":"Cannot read `appID` in the `manifest.json` file.","badApplicationType":"Cannot determine the application type from the `manifest.json` file or it uses an unsupported type.","cannotGeneratePageFile":"Cannot generate the page file for target: {{ targetKey }}.","errorCopyingFreestyleTestTemplates":"An error occurred when copying freestyle test templates: {{ error }}","errorWritingTsConfig":"An error occurred when writing the `tsconfig.json` file: {{ error }}"}}'); ;// ../../../node_modules/@sap-ux/ui5-test-writer/dist/i18n.js const NS = 'ui5-test-writer'; const i18n = i18next.createInstance(); /** * Initialize i18next with the translations for this module. */ async function initI18n() { await i18n.init({ resources: { en: { [NS]: ui5_test_writer_i18n_namespaceObject } }, lng: 'en', fallbackLng: 'en', defaultNS: NS, ns: [NS], showSupportNotice: false }); } /** * Helper function facading the call to i18next. * * @param key i18n key * @param options additional options * @returns {string} localized string stored for the given key */ function t(key, options) { return i18n.t(key, options); } void initI18n().catch(() => undefined); //# sourceMappingURL=i18n.js.map // EXTERNAL MODULE: ../../../node_modules/@sap-ux/project-access/dist/index.js + 26 modules var dist = __webpack_require__(7083); // EXTERNAL MODULE: ../../../node_modules/@sap-ux/ui5-test-writer/node_modules/@sap/ux-specification/dist/types/src/common/index.js var common = __webpack_require__(97242); ;// ../../../node_modules/@sap-ux/ui5-test-writer/dist/utils/tableUtils.js /** * Gets the identifier of a column for OPA5 tests. * Custom columns use the 'Key' entry; standard columns use the 'Value' entry from the schema keys. * * @param column - column item from ux specification * @returns identifier of the column for OPA5 tests; undefined if no matching key entry is found */ function getColumnIdentifier(column) { const key = column.custom ? 'Key' : 'Value'; return column.schema.keys.find((k) => k.name === key)?.value; } /** * Transforms column aggregations from the ux specification model into a map of columns for OPA5 tests. * Each column entry includes the column header label for display verification. * * @param columnAggregations - column aggregations from the ux specification model * @returns a map of column identifiers to column state objects for use with iCheckColumns() */ function transformTableColumns(columnAggregations) { const columns = {}; Object.values(columnAggregations).forEach((column, index) => { const id = getColumnIdentifier(column) ?? String(index); const state = {}; if (column.description) { state['header'] = column.description; } columns[id] = state; }); return columns; } /** * Extracts table column data from a spec model node that contains a 'table' aggregation. * Covers both page-level nodes (List Report, FPM) via their root and section-level nodes * (Object Page body sections) — both are TreeAggregation nodes that expose a 'table' aggregation. * * @param node - tree aggregation node that exposes a 'table' aggregation * @returns a map of column identifiers to column state objects for use with iCheckColumns() */ function extractTableColumnsFromNode(node) { const tableAggregation = getAggregations(node)['table']; if (!tableAggregation) { return {}; } const columnsAggregation = getAggregations(tableAggregation)['columns']; if (!columnsAggregation) { return {}; } const columnItems = getAggregations(columnsAggregation); return transformTableColumns(columnItems); } //# sourceMappingURL=tableUtils.js.map // EXTERNAL MODULE: ../../../node_modules/@sap-ux/ui5-test-writer/node_modules/@sap/ux-specification/dist/types/src/common/page.js var common_page = __webpack_require__(30911); // EXTERNAL MODULE: ../../../node_modules/@sap-ux/edmx-parser/dist/index.js var edmx_parser_dist = __webpack_require__(39933); // EXTERNAL MODULE: ../../../node_modules/@sap-ux/annotation-converter/dist/index.js var annotation_converter_dist = __webpack_require__(91125); ;// ../../../node_modules/@sap-ux/ui5-test-writer/dist/utils/actionUtils.js const DATA_FIELD_FOR_ACTION = 'DataFieldForAction'; /** * Extracts the action method name from a fully qualified action string. * * @param actionName The fully qualified action name * @returns The action method name */ function extractActionMethodName(actionName) { const match = /\.([^.()]+)\(/.exec(actionName); if (match?.[1]) { return match[1]; } const lastDotIndex = actionName.lastIndexOf('.'); const parenIndex = actionName.indexOf('('); if (lastDotIndex >= 0 && parenIndex >= 0 && parenIndex > lastDotIndex) { return actionName.substring(lastDotIndex + 1, parenIndex); } // Handle namespace-qualified name without parentheses (spec model key format: "namespace.Method") if (lastDotIndex >= 0) { return actionName.substring(lastDotIndex + 1); } return actionName; } /** * Finds the Core.OperationAvailable annotation for a specific action. * * @param metadata The converted metadata * @param actionMethodName The action method name * @returns The OperationAvailable annotation value or undefined if not found */ function findOperationAvailableAnnotation(metadata, actionMethodName) { if (metadata.actions) { const foundAction = metadata.actions.find((action) => action.name === actionMethodName || action.fullyQualifiedName?.includes(`.${actionMethodName}(`)); if (foundAction?.annotations?.Core?.OperationAvailable !== undefined) { return foundAction.annotations.Core.OperationAvailable; } } if (metadata.entityContainer?.annotations) { const annotations = metadata.entityContainer.annotations; const matchingKey = Object.keys(annotations).find((key) => key === actionMethodName || key.endsWith(`.${actionMethodName}`)); if (matchingKey && annotations[matchingKey]?.Core?.OperationAvailable !== undefined) { return annotations[matchingKey].Core.OperationAvailable; } } return undefined; } /** * Analyzes Core.OperationAvailable annotation to determine action availability. * Single-entity bound actions (requiring row selection) are disabled by default when no annotation is present. * * @param operationAvailable The OperationAvailable annotation value * @param isEntityBound Whether the action is bound to a single entity (requires row selection to enable) * @returns Object containing enabled state and optional dynamic path */ function analyzeOperationAvailability(operationAvailable, isEntityBound) { if (operationAvailable === undefined) { return { enabled: !isEntityBound }; } if (typeof operationAvailable === 'boolean') { return { enabled: operationAvailable }; } if (typeof operationAvailable === 'object' && operationAvailable !== null) { const pathRecord = operationAvailable; const path = pathRecord.$Path ?? pathRecord.path; if (path) { return { enabled: 'dynamic', dynamicPath: path }; } } return { enabled: true }; } /** * Extracts the enum member value from an annotation. * * @param enumValue The enum value object * @returns The extracted enum value string */ function extractEnumMemberValue(enumValue) { if (typeof enumValue === 'string') { return enumValue; } const enumRecord = enumValue; if (enumRecord?.$EnumMember) { const parts = enumRecord.$EnumMember.split('/'); return parts[1] ?? enumRecord.$EnumMember; } return undefined; } /** * Builds an ActionButtonState object from a DataFieldForAction annotation item. * * @param item The DataFieldForAction annotation item * @param metadata The converted metadata * @returns ActionButtonState for the action */ function buildActionButtonState(item, metadata) { const actionString = item.Action || ''; const actionMethod = extractActionMethodName(actionString); const operationAvailable = findOperationAvailableAnnotation(metadata, actionMethod); // Bound actions whose binding parameter is a single entity (not a collection) require // row selection to be invoked, so they are disabled by default (no row selected). // Collection-bound actions operate on the entity set and are always enabled. const actionTarget = item.ActionTarget; const isEntityBound = actionTarget?.isBound === true && actionTarget?.parameters?.[0]?.isCollection !== true; const { enabled, dynamicPath } = analyzeOperationAvailability(operationAvailable, isEntityBound); return { label: item.Label || '', action: actionString, visible: true, enabled, dynamicPath, invocationGrouping: item.InvocationGrouping ? extractEnumMemberValue(item.InvocationGrouping) : undefined }; } /** * Builds an ActionButtonState from a spec model aggregation key. * * Key format: "DataFieldForAction::<namespace>.<Method>::<namespace>.<EntityType>" * Example: "DataFieldForAction::com.example.Copy::com.example.POEntity". * * @param aggregationKey The spec model aggregation key for the action * @param label Display label from the spec model item description * @param convertedMetadata The converted OData metadata * @param schemaNamespace The OData schema namespace (used as service identifier) * @returns ActionButtonState or undefined if the key is not a DataFieldForAction key */ function buildActionStateFromSpecModelKey(aggregationKey, label, convertedMetadata, schemaNamespace) { const keyParts = aggregationKey.split('::'); if (keyParts[0] !== DATA_FIELD_FOR_ACTION || !keyParts[1]) { return undefined; } const actionFullName = keyParts[1]; // "namespace.Method" const actionMethod = extractActionMethodName(actionFullName); const actionDefinition = convertedMetadata.actions?.find((action) => action.name === actionMethod || action.fullyQualifiedName?.includes(`.${actionMethod}(`)); const firstParameter = actionDefinition?.parameters?.[0]; const isEntityBound = actionDefinition?.isBound === true && firstParameter?.isCollection !== true; const operationAvailable = findOperationAvailableAnnotation(convertedMetadata, actionMethod); const { enabled, dynamicPath } = analyzeOperationAvailability(operationAvailable, isEntityBound); return { label: label ?? '', action: actionMethod, service: schemaNamespace, unbound: !isEntityBound, visible: true, enabled, dynamicPath }; } /** * Analyzes a restriction value (Insertable, Deletable, or Updatable) to determine button state. * * @param value The annotation value — boolean, path object, or undefined * @returns ButtonState indicating visibility and enabled state */ function analyzeRestrictionValue(value) { const defaultState = { visible: true, enabled: true }; if (value === undefined || value === null) { return defaultState; } if (typeof value === 'boolean') { return { visible: value, enabled: value }; } if (typeof value === 'object') { const path = value.$Path ?? value.path; if (path) { return { visible: true, enabled: 'dynamic', dynamicPath: path }; } } return defaultState; } /** * Analyzes InsertRestrictions annotation to determine create button visibility and enabled state. * * @param restriction The InsertRestrictions annotation for the entity set * @returns ButtonState indicating visibility and enabled state based on the Insertable value */ function analyzeInsertRestrictions(restriction) { const value = restriction ? restriction['Insertable'] : undefined; return analyzeRestrictionValue(value); } /** * Analyzes DeleteRestrictions annotation to determine delete button visibility and enabled state. * * @param restriction The DeleteRestrictions annotation for the entity set * @returns ButtonState indicating visibility and enabled state based on the Deletable value */ function analyzeDeleteRestrictions(restriction) { const value = restriction ? restriction['Deletable'] : undefined; return analyzeRestrictionValue(value); } /** * Analyzes UpdateRestrictions annotation to determine edit button visibility and enabled state. * * @param restriction The UpdateRestrictions annotation for the entity set * @returns ButtonState indicating visibility and enabled state based on the Updatable value */ function analyzeUpdateRestrictions(restriction) { const value = restriction ? restriction['Updatable'] : undefined; return analyzeRestrictionValue(value); } /** * Checks the visibility and enabled state of create and delete buttons for a given entity set * by analyzing OData Capabilities annotations in the converted metadata. * * @param convertedMetadata The already-converted OData metadata * @param entitySetName The name of the entity set to check * @returns ButtonVisibilityResult containing the state of create and delete buttons * @throws {Error} If entity set is not found */ function checkButtonVisibilityFromMetadata(convertedMetadata, entitySetName) { const entitySet = convertedMetadata.entitySets.find((es) => es.name === entitySetName); if (!entitySet) { throw new Error(`Entity set '${entitySetName}' not found in metadata`); } const insertRestrictions = entitySet.annotations?.Capabilities?.InsertRestrictions; const deleteRestrictions = entitySet.annotations?.Capabilities?.DeleteRestrictions; return { create: analyzeInsertRestrictions(insertRestrictions), delete: analyzeDeleteRestrictions(deleteRestrictions) }; } /** * Checks the visibility and enabled state of create and delete buttons for a given entity set * by analyzing OData Capabilities annotations in the metadata. * * @param metadataXml The OData metadata XML content as a string * @param entitySetName The name of the entity set to check * @returns ButtonVisibilityResult containing the state of create and delete buttons * @throws {Error} If metadata cannot be parsed or entity set is not found */ function checkButtonVisibility(metadataXml, entitySetName) { try { return checkButtonVisibilityFromMetadata((0,annotation_converter_dist.convert)((0,edmx_parser_dist/* parse */.qg)(metadataXml)), entitySetName); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Failed to analyze button visibility: ${errorMessage}`); } } /** * Checks the visibility and enabled state of the edit button for a given entity set * by analyzing UpdateRestrictions in the metadata. * * @param metadataXml The OData metadata XML content as a string * @param entitySetName The name of the entity set to check * @returns ButtonState for the edit button * @throws {Error} If metadata cannot be parsed or entity set is not found */ function checkEditVisibility(metadataXml, entitySetName) { try { const convertedMetadata = (0,annotation_converter_dist.convert)((0,edmx_parser_dist/* parse */.qg)(metadataXml)); const entitySet = convertedMetadata.entitySets.find((es) => es.name === entitySetName); if (!entitySet) { throw new Error(`Entity set '${entitySetName}' not found in metadata`); } const updateRestrictions = entitySet.annotations?.Capabilities?.UpdateRestrictions; return analyzeUpdateRestrictions(updateRestrictions); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Failed to analyze edit visibility: ${errorMessage}`); } } /** * Safely checks button visibility from already-converted metadata, with error handling. * * @param convertedMetadata The already-converted OData metadata * @param entitySetName The name of the entity set * @param log Optional logger instance * @returns Button visibility result or undefined if error occurs */ function safeCheckButtonVisibilityFromMetadata(convertedMetadata, entitySetName, log) { try { return checkButtonVisibilityFromMetadata(convertedMetadata, entitySetName); } catch (error) { log?.debug(`Failed to check button visibility: ${error instanceof Error ? error.message : String(error)}`); return undefined; } } /** * Safely checks button visibility with error handling. * * @param metadata The OData metadata XML content * @param entitySetName The name of the entity set * @param log Optional logger instance * @returns Button visibility result or undefined if error occurs */ function safeCheckButtonVisibility(metadata, entitySetName, log) { try { return checkButtonVisibility(metadata, entitySetName); } catch (error) { log?.debug(`Failed to check button visibility: ${error instanceof Error ? error.message : String(error)}`); return undefined; } } /** * Safely checks edit button visibility with error handling. * * @param metadata The OData metadata XML content * @param entitySetName The name of the entity set * @param log Optional logger instance * @returns ButtonState for the edit button, or undefined if error occurs */ function safeCheckEditVisibility(metadata, entitySetName, log) { try { return checkEditVisibility(metadata, entitySetName); } catch (error) { log?.debug(`Failed to check edit visibility: ${error instanceof Error ? error.message : String(error)}`); return undefined; } } //# sourceMappingURL=actionUtils.js.map ;// ../../../node_modules/@sap-ux/ui5-test-writer/dist/utils/objectPageUtils.js /** * Extracts feature data for object pages from the application model. * * @param objectPages - the array of object pages extracted from the application model * @param listReportPageKey - the key of the List Report page in the application model, used to find navigation routes to object pages * @param log - optional logger instance * @param metadata - optional metadata for the OPA test generation * @returns a record of object page feature data */ async function getObjectPageFeatures(objectPages, listReportPageKey, log, metadata) { const objectPageFeatures = []; if (!objectPages || objectPages.length === 0) { log?.warn('Object Pages not found in application model. Dynamic tests will not be generated for Object Pages.'); return objectPageFeatures; } // attempt to get individual feature data for each object page const convertedMetadata = metadata ? (0,annotation_converter_dist.convert)((0,edmx_parser_dist/* parse */.qg)(metadata)) : undefined; const schemaNamespace = convertedMetadata?.namespace ?? ''; for (const objectPage of objectPages) { const pageFeatureData = {}; pageFeatureData.name = objectPage.name; pageFeatureData.navigationParents = getObjectPageNavigationParents(objectPage.name, objectPages, listReportPageKey); // extract header sections (facets) pageFeatureData.headerSections = extractObjectPageHeaderSectionsData(objectPage); // extract body sections (includes section-level actions and standard create/delete buttons) pageFeatureData.bodySections = extractObjectPageBodySectionsData(objectPage, convertedMetadata, schemaNamespace, metadata, log); // extract header-level actions pageFeatureData.headerActions = convertedMetadata ? extractHeaderActions(objectPage, convertedMetadata, schemaNamespace) : []; // determine edit button visibility from UpdateRestrictions on the OP entity set if (metadata && objectPage.entitySet) { pageFeatureData.editButton = safeCheckEditVisibility(metadata, objectPage.entitySet, log); } objectPageFeatures.push(pageFeatureData); } return objectPageFeatures; } /** * Retrieves all Object Page definitions from the given application model, as long as the page is reachable via standard navigation routes. * * @param applicationModel - The application model containing page definitions. * @returns An array of Object Page definitions. */ function getObjectPages(applicationModel) { const objectPages = []; for (const pageKey in applicationModel.pages) { const page = applicationModel.pages[pageKey]; if (page.pageType === common_page.PageTypeV4.ObjectPage) { page.name = pageKey; // store page key as name for later identification objectPages.push(page); } } return objectPages; } /** * Finds parent pages for the object page, and returns their identifiers. * * @param targetObjectPageKey - key of the target object page * @param objectPages - the array of object pages extracted from the application model * @param listReportPageKey - the key of the List Report page in the application model, used to find navigation routes to object pages * @returns navigation data including parent page identifiers */ function getObjectPageNavigationParents(targetObjectPageKey, objectPages, listReportPageKey) { const navigationParents = { parentLRName: listReportPageKey ?? '' // app is possibly malformed if no LR found }; objectPages.forEach((objectPage) => { const navigationRoutes = getNavigationRoutes(objectPage); const routeToTargetOP = navigationRoutes.find((nav) => nav.route === targetObjectPageKey); if (routeToTargetOP) { navigationParents.parentOPName = objectPage.name; navigationParents.parentOPTableSection = routeToTargetOP.identifier; } }); return navigationParents; } /** * Extracts header sections data from an object page model. * * @param objectPage - object page from the application model * @returns header sections data */ function extractObjectPageHeaderSectionsData(objectPage) { const headerSections = []; if (objectPage.model) { const headerAggregation = getAggregations(objectPage.model.root)['header']; const sectionsAggregation = getAggregations(headerAggregation)['sections']; const sections = getAggregations(sectionsAggregation); Object.values(sections).forEach((section) => { const facetId = getSectionIdentifier(section); if (!facetId) { // if no identifier can be found for the section, it is not possible to reliably identify it in tests, so skip it return; } const sectionData = { facetId: facetId, stashed: getSectionStashedFlag(section), custom: section.custom, microChart: isSectionMicroChart(section), form: isFormSection(section), // collection: false // TODO: find out how to identify collection facets title: section.title }; if (sectionData.form) { sectionData.fields = getHeaderSectionFormFields(section); } headerSections.push(sectionData); }); } return headerSections; } /** * Extracts body sections data from an object page model. * * @param objectPage - object page from the application model * @param convertedMetadata - optional converted OData metadata for action extraction * @param schemaNamespace - optional OData schema namespace used as service identifier in action assertions * @param metadata - optional raw metadata XML for resolving standard button visibility (Create/Delete) * @param log - optional logger instance * @returns body sections data including sub-sections */ function extractObjectPageBodySectionsData(objectPage, convertedMetadata, schemaNamespace, metadata, log) { const bodySections = []; if (objectPage.model) { const sectionsAggregation = getAggregations(objectPage.model.root)['sections']; const sections = getAggregations(sectionsAggregation); Object.entries(sections).forEach(([sectionKey, section]) => { const sectionId = getSectionIdentifier(section) ?? sectionKey; const subSections = extractBodySubSectionsData(section, sectionId); const navigationProperty = getNavigationPropertyFromKey(sectionKey); const sectionData = { id: sectionId, navigationProperty, isTable: !!section.isTable, custom: !!section.custom, order: section?.order ?? -1, fields: section.custom || section.isTable ? [] : extractFormFields(section), tableColumns: section.custom || !section.isTable ? {} : extractTableColumnsFromNode(section), subSections, actions: !section.custom && convertedMetadata && schemaNamespace ? extractSectionActions(section, convertedMetadata, schemaNamespace) : [] }; // For table sections, resolve Create/Delete visibility from target entity set if (section.isTable && navigationProperty && metadata && convertedMetadata) { const targetEntitySet = resolveNavigationTargetEntitySet(convertedMetadata, objectPage.entitySet, navigationProperty); if (targetEntitySet) { const buttonVisibility = safeCheckButtonVisibility(metadata, targetEntitySet, log); sectionData.createButton = buttonVisibility?.create; sectionData.deleteButton = buttonVisibility?.delete; } } bodySections.push(sectionData); }); } return bodySections; } /** * Extracts header-level action button states from an object page model. * * @param objectPage - object page from the application model * @param convertedMetadata - converted OData metadata for resolving action availability * @param schemaNamespace - OData schema namespace used as service identifier in action assertions * @returns array of action button states for the header toolbar */ function extractHeaderActions(objectPage, convertedMetadata, schemaNamespace) { if (!objectPage.model) { return []; } const headerAgg = getAggregations(objectPage.model.root)['header']; const actionsAgg = getAggregations(headerAgg)['actions']; const actionEntries = getAggregations(actionsAgg); return Object.entries(actionEntries) .map(([key, item]) => buildActionStateFromSpecModelKey(key, item.description, convertedMetadata, schemaNamespace)) .filter((actionState) => actionState !== undefined); } /** * Extracts section-level action button states from a body section. * For table sections, actions are extracted from the table toolbar; for form sections from the form actions aggregation. * * @param section - body section entry from the application model * @param convertedMetadata - converted OData metadata for resolving action availability * @param schemaNamespace - OData schema namespace used as service identifier in action assertions * @returns array of action button states for the section toolbar */ function extractSectionActions(section, convertedMetadata, schemaNamespace) { let actionsAgg; if (section.isTable) { const tableAgg = getAggregations(section)['table']; const toolBarAgg = getAggregations(tableAgg)['toolBar']; actionsAgg = getAggregations(toolBarAgg)['actions']; } else { const formAgg = getAggregations(section)['form']; actionsAgg = getAggregations(formAgg)['actions']; } if (!actionsAgg) { return []; } const actionEntries = getAggregations(actionsAgg); return Object.entries(actionEntries) .map(([key, item]) => buildActionStateFromSpecModelKey(key, item.description, convertedMetadata, schemaNamespace)) .filter((actionState) => actionState !== undefined); } /** * Extracts sub-sections data from a body section. * * @param section - body section entry from the application model * @param parentSectionId - identifier of the parent section (used as fallback key prefix) * @returns array of sub-section feature data */ function extractBodySubSectionsData(section, parentSectionId) { const subSections = []; const subSectionsAggregation = getAggregations(section)['subSections']; const subSectionItems = getAggregations(subSectionsAggregation); Object.entries(subSectionItems).forEach(([subSectionKey, subSection]) => { const subSectionId = getSectionIdentifier(subSection) ?? `${parentSectionId}_${subSectionKey}`; subSections.push({ id: subSectionId, navigationProperty: getNavigationPropertyFromKey(subSectionKey), isTable: !!subSection.isTable, custom: !!subSection.custom, order: subSection?.order ?? -1, // put a negative order number to signal that order was not in spec fields: subSection.custom || subSection.isTable ? [] : extractFormFields(subSection), tableColumns: subSection.custom || !subSection.isTable ? {} : extractTableColumnsFromNode(subSection) }); }); return subSections; } /** * Extracts form field property paths from a body sub-section's form aggregation. * * @param subSection - body sub-section entry from the application model * @returns array of form field property paths for use with iCheckField({ property }) */ function extractFormFields(subSection) { const fields = []; const formAggregation = getAggregations(subSection)['form']; if (!formAggregation) { return fields; } const fieldsAggregation = getAggregations(formAggregation)['fields']; const fieldItems = getAggregations(fieldsAggregation); Object.values(fieldItems).forEach((field) => { const property = field.schema?.keys?.find((key) => key.name === 'Value')?.value; if (property) { fields.push({ property }); } }); return fields; } /** * Extracts the OData navigation property from a spec model section key. * Section keys for table sections follow the pattern `_NavProperty::@annotation`, so the * navigation property is the part before `::` when it starts with an underscore. * * @param sectionKey - the key of the section in the spec model aggregations * @returns navigation property (e.g. '_Booking'), or undefined for non-navigation sections */ function getNavigationPropertyFromKey(sectionKey) { const prefix = sectionKey.split('::')[0]; return prefix.startsWith('_') ? prefix : undefined; } /** * Resolves the target entity set name for a navigation property by looking up navigation * property bindings in the source entity set's metadata. * * @param convertedMetadata - converted OData metadata * @param sourceEntitySetName - the name of the source entity set (the Object Page's entity set) * @param navigationProperty - the navigation property name (e.g. '_Booking') * @returns the target entity set name, or undefined if resolution fails */ function resolveNavigationTargetEntitySet(convertedMetadata, sourceEntitySetName, navigationProperty) { if (!sourceEntitySetName) { return undefined; } const sourceEntitySet = convertedMetadata.entitySets.find((es) => es.name === sourceEntitySetName); if (!sourceEntitySet?.navigationPropertyBinding) { return undefined; } const navPropName = navigationProperty.startsWith('_') ? navigationProperty.substring(1) : navigationProperty; const binding = sourceEntitySet.navigationPropertyBinding[navPropName]; return binding?.name; } /** * Gets the identifier of a section for OPA5 tests. * * @param section - section entry from ux specification * @returns identifier of the section for OPA5 tests */ function getSectionIdentifier(section) { return getSectionIdentifierFromKey(section) ?? getSectionIdentifierFromTitle(section); } /** * Gets the identifier of a section from the 'ID' or 'Key' entry in the schema keys for OPA5 tests. * If no such entry is found, undefined is returned. * * @param section - section entry from ux specification * @returns identifier of the section for OPA5 tests; can be undefined if no 'ID' or 'Key' entry is found */ function getSectionIdentifierFromKey(section) { const keyEntry = section?.schema?.keys?.find((key) => key.name === 'ID' || key.name === 'Key'); return keyEntry ? keyEntry.value.replace('#', '::') : undefined; } /** * Gets the identifier of a section from its title for OPA5 tests. * * @param section - section entry from ux specification * @returns identifier of the section for OPA5 tests; can be undefined if title is not in expected format */ function getSectionIdentifierFromTitle(section) { return section.title?.slice(section.title?.lastIndexOf('.') + 1).replace('#', '::') ?? undefined; } /** * Gets the stashed flag of a header section for OPA5 tests. * * @param headerSection - header section entry from ux specification * @returns stashed flag of the header section for OPA5 tests; can be a boolean or a string depending on the specification version */ function getSectionStashedFlag(headerSection) { return headerSection?.properties?.stashed?.freeText ?? false; } /** * Gets form fields of a header section for OPA5 tests. * * @param section - section entry from ux specification * @returns an array of form fields with their identifiers and bound properties for OPA5 tests */ function getHeaderSectionFormFields(section) { const formFields = []; const formAggregation = getAggregations(section)?.form; const fieldsAggregation = getAggregations(formAggregation)?.fields; const fields = getAggregations(fieldsAggregation); if (fields) { Object.keys(fields).forEach((fieldKey) => { const field = fields[fieldKey]; const fieldData = getFormFieldData(field, formAggregation); if (fieldData) { formFields.push(fieldData); } }); } return formFields; } /** * Gets field data for a form field in a header section for OPA5 tests, including its identifier, bound property, and target annotation. * * @param field - field entry from ux specification * @param formAggregation - form aggregation entry from ux specification, used to get field group qualifier for the field * @returns field data including its identifier, bound property, and target annotation for OPA5 tests; can be undefined if the field type is not supported or necessary information is missing */ function getFormFieldData(field, formAggregation) { if (!field.name) { return undefined; } let [_, propertyName, targetAnnotation] = field.name.split('::'); // fall back to Value property in case of malformed or otherwise irregular field name if (!propertyName) { propertyName = field.schema.keys.find((key) => key.name === 'Value')?.value; } const fieldIdentifier = { fieldGroupQualifier: getFieldGroupQualifier(formAggregation), field: propertyName, targetAnnotation: targetAnnotation }; // avoid creating identifier if field property could not be determined return fieldIdentifier.field ? fieldIdentifier : undefined; } /** * Gets the field group qualifier of a form aggregation for OPA5 tests. * * @param formAggregation - form aggregation entry from ux specification * @returns field group qualifier for OPA5 tests; can be undefined if not found */ function getFieldGroupQualifier(formAggregation) { const fullTarget = formAggregation?.schema?.keys?.find((key) => key.name === 'Target')?.value; return fullTarget?.split('#')[1]; } /** * Checks if the section contains a microChart based on it's name. * * @param section - section entry from ux specification * @returns true if the section seems to contain a microChart, false otherwise */ function isSectionMicroChart(section) { return section?.schema?.dataType === 'ChartDefinition'; } /** * Checks if the section contains a form based on it's aggregations. * * @param section - section entry from ux specification * @returns true if the section seems to contain a form, false otherwise */ function isFormSection(section) { return getAggregations(section)?.form !== undefined; } /** * Retrieves navigation targets from the given page model. * * @param pageModel - The page model containing navigation definitions. * @returns An array of navigation target identifiers. */ function getNavigationRoutes(pageModel) { const navigationTargets = []; if (!pageModel?.navigation) { return navigationTargets; } Object.keys(pageModel.navigation).forEach((navigationKey) => { if (pageModel.navigation) { const navigationEntry = pageModel.navigation[navigationKey]; navigationTargets.push({ identifier: navigationKey, route: typeof navigationEntry === 'string' ? navigationEntry : navigationEntry.route }); } }); return navigationTargets; } //# sourceMappingURL=objectPageUtils.js.map ;// ../../../node_modules/@sap-ux/ui5-test-writer/dist/utils/listReportUtils.js /* unused harmony import specifier */ var parse; /* unused harmony import specifier */ var convert; /** * Builds a button state object from button visibility result. * * @param buttonState - The button state from visibility check * @returns Button state object with visible, enabled, and optional dynamicPath properties */ function buildButtonState(buttonState) { return { visible: !!buttonState?.visible, enabled: buttonState?.enabled, dynamicPath: buttonState?.enabled === 'dynamic' ? buttonState.dynamicPath : undefined }; } /** * Safely checks action button states with error handling. * * @param convertedMetadata - The already-converted OData metadata * @param entitySetName - The name of the entity set * @param actionNames - List of action names to check * @param log - Optional logger instance * @returns Array of action button states or empty array if error occurs */ function safeCheckActionButtonStates(convertedMetadata, entitySetName, actionNames, log) { try { return checkActionButtonStatesFromMetadata(convertedMetadata, entitySetName, actionNames).actions; } catch (error) { log?.debug(`Failed to check action button states: ${error instanceof Error ? error.message : String(error)}`); return []; } } /** * Safely gets semantic key properties with error handling. * * @param convertedMetadata - The already-converted OData metadata * @param entitySetName - The name of the entity set * @param log - Optional logger instance * @returns Array of semantic key properties or undefined if error occurs */ function safeGetSemanticKeyProperties(convertedMetadata, entitySetName, log) { try { return getSemanticKeyPropertiesFromMetadata(convertedMetadata, entitySetName, true); } catch (error) { log?.debug(`Failed to get semantic key properties: ${error instanceof Error ? error.message : String(error)}`); return undefined; } } /** * Returns true when a ListReport manifest target is configured as an Analytical List Page. * ALP targets have a `views.paths` array where at least one entry contains a `primary` array, * indicating the dual-view (chart + table) layout used by ALP. * * @param target - the manifest routing target to inspect * @returns true if the target represents an ALP configuration */ function isALPManifestTarget(target) { return (target.options?.settings?.views?.paths?.some((path) => Array.isArray(path.primary) && path.primary.length > 0) ?? false); } /** * Returns true if any ListReport target in the manifest is configured as an Analytical List Page. * * @param manifest - the application manifest * @param targetKey - optional specific target key to check; if omitted all ListReport targets are checked * @returns true if the target (or any ListReport target) is an ALP */ function isALPFromManifest(manifest, targetKey) { const targets = manifest['sap.ui5']?.routing?.targets; if (!targets) { return false; } const keysToCheck = targetKey ? [targetKey] : Object.keys(targets); return keysToCheck.some((key) => { const target = targets[key]; return target?.name === 'sap.fe.templates.ListReport' && isALPManifestTarget(target); }); } /** * Gets List Report features from the page model using ux-specification. * * @param listReportPage - the List Report page containing the tree model with feature definitions * @param log - optional logger instance * @param metadata - optional metadata for the OPA test generation * @param manifest - optional application manifest, used to detect ALP configuration * @returns feature data extracted from the List Report page model */ function getListReportFeatures(listReportPage, log, metadata, manifest) { const toolbarActions = getToolBarActionNames(listReportPage.model, log); const filterBarItems = getFilterFieldNames(listReportPage.model, log); let buttonVisibility; let semanticKeyProperties; let toolBarActions = []; if (metadata && listReportPage.entitySet) { const entitySetName = listReportPage.entitySet; try { const convertedMetadata = (0,annotation_converter_dist.convert)((0,edmx_parser_dist/* parse */.qg)(metadata)); buttonVisibility = safeCheckButtonVisibilityFromMetadata(convertedMetadata, entitySetName, log); semanticKeyProperties = safeGetSemanticKeyProperties(convertedMetadata, entitySetName, log); toolBarActions = safeCheckActionButtonStates(convertedMetadata, entitySetName, toolbarActions, log); } catch (error) { log?.debug(`Failed to parse metadata: ${error instanceof Error ? error.message : String(error)}`); } } const missingKeys = semanticKeyProperties?.length && filterBarItems.length ? semanticKeyProperties.filter((key) => !filterBarItems.includes(key)) : undefined; return { name: listReportPage.name, createButton: buildButtonState(buttonVisibility?.create), deleteButton: buildButtonState(buttonVisibility?.delete), filterBarItems, tableColumns: getTableColumnData(listReportPage.model, log), toolBarActions, isALP: manifest ? isALPFromManifest(manifest, listReportPage.name) : false, semanticKey: { semanticKeyProperties, missingFromFilterBar: missingKeys?.length ? missingKeys : undefined } }; } /** * Retrieves toolbar action definitions from the given tree model. * * @param pageModel - The tree model containing toolbar definitions. * @returns The toolbar actions aggregation object. */ function getToolBarActions(pageModel) { const table = getAggregations(pageModel.root)['table']; const tableAggregations = getAggregations(table); const toolBar = tableAggregations['toolBar']; const toolBarAggregations = getAggregations(toolBar); const actions = toolBarAggregations['actions']; const actionAggregations = getAggregations(actions); return actionAggregations; } /** * Retrieves filter field names from the page model using ux-specification. * * @param pageModel - the tree model containing filter bar definitions * @param log - optional logger instance * @returns - an array of filter field names */ function getFilterFieldNames(pageModel, log) { let filterBarItems = []; try { const filterBarAggregations = getFilterFields(pageModel); filterBarItems = getSelectionFieldItems(filterBarAggregations); } catch (error) { log?.debug(error); } if (!filterBarItems?.length) { log?.warn('Unable to extract filter fields from project model using specification. No filter field tests will be generated.'); } return filterBarItems; } /** * Checks the state of action buttons defined in UI.LineItem annotations for a given entity set. * * @param convertedMetadata The already-converted OData metadata * @param entitySetName The name of the entity set to check * @param actionNames Optional list of action names to filter (e.g., ['Check', 'deductDiscount']). If not provided, returns all actions. * @returns ActionButtonsResult containing the list of action buttons and their states * @throws {Error} If entity set is not found */ function checkActionButtonStatesFromMetadata(convertedMetadata, entitySetName, actionNames) { const entitySet = convertedMetadata.entitySets.find((es) => es.name === entitySetName); if (!entitySet) { throw new Error(`Entity set '${entitySetName}' not found in metadata`); } const entityType = entitySet.entityType; if (!entityType) { throw new Error(`Entity type not found for entity set '${entitySetName}'`); } const lineItemAnnotation = entityType.annotations?.UI?.LineItem; if (!lineItemAnnotation || !Array.isArray(lineItemAnnotation)) { return { actions: [], entityType: entityType.name }; } const dataFieldForActions = lineItemAnnotation.filter((item) => item.$Type === 'com.sap.vocabularies.UI.v1.DataFieldForAction'); const actions = actionNames ? findActionStates(dataFieldForActions, actionNames, convertedMetadata) : extractAllActionStates(dataFieldForActions, convertedMetadata); return { actions, entityType: entityType.name }; } /** * Checks the state of action buttons defined in UI.LineItem annotations for a given entity set. * * @param metadataXml The OData metadata XML content as a string * @param entitySetName The name of the entity set to check * @param actionNames Optional list of action names to filter (e.g., ['Check', 'deductDiscount']). If not provided, returns all actions. * @returns ActionButtonsResult containing the list of action buttons and their states * @throws {Error} If metadata cannot be parsed or entity set is not found */ function checkActionButtonStates(metadataXml, entitySetName, actionNames) { try { return checkActionButtonStatesFromMetadata(convert(parse(metadataXml)), entitySetName, actionNames); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Failed to analyze action button states: ${errorMessage}`); } } /** * Finds action states for a specific list of action names. * * @param dataFieldForActions List of DataFieldForAction items from UI.LineItem * @param actionNames List of action names to find * @param metadata The converted metadata * @returns List of action button states for the specified actions */ function findActionStates(dataFieldForActions, actionNames, metadata) { const actionStates = []; for (const actionName of actionNames) { const item = dataFieldForActions.find((dfa) => { const actionMethod = extractActionMethodName(dfa.Action || ''); return actionMethod === actionName || dfa.Label === actionName; }); if (item) { actionStates.push(buildActionButtonState(item, metadata)); } } return actionStates; } /** * Extracts action states for all DataFieldForAction items. * * @param dataFieldForActions List of DataFieldForAction items from UI.LineItem * @param metadata The converted metadata * @returns List of all action button states */ function extractAllActionStates(dataFieldForActions, metadata) { return dataFieldForActions.map((item) => buildActionButtonState(item, metadata)); } /** * Retrieves toolbar action names from the page model using ux-specification. * * @param pageModel - the tree model containing