@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
238 lines (211 loc) • 9.05 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2026 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
sap.ui.define([
"sap/base/util/isPlainObject",
'sap/ui/test/matchers/Matcher',
'sap/ui/model/StaticBinding'
], function (isPlainObject, Matcher, StaticBinding) {
"use strict";
/**
* @class
* Checks if a control has a binding context with the exact same binding path.
*
* As of version 1.60, comparison is strict and can include one or more binding criteria:
* <ul>
* <li>context path (matches children of bound controls, eg: items in a table)</li>
* <li>property path (matches controls with no context and a single bound property, eg: Text with binding for property text)</li>
* <li>context path + property path (matches children of bound controls, where the child has a binding for a certain property within the context)</li>
* <li>as of version 1.86, static value is also accepted. Use this only for {@link sap.ui.model.StaticBinding} </li>
* </ul>
*
* <b>Note:</b> Before version 1.60, the only available criteria is binding context path.
*
* As of version 1.72, it is available as a declarative matcher with the following syntax:
* <code><pre>{
* bindingPath: {
* path: "string",
* modelName: "string",
* propertyPath: "string"
* }
* }
* </code></pre>
* As of version 1.81, you can use regular expressions in declarative syntax:
* <code><pre>{
* bindingPath: {
* path: {
* regex: {
* source: "binding.*PathValue$",
* flags: "ig"
* }
* }
* }
* }
* </code></pre>
*
* @extends sap.ui.test.matchers.Matcher
* @param {object} [mSettings] Map/JSON-object with initial settings for the new BindingPath.
* @public
* @name sap.ui.test.matchers.BindingPath
* @author SAP SE
* @since 1.32
*/
return Matcher.extend("sap.ui.test.matchers.BindingPath", /** @lends sap.ui.test.matchers.BindingPath.prototype */ {
metadata: {
publicMethods: ["isMatching"],
properties: {
/**
* The value of the binding context path that is used for matching.
* As of version 1.81, it can also be a regular expression.
*/
path: {
type: "any"
},
/**
* The name of the binding model that is used for matching.
*/
modelName: {
type: "string"
},
/**
* The value of the binding property path that is used for matching.
* If (context) path is also set, propertyPath will be assumed to be relative to the binding context path
* As of version 1.81, it can also be a regular expression.
* @since 1.60
*/
propertyPath: {
type: "any"
},
/**
* value of a static binding property. Use this only for {@link sap.ui.model.StaticBinding}
* @since 1.86
*/
value: {
type: "any"
}
}
},
/**
* Checks if the control has a binding with matching path
*
* @param {sap.ui.core.Control} oControl the control that is checked by the matcher
* @return {boolean} true if the binding values match strictly
* @public
*/
isMatching: function (oControl) {
var sModelName = this.getModelName() || undefined; // ensure nameless models will be retrieved
var sMatcherPropertyPath = this.getPropertyPath();
var sMatcherContextPath = this.getPath();
var vMatcherStaticValue = this.getValue();
if (!sMatcherContextPath && !sMatcherPropertyPath && !vMatcherStaticValue) {
this._oLogger.debug("Matcher requires context path, property path or value but none is defined! No controls will be matched");
return false;
}
var bContextMatches = true;
var bPropertyPathMatches = true;
var bStaticValueMatches = true;
var oObjectBindingInfo = oControl.mObjectBindingInfos && oControl.mObjectBindingInfos[sModelName];
var oBindingContext = oControl.getBindingContext(sModelName);
if (sMatcherContextPath) {
if (oObjectBindingInfo) {
bContextMatches = _pathMatches(oObjectBindingInfo.path, sMatcherContextPath);
if (bContextMatches) {
this._oLogger.debug("Control '" + oControl + "' has object binding with the expected context path '" +
sMatcherContextPath + "' for model '" + sModelName + "'");
} else {
this._oLogger.debug("Control '" + oControl + "' has object binding with context path '" +
oObjectBindingInfo.path + "' for model '" + sModelName + "' but should have context path '" + sMatcherContextPath + "'");
}
} else {
bContextMatches = !!oBindingContext && _pathMatches(oBindingContext.getPath(), sMatcherContextPath);
if (bContextMatches) {
this._oLogger.debug("Control '" + oControl + "' has binding context with the expected path '" +
sMatcherContextPath + "' for model '" + sModelName + "'");
} else if (oBindingContext){
this._oLogger.debug("Control '" + oControl + "' has binding context with path '" +
oBindingContext.getPath() + "' for model '" + sModelName + "' but should have context path '" + sMatcherContextPath + "'");
} else {
this._oLogger.debug("Control '" + oControl + "' does not have a binding context for model '" + sModelName +
"' but should have a binding context with path '" + sMatcherContextPath + "'");
}
}
}
if (sMatcherPropertyPath) {
var aActualPathsForModel = [];
var aMatchingBindingInfos = Object.keys(oControl.mBindingInfos).filter(function (sBinding) {
var mBindingInfo = oControl.mBindingInfos[sBinding];
var aBindingParts = mBindingInfo.parts ? mBindingInfo.parts : [mBindingInfo];
var aMatchingParts = aBindingParts.filter(function (mPart) {
var bPathMatches = _pathMatches(mPart.path, sMatcherPropertyPath, !!oBindingContext);
var bModelMatches = oObjectBindingInfo || mPart.model === sModelName;
if (!bPathMatches && bModelMatches) {
// for bindings to the matching model, save the actual paths for debug logging
aActualPathsForModel.push(mPart.path);
}
return bPathMatches && bModelMatches;
});
return !!aMatchingParts.length;
});
bPropertyPathMatches = !!aMatchingBindingInfos.length;
if (bPropertyPathMatches) {
this._oLogger.debug("Control '" + oControl + "' has the expected binding property path '" +
sMatcherPropertyPath + "' for model '" + sModelName + "'");
} else if (aActualPathsForModel.length){
this._oLogger.debug("Control '" + oControl + "' has binding property paths ['" +
aActualPathsForModel.join("', '") + "'] for model '" + sModelName + "' but should have binding property path '" + sMatcherPropertyPath + "'");
} else {
this._oLogger.debug("Control '" + oControl + "' has no binding property paths for model '" + sModelName +
"' but should have binding property path '" + sMatcherPropertyPath + "'");
}
}
if (vMatcherStaticValue) {
var aMatchingBindingInfos = Object.keys(oControl.mBindingInfos).filter(function (sBinding) {
var oBinding = oControl.getBinding(sBinding);
var mBindingInfo = oControl.mBindingInfos[sBinding];
var aBindingParts = mBindingInfo.parts ? mBindingInfo.parts : [mBindingInfo];
var aMatchingParts = aBindingParts.filter(function (mPart, index) {
var oPartBinding = oBinding.getBindings ? oBinding.getBindings()[index] : oBinding;
return oPartBinding instanceof StaticBinding && oPartBinding.getValue() === vMatcherStaticValue;
});
return !!aMatchingParts.length;
});
bStaticValueMatches = !!aMatchingBindingInfos.length;
if (bStaticValueMatches) {
this._oLogger.debug("Control '" + oControl + "' has the expected static binding value '" + vMatcherStaticValue + "'");
}
}
return bContextMatches && bPropertyPathMatches;
}
});
function _pathMatches(sPath, vMatcherPath, bWithContext) {
if (isPlainObject(vMatcherPath) && vMatcherPath.regex && vMatcherPath.regex.source) {
// declarative syntax
vMatcherPath = new RegExp(vMatcherPath.regex.source, vMatcherPath.regex.flags);
}
// paths are set in test window (on matcher instantiation) -> match them against the test context's RegExp constructor
if (vMatcherPath instanceof RegExp) {
var oMatcherRegex;
var aDelimiterMatch = vMatcherPath.source.match(/\^?\//);
if (bWithContext && aDelimiterMatch) {
oMatcherRegex = new RegExp(vMatcherPath.source.substr(aDelimiterMatch.index + 1), vMatcherPath.flags);
} else if (!bWithContext && !aDelimiterMatch) {
oMatcherRegex = new RegExp("\/" + vMatcherPath.source, vMatcherPath.flags);
} else {
oMatcherRegex = vMatcherPath;
}
return oMatcherRegex.test(sPath);
} else if (sPath) {
var bHasDelimiter = sPath.charAt(0) === "/";
if (bWithContext && bHasDelimiter) {
vMatcherPath = vMatcherPath.substr(1);
} else if (!bWithContext && !bHasDelimiter) {
vMatcherPath = "/" + vMatcherPath;
}
return sPath === vMatcherPath;
} else {
return false;
}
}
});