/*!
 * OpenUI5
 * (c) Copyright 2009-2026 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', [
	"sap/base/Log",
	"sap/base/strings/camelize",
	"sap/base/strings/capitalize",
	"sap/base/util/extend",
	"sap/base/util/ObjectPath",
	"sap/ui/base/DataType",
	"sap/ui/core/Element",
	"sap/ui/events/KeyCodes",
	"sap/ui/thirdparty/jquery",
	"sap/ui/dom/jquery/control" // jQuery Plugin "control"
],
	function(
		Log,
		camelize,
		capitalize,
		extend,
		ObjectPath,
		DataType,
		Element,
		KeyCodes,
		jQuery
	) {
	"use strict";

	/**
	 * @deprecated As of 1.120
	 */
	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 = new URLSearchParams(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
	 * @deprecated As of version 1.120, not needed with property async test design. If tests depend on theming,
	 *    they rather should use the waitForTheme option or module <code>waitForThemeApplied</code>.
	 */
	QUtils.delayTestStart = function(iDelay){
		QUnit.config.autostart = false;
		if (iDelay) {
			window.setTimeout(function() {
				QUnit.start();
			}, iDelay);
		} else {
			jQuery(function() {
				QUnit.start();
			});
		}
	};

	var noop = function() {};

	var fixOriginalEvent = 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 || noop;
				origEvent.stopPropagation = origEvent.stopPropagation || noop;
				origEvent.stopImmediatePropagation = origEvent.stopImmediatePropagation || noop;
			}
		};

		const OrigjQEvent = jQuery.Event;
		jQuery.Event = function(src, props) {
			const 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());
	};

	// --------------------------------------------------------------------------------------------------

	/**
	 * @deprecated No longer used in DIST layer
	 */
	var FONT_WEIGHTS = {
		'normal': 400,
		'bold': 700
	};

	/**
	 * @deprecated No longer used in DIST layer
	 */
	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" ) {
				extend(mDefaultTestValues, sType);
			}
		};

		/**
		 * @TODO DESCRIBE AND CHECK VISIBILITY!
		 * @private
		 */
		QUtils.createSettingsDomain = function(oClass, oPredefinedValues) {

			function createValues(sType) {
				if ( !mDefaultTestValues[sType] ) {
					var oType = DataType.getType(sType);
					var oEnumValues = oType && oType.isEnum() ? oType.getEnumValues() : undefined;
					/**
					 * @deprecated As of 1.120
					 */
					if (!oType) {
						try {
							sap.ui.requireSync(sType.replace(/\./g, "/")); // legacy-relevant legacy fallback
						} catch (e) {
							// ignore
						}
						oEnumValues = ObjectPath.get(sType);
					}
					if (oEnumValues && !(oEnumValues instanceof DataType)) {
						mDefaultTestValues[sType] = Object.keys(oEnumValues);
					} else {
						mDefaultTestValues[sType] = [];
					}
				}
				return mDefaultTestValues[sType];
			}

			oClass = new oClass().getMetadata().getClass(); // resolves proxy
			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;
			}

			oClass = new oClass().getMetadata().getClass(); // resolves proxy
			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;
			};
		};

	}());

	// legacy global exports
	/**
	 *  TODO: Get rid of the old namespace and adapt the existing tests accordingly
	 * @deprecated
	 */
	ObjectPath.set("sap.ui.test.qunit", QUtils);
	/**
	 * @deprecated
	 */
	window.qutils = QUtils;

	return QUtils;

}, /* bExport= */ true);
