@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,089 lines (951 loc) • 34.3 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2023 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
/*global QUnit, Date:true */
/**
* SAPUI5 test utilities
*
* @namespace
* @name sap.ui.test
* @public
*/
// The module ID argument is given because QUnitUtils.js often was included as a script Element in the past.
// It is now recommended to use it via a module dependency (sap.ui.define).
sap.ui.define('sap/ui/qunit/QUnitUtils', [
'jquery.sap.global',
'sap/base/util/ObjectPath',
'sap/ui/base/DataType',
'sap/ui/events/KeyCodes',
"sap/base/strings/camelize",
"sap/base/strings/capitalize",
"sap/base/util/UriParameters",
"sap/base/Log",
"sap/ui/core/Element",
"sap/ui/dom/jquery/control" // jQuery Plugin "control"
],
function(
jQuery,
ObjectPath,
DataType,
KeyCodes,
camelize,
capitalize,
UriParameters,
Log,
Element
) {
"use strict";
if ( typeof QUnit !== 'undefined' ) {
// any version < 2.0 activates legacy support
// note that the strange negated condition properly handles NaN
var bLegacySupport = !(parseFloat(QUnit.version) >= 2.0);
// extract the URL parameters
var mParams = UriParameters.fromQuery(window.location.search);
if ( bLegacySupport ) {
// TODO: Remove deprecated code once all projects adapted
QUnit.equals = window.equals = window.equal;
}
// Set a timeout for all tests, either to a value given via URL
// or - when no other value has been configured yet - to a static default
var sTimeout = mParams.get("sap-ui-qunittimeout");
if (sTimeout != null || !("testTimeout" in QUnit.config)) {
if (!sTimeout || isNaN(sTimeout)) {
sTimeout = "30000"; // 30s: default timeout of an individual QUnit test!
}
QUnit.config.testTimeout = parseInt(sTimeout);
}
if ( bLegacySupport ) {
// Do not reorder tests, as most of the tests depend on each other
QUnit.config.reorder = false;
}
// only when instrumentation is done on server-side blanket itself doesn't
// take care about rendering the report - in this case we do it manually
// when the URL parameter "coverage-report" is set to true or x
if (window["sap-ui-qunit-coverage"] !== "client" && /x|true/i.test(mParams.get("coverage-report"))) {
QUnit.done(function(failures, total) {
// only when coverage is available
if (window._$blanket) {
// we remove the QUnit object to avoid blanket to automatically
// trigger start on QUnit which leads to failures in qunit-reporter-junit
var QUnit = window.QUnit;
window.QUnit = undefined;
// load the blanket instance
sap.ui.requireSync("sap/ui/thirdparty/blanket"); // legacy-relevant
// restore the QUnit object
window.QUnit = QUnit;
// trigger blanket to display the coverage report
window.blanket.report({});
}
});
}
}
// Re-implement jQuery.now to always delegate to Date.now.
//
// Otherwise, fake timers that are installed after jQuery don't work with jQuery animations
// as those animations internally use jQuery.now which then is a reference to the original,
// native Date.now.
jQuery.now = function() {
return Date.now();
};
/**
* Contains helper functionality for QUnit tests.
*
* @namespace
* @alias sap.ui.test.qunit
* @public
*/
var QUtils = {};
/**
* Delays the start of the test until everything is rendered or - if given - for the specified milliseconds.
* This function must be called before the first test function.
*
* @param {int} [iDelay] optional delay in milliseconds
*
* @public
*/
QUtils.delayTestStart = function(iDelay){
QUnit.config.autostart = false;
if (iDelay) {
window.setTimeout(function() {
QUnit.start();
}, iDelay);
} else {
jQuery(function() {
QUnit.start();
});
}
};
var fixOriginalEvent = jQuery.noop;
try {
// check whether preventDefault throws an error for a dummy event
new jQuery.Event({type: "mousedown"}).preventDefault();
} catch (e) {
// if so, we might be running on top of jQuery 2.2.0 or higher and we have to add the native Event methods to the 'originalEvent'
fixOriginalEvent = function(origEvent) {
if ( origEvent ) {
origEvent.preventDefault = origEvent.preventDefault || jQuery.noop;
origEvent.stopPropagation = origEvent.stopPropagation || jQuery.noop;
origEvent.stopImmediatePropagation = origEvent.stopImmediatePropagation || jQuery.noop;
}
};
var OrigjQEvent = jQuery.Event;
jQuery.Event = function(src, props) {
var event = new OrigjQEvent(src, props);
fixOriginalEvent(event.originalEvent);
return event;
};
jQuery.Event.prototype = OrigjQEvent.prototype;
}
/*
* Creates a fake event of type jQuery.Event, according to current UI5 practices; it will always
* contain a pseudo browser event (property <code>originalEvent</code>).
*
* Please note that the <code>originalEvent</code> could be created as a native browser event (class <code>Event</code>)
* as some existing test cases specify a <code>target</code> property which is readonly in the <code>Event</code> class.
*
* Any given <code>oParams</code> are added to the new <code>jQuery.Event</code> as well as to its <code>originalEvent</code>
* object. To be compatible with older versions, this function does not propagate properties to <code>originalEvent</code> when
* they are given before an eventual <code>originalEvent</code> property in <code>oParams</code>.
*
* @param {string} sEventName mandatory name (type) of the newly created event
* @param {Element} [oTarget] optional target of the event
* @param {object} [oParams] optional map of properties to be added to the event
*/
function fakeEvent(sEventName, oTarget, oParams) {
var oEvent = jQuery.Event({type : sEventName});
if ( oTarget != null ) {
oEvent.target = oTarget;
}
if (oParams) {
for (var x in oParams) {
// propagate property to event
oEvent[x] = oParams[x];
if ( x === 'originalEvent' ) {
// if 'originalEvent' has been changed, fix it
fixOriginalEvent(oEvent[x]);
} else {
// otherwise propagate property to 'originalEvent' as well
oEvent.originalEvent[x] = oParams[x];
}
}
}
return oEvent;
}
/**
* Programmatically triggers an event specified by its name on a specified target with some optional parameters.
* @see http://api.jquery.com/trigger/
*
* @param {string} sEventName The name of the browser event (like "click")
* @param {string | Element} oTarget The ID of a DOM element or a DOM element which serves as target of the event
* @param {object} [oParams] The parameters which should be attached to the event in JSON notation (depending on the event type).
* @public
*/
QUtils.triggerEvent = function(sEventName, oTarget, oParams) {
if (typeof (oTarget) == "string") {
oTarget = oTarget ? document.getElementById(oTarget) : null;
}
var oEvent = fakeEvent(sEventName, /* no target */ null, oParams);
jQuery(oTarget).trigger(oEvent);
};
var fnClosestTo = Element.closestTo && Element.closestTo.bind(Element);
/**
* @deprecated Since 1.106
*/
if ( fnClosestTo == null ) {
fnClosestTo = function(oElement) {
return jQuery(oElement).control(0); // legacy-relevant: fallback for older UI5 versions
};
}
/**
* Programmatically triggers a touch event specified by its name.
* The onEVENTNAME functions are called directly on the "nearest" control / element of the given target.
*
* @param {string} sEventName The name of the touch event (touchstart, touchmove, touchend)
* @param {string | Element} oTarget The ID of a DOM element or a DOM element which serves as target of the event
* @param {object} [oParams] The parameters which should be attached to the event in JSON notation (depending on the event type).
* @param {string} [sEventHandlerPrefix='on'] prefix to use for the event handler name, defaults to 'on'
* @public
*/
QUtils.triggerTouchEvent = function(sEventName, oTarget, oParams, sEventHandlerPrefix) {
if (typeof (oTarget) == "string") {
oTarget = oTarget ? document.getElementById(oTarget) : null;
}
var oEvent = fakeEvent(sEventName, oTarget, oParams),
oElement = fnClosestTo(oTarget),
sEventHandlerName = (sEventHandlerPrefix == null ? 'on' : sEventHandlerPrefix) + sEventName;
if (oElement && oElement[sEventHandlerName]) {
oElement[sEventHandlerName].call(oElement, oEvent);
}
};
function mapKeyCodeToLocation(sKey) {
if (!sKey) {
return undefined;
}
if (!isNaN(sKey)) {
var aKeys = Object.keys(KeyCodes).filter(function (sKeyName) {
return KeyCodes[sKeyName] === sKey;
});
if (aKeys.length === 1) {
sKey = aKeys[0];
}
}
if (sKey.toLowerCase().startsWith("numpad_")) {
return "NUMPAD";
}
}
/**
* Maps the input keyCode to key property
* @param sKeyCode {string|Integer} keyCode number or string, e.g. 27 or ESCAPE
* @returns {*} the key property of KeyBoardEvent, e.g. Escape
*/
function mapKeyCodeToKey(sKeyCode) {
// look up number in KeyCodes enum to get the string
if (!isNaN(sKeyCode)) {
sKeyCode = getKeyCodeStringFromNumber(sKeyCode);
}
if (!sKeyCode) {
return undefined;
}
sKeyCode = sKeyCode.toLowerCase();
// replace underscores with dash character such as 'ARROW_LEFT' --> 'ARROW-LEFT' and then camelize it --> 'ArrowLeft'
sKeyCode = camelize(sKeyCode.replace(/_/g, "-"));
// capitalize key
var sKey = capitalize(sKeyCode);
// remove "Digit" and "Numpad" from the resulting string as this info is present within the Location property and not the key property
// e.g. "Digit9" --> "9"
if (sKey.startsWith("Digit")) {
return sKey.substring("Digit".length);
} else if (sKey.startsWith("Numpad")) {
sKey = sKey.substring("Numpad".length);
}
// special handling where KeyCodes[sKeyCode] does not match
// e.g. KeyCodes.BREAK --> 'Pause' instead of 'Break'
switch (sKey) {
case "Break": return "Pause";
case "Space": return " ";
case "Print": return "PrintScreen";
case "Windows": return "Meta";
case "Sleep": return "Standby";
case "TurnOff": return "PowerOff";
case "Asterisk": return "*";
case "Plus": return "+";
case "Minus": return "-";
case "Comma": return ",";
case "Slash": return "/";
case "OpenBracket": return ";";
case "Dot": return ".";
case "Pipe": return "|";
case "Semicolon": return ";";
case "Equals": return "=";
case "SingleQUote": return "=";
case "Backslash": return "\\";
case "GreatAccent": return "`";
default: return sKey;
}
}
/**
* Retrieves keycode string from number
* @param iKeyCode
* @returns {string}
*/
function getKeyCodeStringFromNumber(iKeyCode) {
for (var sKey in KeyCodes) {
if (KeyCodes.hasOwnProperty(sKey)) {
if (KeyCodes[sKey] === iKeyCode) {
return sKey;
}
}
}
}
/**
* Programmatically triggers a keyboard event specified by its name on a specified target.
* @see sap.ui.test.qunit.triggerEvent
*
* @param {string} sEventType The name of the browser keyboard event (like "keydown")
* @param {string | Element} oTarget The ID of a DOM element or a DOM element which serves as target of the event
* @param {string | int} sKey The keys name as defined in {@link sap.ui.events.KeyCodes} or its key code
* @param {boolean} bShiftKey Indicates whether the shift key is down in addition
* @param {boolean} bAltKey Indicates whether the alt key is down in addition
* @param {boolean} bCtrlKey Indicates whether the ctrl key is down in addition
* @public
*/
QUtils.triggerKeyEvent = function(sEventType, oTarget, sKey, bShiftKey, bAltKey, bCtrlKey) {
var oParams = {};
var bKeyIsNumber = !isNaN(sKey);
oParams.keyCode = bKeyIsNumber ? sKey : KeyCodes[sKey];
// set the "key" property
if (bKeyIsNumber) {
// look up number in KeyCodes enum to get the string
sKey = getKeyCodeStringFromNumber(sKey);
}
oParams.key = mapKeyCodeToKey(sKey);
oParams.location = mapKeyCodeToLocation(sKey);
oParams.which = oParams.keyCode;
oParams.shiftKey = !!bShiftKey;
oParams.altKey = !!bAltKey;
oParams.metaKey = !!bCtrlKey;
oParams.ctrlKey = !!bCtrlKey;
QUtils.triggerEvent(sEventType, oTarget, oParams);
};
/**
* Programmatically triggers a 'keydown' event on a specified target.
* @see sap.ui.test.qunit.triggerKeyEvent
*
* @param {string | Element} oTarget The ID of a DOM element or a DOM element which serves as target of the event
* @param {string | int} sKey The keys name as defined in {@link sap.ui.events.KeyCodes} or its key code
* @param {boolean} bShiftKey Indicates whether the shift key is down in addition
* @param {boolean} bAltKey Indicates whether the alt key is down in addition
* @param {boolean} bCtrlKey Indicates whether the ctrl key is down in addition
* @public
*/
QUtils.triggerKeydown = function(oTarget, sKey, bShiftKey, bAltKey, bCtrlKey) {
QUtils.triggerKeyEvent("keydown", oTarget, sKey, bShiftKey, bAltKey, bCtrlKey);
};
/**
* Programmatically triggers a 'keyup' event on a specified target.
* @see sap.ui.test.qunit.triggerKeyEvent
*
* @param {string | Element} oTarget The ID of a DOM element or a DOM element which serves as target of the event
* @param {string | int} sKey The keys name as defined in {@link sap.ui.events.KeyCodes} or its key code
* @param {boolean} bShiftKey Indicates whether the shift key is down in addition
* @param {boolean} bAltKey Indicates whether the alt key is down in addition
* @param {boolean} bCtrlKey Indicates whether the ctrl key is down in addition
* @public
*/
QUtils.triggerKeyup = function(oTarget, sKey, bShiftKey, bAltKey, bCtrlKey) {
QUtils.triggerKeyEvent("keyup", oTarget, sKey, bShiftKey, bAltKey, bCtrlKey);
};
/**
* @param {object} oTarget
* @param {string} sKey
* @param {boolean} bShiftKey
* @param {boolean} bAltKey
* @param {boolean} bCtrlKey
* @deprecated Use <code>sap.ui.test.qunit.triggerKeydown</code> instead.
* @see sap.ui.test.qunit.triggerKeydown
* @public
*/
QUtils.triggerKeyboardEvent = function(oTarget, sKey, bShiftKey, bAltKey, bCtrlKey) {
QUtils.triggerKeydown(oTarget, sKey, bShiftKey, bAltKey, bCtrlKey);
};
/**
* Programmatically triggers a 'keypress' event on a specified target.
* @see sap.ui.test.qunit.triggerEvent
*
* @param {string | Element} oTarget The ID of a DOM element or a DOM element which serves as target of the event
* @param {string} sChar Only the first char of the string will be passed via keypress event
* @param {boolean} bShiftKey Indicates whether the shift key is down in addition
* @param {boolean} bAltKey Indicates whether the alt key is down in addition
* @param {boolean} bCtrlKey Indicates whether the ctrl key is down in addition
* @public
*/
QUtils.triggerKeypress = function(oTarget, sChar, bShiftKey, bAltKey, bCtrlKey) {
var _sChar = sChar && sChar.toUpperCase();
if (KeyCodes[_sChar] === null) {
QUnit.ok(false, "Invalid character for triggerKeypress: '" + sChar + "'");
}
var _iCharCode = sChar.charCodeAt(0);
var oParams = {};
oParams.charCode = _iCharCode;
oParams.which = _iCharCode;
oParams.key = mapKeyCodeToKey(_sChar);
oParams.location = mapKeyCodeToLocation(_sChar);
oParams.shiftKey = !!bShiftKey;
oParams.altKey = !!bAltKey;
oParams.metaKey = !!bCtrlKey;
oParams.ctrlKey = !!bCtrlKey;
QUtils.triggerEvent("keypress", oTarget, oParams);
};
/**
* Programmatically triggers a 'keypress' event on a specified input field target and appends the character to the value
* of this input field.
* @see sap.ui.test.qunit.triggerKeypress
*
* @param {string | Element} oInput The ID of a DOM input field or a DOM input field which serves as target
* @param {string} sChar Only the first char of the string will be passed via keypress event
* @param {string} [sValue] If passed, this will be set as the new value of the input and the method will not rely on the old value of the input
* @public
*/
QUtils.triggerCharacterInput = function(oInput, sChar, sValue) {
QUtils.triggerKeypress(oInput, sChar);
if (typeof (oInput) == "string") {
oInput = oInput ? document.getElementById(oInput) : null;
}
var $Input = jQuery(oInput);
if (typeof sValue !== "undefined") {
$Input.val(sValue);
} else {
$Input.val($Input.val() + sChar);
}
};
/**
* Programmatically triggers a mouse event specified by its name on a specified target.
* @see sap.ui.test.qunit.triggerEvent
*
* @param {string | Element} oTarget The ID of a DOM element or a DOM element which serves as target of the event
* @param {string} sEventType The name of the browser mouse event (like "click")
* @param {int} iOffsetX The offset X position of the mouse pointer during the event
* @param {int} iOffsetY The offset Y position of the mouse pointer during the event
* @param {int} iPageX The page X position of the mouse pointer during the event
* @param {int} iPageY The page Y position of the mouse pointer during the event
* @param {int} iButton The button of the mouse during the event (e.g. 0: LEFT, 1: MIDDLE, 2: RIGHT)
* @public
*/
QUtils.triggerMouseEvent = function(oTarget, sEventType, iOffsetX, iOffsetY, iPageX, iPageY, iButton) {
var oParams = {};
oParams.offsetX = iOffsetX;
oParams.offsetY = iOffsetY;
oParams.pageX = iPageX;
oParams.pageY = iPageY;
oParams.button = iButton;
QUtils.triggerEvent(sEventType, oTarget, oParams);
};
/**
* Removes any kind of whitespaces from the given <code>sText</code>
*
* @param {string} sText The text
* @returns {string} The text without any kind of whitespaces
* @private
*/
QUtils._removeAllWhitespaces = function(sText){
return sText.replace(/\s/g, "");
};
/**
* Performs a "SelectAll" also known as CTRL + A on the whole browser window
*
* @protected
*/
QUtils.triggerSelectAll = function(){
document.getSelection().selectAllChildren(document.body);
};
/**
* Checks if the given <code>sText</code> is equal with the selected text. If no <code>sText</code> is given, its checked if the there is any text selected
*
* @param {string} [sText] The given text
* @returns {boolean} If the selected text is equal with the given <code>sText</code>
* @protected
*/
QUtils.isSelectedTextEqual = function(sText){
var sSelectedText = QUtils.getSelectedText();
return sText ? sText === sSelectedText : !!sSelectedText;
};
/**
* Checks if the given <code>sText</code> is included in the selected text. If no <code>sText</code> is given, its checked if the there is any text selected
*
* @param {string | string[]} [vText] The given text or an array of string
* @returns {boolean} If the selected text contains the given <code>sText</code>
* @protected
*/
QUtils.includesSelectedText = function(vText){
var sSelectedText = QUtils.getSelectedText();
if (!vText){
return !!sSelectedText;
}
if (!Array.isArray(vText)){
vText = [vText];
}
return vText.every(function(sText){
return sSelectedText.indexOf(sText) > -1;
});
};
/**
* Determines the selected text, if no text is selected an empty string is returned
*
* Any kind of whitespaces are removed, because depending on OS and/or browser type different
* types and amount of whitespaces are determined by the Selection-API
*
* @returns {string} The selected text
* @protected
*/
QUtils.getSelectedText = function(){
return QUtils._removeAllWhitespaces(document.getSelection().toString());
};
// --------------------------------------------------------------------------------------------------
var FONT_WEIGHTS = {
'normal': 400,
'bold': 700
};
jQuery.fn.extend({
/**
* jQuery plugin (function) to retrieve the internal event data even for jQuery >= 1.9
*
* This is only a HACK and not guaranteed to work with future versions of jQuery.
* It is only intended to be used in test cases.
*
* @see http://jquery.com/upgrade-guide/1.9/#data-events-
*
* @public
* @name jQuery#_sapTest_dataEvents
* @deprecated Tests that rely on this function should try to substitute it with other tests
*/
_sapTest_dataEvents : function() {
var elem = this[0];
return elem ? jQuery._data(elem, "events") : null;
},
/**
* jQuery plugin (function) that normalizes textual font-weight values to numerical ones.
*
* Webkit browsers preserve string values and even convert well known numerical values to
* string values (e.g. 700 -> bold, 400 -> normal).
*
* Starting with jQuery 1.10, jQuery normalizes some of these values to a numerical value.
*
* This method hides all these differences (browser, jQuery version) and returns a numerical value
*
* @public
* @name jQuery#_sapTest_cssFontWeight
* @deprecated Tests that rely on this function should try to substitute it with other tests
*/
_sapTest_cssFontWeight : function() {
var v = this.css("font-weight");
return v ? FONT_WEIGHTS[v] || v : v;
}
});
//************************************
//TODO: Check JS Doc starting here -> describe and check visibility for stuff in namespace sap.ui.test.qunit
(function() {
/*
* wrapper around window.console
*/
function info(msg) {
Log.info(msg);
}
var M_DEFAULT_TEST_VALUES = {
"boolean" : [false, true],
"int" : [0, 1, 5, 10, 100],
"float" : [NaN, 0.0, 0.01, 3.14, 97.7],
"string" : ["", "some", "very long otherwise not normal and so on whatever", "<" + "script>alert('XSS attack!');</" + "script>"]
};
var mDefaultTestValues = Object.create(M_DEFAULT_TEST_VALUES);
function ensureArray(o) {
return o && !(o instanceof Array) ? [o] : o;
}
/**
* @TODO DESCRIBE AND CHECK VISIBILITY!
* @private
*/
QUtils.resetDefaultTestValues = function(sType) {
if ( typeof sType === "string" ) {
delete mDefaultTestValues[sType];
} else {
mDefaultTestValues = Object.create(M_DEFAULT_TEST_VALUES);
}
};
/**
* @TODO DESCRIBE AND CHECK VISIBILITY!
* @private
*/
QUtils.setDefaultTestValues = function(sType, aValues) {
if ( typeof sType === "string" ) {
mDefaultTestValues[sType] = ensureArray(aValues);
} else if ( typeof sType === "object" ) {
jQuery.extend(mDefaultTestValues, sType);
}
};
/**
* @TODO DESCRIBE AND CHECK VISIBILITY!
* @private
*/
QUtils.createSettingsDomain = function(oClass, oPredefinedValues) {
function createValues(sType) {
if ( mDefaultTestValues[sType] ) {
return mDefaultTestValues[sType];
}
try {
//TODO: global jquery call found
jQuery.sap.require(sType);
} catch (e) {
//escape eslint check for empty block
}
var oType = ObjectPath.get(sType);
if ( !(oType instanceof DataType) ) {
var r = [];
for (var n in oType) {
r.push(oType[n]);
}
mDefaultTestValues[sType] = r;
return r;
}
return [];
}
var oClass = new oClass().getMetadata().getClass(); // resolves proxy
var oPredefinedValues = oPredefinedValues || {};
var result = {};
var oProps = oClass.getMetadata().getAllProperties();
for (var name in oProps) {
result[name] = ensureArray(oPredefinedValues[name]) || createValues(oProps[name].type);
}
/*
var oAggr = oClass.getMetadata().getAllAggregations();
for (var name in oAggr) {
if ( oAggr[name].altTypes && oAggr[name].altTypes[0] === 'string' ) {
result[name] = ensureArray(oPredefinedValues[name]) || createValues(oAggr[name].altTypes[0].type);
}
}
*/
return result;
};
/**
* @TODO DESCRIBE AND CHECK VISIBILITY!
* @private
*/
QUtils.genericTest = function(oClass, sUIArea, oTestConfig) {
if ( oTestConfig && oTestConfig.skip === true ) {
return;
}
var oClass = new oClass().getMetadata().getClass(); // resolves proxy
var oTestConfig = oTestConfig || {};
var oTestValues = QUtils.createSettingsDomain(oClass, oTestConfig.allPairTestValues || {});
info("domain");
for (var name in oTestValues) {
var l = oTestValues[name].length;
var s = [];
s.push(" ", name, ":", "[");
for (var i = 0; i < l; i++) {
s.push(oTestValues[name][i], ",");
}
s.push("]");
info(s.join(""));
}
function method(sPrefix, sName) {
return sPrefix + sName.substring(0,1).toUpperCase() + sName.substring(1);
}
function getActualSettings(oControl, oSettings) {
var oActualSettings = {};
for (var settingsName in oSettings) {
if ( oControl[method("get", settingsName)] ) {
oActualSettings[settingsName] = oControl[method("get", settingsName)]();
}
}
return oActualSettings;
}
var oControl;
var oSettings;
// generate "AllPairs" test cases
var apg = new QUtils.AllPairsGenerator(oTestValues);
var aTestCases = [];
while ( apg.hasNext() ) {
aTestCases.push(apg.next());
}
var index = 0;
function testNextCombination() {
info("testNextCombination(" + index + ")");
if ( index >= aTestCases.length ) {
// continue with other tests
info("last combination -> done");
QUnit.start();
return;
}
// constructor test
oControl = new oClass(oSettings);
var oActualSettings = getActualSettings(oControl, oSettings);
QUnit.deepEqual(oActualSettings, oSettings, "settings");
/*
// individual setters
oControl = new oClass();
for(var name in oSettings) {
var r = oControl[method("set", name)](oSettings[name]);
QUnit.equal(oControl[method("get", name)](), oSettings[name], "setter for property '" + name + "'");
QUnit.ok(r == oControl, "setter for property '" + name + "' supports chaining");
}
*/
// rendering test
oControl.placeAt(sUIArea);
info("before explicit rerender");
oControl.getUIArea().rerender();
info("after explicit rerender");
info("info");
setTimeout(continueAfterRendering, 0);
}
QUnit.stop(15000);
testNextCombination();
function continueAfterRendering() {
info("continueAfterRendering(" + index + ")");
var oTestSettings = aTestCases[aTestCases.length - index - 1];
for (var settingsName in oTestSettings) {
var r = oControl[method("set", settingsName)](oTestSettings[settingsName]);
QUnit.equal(oControl[method("get", settingsName)](), oTestSettings[settingsName], "setter for property '" + settingsName + "'");
QUnit.ok(r == oControl, "setter for property '" + settingsName + "' supports chaining (after rendering)");
}
index = index + 1;
setTimeout(testNextCombination, 0);
}
};
/**
* @TODO DESCRIBE AND CHECK VISIBILITY!
* @private
*/
QUtils.suppressErrors = function(bSuppress) {
//var lastErrorHandler;
if ( bSuppress !== false ) {
info("suppress global errors");
//lastErrorHandler = window.onerror;
// window.onerror = function(msg) {
// info("global error handler: " + msg);
// return true;
// };
} else {
info("reenable global errors");
// window.onerror = lastErrorHandler;
//lastErrorHandler = undefined;
}
};
/**
* @TODO DESCRIBE AND CHECK VISIBILITY!
* @private
*/
QUtils.RandomPairsGenerator = function(oDomain) {
var iCombinations = 0;
for (var name in oDomain) {
if ( oDomain[name] && !(oDomain[name] instanceof Array) ) {
oDomain[name] = [ oDomain[name] ];
}
if ( oDomain[name] && oDomain[name].length > 0 ) {
if ( iCombinations == 0 ) {
iCombinations = oDomain[name].length;
} else {
iCombinations = iCombinations * oDomain[name].length;
}
}
}
function createSettings(iCombination) {
var oSettings = {};
for (var domainName in oDomain) {
var l = oDomain[domainName] && oDomain[domainName].length;
if ( l == 1 ) {
oSettings[domainName] = oDomain[domainName][0];
//info(" " + name + ":" + "0");
} else if ( l > 1 ) {
var c = iCombination % l;
oSettings[domainName] = oDomain[domainName][c];
iCombination = (iCombination - c) / l;
}
}
return oSettings;
}
this.hasNext = function() {
return true;
};
this.next = function() {
return createSettings(Math.floor(100 * iCombinations * Math.random()));
};
};
/**
* @TODO DESCRIBE AND CHECK VISIBILITY!
* @private
*/
QUtils.AllPairsGenerator = function (oDomain) {
// more suitable access to the params
var params = [];
for (var name in oDomain) {
params.push({
name : name,
n : oDomain[name].length,
values : oDomain[name]
});
}
var N = params.length;
/**
* Number of occurrences for each possible property value pair.
* A value of 0 indicates that there is no test case yet, so the pair must
* be incorporated in another test case. A value of 1 is the optimum,
* values greater than 1 indicate that the pair has been used (too) often.
*
* The algorithm of this generator guarantees to create values > 0 for all
* pairs but still tries to minimize the values. It does not guarantee
* to find the most optimal solution.
*
* Let a and b be two different properties from oDomain (e.g. a='text' and b='visible'),
* with (WOLG) params.indexOf(a) < params.indexOf(b). Let further a have
* n values (== oDomain[a].length) and b have m values (== oDomain[b].length).
*
* Then the occurrences counters for the n*m possible value combinations
* (pairs) of a and b are stored in a contiguous segment of the occurs[] array.
*
* The segments for all combinations of a and b themselves are ordered
* first by a, then by b (same params.indexOf() ordering applies).
*
* The position of the segment for a given combination (a,b) could be calculated
* as the sum of the size of all preceding segments, but this would be too
* expensive (access to the occurs[] is used very often and therefore must be efficient).
* Therefore, during initialization, the offset for each segment is stored in
* an additional array abOffset[]. To further simplify access, that array uses N*N
* space, but only the entries params.indexOf(a) * N + params.indexOf(b) (with
* params.indexOf(a) < params.indexOf(b)) are filled.
*/
var occurs = [];
/**
* Offset for a given combination of properties (a,b) into the occurs[]
* array. For a details description see the occurs[] array.
*/
var abOffset = [];
/**
* Number of pairs for which occurs[(a,b)] == 0.
*
* Note: during initialization, this variable also represents the number
* of created entries in the occurs[] array. As all entries are created with
* a value of 0, the definition above still holds.
*/
var nPairs = 0;
/*
* Initialization. Loops over all a,b combinations with (a<b)
* and creates the initial occurs[] and abOccurs[] values.
*/
for (var a = 0; a < N - 1; a++) {
var pa = params[a];
for (var b = a + 1; b < N; b++) {
var pb = params[b];
// remember offset into occurs array
abOffset[a * N + b] = nPairs;
// set occurrences for all n*m values to 0
for (var i = pa.n * pb.n; i > 0; i--) {
occurs[nPairs++] = 0;
}
}
}
/**
* Helper that calculates the offset into the occurs array
* for a given combination of a,b and the values of a and b.
*/
function offset(a,b,va,vb) {
return abOffset[a * N + b] + va * params[b].n + vb;
}
function findTestCase() {
var value_index = [];
/**
* Calculates a cost function for the case where for
* property 'a' the value 'va' is taken.
*
* The calculated cost consists of two parts:
* - pairs : number of newly addressed unique pairs
* - redundant : number of redundantly added pairs
*
* @param a
* @param va
* @return
*/
function calcCost(a, va) {
var score = { va: va, pairs:0, redundant:0 };
for (var c = 0; c < N; c++) {
var count;
if ( c < a ) {
count = occurs[offset(c,a,value_index[c],va)];
} else if ( c > a ) {
var j = offset(a,c,va,0),
end = j + params[c].n;
for (count = occurs[j]; count > 0 && j < end; j++ ) {
if ( occurs[j] < count ) {
count = occurs[j];
}
}
}
score.redundant = score.redundant + count;
if ( count == 0 ) {
score.pairs++;
}
}
return score;
}
// loop over all properties and find the "best" value
for (var d = 0; d < N; d++) {
var pd = params[d];
// measure the quality of the first possible value
var bestCost = calcCost(d, 0);
for (var va = 1; va < pd.n; va++) {
// measure the quality of the new combination
var cost = calcCost(d, va);
// a new combination is preferred if it either incorporates more unique pairs or if
// it incorporates the same number of pairs but with less redundant combinations
if ( cost.pairs > bestCost.pairs || (cost.pairs == bestCost.pairs && cost.redundant < bestCost.redundant) ) {
bestCost = cost;
}
}
value_index[d] = bestCost.va;
}
return value_index;
}
/**
* Iff there are still unused pairs, then there will be another test case.
* @return whether another test cases is needed.
* @private
*/
this.hasNext = function() {
return nPairs > 0;
};
var lastTest;
var lastPairs = -1;
/**
*
* @return
* @private
*/
this.next = function() {
lastTest = findTestCase();
lastPairs = 0;
var test = {};
for (var a = 0; a < N; a++) {
for (var b = a + 1; b < N; b++) {
var i = offset(a,b,lastTest[a],lastTest[b]);
if ( occurs[i] == 0 ) {
nPairs--;
lastPairs++;
}
occurs[i]++;
}
test[params[a].name] = params[a].values[lastTest[a]];
}
return test;
};
this.lastPairs = function() {
return lastPairs;
};
};
}());
// export
// TODO: Get rid of the old namespace and adapt the existing tests accordingly
ObjectPath.set("sap.ui.test.qunit", QUtils);
window.qutils = QUtils;
return QUtils;
}, /* bExport= */ true);