@sap-ux/preview-middleware 1.0.18 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -71,7 +71,7 @@ Array of additional application configurations:
71
71
  | `framework` | `string` | mandatory | `undefined` | Currently `OPA5`, `QUnit` (only QUnit 2.3.2 provided as third-party module using [OpenUI5](https://github.com/SAP/openui5/blob/master/THIRDPARTY.txt)/SAPUI5) and `Testsuite` are supported. `Testsuite` will generate a testsuite for all configured frameworks that can be be used with a test runner (such as karma) |
72
72
  | `path` | `string` | optional | `(calculated)` | The mount point to be used for test suite. By default, `/test/opaTests.qunit.html` is used for `OPA5`, `/test/unitTests.qunit.html` is used for `QUnit`, and `/test/testsuite.qunit.html` is used for `Testsuite` |
73
73
  | `init` | `string` | optional | `undefined` | The mount point to be used for custom test runner script |
74
- | `pattern` | `string` | optional | `(calculated)` | Optional glob pattern to find the tests. By default, `/test/**/*Journey.{js,ts}` is used for `OPA5` and `/test/**/*Test.{js,ts}` is used for `QUnit` (not applicable for `Testsuite`) |
74
+ | `pattern` | `string` | optional | `(calculated)` | Optional glob pattern to find the tests. By default, `/test/**/*Journey{,.gen}.{js,ts}` is used for `OPA5` and `/test/**/*Test.{js,ts}` is used for `QUnit` (not applicable for `Testsuite`) |
75
75
 
76
76
 
77
77
  ## [Usage](#usage)
@@ -149,6 +149,16 @@ export declare class FlpSandbox {
149
149
  * @private
150
150
  */
151
151
  private addCardGeneratorMiddlewareRoute;
152
+ /**
153
+ * Extracts protocol and baseUrl from a request and calls getUi5Version.
154
+ * Handles both express Request and connect IncomingMessage (karma test runner).
155
+ *
156
+ * @param req - the incoming request
157
+ * @param baseUrl - the base path to include when fetching the UI5 version
158
+ * @returns the parsed UI5 version
159
+ * @private
160
+ */
161
+ private getUi5VersionFromRequest;
152
162
  /**
153
163
  * Read the UI5 version.
154
164
  * In case of an error, the default UI5 version '1.121.0' is returned.
package/dist/base/flp.js CHANGED
@@ -350,11 +350,7 @@ export class FlpSandbox {
350
350
  else {
351
351
  // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
352
352
  this.templateConfig.baseUrl = ('ui5-patched-router' in req && req['ui5-patched-router']?.baseUrl) || '';
353
- const ui5Version = await this.getUi5Version(
354
- //use protocol from request header referer as fallback for connect API (karma test runner)
355
- 'protocol' in req
356
- ? req.protocol
357
- : (req.headers.referer?.substring(0, req.headers.referer.indexOf(':')) ?? 'http'), req.headers.host, this.templateConfig.baseUrl);
353
+ const ui5Version = await this.getUi5VersionFromRequest(req, this.templateConfig.baseUrl);
358
354
  this.checkDeleteConnectors(ui5Version.major, ui5Version.minor, ui5Version.isCdn);
359
355
  if (ui5Version.major === 1 && ui5Version.minor < 120) {
360
356
  this.removeFlexExtensionPointEnabled();
@@ -388,9 +384,37 @@ export class FlpSandbox {
388
384
  this.logger.debug(`Add route for ${previewGeneratorPath}`);
389
385
  this.router.get(previewGeneratorPath, async (req, res, next) => {
390
386
  this.templateConfig.enableCardGenerator = !!this.cardGenerator?.path;
387
+ if (this.templateConfig.enableCardGenerator) {
388
+ // check for min ui5 version of card generator feature
389
+ const baseUrl = 'ui5-patched-router' in req ? (req['ui5-patched-router']?.baseUrl ?? '') : '';
390
+ const ui5Version = await this.getUi5VersionFromRequest(req, baseUrl);
391
+ const minMinor = this.projectType === 'CAPNodejs' || this.projectType === 'CAPJava' ? 149 : 121;
392
+ if ((ui5Version.major === 1 && ui5Version.minor < minMinor) ||
393
+ ui5Version.major >= 2 ||
394
+ ui5Version.label?.includes('legacy-free')) {
395
+ this.templateConfig.enableCardGenerator = false;
396
+ this.logger.warn(`Feature cardGenerator disabled: UI5 version ${ui5Version.major}.${ui5Version.minor}.${ui5Version.patch} does not meet the minimum required version 1.${minMinor}.0 for project type '${this.projectType}'.`);
397
+ }
398
+ }
391
399
  await this.flpGetHandler(req, res, next);
392
400
  });
393
401
  }
402
+ /**
403
+ * Extracts protocol and baseUrl from a request and calls getUi5Version.
404
+ * Handles both express Request and connect IncomingMessage (karma test runner).
405
+ *
406
+ * @param req - the incoming request
407
+ * @param baseUrl - the base path to include when fetching the UI5 version
408
+ * @returns the parsed UI5 version
409
+ * @private
410
+ */
411
+ async getUi5VersionFromRequest(req, baseUrl = '') {
412
+ // use protocol from request header referer as fallback for connect API (karma test runner)
413
+ const protocol = 'protocol' in req
414
+ ? req.protocol
415
+ : (req.headers.referer?.substring(0, req.headers.referer.indexOf(':')) ?? 'http');
416
+ return this.getUi5Version(protocol, req.headers.host, baseUrl);
417
+ }
394
418
  /**
395
419
  * Read the UI5 version.
396
420
  * In case of an error, the default UI5 version '1.121.0' is returned.
@@ -425,6 +449,7 @@ export class FlpSandbox {
425
449
  }
426
450
  const [major, minor, patch] = version.split('.').map((versionPart) => Number.parseInt(versionPart, 10));
427
451
  const label = version.split(/-(.*)/s)?.[1];
452
+ // check for min ui5 version of flp.enhancedHomePage feature
428
453
  if (this.flpConfig.enhancedHomePage &&
429
454
  ((major < 2 && minor < 123) || major >= 2 || label?.includes('legacy-free'))) {
430
455
  this.flpConfig.enhancedHomePage = this.templateConfig.enhancedHomePage = false;
package/dist/base/test.js CHANGED
@@ -8,7 +8,7 @@ const DEFAULTS = {
8
8
  opa5: {
9
9
  path: '/test/opaTests.qunit.html',
10
10
  init: '/test/opaTests.qunit.js',
11
- pattern: '/test/**/*Journey.{js,ts}',
11
+ pattern: '/test/**/*Journey{,.gen}.{js,ts}',
12
12
  framework: 'OPA5'
13
13
  },
14
14
  testsuite: {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- sap.ui.define(["sap/ui/model/json/JSONModel", "sap/ui/dt/OverlayRegistry", "open/ux/preview/client/thirdparty/@sap-ux-private/control-property-editor-common", "../../cpe/communication-service", "../../i18n", "sap/ui/core/library", "../../utils/additional-change-info", "../../utils/error", "../../utils/info-center-message", "../api-handler", "../command-executor", "../control-utils", "./BaseDialog.controller"], function (JSONModel, OverlayRegistry, ___sap_ux_private_control_property_editor_common, ____cpe_communication_servicejs, ____i18njs, sap_ui_core_library, ____utils_additional_change_infojs, ____utils_errorjs, ____utils_info_center_messagejs, ___api_handlerjs, __CommandExecutor, __ControlUtils, __BaseDialog) {
3
+ sap.ui.define(["sap/ui/model/json/JSONModel", "sap/ui/dt/OverlayRegistry", "open/ux/preview/client/thirdparty/@sap-ux-private/control-property-editor-common", "../../cpe/communication-service", "../../i18n", "sap/ui/core/library", "../../utils/additional-change-info", "../../utils/changes", "../../utils/error", "../../utils/info-center-message", "../api-handler", "../command-executor", "../control-utils", "./BaseDialog.controller"], function (JSONModel, OverlayRegistry, ___sap_ux_private_control_property_editor_common, ____cpe_communication_servicejs, ____i18njs, sap_ui_core_library, ____utils_additional_change_infojs, ____utils_changesjs, ____utils_errorjs, ____utils_info_center_messagejs, ___api_handlerjs, __CommandExecutor, __ControlUtils, __BaseDialog) {
4
4
  "use strict";
5
5
 
6
6
  function _interopRequireDefault(obj) {
@@ -12,6 +12,7 @@ sap.ui.define(["sap/ui/model/json/JSONModel", "sap/ui/dt/OverlayRegistry", "open
12
12
  const getResourceModel = ____i18njs["getResourceModel"];
13
13
  const ValueState = sap_ui_core_library["ValueState"];
14
14
  const setAdditionalChangeInfoForChangeFile = ____utils_additional_change_infojs["setAdditionalChangeInfoForChangeFile"];
15
+ const getChangeDefinition = ____utils_changesjs["getChangeDefinition"];
15
16
  const getError = ____utils_errorjs["getError"];
16
17
  const sendInfoCenterMessage = ____utils_info_center_messagejs["sendInfoCenterMessage"];
17
18
  const getFragments = ___api_handlerjs["getFragments"];
@@ -218,7 +219,10 @@ sap.ui.define(["sap/ui/model/json/JSONModel", "sap/ui/dt/OverlayRegistry", "open
218
219
  const command = await this.commandExecutor.getCommand(targetObject, 'addXML', modifiedValue, flexSettings, designMetadata);
219
220
  const templateName = fragment.targetAggregation === COLUMNS_AGGREGATION ? `V2_SMART_TABLE_COLUMN` : 'V2_SMART_TABLE_CELL';
220
221
  const preparedChange = command.getPreparedChange();
221
- setAdditionalChangeInfoForChangeFile(preparedChange.getDefinition().fileName, {
222
+ const {
223
+ fileName
224
+ } = getChangeDefinition(preparedChange);
225
+ setAdditionalChangeInfoForChangeFile(fileName, {
222
226
  templateName
223
227
  });
224
228
  compositeCommand.addCommand(command, false);
@@ -32,8 +32,10 @@ import Input from 'sap/m/Input';
32
32
  import ManagedObject from 'sap/ui/base/ManagedObject';
33
33
  import Control from 'sap/ui/core/Control';
34
34
  import { ValueState } from 'sap/ui/core/library';
35
+ import Change from 'sap/ui/fl/Change';
35
36
  import { QuickActionTelemetryData } from '../../cpe/quick-actions/quick-action-definition.js';
36
37
  import { setAdditionalChangeInfoForChangeFile } from '../../utils/additional-change-info.js';
38
+ import { getChangeDefinition } from '../../utils/changes.js';
37
39
  import { getError } from '../../utils/error.js';
38
40
  import { sendInfoCenterMessage } from '../../utils/info-center-message.js';
39
41
  import { getFragments } from '../api-handler.js';
@@ -295,8 +297,10 @@ export default class AddTableColumnFragments extends BaseDialog<AddTableColumnsF
295
297
 
296
298
  const templateName =
297
299
  fragment.targetAggregation === COLUMNS_AGGREGATION ? `V2_SMART_TABLE_COLUMN` : 'V2_SMART_TABLE_CELL';
298
- const preparedChange = command.getPreparedChange();
299
- setAdditionalChangeInfoForChangeFile(preparedChange.getDefinition().fileName, { templateName });
300
+ const preparedChange =
301
+ command.getPreparedChange() as unknown as Change<AddTableCellFragmentChangeContentType>;
302
+ const { fileName } = getChangeDefinition(preparedChange);
303
+ setAdditionalChangeInfoForChangeFile(fileName, { templateName });
300
304
  compositeCommand.addCommand(command, false);
301
305
  }
302
306
 
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- sap.ui.define(["sap/ui/fl/Utils", "../utils/core", "../utils/version"], function (FlexUtils, ___utils_corejs, ___utils_versionjs) {
3
+ sap.ui.define(["sap/ui/fl/Utils", "../utils/core", "../utils/changes", "../utils/version"], function (FlexUtils, ___utils_core, ___utils_changesjs, ___utils_versionjs) {
4
4
  "use strict";
5
5
 
6
6
  function __ui5_require_async(path) {
@@ -20,7 +20,9 @@ sap.ui.define(["sap/ui/fl/Utils", "../utils/core", "../utils/version"], function
20
20
  });
21
21
  });
22
22
  }
23
- const getControlById = ___utils_corejs["getControlById"];
23
+ const getControlById = ___utils_core["getControlById"];
24
+ const getChangeDefinition = ___utils_changesjs["getChangeDefinition"];
25
+ const getFlexChangeList = ___utils_changesjs["getFlexChangeList"];
24
26
  const isLowerThanMinimalUi5Version = ___utils_versionjs["isLowerThanMinimalUi5Version"];
25
27
  let reuseComponentChecker;
26
28
 
@@ -99,15 +101,11 @@ sap.ui.define(["sap/ui/fl/Utils", "../utils/core", "../utils/version"], function
99
101
  * @returns {boolean} Returns true if the command's change contains the specified property with the matching value; otherwise, returns false.
100
102
  */
101
103
  function matchesChangeProperty(command, propertyPath, propertyValue) {
102
- if (typeof command.getPreparedChange !== 'function') {
103
- return false;
104
- }
105
- const change = command.getPreparedChange()?.getDefinition?.();
106
- if (!change) {
107
- return false;
108
- }
109
- const nestedProperty = getNestedProperty(change, propertyPath);
110
- return typeof nestedProperty === 'string' ? nestedProperty.includes(propertyValue) : false;
104
+ return getFlexChangeList(command).some(change => {
105
+ const changeDefinition = getChangeDefinition(change);
106
+ const nestedProperty = getNestedProperty(changeDefinition, propertyPath);
107
+ return typeof nestedProperty === 'string' ? nestedProperty.includes(propertyValue) : false;
108
+ });
111
109
  }
112
110
  /**
113
111
  * Gets controller name and view ID for the given UI5 control.
@@ -1,12 +1,13 @@
1
- import type FlexCommand from 'sap/ui/rta/command/FlexCommand';
2
1
  import type ManagedObject from 'sap/ui/base/ManagedObject';
3
2
  import type ElementOverlay from 'sap/ui/dt/ElementOverlay';
4
3
  import FlexUtils from 'sap/ui/fl/Utils';
5
- import IsReuseComponentApi from 'sap/ui/rta/util/isReuseComponent';
6
- import { getControlById } from '../utils/core.js';
4
+ import type FlexCommand from 'sap/ui/rta/command/FlexCommand';
7
5
  import type { Manifest } from 'sap/ui/rta/RuntimeAuthoring';
8
6
  import RuntimeAuthoring from 'sap/ui/rta/RuntimeAuthoring';
7
+ import IsReuseComponentApi from 'sap/ui/rta/util/isReuseComponent';
8
+ import { getControlById } from '../utils/core';
9
9
 
10
+ import { getChangeDefinition, getFlexChangeList } from '../utils/changes.js';
10
11
  import { isLowerThanMinimalUi5Version, Ui5VersionInfo } from '../utils/version.js';
11
12
 
12
13
  export interface Deferred<T> {
@@ -106,16 +107,11 @@ export function getNestedProperty(obj: object, path: string): unknown {
106
107
  * @returns {boolean} Returns true if the command's change contains the specified property with the matching value; otherwise, returns false.
107
108
  */
108
109
  export function matchesChangeProperty(command: FlexCommand, propertyPath: string, propertyValue: string): boolean {
109
- if (typeof command.getPreparedChange !== 'function') {
110
- return false;
111
- }
112
- const change = command.getPreparedChange()?.getDefinition?.();
113
- if (!change) {
114
- return false;
115
- }
116
-
117
- const nestedProperty = getNestedProperty(change, propertyPath);
118
- return typeof nestedProperty === 'string' ? nestedProperty.includes(propertyValue) : false;
110
+ return getFlexChangeList(command).some((change) => {
111
+ const changeDefinition = getChangeDefinition(change);
112
+ const nestedProperty = getNestedProperty(changeDefinition, propertyPath);
113
+ return typeof nestedProperty === 'string' ? nestedProperty.includes(propertyValue) : false;
114
+ });
119
115
  }
120
116
  interface ControllerInfo {
121
117
  controllerName: string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- sap.ui.define(["open/ux/preview/client/thirdparty/@sap-ux-private/control-property-editor-common", "sap/base/Log", "../../i18n", "../../utils/additional-change-info", "../../utils/core", "../../utils/error", "../../utils/info-center-message", "../rta-service", "./flex-change", "./generic-change"], function (___sap_ux_private_control_property_editor_common, Log, ____i18njs, ____utils_additional_change_infojs, ____utils_corejs, ____utils_errorjs, ____utils_info_center_messagejs, ___rta_servicejs, ___flex_changejs, ___generic_changejs) {
3
+ sap.ui.define(["open/ux/preview/client/thirdparty/@sap-ux-private/control-property-editor-common", "sap/base/Log", "../../i18n", "../../utils/additional-change-info", "../../utils/changes", "../../utils/core", "../../utils/error", "../../utils/info-center-message", "../rta-service", "./flex-change", "./generic-change"], function (___sap_ux_private_control_property_editor_common, Log, ____i18njs, ____utils_additional_change_infojs, ____utils_changesjs, ____utils_corejs, ____utils_errorjs, ____utils_info_center_messagejs, ___rta_servicejs, ___flex_changejs, ___generic_changejs) {
4
4
  "use strict";
5
5
 
6
6
  const changeProperty = ___sap_ux_private_control_property_editor_common["changeProperty"];
@@ -17,6 +17,9 @@ sap.ui.define(["open/ux/preview/client/thirdparty/@sap-ux-private/control-proper
17
17
  const UNKNOWN_CHANGE_KIND = ___sap_ux_private_control_property_editor_common["UNKNOWN_CHANGE_KIND"];
18
18
  const getTextBundle = ____i18njs["getTextBundle"];
19
19
  const setAdditionalChangeInfo = ____utils_additional_change_infojs["setAdditionalChangeInfo"];
20
+ const getChangeDefinition = ____utils_changesjs["getChangeDefinition"];
21
+ const getFlexChangeList = ____utils_changesjs["getFlexChangeList"];
22
+ const getFlexXMLChangeList = ____utils_changesjs["getFlexXMLChangeList"];
20
23
  const getControlById = ____utils_corejs["getControlById"];
21
24
  const isA = ____utils_corejs["isA"];
22
25
  const getError = ____utils_errorjs["getError"];
@@ -354,11 +357,11 @@ sap.ui.define(["open/ux/preview/client/thirdparty/@sap-ux-private/control-proper
354
357
  * @returns {Promise<void>} A promise that resolves when the command is handled.
355
358
  */
356
359
  async handleCommand(command, inactiveCommandCount, index, pendingChanges) {
357
- setAdditionalChangeInfo(command?.getPreparedChange?.(), this.options.rta.getRootControlInstance());
358
- const pendingChange = await this.prepareChangeType(command, inactiveCommandCount, index);
359
- if (pendingChange) {
360
- pendingChanges.push(pendingChange);
361
- }
360
+ const flexXMLChanges = getFlexXMLChangeList(command);
361
+ const rootController = this.options.rta.getRootControlInstance();
362
+ setAdditionalChangeInfo(flexXMLChanges, rootController);
363
+ const changes = await this.buildPendingChanges(command, inactiveCommandCount, index);
364
+ pendingChanges.push(...changes);
362
365
  }
363
366
  trackPendingConfigChanges(result) {
364
367
  for (const id of result?.controlId ?? []) {
@@ -377,11 +380,11 @@ sap.ui.define(["open/ux/preview/client/thirdparty/@sap-ux-private/control-proper
377
380
  * @param changeType - the change type string
378
381
  * @param handler - the generic change handler
379
382
  * @param isActive - whether the change is currently active
380
- * @param fileName - file name of the change
381
383
  * @param textBundle - i18n text bundle
382
384
  * @returns Promise resolving to PendingGenericChange
383
385
  */
384
- async buildGenericChange(changeDefinition, changeType, handler, isActive, fileName, textBundle) {
386
+ async buildGenericChange(changeDefinition, changeType, handler, isActive, textBundle) {
387
+ const fileName = changeDefinition.fileName;
385
388
  const {
386
389
  properties,
387
390
  changeTitle,
@@ -453,31 +456,27 @@ sap.ui.define(["open/ux/preview/client/thirdparty/@sap-ux-private/control-proper
453
456
  }
454
457
 
455
458
  /**
456
- * Prepares the type of change based on the command and other parameters.
459
+ * Build the list of pending changes based on the command and other parameters.
457
460
  *
458
461
  * @param {FlexCommand} command - The command to process.
459
462
  * @param {number} inactiveCommandCount - The number of inactive commands.
460
463
  * @param {number} index - The index of the current command being processed.
461
- * @returns {Promise<PendingChange | undefined>} - A promise that resolves to a `PendingChange` or `undefined`.
464
+ * @returns {Promise<PendingChange[]>} - A promise that resolves to a `PendingChange` list.
462
465
  */
463
- async prepareChangeType(command, inactiveCommandCount, index) {
464
- const change = command?.getPreparedChange?.();
466
+ async buildPendingChanges(command, inactiveCommandCount, index) {
465
467
  const textBundle = await getTextBundle();
466
- const selectorId = typeof change?.getSelector === 'function' ? await getControlIdByChange(change, this.options.rta.getRootControlInstance()) : this.getCommandSelectorId(command);
467
- const changeType = this.getCommandChangeType(command);
468
- if (!changeType) {
469
- return undefined;
470
- }
471
- const changeDefinition = change.getDefinition ? change.getDefinition() : change.getJson();
472
- const {
473
- fileName
474
- } = changeDefinition;
475
- const handler = GENERIC_CHANGE_HANDLER[changeType];
476
- const isActive = index >= inactiveCommandCount;
477
- if (handler) {
478
- return this.buildGenericChange(changeDefinition, changeType, handler, isActive, fileName, textBundle);
479
- }
480
- return this.buildFallbackChange(changeType, selectorId, isActive, fileName);
468
+ const rootController = this.options.rta.getRootControlInstance();
469
+ return Promise.all(getFlexChangeList(command).map(async change => {
470
+ const selectorId = typeof change?.getSelector === 'function' ? await getControlIdByChange(change, rootController) : this.getCommandSelectorId(command);
471
+ const changeType = change.getChangeType?.() ?? command.getChangeType?.();
472
+ const changeDefinition = getChangeDefinition(change);
473
+ const {
474
+ fileName
475
+ } = changeDefinition;
476
+ const handler = GENERIC_CHANGE_HANDLER[changeType];
477
+ const isActive = index >= inactiveCommandCount;
478
+ return handler ? this.buildGenericChange(changeDefinition, changeType, handler, isActive, textBundle) : this.buildFallbackChange(changeType, selectorId, isActive, fileName);
479
+ })).then(pendingChanges => pendingChanges.filter(change => !!change));
481
480
  }
482
481
 
483
482
  /**
@@ -502,16 +501,6 @@ sap.ui.define(["open/ux/preview/client/thirdparty/@sap-ux-private/control-proper
502
501
  return undefined;
503
502
  }
504
503
 
505
- /**
506
- * Get command change type.
507
- *
508
- * @param command to be executed for creating change
509
- * @returns command change type or undefined
510
- */
511
- getCommandChangeType(command) {
512
- return this.retryOperations([() => command.getChangeType(), () => command.getPreparedChange().getDefinition().changeType]);
513
- }
514
-
515
504
  /**
516
505
  * Get command selector id.
517
506
  *
@@ -25,10 +25,11 @@ import {
25
25
  import Log from 'sap/base/Log';
26
26
  import type Event from 'sap/ui/base/Event';
27
27
  import UI5Element from 'sap/ui/core/Element';
28
- import { ChangeDefinition } from 'sap/ui/fl/Change';
28
+ import Change, { ChangeDefinition } from 'sap/ui/fl/Change';
29
29
  import type FlexCommand from 'sap/ui/rta/command/FlexCommand';
30
30
  import { getTextBundle } from '../../i18n.js';
31
31
  import { setAdditionalChangeInfo } from '../../utils/additional-change-info.js';
32
+ import { getChangeDefinition, getFlexChangeList, getFlexXMLChangeList } from '../../utils/changes.js';
32
33
  import { getControlById, isA } from '../../utils/core.js';
33
34
  import { getError } from '../../utils/error.js';
34
35
  import { sendInfoCenterMessage } from '../../utils/info-center-message.js';
@@ -56,6 +57,12 @@ export interface StackChangedEventDetail {
56
57
 
57
58
  type SavedChangesResponse = Record<string, ConfigChange | GenericChange>;
58
59
 
60
+ interface ChangeContent {
61
+ property: string;
62
+ newValue: string;
63
+ newBinding: string;
64
+ }
65
+
59
66
  /**
60
67
  * Modify rta message.
61
68
  *
@@ -416,11 +423,11 @@ export class ChangeService extends EventTarget {
416
423
  index: number,
417
424
  pendingChanges: PendingChange[]
418
425
  ): Promise<void> {
419
- setAdditionalChangeInfo(command?.getPreparedChange?.(), this.options.rta.getRootControlInstance());
420
- const pendingChange = await this.prepareChangeType(command, inactiveCommandCount, index);
421
- if (pendingChange) {
422
- pendingChanges.push(pendingChange);
423
- }
426
+ const flexXMLChanges = getFlexXMLChangeList(command);
427
+ const rootController = this.options.rta.getRootControlInstance();
428
+ setAdditionalChangeInfo(flexXMLChanges, rootController);
429
+ const changes = await this.buildPendingChanges(command, inactiveCommandCount, index);
430
+ pendingChanges.push(...changes);
424
431
  }
425
432
 
426
433
  private trackPendingConfigChanges(result: PendingGenericChange): void {
@@ -440,7 +447,6 @@ export class ChangeService extends EventTarget {
440
447
  * @param changeType - the change type string
441
448
  * @param handler - the generic change handler
442
449
  * @param isActive - whether the change is currently active
443
- * @param fileName - file name of the change
444
450
  * @param textBundle - i18n text bundle
445
451
  * @returns Promise resolving to PendingGenericChange
446
452
  */
@@ -449,9 +455,9 @@ export class ChangeService extends EventTarget {
449
455
  changeType: string,
450
456
  handler: ChangeHandler<GenericChange>,
451
457
  isActive: boolean,
452
- fileName: string,
453
458
  textBundle: Awaited<ReturnType<typeof getTextBundle>>
454
459
  ): Promise<PendingGenericChange> {
460
+ const fileName = changeDefinition.fileName;
455
461
  const {
456
462
  properties,
457
463
  changeTitle,
@@ -522,39 +528,42 @@ export class ChangeService extends EventTarget {
522
528
  }
523
529
 
524
530
  /**
525
- * Prepares the type of change based on the command and other parameters.
531
+ * Build the list of pending changes based on the command and other parameters.
526
532
  *
527
533
  * @param {FlexCommand} command - The command to process.
528
534
  * @param {number} inactiveCommandCount - The number of inactive commands.
529
535
  * @param {number} index - The index of the current command being processed.
530
- * @returns {Promise<PendingChange | undefined>} - A promise that resolves to a `PendingChange` or `undefined`.
536
+ * @returns {Promise<PendingChange[]>} - A promise that resolves to a `PendingChange` list.
531
537
  */
532
- private async prepareChangeType(
538
+ private async buildPendingChanges(
533
539
  command: FlexCommand,
534
540
  inactiveCommandCount: number,
535
541
  index: number
536
- ): Promise<PendingChange | undefined> {
537
- const change = command?.getPreparedChange?.();
542
+ ): Promise<PendingChange[]> {
538
543
  const textBundle = await getTextBundle();
539
- const selectorId =
540
- typeof change?.getSelector === 'function'
541
- ? await getControlIdByChange(change, this.options.rta.getRootControlInstance())
542
- : this.getCommandSelectorId(command);
543
-
544
- const changeType = this.getCommandChangeType(command);
545
-
546
- if (!changeType) {
547
- return undefined;
548
- }
549
-
550
- const changeDefinition = change.getDefinition ? change.getDefinition() : (change.getJson() as ChangeDefinition);
551
- const { fileName } = changeDefinition;
552
- const handler = GENERIC_CHANGE_HANDLER[changeType as ChangeType] as unknown as ChangeHandler<GenericChange>;
553
- const isActive = index >= inactiveCommandCount;
554
- if (handler) {
555
- return this.buildGenericChange(changeDefinition, changeType, handler, isActive, fileName, textBundle);
556
- }
557
- return this.buildFallbackChange(changeType, selectorId, isActive, fileName);
544
+ const rootController = this.options.rta.getRootControlInstance();
545
+ return Promise.all(
546
+ getFlexChangeList(command).map(async (change) => {
547
+ const selectorId =
548
+ typeof change?.getSelector === 'function'
549
+ ? await getControlIdByChange(change as Change<ChangeContent>, rootController)
550
+ : this.getCommandSelectorId(command);
551
+
552
+ const changeType = change.getChangeType?.() ?? command.getChangeType?.();
553
+
554
+ const changeDefinition = getChangeDefinition(change);
555
+ const { fileName } = changeDefinition;
556
+
557
+ const handler = GENERIC_CHANGE_HANDLER[
558
+ changeType as ChangeType
559
+ ] as unknown as ChangeHandler<GenericChange>;
560
+ const isActive = index >= inactiveCommandCount;
561
+
562
+ return handler
563
+ ? this.buildGenericChange(changeDefinition, changeType, handler, isActive, textBundle)
564
+ : this.buildFallbackChange(changeType, selectorId, isActive, fileName);
565
+ })
566
+ ).then((pendingChanges) => pendingChanges.filter((change) => !!change));
558
567
  }
559
568
 
560
569
  /**
@@ -579,19 +588,6 @@ export class ChangeService extends EventTarget {
579
588
  return undefined;
580
589
  }
581
590
 
582
- /**
583
- * Get command change type.
584
- *
585
- * @param command to be executed for creating change
586
- * @returns command change type or undefined
587
- */
588
- private getCommandChangeType(command: FlexCommand): string | undefined {
589
- return this.retryOperations([
590
- () => command.getChangeType(),
591
- () => command.getPreparedChange().getDefinition().changeType
592
- ]);
593
- }
594
-
595
591
  /**
596
592
  * Get command selector id.
597
593
  *
@@ -1,40 +1,37 @@
1
1
  "use strict";
2
2
 
3
- sap.ui.define(["../cpe/additional-change-info/add-xml-additional-info"], function (___cpe_additional_change_info_add_xml_additional_infojs) {
3
+ sap.ui.define(["../cpe/additional-change-info/add-xml-additional-info", "./changes"], function (___cpe_additional_change_info_add_xml_additional_infojs, ___changesjs) {
4
4
  "use strict";
5
5
 
6
6
  const getAddXMLAdditionalInfo = ___cpe_additional_change_info_add_xml_additional_infojs["getAddXMLAdditionalInfo"];
7
+ const getChangeDefinition = ___changesjs["getChangeDefinition"];
7
8
  const additionalChangeInfoMap = new Map();
8
9
 
9
10
  /**
10
11
  * This function is used to set additional change information for a given change.
11
12
  *
12
- * @param change - The change object for which additional information is to be set.
13
+ * @param changes - An array of change objects, for which additional information is to be set.
13
14
  * @param appComponent - The app component (optional), used to resolve controls in projects with local IDs.
14
15
  */
15
- function setAdditionalChangeInfo(change, appComponent) {
16
- if (!change) {
17
- return;
18
- }
19
- let additionalChangeInfo;
20
- const key = change.getDefinition().fileName;
21
- if (change?.getChangeType?.() === 'addXML') {
22
- additionalChangeInfo = getAddXMLAdditionalInfo(change, appComponent);
23
- }
24
- if (additionalChangeInfo) {
25
- const existingInfo = additionalChangeInfoMap.get(key);
26
- if (existingInfo) {
27
- // Merge new info with existing info, keeping existing values and only adding new ones
28
- const mergedInfo = {
29
- ...additionalChangeInfo,
30
- ...existingInfo
31
- };
32
- additionalChangeInfoMap.set(key, mergedInfo);
16
+ function setAdditionalChangeInfo(changes, appComponent) {
17
+ changes.forEach(change => {
18
+ const {
19
+ fileName
20
+ } = getChangeDefinition(change);
21
+ const changeInfo = getAddXMLAdditionalInfo(change, appComponent);
22
+ if (!changeInfo) {
23
+ return;
24
+ }
25
+ if (additionalChangeInfoMap.has(fileName)) {
26
+ const storedChangeInfo = additionalChangeInfoMap.get(fileName);
27
+ additionalChangeInfoMap.set(fileName, {
28
+ ...changeInfo,
29
+ ...storedChangeInfo
30
+ });
33
31
  } else {
34
- // No existing info, set the new info
35
- additionalChangeInfoMap.set(key, additionalChangeInfo);
32
+ additionalChangeInfoMap.set(fileName, changeInfo);
36
33
  }
37
- }
34
+ });
38
35
  }
39
36
  function setAdditionalChangeInfoForChangeFile(fileName, additionalChangeInfo) {
40
37
  additionalChangeInfoMap.set(fileName, additionalChangeInfo);
@@ -1,3 +1,4 @@
1
+ import type Component from 'sap/ui/core/Component';
1
2
  import FlexChange from 'sap/ui/fl/Change';
2
3
  import {
3
4
  getAddXMLAdditionalInfo,
@@ -5,46 +6,38 @@ import {
5
6
  type AddXMLChangeContent
6
7
  } from '../cpe/additional-change-info/add-xml-additional-info.js';
7
8
  import { FlexChange as Change } from '../flp/common.js';
8
- import type Component from 'sap/ui/core/Component';
9
+ import { getChangeDefinition } from './changes.js';
9
10
 
10
11
  export type AdditionalChangeInfo = AddXMLAdditionalInfo | undefined;
12
+ type FlexXMLChange = FlexChange<AddXMLChangeContent>;
11
13
 
12
14
  const additionalChangeInfoMap = new Map<string, AdditionalChangeInfo>();
13
15
 
14
16
  /**
15
17
  * This function is used to set additional change information for a given change.
16
18
  *
17
- * @param change - The change object for which additional information is to be set.
19
+ * @param changes - An array of change objects, for which additional information is to be set.
18
20
  * @param appComponent - The app component (optional), used to resolve controls in projects with local IDs.
19
21
  */
20
- export function setAdditionalChangeInfo(
21
- change: FlexChange<AddXMLChangeContent> | undefined,
22
- appComponent?: Component
23
- ): void {
24
- if (!change) {
25
- return;
26
- }
22
+ export function setAdditionalChangeInfo(changes: FlexXMLChange[], appComponent?: Component): void {
23
+ changes.forEach((change) => {
24
+ const { fileName } = getChangeDefinition(change);
25
+ const changeInfo = getAddXMLAdditionalInfo(change, appComponent);
27
26
 
28
- let additionalChangeInfo;
29
- const key = change.getDefinition().fileName;
30
- if (change?.getChangeType?.() === 'addXML') {
31
- additionalChangeInfo = getAddXMLAdditionalInfo(change, appComponent);
32
- }
27
+ if (!changeInfo) {
28
+ return;
29
+ }
33
30
 
34
- if (additionalChangeInfo) {
35
- const existingInfo = additionalChangeInfoMap.get(key);
36
- if (existingInfo) {
37
- // Merge new info with existing info, keeping existing values and only adding new ones
38
- const mergedInfo = {
39
- ...additionalChangeInfo,
40
- ...existingInfo
41
- };
42
- additionalChangeInfoMap.set(key, mergedInfo);
31
+ if (additionalChangeInfoMap.has(fileName)) {
32
+ const storedChangeInfo = additionalChangeInfoMap.get(fileName);
33
+ additionalChangeInfoMap.set(fileName, {
34
+ ...changeInfo,
35
+ ...storedChangeInfo
36
+ });
43
37
  } else {
44
- // No existing info, set the new info
45
- additionalChangeInfoMap.set(key, additionalChangeInfo);
38
+ additionalChangeInfoMap.set(fileName, changeInfo);
46
39
  }
47
- }
40
+ });
48
41
  }
49
42
 
50
43
  export function setAdditionalChangeInfoForChangeFile(
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+
3
+ sap.ui.define(["../cpe/changes/generic-change"], function (___cpe_changes_generic_change) {
4
+ "use strict";
5
+
6
+ const ADD_XML_CHANGE = ___cpe_changes_generic_change["ADD_XML_CHANGE"];
7
+ /**
8
+ * Extracts the list of changes from a flex command.
9
+ *
10
+ * @param command - The flex command from which to extract changes.
11
+ * @returns An array of flex changes associated with the command, or an empty array if no changes are available.
12
+ */
13
+ function getFlexChangeList(command) {
14
+ const changes = command?.getPreparedChange?.();
15
+ if (!changes) {
16
+ return [];
17
+ }
18
+ return Array.isArray(changes) ? changes : [changes];
19
+ }
20
+
21
+ /**
22
+ * Extracts the list of XML changes from a flex command, filtering only changes of type 'addXML'.
23
+ *
24
+ * @param command - The flex command from which to extract XML changes.
25
+ * @returns An array of flex XML changes associated with the command.
26
+ */
27
+ function getFlexXMLChangeList(command) {
28
+ return getFlexChangeList(command).filter(change => change.getChangeType?.() === ADD_XML_CHANGE);
29
+ }
30
+
31
+ /**
32
+ * Retrieves the change definition from a flex change object, supporting both modern and legacy UI5 APIs.
33
+ *
34
+ * In UI5 2.x, the change base class is a FlexObject exposing `convertToFileContent()`.
35
+ * In older UI5 versions (e.g. 1.96.x), the base class is `Change` which uses the now-deprecated
36
+ * `getDefinition()` method and lacks `convertToFileContent`. This function falls back to
37
+ * `getDefinition()` for backward compatibility with those older versions.
38
+ *
39
+ * @param change - The flex change object (modern FlexObject or legacy Change instance).
40
+ * @returns The change definition.
41
+ * @throws Error if the change object supports neither API.
42
+ */
43
+ function getChangeDefinition(change) {
44
+ if ('convertToFileContent' in change) {
45
+ return change.convertToFileContent();
46
+ }
47
+ if ('getDefinition' in change) {
48
+ return change.getDefinition();
49
+ }
50
+ throw new Error('Unsupported change object');
51
+ }
52
+ var __exports = {
53
+ __esModule: true
54
+ };
55
+ __exports.getFlexChangeList = getFlexChangeList;
56
+ __exports.getFlexXMLChangeList = getFlexXMLChangeList;
57
+ __exports.getChangeDefinition = getChangeDefinition;
58
+ return __exports;
59
+ });
60
+ //# sourceMappingURL=changes.js.map
@@ -0,0 +1,60 @@
1
+ import FlexChange, { ChangeDefinition } from 'sap/ui/fl/Change';
2
+ import FlexCommand from 'sap/ui/rta/command/FlexCommand';
3
+ import { AddXMLChangeContent } from '../cpe/additional-change-info/add-xml-additional-info.js';
4
+ import { ADD_XML_CHANGE } from '../cpe/changes/generic-change';
5
+
6
+ type FlexXMLChange = FlexChange<AddXMLChangeContent>;
7
+ type FlexBaseChange = Omit<FlexChange<unknown>, 'setContent'>;
8
+ interface FlexLegacyBaseChange {
9
+ getDefinition: () => ChangeDefinition;
10
+ }
11
+
12
+ /**
13
+ * Extracts the list of changes from a flex command.
14
+ *
15
+ * @param command - The flex command from which to extract changes.
16
+ * @returns An array of flex changes associated with the command, or an empty array if no changes are available.
17
+ */
18
+ export function getFlexChangeList(command?: FlexCommand): FlexBaseChange[] {
19
+ const changes = command?.getPreparedChange?.();
20
+ if (!changes) {
21
+ return [];
22
+ }
23
+ return Array.isArray(changes) ? changes : [changes];
24
+ }
25
+
26
+ /**
27
+ * Extracts the list of XML changes from a flex command, filtering only changes of type 'addXML'.
28
+ *
29
+ * @param command - The flex command from which to extract XML changes.
30
+ * @returns An array of flex XML changes associated with the command.
31
+ */
32
+ export function getFlexXMLChangeList(command?: FlexCommand): FlexXMLChange[] {
33
+ return getFlexChangeList(command).filter(
34
+ (change): change is FlexXMLChange => change.getChangeType?.() === ADD_XML_CHANGE
35
+ );
36
+ }
37
+
38
+ /**
39
+ * Retrieves the change definition from a flex change object, supporting both modern and legacy UI5 APIs.
40
+ *
41
+ * In UI5 2.x, the change base class is a FlexObject exposing `convertToFileContent()`.
42
+ * In older UI5 versions (e.g. 1.96.x), the base class is `Change` which uses the now-deprecated
43
+ * `getDefinition()` method and lacks `convertToFileContent`. This function falls back to
44
+ * `getDefinition()` for backward compatibility with those older versions.
45
+ *
46
+ * @param change - The flex change object (modern FlexObject or legacy Change instance).
47
+ * @returns The change definition.
48
+ * @throws Error if the change object supports neither API.
49
+ */
50
+ export function getChangeDefinition(change: FlexBaseChange | FlexLegacyBaseChange): ChangeDefinition {
51
+ if ('convertToFileContent' in change) {
52
+ return change.convertToFileContent();
53
+ }
54
+
55
+ if ('getDefinition' in change) {
56
+ return change.getDefinition();
57
+ }
58
+
59
+ throw new Error('Unsupported change object');
60
+ }
@@ -98,7 +98,7 @@ export type TestConfigDefaults = {
98
98
  opa5: {
99
99
  path: '/test/opaTests.qunit.html';
100
100
  init: '/test/opaTests.qunit.js';
101
- pattern: '/test/**/*Journey.{js,ts}';
101
+ pattern: '/test/**/*Journey{,.gen}.{js,ts}';
102
102
  framework: 'OPA5';
103
103
  };
104
104
  testsuite: {
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "bugs": {
11
11
  "url": "https://github.com/SAP/open-ux-tools/issues?q=is%3Aopen+is%3Aissue+label%3Abug+label%3Apreview-middleware"
12
12
  },
13
- "version": "1.0.18",
13
+ "version": "1.0.21",
14
14
  "license": "Apache-2.0",
15
15
  "author": "@SAP/ux-tools-team",
16
16
  "main": "dist/index.js",
@@ -28,7 +28,7 @@
28
28
  "mem-fs-editor": "9.4.0",
29
29
  "qrcode": "1.5.4",
30
30
  "@sap/bas-sdk": "3.13.7",
31
- "@sap-ux/adp-tooling": "1.0.14",
31
+ "@sap-ux/adp-tooling": "1.0.15",
32
32
  "@sap-ux/btp-utils": "2.0.2",
33
33
  "@sap-ux/control-property-editor-sources": "npm:@sap-ux/control-property-editor@1.0.5",
34
34
  "@sap-ux/feature-toggle": "1.0.2",
@@ -54,7 +54,7 @@
54
54
  "nock": "14.0.11",
55
55
  "npm-run-all2": "8.0.4",
56
56
  "supertest": "7.2.2",
57
- "@private/preview-middleware-client": "npm:@sap-ux-private/preview-middleware-client@1.0.18",
57
+ "@private/preview-middleware-client": "npm:@sap-ux-private/preview-middleware-client@1.0.21",
58
58
  "@sap-ux-private/playwright": "1.0.2",
59
59
  "@sap-ux/axios-extension": "2.0.3",
60
60
  "@sap-ux/store": "2.0.1",