From bc2de3b81fb53768b85ef02a9ec0f598da1a27bf Mon Sep 17 00:00:00 2001 From: Davis Voicescuks Date: Tue, 14 Oct 2025 11:40:20 +0300 Subject: [PATCH 1/2] refactor: improve type checking --- .../preview-middleware-client/.eslintrc.js | 1 + .../common/op-add-header-field.ts | 2 +- .../quick-actions/fe-v2/add-new-subpage.ts | 3 +- .../fe-v2/change-table-columns.ts | 6 +- .../fe-v2/create-table-action.ts | 8 +- .../fe-v2/create-table-custom-column.ts | 29 ++--- ...r-enable-semantic-date-range-filter-bar.ts | 6 +- .../fe-v2/lr-enable-table-filtering.ts | 2 +- .../fe-v2/op-enable-empty-row-mode.ts | 2 +- .../src/adp/quick-actions/fe-v2/utils.ts | 4 +- .../quick-actions/fe-v4/add-new-subpage.ts | 6 +- .../fe-v4/op-enable-empty-row-mode.ts | 2 +- .../quick-actions/table-quick-action-base.ts | 62 +++++----- .../src/cpe/changes/service.ts | 2 +- .../src/cpe/quick-actions/utils.ts | 25 ++-- .../src/utils/core.ts | 16 +-- .../src/utils/fe-v4.ts | 9 +- .../test/unit/utils/fe-v4/utils.test.ts | 68 +++++++---- .../types/global.d.ts | 112 ++++++++++++++++++ .../types/sap.ui.core.d.ts | 5 +- 20 files changed, 249 insertions(+), 121 deletions(-) diff --git a/packages/preview-middleware-client/.eslintrc.js b/packages/preview-middleware-client/.eslintrc.js index 4b940075174..dc5fb986de9 100644 --- a/packages/preview-middleware-client/.eslintrc.js +++ b/packages/preview-middleware-client/.eslintrc.js @@ -7,6 +7,7 @@ module.exports = { }, rules: { 'quotes': ['error', 'single', { 'allowTemplateLiterals': true }], + 'no-undef': 'off', 'valid-jsdoc': [ 'error', { diff --git a/packages/preview-middleware-client/src/adp/quick-actions/common/op-add-header-field.ts b/packages/preview-middleware-client/src/adp/quick-actions/common/op-add-header-field.ts index 1221503d22d..a5479370c6a 100644 --- a/packages/preview-middleware-client/src/adp/quick-actions/common/op-add-header-field.ts +++ b/packages/preview-middleware-client/src/adp/quick-actions/common/op-add-header-field.ts @@ -46,7 +46,7 @@ export class AddHeaderFieldQuickAction const headerContent = this.control.getHeaderContent(); // check if only flex box exist in the headerContent. - if (headerContent.length === 1 && isA('sap.m.FlexBox', headerContent[0])) { + if (headerContent.length === 1 && isA('sap.m.FlexBox', headerContent[0])) { const overlay = OverlayRegistry.getOverlay(headerContent[0]) || []; await DialogFactory.createDialog( overlay, diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/add-new-subpage.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/add-new-subpage.ts index 06a001011f3..78bec3c0bbb 100644 --- a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/add-new-subpage.ts +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/add-new-subpage.ts @@ -1,6 +1,5 @@ import ODataModelV2 from 'sap/ui/model/odata/v2/ODataModel'; import ODataMetaModelV2, { EntityContainer, EntitySet, EntityType } from 'sap/ui/model/odata/ODataMetaModel'; -import TemplateComponent from 'sap/suite/ui/generic/template/lib/TemplateComponent'; import type AppComponent from 'sap/suite/ui/generic/template/lib/AppComponent'; import { getV2ApplicationPages } from '../../../utils/fe-v2'; @@ -53,7 +52,7 @@ export class AddNewSubpage extends AddNewSubpageBase { } protected getEntitySetNameFromPageComponent(component: Component | undefined): Promise { - if (!isA('sap.suite.ui.generic.template.lib.TemplateComponent', component)) { + if (!isA('sap.suite.ui.generic.template.lib.TemplateComponent', component)) { throw new Error('Unexpected type of page owner component'); } return Promise.resolve(component.getEntitySet()); diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/change-table-columns.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/change-table-columns.ts index d42e83e5ee2..3c2684d11c5 100644 --- a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/change-table-columns.ts +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/change-table-columns.ts @@ -1,6 +1,4 @@ import FlexCommand from 'sap/ui/rta/command/FlexCommand'; -import type Table from 'sap/m/Table'; -import type SmartTable from 'sap/ui/comp/smarttable/SmartTable'; import ManagedObject from 'sap/ui/base/ManagedObject'; import { QuickActionContext, NestedQuickActionDefinition } from '../../../cpe/quick-actions/quick-action-definition'; @@ -51,9 +49,9 @@ export class ChangeTableColumnsQuickAction if (changeColumnActionId) { const executeAction = async () => await this.context.actionService.execute(table.getId(), changeColumnActionId); - if (isA(SMART_TABLE_TYPE, table)) { + if (isA(SMART_TABLE_TYPE, table)) { await executeAction(); - } else if (isA(M_TABLE_TYPE, table)) { + } else if (isA(M_TABLE_TYPE, table)) { // if table is busy, i.e. lazy loading, then we subscribe to 'updateFinished' event and call action service when loading is done // to avoid reopening the dialog after close if (this.isTableLoaded(table)) { diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-table-action.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-table-action.ts index 888dd6a743c..3df4b52c720 100644 --- a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-table-action.ts +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-table-action.ts @@ -1,6 +1,4 @@ import FlexCommand from 'sap/ui/rta/command/FlexCommand'; -import type Table from 'sap/m/Table'; -import type SmartTable from 'sap/ui/comp/smarttable/SmartTable'; import OverlayRegistry from 'sap/ui/dt/OverlayRegistry'; import ManagedObject from 'sap/ui/base/ManagedObject'; import UI5Element from 'sap/ui/core/Element'; @@ -88,13 +86,13 @@ export class AddTableActionQuickAction extends TableQuickActionDefinitionBase im getHeaderToolbar(table: UI5Element): ManagedObject | ManagedObject[] | OverflowToolbar | null | undefined { let headerToolbar; - if (isA(SMART_TABLE_TYPE, table)) { + if (isA(SMART_TABLE_TYPE, table)) { for (const item of table.getAggregation('items') as ManagedObject[]) { if (item.getAggregation('headerToolbar')) { headerToolbar = item.getAggregation('headerToolbar'); break; } - if (isA('sap.m.OverflowToolbar', item)) { + if (isA('sap.m.OverflowToolbar', item)) { headerToolbar = item; break; } @@ -102,7 +100,7 @@ export class AddTableActionQuickAction extends TableQuickActionDefinitionBase im if (!headerToolbar) { headerToolbar = table.getToolbar(); } - } else if (isA
(M_TABLE_TYPE, table)) { + } else if (isA(M_TABLE_TYPE, table)) { headerToolbar = table.getAggregation('headerToolbar'); } return headerToolbar; diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-table-custom-column.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-table-custom-column.ts index f9ebac31b71..71dee1374f0 100644 --- a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-table-custom-column.ts +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-table-custom-column.ts @@ -10,8 +10,6 @@ import ObjectPageLayout from 'sap/uxap/ObjectPageLayout'; import IconTabBar from 'sap/m/IconTabBar'; -import type SmartTable from 'sap/ui/comp/smarttable/SmartTable'; - import { QuickActionContext, NestedQuickActionDefinition } from '../../../cpe/quick-actions/quick-action-definition'; import { getControlById, isA } from '../../../utils/core'; import { DialogNames, DialogFactory } from '../../dialog-factory'; @@ -90,20 +88,12 @@ export class AddTableCustomColumnQuickAction preprocessActionExecution(table, sectionInfo, this.iconTabBar, iconTabBarFilterKey); this.selectOverlay(table); - let tableInternal: ManagedObject | undefined = table; - if (isA(SMART_TABLE_TYPE, table)) { - const itemsAggregation = table.getAggregation('items') as ManagedObject[]; - tableInternal = itemsAggregation.find((item) => { - return [M_TABLE_TYPE, TREE_TABLE_TYPE, ANALYTICAL_TABLE_TYPE, GRID_TABLE_TYPE].some((tType) => - isA(tType, item) - ); - }); - if (!tableInternal) { - return []; - } + const tableInternal = this.getInternalTable(table); + if (!tableInternal) { + return []; } - const overlay = OverlayRegistry.getOverlay(tableInternal as UI5Element) || []; + const overlay = OverlayRegistry.getOverlay(tableInternal); if (!overlay) { return []; } @@ -119,11 +109,12 @@ export class AddTableCustomColumnQuickAction }); return []; } - const dialog = [TREE_TABLE_TYPE, ANALYTICAL_TABLE_TYPE, GRID_TABLE_TYPE].some((type) => - isA(type, tableInternal) - ) - ? DialogNames.ADD_FRAGMENT - : DialogNames.ADD_TABLE_COLUMN_FRAGMENTS; + const dialog = + isA(TREE_TABLE_TYPE, tableInternal) || + isA(ANALYTICAL_TABLE_TYPE, tableInternal) || + isA(GRID_TABLE_TYPE, tableInternal) + ? DialogNames.ADD_FRAGMENT + : DialogNames.ADD_TABLE_COLUMN_FRAGMENTS; await DialogFactory.createDialog( overlay, this.context.rta, diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/lr-enable-semantic-date-range-filter-bar.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/lr-enable-semantic-date-range-filter-bar.ts index 31ba57993cf..8973fc17680 100644 --- a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/lr-enable-semantic-date-range-filter-bar.ts +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/lr-enable-semantic-date-range-filter-bar.ts @@ -62,14 +62,14 @@ export class ToggleSemanticDateRangeFilterBar const version = await getUi5Version(); const isLowerMinimalVersion = isLowerThanMinimalUi5Version(version, { major: 1, minor: 126 }); let entitySet; - if (isLowerMinimalVersion && isA(CONTROL_TYPE_LR, this.control)) { + if (isLowerMinimalVersion && isA(CONTROL_TYPE_LR, this.control)) { // In older versions of UI5, the getEntitySet method is unavailable, so this workaround has been introduced. const regex = /::([^:]+)--/; entitySet = regex.exec(this.control?.getId() ?? '')?.[1]; } else { entitySet = - isA(CONTROL_TYPE_LR, this.control) || - isA(CONTROL_TYPE_ALP, this.control) + isA(CONTROL_TYPE_LR, this.control) || + isA(CONTROL_TYPE_ALP, this.control) ? this.control.getEntitySet() : undefined; } diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/lr-enable-table-filtering.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/lr-enable-table-filtering.ts index 5a8d6a79ce1..92fcd78efb6 100644 --- a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/lr-enable-table-filtering.ts +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/lr-enable-table-filtering.ts @@ -82,7 +82,7 @@ export class EnableTableFilteringQuickAction return []; } - const entitySet = isA(SMART_TABLE_TYPE, this.control) ? this.control.getEntitySet() : undefined; + const entitySet = isA(SMART_TABLE_TYPE, this.control) ? this.control.getEntitySet() : undefined; const command = await prepareManifestChange( this.context, 'component/settings', diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/op-enable-empty-row-mode.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/op-enable-empty-row-mode.ts index df5ca86937d..13358098ccc 100644 --- a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/op-enable-empty-row-mode.ts +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/op-enable-empty-row-mode.ts @@ -14,7 +14,7 @@ import { preprocessActionExecution } from './create-table-custom-column'; export const ENABLE_TABLE_EMPTY_ROW_MODE = 'enable-table-empty-row-mode'; const CONTROL_TYPES = [SMART_TABLE_TYPE]; -const UNSUPPORTED_TABLES = [ANALYTICAL_TABLE_TYPE, TREE_TABLE_TYPE]; +const UNSUPPORTED_TABLES: (keyof TypeMap)[] = [ANALYTICAL_TABLE_TYPE, TREE_TABLE_TYPE]; const CREATION_ROWS_MODE = 'creationRows'; const OBJECT_PAGE_COMPONENT_NAME = 'sap.suite.ui.generic.template.ObjectPage'; diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/utils.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/utils.ts index 0113ce5bb02..77028a8ae56 100644 --- a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/utils.ts +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/utils.ts @@ -9,7 +9,7 @@ import Component from 'sap/ui/core/Component'; import type AppComponent from 'sap/suite/ui/generic/template/lib/AppComponent'; import type ManagedObject from 'sap/ui/base/ManagedObject'; import type TemplateComponent from 'sap/suite/ui/generic/template/lib/TemplateComponent'; -import SmartTableExtended from 'sap/ui/comp/smarttable'; +import { isA } from '../../../utils/core'; /** * Gets app component of a v2 project. @@ -120,7 +120,7 @@ export function isVariantManagementEnabledOPPage( if (typeof id !== 'string') { throw new Error('Could not retrieve configuration property because control id is not valid!'); } - if (!control.isA('sap.ui.comp.smarttable.SmartTable')) { + if (!isA('sap.ui.comp.smarttable.SmartTable', control)) { // variant management is only supported by SmartTable return false; } diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v4/add-new-subpage.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v4/add-new-subpage.ts index d15ec979ae4..79d33bbf4bf 100644 --- a/packages/preview-middleware-client/src/adp/quick-actions/fe-v4/add-new-subpage.ts +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v4/add-new-subpage.ts @@ -6,8 +6,6 @@ import AppComponent from 'sap/fe/core/AppComponent'; import { getV4AppComponent, getV4ApplicationPages } from '../../../utils/fe-v4'; import { AddNewSubpageBase, ApplicationPageData } from '../add-new-subpage-quick-action-base'; import { isA } from '../../../utils/core'; -import FEObjectPageComponent from 'sap/fe/templates/ObjectPage/Component'; -import FEListReportComponent from 'sap/fe/templates/ListReport/Component'; import { getUi5Version, isLowerThanMinimalUi5Version } from '../../../utils/version'; import { PageDescriptorV4 } from '../../controllers/AddSubpage.controller'; @@ -122,8 +120,8 @@ export class AddNewSubpage extends AddNewSubpageBase { metaModel: ODataMetaModelV4 ): Promise { if ( - !isA('sap.fe.templates.ListReport.Component', component) && - !isA('sap.fe.templates.ObjectPage.Component', component) + !isA('sap.fe.templates.ListReport.Component', component) && + !isA('sap.fe.templates.ObjectPage.Component', component) ) { throw new Error('Unexpected type of page owner component'); } diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v4/op-enable-empty-row-mode.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v4/op-enable-empty-row-mode.ts index 389cdc40ddd..f00ce8c1a46 100644 --- a/packages/preview-middleware-client/src/adp/quick-actions/fe-v4/op-enable-empty-row-mode.ts +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v4/op-enable-empty-row-mode.ts @@ -12,7 +12,7 @@ import { preprocessActionExecution } from '../fe-v2/create-table-custom-column'; export const ENABLE_TABLE_EMPTY_ROW_MODE = 'enable-table-empty-row-mode'; const CONTROL_TYPES = [MDC_TABLE_TYPE, GRID_TABLE_TYPE, ANALYTICAL_TABLE_TYPE, TREE_TABLE_TYPE]; -const UNSUPPORTED_TABLES = [ANALYTICAL_TABLE_TYPE, TREE_TABLE_TYPE]; +const UNSUPPORTED_TABLES: (keyof TypeMap)[] = [ANALYTICAL_TABLE_TYPE, TREE_TABLE_TYPE]; const INLINE_CREATION_ROWS_MODE = 'InlineCreationRows'; /** diff --git a/packages/preview-middleware-client/src/adp/quick-actions/table-quick-action-base.ts b/packages/preview-middleware-client/src/adp/quick-actions/table-quick-action-base.ts index 3452ce25d7d..5b1cc4c2597 100644 --- a/packages/preview-middleware-client/src/adp/quick-actions/table-quick-action-base.ts +++ b/packages/preview-middleware-client/src/adp/quick-actions/table-quick-action-base.ts @@ -1,10 +1,6 @@ import UI5Element from 'sap/ui/core/Element'; import { NESTED_QUICK_ACTION_KIND, NestedQuickAction } from '@sap-ux-private/control-property-editor-common'; import type IconTabBar from 'sap/m/IconTabBar'; -import type IconTabFilter from 'sap/m/IconTabFilter'; -import type Table from 'sap/m/Table'; -import type MdcTable from 'sap/ui/mdc/Table'; -import type SmartTable from 'sap/ui/comp/smarttable/SmartTable'; import { QuickActionContext } from '../../cpe/quick-actions/quick-action-definition'; import OverlayUtil from 'sap/ui/dt/OverlayUtil'; import type { NestedQuickActionChild } from '@sap-ux-private/control-property-editor-common'; @@ -14,7 +10,6 @@ import { getUi5Version, isLowerThanMinimalUi5Version } from '../../utils/version import ObjectPageSection from 'sap/uxap/ObjectPageSection'; import ObjectPageSubSection from 'sap/uxap/ObjectPageSubSection'; import ObjectPageLayout from 'sap/uxap/ObjectPageLayout'; -import ManagedObject from 'sap/ui/base/ManagedObject'; import { EnablementValidator } from './enablement-validator'; import { QuickActionDefinitionBase } from './quick-action-base'; import { @@ -26,7 +21,6 @@ import { TREE_TABLE_TYPE } from './control-types'; import { isVariantManagementEnabledOPPage } from './fe-v2/utils'; - const SMART_TABLE_ACTION_ID = 'CTX_COMP_VARIANT_CONTENT'; const M_TABLE_ACTION_ID = 'CTX_ADD_ELEMENTS_AS_CHILD'; const SETTINGS_ID = 'CTX_SETTINGS'; @@ -145,7 +139,7 @@ export abstract class TableQuickActionDefinitionBase extends QuickActionDefiniti this.controlTypes )) { const tabKey = Object.keys(iconTabBarfilterMap).find((key) => table.getId().endsWith(key)); - const section = getParentContainer(table, 'sap.uxap.ObjectPageSection'); + const section = getParentContainer(table, 'sap.uxap.ObjectPageSection'); if (section) { await this.collectChildrenInSection(section, table); } else if (this.iconTabBar && tabKey) { @@ -176,17 +170,27 @@ export abstract class TableQuickActionDefinitionBase extends QuickActionDefiniti */ protected getInternalTable(table: UI5Element): UI5Element | undefined { try { - let tableInternal: ManagedObject | undefined; + if (!isA(SMART_TABLE_TYPE, table)) { + return undefined; + } - if (isA(SMART_TABLE_TYPE, table)) { - const itemsAggregation = table.getAggregation('items') as ManagedObject[]; - tableInternal = itemsAggregation.find((item) => - [M_TABLE_TYPE, TREE_TABLE_TYPE, ANALYTICAL_TABLE_TYPE, GRID_TABLE_TYPE].some((tType) => - isA(tType, item) - ) - ); + const itemsAggregation = table.getAggregation('items'); + if (!Array.isArray(itemsAggregation)) { + return undefined; + } + + for (const item of itemsAggregation) { + if ( + isA(M_TABLE_TYPE, item) || + isA(TREE_TABLE_TYPE, item) || + isA(ANALYTICAL_TABLE_TYPE, item) || + isA(GRID_TABLE_TYPE, item) + ) { + return item; + } } - return tableInternal as UI5Element | undefined; + + return undefined; } catch (error) { return undefined; } @@ -198,12 +202,12 @@ export abstract class TableQuickActionDefinitionBase extends QuickActionDefiniti * @returns table label if found or 'Unnamed table' */ private getTableLabel(table: UI5Element): string { - if (isA(SMART_TABLE_TYPE, table) || isA(MDC_TABLE_TYPE, table)) { + if (isA(SMART_TABLE_TYPE, table) || isA(MDC_TABLE_TYPE, table)) { const header = table.getHeader(); if (header) { return `'${header}' table`; } - } else if (isA
(M_TABLE_TYPE, table)) { + } else if (isA(M_TABLE_TYPE, table)) { const title = table?.getHeaderToolbar()?.getTitleControl()?.getText(); if (title) { return `'${title}' table`; @@ -226,10 +230,10 @@ export abstract class TableQuickActionDefinitionBase extends QuickActionDefiniti ])[0]; if (tabBar) { const control = getControlById(tabBar.getId()); - if (isA(ICON_TAB_BAR_TYPE, control)) { + if (isA(ICON_TAB_BAR_TYPE, control)) { this.iconTabBar = control; for (const item of control.getItems()) { - if (isManagedObject(item) && isA('sap.m.IconTabFilter', item)) { + if (isManagedObject(item) && isA('sap.m.IconTabFilter', item)) { iconTabBarFilterMap[item.getKey()] = item.getText(); } } @@ -245,9 +249,9 @@ export abstract class TableQuickActionDefinitionBase extends QuickActionDefiniti * @param table - table element */ private async collectChildrenInSection(section: ObjectPageSection, table: UI5Element): Promise { - const layout = getParentContainer(table, 'sap.uxap.ObjectPageLayout'); + const layout = getParentContainer(table, 'sap.uxap.ObjectPageLayout'); const subSections = section.getSubSections(); - const subSection = getParentContainer(table, 'sap.uxap.ObjectPageSubSection'); + const subSection = getParentContainer(table, 'sap.uxap.ObjectPageSubSection'); if (subSection) { if (subSections?.length === 1) { await this.processTable(table, { section, subSection: subSections[0], layout }); @@ -293,14 +297,12 @@ export abstract class TableQuickActionDefinitionBase extends QuickActionDefiniti ): Promise { const tableMapKey = this.children.length.toString(); if ( - [ - SMART_TABLE_TYPE, - M_TABLE_TYPE, - MDC_TABLE_TYPE, - TREE_TABLE_TYPE, - GRID_TABLE_TYPE, - ANALYTICAL_TABLE_TYPE - ].some((type) => isA(type, table)) + isA(SMART_TABLE_TYPE, table) || + isA(M_TABLE_TYPE, table) || + isA(MDC_TABLE_TYPE, table) || + isA(TREE_TABLE_TYPE, table) || + isA(GRID_TABLE_TYPE, table) || + isA(ANALYTICAL_TABLE_TYPE, table) ) { const label = this.getTableLabel(table); const child = this.createChild(label, table, tableMapKey); diff --git a/packages/preview-middleware-client/src/cpe/changes/service.ts b/packages/preview-middleware-client/src/cpe/changes/service.ts index 22255ee3ead..d347138be74 100644 --- a/packages/preview-middleware-client/src/cpe/changes/service.ts +++ b/packages/preview-middleware-client/src/cpe/changes/service.ts @@ -355,7 +355,7 @@ export class ChangeService extends EventTarget { .map((id: string) => { return getControlById(id); }) - .filter((ui5Element) => isA('sap.ui.core.Element', ui5Element)); + .filter((ui5Element) => isA('sap.ui.core.Element', ui5Element)); acc.push(...controls); return acc; }, []) diff --git a/packages/preview-middleware-client/src/cpe/quick-actions/utils.ts b/packages/preview-middleware-client/src/cpe/quick-actions/utils.ts index 8576d0ad055..a7be5c4c62b 100644 --- a/packages/preview-middleware-client/src/cpe/quick-actions/utils.ts +++ b/packages/preview-middleware-client/src/cpe/quick-actions/utils.ts @@ -6,7 +6,6 @@ import { FEAppPage } from 'sap/ui/rta/RuntimeAuthoring'; import { getControlById, isA } from '../../utils/core'; import type { ControlTreeIndex } from '../types'; -import Component from 'sap/ui/core/Component'; export interface FEAppPageInfo { page: FEAppPage; @@ -18,7 +17,7 @@ export interface FEAppPagesMap { /** * Checks if control is visible in the page. - * + * * @param page - Page control. * @param controlId - UI5 control id. * @returns True if control is visible in the page. @@ -28,10 +27,9 @@ export function pageHasControlId(page: Control, controlId: string): boolean { return !!controlDomElement && !!page?.getDomRef()?.contains(controlDomElement); } - /** * Checks if control is a child element of the rootControl. - * + * * @param control - UI5 Control to be tested. * @param rootControl - UI5 root control. * @returns True if control is the child of the specified rootControl. @@ -44,7 +42,7 @@ function isDescendantOfPage(control: ManagedObject | null | undefined, rootContr } // if parent is a reusable component, use oContainer to find the parent if ( - isA('sap.ui.core.Component', currentControl) && + isA('sap.ui.core.Component', currentControl) && (currentControl as unknown as { oContainer: Control })?.oContainer ) { currentControl = (currentControl as unknown as { oContainer: Control }).oContainer.getParent(); @@ -57,7 +55,7 @@ function isDescendantOfPage(control: ManagedObject | null | undefined, rootContr /** * Find all controls in page that match the provided types. - * + * * @param controlIndex - Control tree index. * @param activePage - Active page control. * @param controlTypes - Relevant control types. @@ -91,24 +89,25 @@ export function getRelevantControlFromActivePage( +export function getParentContainer( control: ManagedObject | null | undefined, - type: string -): T | undefined { + type: T +): TypeMap[typeof type] | undefined { let currentControl = control; while (currentControl) { - if (isA(type, currentControl)) { + if (isA(type, currentControl)) { return currentControl; } // if parent is a reusable component, use oContainer to find the parent + // currentControl type somehow gets converted to "never" so we need to use type assertion here if ( - isA('sap.ui.core.Component', currentControl) && + isA('sap.ui.core.Component', currentControl as ManagedObject) && (currentControl as unknown as { oContainer: Control })?.oContainer ) { currentControl = (currentControl as unknown as { oContainer: Control }).oContainer.getParent(); } else { - currentControl = currentControl.getParent(); + currentControl = (currentControl as ManagedObject).getParent(); } } return undefined; -} \ No newline at end of file +} diff --git a/packages/preview-middleware-client/src/utils/core.ts b/packages/preview-middleware-client/src/utils/core.ts index 322ef0c8c8a..40c27e81c10 100644 --- a/packages/preview-middleware-client/src/utils/core.ts +++ b/packages/preview-middleware-client/src/utils/core.ts @@ -49,6 +49,8 @@ export function isManagedObject(element: object | undefined): element is Managed return false; } + + /** * Checks whether this object is an instance of the named type. * @@ -56,11 +58,14 @@ export function isManagedObject(element: object | undefined): element is Managed * @param element - Object to check * @returns Whether this object is an instance of the given type. */ -export function isA(type: string, element: ManagedObject | undefined): element is T { +export function isA( + type: T, + element: ManagedObject | undefined +): element is TypeMap[typeof type] { return !!element?.isA(type); } -export function hasParent(component: ManagedObject, parentIdToFind: string): boolean { +export function hasParent(component: ManagedObject, parentIdToFind: string): boolean { const parent = component.getParent(); if (!parent) { return false; @@ -71,10 +76,7 @@ export function hasParent(component: ManagedObject, parentIdToFind: string): boo return hasParent(parent, parentIdToFind); } -export function findNestedElements( - ownerElement: Element, - candidates: Element[] -): Element[] { +export function findNestedElements(ownerElement: Element, candidates: Element[]): Element[] { const ownerId = ownerElement.getId(); return candidates.filter((item) => hasParent(item, ownerId)); -} \ No newline at end of file +} diff --git a/packages/preview-middleware-client/src/utils/fe-v4.ts b/packages/preview-middleware-client/src/utils/fe-v4.ts index 77ddf83de84..3c0e6d96e7f 100644 --- a/packages/preview-middleware-client/src/utils/fe-v4.ts +++ b/packages/preview-middleware-client/src/utils/fe-v4.ts @@ -2,7 +2,6 @@ import ManagedObject from 'sap/ui/base/ManagedObject'; import TemplateComponent from 'sap/fe/core/TemplateComponent'; import Component from 'sap/ui/core/Component'; import AppComponent from 'sap/fe/core/AppComponent'; -import XMLView from 'sap/ui/core/mvc/XMLView'; import type { FlexSettings, Manifest } from 'sap/ui/rta/RuntimeAuthoring'; import { isA } from './core'; @@ -48,7 +47,10 @@ export function getV4PageType(control: ManagedObject): 'ObjectPage' | 'ListRepor return undefined; } const view = component.getRootControl(); - const name = (view as XMLView).getViewName(); + if (!isA('sap.ui.core.mvc.XMLView', view)) { + return undefined; + } + const name = view.getViewName(); if (name === 'sap.fe.templates.ObjectPage.ObjectPage') { return 'ObjectPage'; } @@ -66,7 +68,7 @@ export function getV4PageType(control: ManagedObject): 'ObjectPage' | 'ListRepor */ export function getPageName(control: ManagedObject): string | undefined { const component = Component.getOwnerComponentFor(control); - if (!isA('sap.fe.core.TemplateComponent', component)) { + if (!isA('sap.fe.core.TemplateComponent', component)) { return undefined; } const view = component.getRootControl(); @@ -117,7 +119,6 @@ export async function createManifestPropertyChange( } else { adjustedChanges[key] = value; } - } const [manifestPropertyChange] = overlayData.manifestPropertyChange( adjustedChanges, diff --git a/packages/preview-middleware-client/test/unit/utils/fe-v4/utils.test.ts b/packages/preview-middleware-client/test/unit/utils/fe-v4/utils.test.ts index ebec2350bd1..67121c8ae99 100644 --- a/packages/preview-middleware-client/test/unit/utils/fe-v4/utils.test.ts +++ b/packages/preview-middleware-client/test/unit/utils/fe-v4/utils.test.ts @@ -1,3 +1,4 @@ +import ManagedObject from 'sap/ui/base/ManagedObject'; import { getV4AppComponent, getReference, getV4PageType } from '../../../../src/utils/fe-v4'; import ComponentMock from 'mock/sap/ui/core/Component'; @@ -10,44 +11,69 @@ describe('fe-v4/utils', () => { } }) }; - ComponentMock.getOwnerComponentFor = jest.fn().mockReturnValue({ - isA: jest - .fn() - .mockReturnValueOnce(false) - .mockReturnValueOnce(true) - .mockReturnValueOnce(false) - .mockReturnValue(true), - getAppComponent: jest.fn().mockReturnValue(appComponent), - getRootControl: jest.fn().mockReturnValue({ - getId: jest.fn().mockReturnValue('test::ObjectPage'), - getViewName: jest - .fn() - .mockReturnValueOnce('sap.fe.templates.ObjectPage.ObjectPage') - .mockReturnValue('sap.fe.templates.ListReport.ListReport') - }) + + function createMock(isTemplateComponent: boolean, rootControlId: string, rootViewName: string) { + return jest.fn().mockReturnValue({ + isA: jest.fn().mockReturnValue(isTemplateComponent), + getAppComponent: jest.fn().mockReturnValue(appComponent), + getRootControl: jest.fn().mockReturnValue({ + isA: jest.fn().mockImplementation((type) => type === 'sap.ui.core.mvc.XMLView'), + getId: jest.fn().mockReturnValue(rootControlId), + getViewName: jest.fn().mockReturnValue(rootViewName) + }) + }); + } + + afterEach(() => { + jest.clearAllMocks(); }); test('getAppComponent - undefined', () => { - expect(getV4AppComponent({ id: 'buttonId' } as any)).toBe(undefined); + const isASpy = jest.fn().mockReturnValue(false); + ComponentMock.getOwnerComponentFor = jest.fn().mockReturnValue({ + isA: isASpy + }); + expect(getV4AppComponent({ id: 'buttonId' } as unknown as ManagedObject)).toBe(undefined); + expect(isASpy).toHaveBeenCalledWith('sap.fe.core.TemplateComponent'); }); test('getAppComponent - defined', () => { - expect(getV4AppComponent({ id: 'buttonId2' } as any)).toBe(appComponent); + ComponentMock.getOwnerComponentFor = createMock( + true, + 'test::ObjectPage', + 'sap.fe.templates.ObjectPage.ObjectPage' + ); + expect(getV4AppComponent({ id: 'buttonId2' } as unknown as ManagedObject)).toBe(appComponent); }); test('getV4PageType - undefined', () => { - expect(getV4PageType({ id: 'buttonId' } as any)).toBe(undefined); + const isASpy = jest.fn().mockReturnValue(false); + ComponentMock.getOwnerComponentFor = jest.fn().mockReturnValue({ + isA: isASpy + }); + expect(getV4PageType({ id: 'buttonId' } as unknown as ManagedObject)).toBe(undefined); + expect(isASpy).toHaveBeenCalledWith('sap.fe.core.TemplateComponent'); }); test('getV4PageType - ObjectPage', () => { - expect(getV4PageType({ id: 'buttonId2' } as any)).toBe('ObjectPage'); + ComponentMock.getOwnerComponentFor = createMock( + true, + 'test::ObjectPage', + 'sap.fe.templates.ObjectPage.ObjectPage' + ); + expect(getV4PageType({ id: 'buttonId2' } as unknown as ManagedObject)).toBe('ObjectPage'); }); test('getV4PageType - ListReport', () => { - expect(getV4PageType({ id: 'buttonId3' } as any)).toBe('ListReport'); + ComponentMock.getOwnerComponentFor = createMock( + true, + 'test::ListReport', + 'sap.fe.templates.ListReport.ListReport' + ); + expect(getV4PageType({ id: 'buttonId3' } as unknown as ManagedObject)).toBe('ListReport'); }); test('getReference', () => { - expect(getReference({ id: 'buttonId' } as any)).toBe('test'); + expect(getReference({ id: 'buttonId' } as unknown as ManagedObject)).toBe('test'); }); }); diff --git a/packages/preview-middleware-client/types/global.d.ts b/packages/preview-middleware-client/types/global.d.ts index d50895aa1cc..0c5d7c5b1af 100644 --- a/packages/preview-middleware-client/types/global.d.ts +++ b/packages/preview-middleware-client/types/global.d.ts @@ -7,3 +7,115 @@ export interface Window { }; [key: string]: string; } + +/** + * Core + */ +declare global { + import type Element from 'sap/ui/core/Element'; + import type View from 'sap/ui/core/mvc/View'; + import type XMLView from 'sap/ui/core/mvc/XMLView'; + import type Component from 'sap/ui/core/Component'; + import type ManagedObject from 'sap/ui/base/ManagedObject'; + + interface TypeMap { + 'sap.ui.core.Element': Element; + 'sap.ui.core.mvc.View': View; + 'sap.ui.core.mvc.XMLView': XMLView; + 'sap.ui.core.Component': Component; + 'sap.ui.base.ManagedObject': ManagedObject; + } +} + +/** + * ui controls + */ +declare global { + import type MdcTable from 'sap/ui/mdc/Table'; + import type GridTable from 'sap/ui/table/Table'; + import type TreeTable from 'sap/ui/table/TreeTable'; + import type AnalyticalTable from 'sap/ui/table/AnalyticalTable'; + interface TypeMap { + 'sap.ui.mdc.Table': MdcTable; + 'sap.ui.table.TreeTable': TreeTable; + 'sap.ui.table.AnalyticalTable': AnalyticalTable; + 'sap.ui.table.Table': GridTable; + } +} + +/** + * m controls + */ +declare global { + import type Input from 'sap/m/Input'; + import type IconTabBar from 'sap/m/IconTabBar'; + import type Table from 'sap/m/Table'; + import type FlexBox from 'sap/m/FlexBox'; + import type IconTabFilter from 'sap/m/IconTabFilter'; + import type OverflowToolbar from 'sap/m/OverflowToolbar'; + interface TypeMap { + 'sap.m.Input': Input; + 'sap.m.OverflowToolbar': OverflowToolbar; + 'sap.m.IconTabBar': IconTabBar; + 'sap.m.IconTabFilter': IconTabFilter; + 'sap.m.Table': Table; + + 'sap.m.FlexBox': FlexBox; + } +} + +/** + * Suite Controls + */ +declare global { + import type SmartFilterBar from 'sap/ui/comp/smartfilterbar/SmartFilterBar'; + import type SmartTableExtended from 'sap/ui/comp/smarttable'; + interface TypeMap { + 'sap.ui.comp.smarttable.SmartTable': SmartTableExtended; + 'sap.ui.comp.smartfilterbar.SmartFilterBar': SmartFilterBar; + } +} + +/** + * uxap + */ +declare global { + import type ObjectPageLayout from 'sap/uxap/ObjectPageLayout'; + import type ObjectPageSubSection from 'sap/uxap/ObjectPageSubSection'; + import type ObjectPageSection from 'sap/uxap/ObjectPageSection'; + + interface TypeMap { + 'sap.uxap.ObjectPageLayout': ObjectPageLayout; + 'sap.uxap.ObjectPageSubSection': ObjectPageSubSection; + 'sap.uxap.ObjectPageSection': ObjectPageSection; + } +} + +/** + * FE V2 + */ +declare global { + import type SmartTableExtended from 'sap/ui/comp/smarttable'; + import type TemplateComponent from 'sap/suite/ui/generic/template/lib/TemplateComponent'; + import type ListReportComponent from 'sap/suite/ui/generic/template/ListReport'; + interface TypeMap { + 'sap.suite.ui.generic.template.lib.TemplateComponent': TemplateComponent; + 'sap.suite.ui.generic.template.ListReport.Component': ListReportComponent; + 'sap.suite.ui.generic.template.AnalyticalListPage.control.SmartFilterBarExt': SmartTableExtended; // Does not have exported types + } +} + +/** + * FE V4 + */ +declare global { + import type TemplateComponent from 'sap/fe/core/TemplateComponent'; + import type ListReportComponent from 'sap/fe/templates/ListReport/Component'; + import type ObjectPageComponent from 'sap/fe/templates/ObjectPage/Component'; + interface TypeMap { + 'sap.fe.core.TemplateComponent': TemplateComponent; + + 'sap.fe.templates.ListReport.Component': ListReportComponent; + 'sap.fe.templates.ObjectPage.Component': ObjectPageComponent; + } +} diff --git a/packages/preview-middleware-client/types/sap.ui.core.d.ts b/packages/preview-middleware-client/types/sap.ui.core.d.ts index 01872959596..9eecb5753f2 100644 --- a/packages/preview-middleware-client/types/sap.ui.core.d.ts +++ b/packages/preview-middleware-client/types/sap.ui.core.d.ts @@ -14,7 +14,7 @@ declare module 'sap/ui/core/util/reflection/JsControlTreeModifier' { declare module 'sap/ui/core/mvc/XMLView' { import type XMLViewOriginal from 'sap/ui/core/mvc/XMLView'; - + export default interface XMLView extends XMLViewOriginal { /** * Returns the name of the controller module associated with the XML view. @@ -24,4 +24,5 @@ declare module 'sap/ui/core/mvc/XMLView' { */ getControllerModuleName(): string; } -} \ No newline at end of file +} + From 6a1d57305fc793e3b9be8e3d07cad8a2b8820f07 Mon Sep 17 00:00:00 2001 From: Davis Voicescuks Date: Tue, 14 Oct 2025 11:43:23 +0300 Subject: [PATCH 2/2] chore: formatting --- packages/preview-middleware-client/src/utils/core.ts | 2 -- packages/preview-middleware-client/types/sap.ui.core.d.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/packages/preview-middleware-client/src/utils/core.ts b/packages/preview-middleware-client/src/utils/core.ts index 40c27e81c10..cc32a501570 100644 --- a/packages/preview-middleware-client/src/utils/core.ts +++ b/packages/preview-middleware-client/src/utils/core.ts @@ -49,8 +49,6 @@ export function isManagedObject(element: object | undefined): element is Managed return false; } - - /** * Checks whether this object is an instance of the named type. * diff --git a/packages/preview-middleware-client/types/sap.ui.core.d.ts b/packages/preview-middleware-client/types/sap.ui.core.d.ts index 9eecb5753f2..b66bc8ddaf1 100644 --- a/packages/preview-middleware-client/types/sap.ui.core.d.ts +++ b/packages/preview-middleware-client/types/sap.ui.core.d.ts @@ -14,7 +14,6 @@ declare module 'sap/ui/core/util/reflection/JsControlTreeModifier' { declare module 'sap/ui/core/mvc/XMLView' { import type XMLViewOriginal from 'sap/ui/core/mvc/XMLView'; - export default interface XMLView extends XMLViewOriginal { /** * Returns the name of the controller module associated with the XML view. @@ -25,4 +24,3 @@ declare module 'sap/ui/core/mvc/XMLView' { getControllerModuleName(): string; } } -