text: /*!
   * OpenUI5
   * (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
   * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
   */
  
  (function () {
  	"use strict";
  	/*global _$blanket, blanket, falafel, Map, QUnit */
  	/*eslint no-alert: 0, no-warning-comments: 0 */
  
  	var aFileNames = [], // maps a file's index to its name
  		rParentheses = /[()]/g,
  		oScript = getScriptTag(),
  		aStatistics = [], // maps a file's index to its "hits" array (and statistics record)
  		iThreshold,
  		rWordChar = /\w/; // a "word" (= identifier) character
  
  	/**
  	 * Keep track of branch coverage.
  	 *
  	 * @param {number} iFileIndex
  	 *   The current file's index
  	 * @param {number} iBranchIndex
  	 *   The current branch's index
  	 * @param {any} vCondition
  	 *   The current branch's condition
  	 * @param {number} [iLine]
  	 *   The current branch's line number (for statement coverage)
  	 * @returns {any}
  	 *   <code>vCondition</code>
  	 */
  	function branchTracking(iFileIndex, iBranchIndex, vCondition, iLine) {
  		if (iBranchIndex >= 0) {
  			if (vCondition) {
  				aStatistics[iFileIndex].branchTracking[iBranchIndex].truthy += 1;
  			} else {
  				aStatistics[iFileIndex].branchTracking[iBranchIndex].falsy += 1;
  			}
  		}
  
  		if (iLine >= 0) {
  			lineTracking(iFileIndex, iLine);
  		}
  
  		return vCondition;
  	}
  
  	/**
  	 * Returns the element's attribute as an integer.
  	 *
  	 * @param {Element} oElement The element
  	 * @param {string} sAttributeName The attribute name
  	 * @param {number} iDefault The default value
  	 * @returns {number} The attribute value or the default value if the attribute value is not a
  	 *   positive number
  	 */
  	function getAttributeAsInteger(oElement, sAttributeName, iDefault) {
  		var iValue = parseInt(oElement.getAttribute(sAttributeName));
  
  		// Note: if the value is not a number, the result is NaN which is not greater than 0
  		return iValue > 0 ? iValue : iDefault;
  	}
  
  	/**
  	 * Determines the script tag that loaded this script.
  	 *
  	 * @return {Element}
  	 *   The script tag that loaded this script
  	 */
  	function getScriptTag() {
  		var oScript,
  			aScripts = document.getElementsByTagName("script"),
  			i, n;
  
  		for (i = 0, n = aScripts.length; i < n; i += 1) {
  			oScript = aScripts[i];
  
  			if (/BranchTracking.js$/.test(oScript.getAttribute("src"))) {
  				return oScript;
  			}
  		}
  	}
  
  	/**
  	 * Replacement for Blanket.js' <code>instrument</code> function.
  	 *
  	 * @param {object} oConfiguration
  	 *   Configuration object
  	 * @param {string} oConfiguration.inputFile
  	 *   The current file's original source code
  	 * @param {string} oConfiguration.inputFileName
  	 *   The current file's name
  	 * @param {function} fnSuccess
  	 *   Success handler, called with resulting instrumented source code
  	 */
  	function instrument(oConfiguration, fnSuccess){
  		var bBranchTracking = blanket.options("branchTracking"),
  			iFileIndex = aFileNames.length,
  			sFileName = oConfiguration.inputFileName,
  			iNoOfOutputLines,
  			sScriptInput = oConfiguration.inputFile,
  			sScriptOutput;
  
  		aFileNames.push(sFileName);
  		aStatistics[iFileIndex] = _$blanket[sFileName] = []; // hits
  		if (bBranchTracking) {
  			_$blanket[sFileName].branchTracking = [];
  		}
  		_$blanket[sFileName].source = sScriptInput.split("\n");
  		_$blanket[sFileName].source.unshift(""); // line 0 does not exist!
  		_$blanket[sFileName].warnings = [];
  
  		sScriptOutput = "" + falafel(sScriptInput, {
  //				attachComment : true, // interesting for meta comments!
  //				comment : true,
  				loc : true,
  				range : true,
  				source : sScriptInput // is simply attached to each Location
  //				tokens : false,
  //				tolerant : false
  			}, visit.bind(null, bBranchTracking, iFileIndex));
  
  		iNoOfOutputLines = sScriptOutput.split("\n").length + 1; // account for line 0 here as well
  		if (iNoOfOutputLines !== _$blanket[sFileName].source.length) {
  			warn(sFileName, "Line length mismatch! " + _$blanket[sFileName].source.length + " vs. "
  				+ iNoOfOutputLines);
  		}
  
  		fnSuccess(sScriptOutput);
  	}
  
  	/**
  	 * Keep track of statement coverage.
  	 *
  	 * @param {number} iFileIndex
  	 *   The current file's index
  	 * @param {number} iLine
  	 *   The current line number
  	 */
  	function lineTracking(iFileIndex, iLine) {
  		aStatistics[iFileIndex][iLine] += 1;
  	}
  
  	/**
  	 * Visit the given node, maybe instrument it.
  	 *
  	 * Note: "The recursive walk is a pre-traversal, so children get called before their parents."
  	 *
  	 * Note: We make no attempt to check that the global variable <code>blanket</code> is not
  	 * redefined. We don't rely on <code>window</code>. No support for labeled statements
  	 * (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label).
  	 * We rely on ESLint, e.g. http://eslint.org/docs/rules/curly.
  	 *
  	 * @param {boolean} bBranchTracking
  	 *   Whether branch tracking is on
  	 * @param {number} iFileIndex
  	 *   The current file's index
  	 * @param {object} oNode
  	 *   AST node
  	 * @returns {boolean}
  	 *   Whether <code>oNode.update()</code> has been used (Note: works only once!). This is just
  	 *   meant to keep track for future internal usage and is actually ignored by Falafel's
  	 *   <code>walk</code>.
  	 */
  	function visit(bBranchTracking, iFileIndex, oNode) {
  		var aHits = aStatistics[iFileIndex],
  			aBranchTracking = aHits.branchTracking,
  			iLine = oNode.loc.start.line,
  			sNewSource;
  
  		/*
  		 * Adds line tracking instrumentation to the current node.
  		 *
  		 * @returns {boolean}
  		 *   <code>true</code> because <code>oNode.update()</code> has been used
  		 */
  		function addLineTracking() {
  			oNode.update("blanket.$l(" + iFileIndex + ", " + iLine + "); " + oNode.source());
  			initHits();
  			return true;
  		}
  
  		/*
  		 * Initialize hits count for current line, double check for duplicates.
  		 */
  		function initHits() {
  			if (iLine in aHits) {
  				warn(iFileIndex, "Multiple statements on same line detected"
  					+ " – minified code not supported! Line number " + iLine);
  			}
  			aHits[iLine] = 0;
  		}
  
  		/*
  		 * Try to preserve operator's source code incl. comments and line breaks, but avoid leading
  		 * closing or trailing opening parentheses. Note: this also removes parentheses inside a
  		 * comment, but who cares ;-)
  		 *
  		 * Note: outer parentheses are absorbed by operators and do not appear in operand's source!
  		 *
  		 * @returns {string}
  		 */
  		function operator() {
  			return oNode.loc.source.slice(oNode.left.range[1], oNode.right.range[0])
  				.replace(rParentheses, "");
  		}
  
  		switch (oNode.type) {
  			case "FunctionDeclaration":
  			case "FunctionExpression":
  				if (oNode.body.body[0] && iLine === oNode.body.body[0].loc.start.line) {
  					warn(iFileIndex, "Function body must not start on same line! Line number "
  						+ iLine);
  //					aHits[iLine] = NaN; //TODO find an easy way to mark this line as "missed"
  				}
  				break;
  
  			default:
  		}
  
  		switch (oNode.type) {
  			case "AssignmentExpression":
  			case "ArrayExpression":
  			case "BlockStatement":
  			case "BinaryExpression":
  			case "Block": // block comment
  			case "CallExpression":
  			case "CatchClause": //TODO coverage for empty blocks?
  			case "DebuggerStatement":
  			case "EmptyStatement": //TODO coverage?!
  			case "FunctionExpression":
  			case "Identifier":
  			case "Line": // end-of-line comment
  			case "Literal":
  			case "MemberExpression":
  			case "NewExpression":
  			case "ObjectExpression":
  			case "Program":
  			case "Property":
  			case "SequenceExpression":
  			case "SwitchCase": //TODO (statement) coverage!
  			case "ThisExpression":
  			case "UnaryExpression":
  			case "UpdateExpression":
  			case "VariableDeclarator":
  				return false;
  
  			case "ConditionalExpression":
  				if (!bBranchTracking) {
  					return false;
  				}
  				oNode.test.update("blanket.$b(" + iFileIndex + ", " + aBranchTracking.length + ", "
  					+ oNode.test.source() + ")"
  				);
  				aBranchTracking.push({
  					alternate : oNode.alternate.loc,
  					consequent : oNode.consequent.loc,
  					falsy : 0,
  					truthy : 0
  				});
  				return true;
  
  			case "ExpressionStatement":
  				if (oNode.expression.type === "Literal"
  					&& oNode.expression.value === "use strict") {
  					return false; // do not instrument "use strict"; it would break it!
  				}
  				// fall through
  			case "DoWhileStatement":
  			case "ForInStatement":
  			case "ForStatement":
  			case "WhileStatement":
  			case "WithStatement":
  				// fall through; Note: we assume block statements only (@see blanket._blockifyIf)
  			case "BreakStatement":
  			case "ContinueStatement":
  			case "FunctionDeclaration":
  			case "ReturnStatement":
  			case "SwitchStatement":
  			case "ThrowStatement":
  			case "TryStatement":
  				return addLineTracking(oNode);
  
  			case "IfStatement":
  				// Note: we assume block statements only (@see blanket._blockifyIf)
  				oNode.test.update("blanket.$b(" + iFileIndex + ", "
  					+ (bBranchTracking ? aBranchTracking.length : -1) + ", "
  					+ oNode.test.source() + ", " + iLine + ")"
  				);
  				initHits();
  				if (bBranchTracking) {
  					aBranchTracking.push({
  						// Note: in case of missing "else" we blame it on the condition
  						alternate : (oNode.alternate || oNode.test).loc,
  						consequent : oNode.consequent.loc,
  						falsy : 0,
  						truthy : 0
  					});
  				} // else would like to fall through to line tracking, but that does not work for
  				// "else if" unless s.th. like blanket._blockifyIf is used!
  				return true;
  
  			case "LogicalExpression":
  				if (!bBranchTracking) {
  					return false;
  				}
  				if (oNode.operator === "||" || oNode.operator === "&&") {
  					// Note: (...) around right source!
  					sNewSource = "blanket.$b(" + iFileIndex + ", " + aBranchTracking.length + ", "
  						+ oNode.left.source() + ") "
  						+ operator() + " (" + oNode.right.source() + ")";
  					if (!rWordChar.test(oNode.loc.source[oNode.range[0]])) {
  						// Note: handle minified code like "return!x||y;"
  						sNewSource = " " + sNewSource;
  					}
  					oNode.update(sNewSource);
  					aBranchTracking.push({
  						alternate : oNode.operator === "&&"
  							? oNode.left.loc
  							: oNode.right.loc,
  						consequent : oNode.operator === "&&"
  							? oNode.right.loc
  							: oNode.left.loc,
  						falsy : 0,
  						truthy : 0
  					});
  				}
  				return true;
  
  			case "VariableDeclaration":
  				if (oNode.parent.type === "ForInStatement"
  					|| oNode.parent.type === "ForStatement") {
  					return false;
  				}
  				return addLineTracking(oNode);
  
  			case "LabeledStatement":
  			default:
  				throw new Error(oNode.source());
  		}
  	}
  
  	/**
  	 * Logs the given message related to the given file both as a warning on console and as a
  	 * warning to be reported inside QUnit.module's "before" hook.
  	 *
  	 * @param {number|string} vFile - the affected file's index or name
  	 * @param {string} sMessage - a message
  	 */
  	function warn(vFile, sMessage) {
  		var sFileName = typeof vFile === "string"
  				? vFile
  				: aFileNames[vFile];
  
  		jQuery.sap.log.warning(sMessage, sFileName, "sap.ui.test.BranchTracking");
  		_$blanket[sFileName].warnings.push(sMessage);
  	}
  
  	/**
  	 * Listens on QUnit and delivers a function that returns the tested modules.
  	 *
  	 * @returns {function} A function that delivers the tested modules or <code>undefined</code> if
  	 *   all modules have been tested
  	 */
  	function listenOnQUnit() {
  		var mModules = {},
  			aTestedModules = [],
  			iTotalModules;
  
  		QUnit.begin(function (oDetails) {
  			iTotalModules = oDetails.modules.length;
  			oDetails.modules.forEach(function (oModule) {
  				mModules[oModule.name] = oModule;
  			});
  		});
  
  		QUnit.moduleStart(function (oModule) {
  			// Why, oh why, is the module name different here?
  			aTestedModules = aTestedModules.concat(
  				Object.keys(mModules).filter(function (sModuleName) {
  					return mModules[sModuleName].tests === oModule.tests;
  				})
  			);
  		});
  
  		return function () {
  			return aTestedModules.length < iTotalModules ? aTestedModules : undefined;
  		};
  	}
  
  	if (window.blanket) {
  		window._$blanket = {}; // maps a file's name to its statistics array
  		blanket.$b = branchTracking;
  		blanket.$l = lineTracking;
  		blanket.instrument = instrument; // self-made "plug-in" ;-)
  
  		var fnGetTestedModules = listenOnQUnit(),
  			iLinesOfContext = getAttributeAsInteger(oScript, "data-lines-of-context", 3);
  
  		iThreshold = Math.min(getAttributeAsInteger(oScript, "data-threshold", 0), 100);
  
  		// Note: instrument() MUST have been replaced before!
  		sap.ui.require(["sap/ui/test/BlanketReporter"], function (BlanketReporter) {
  			blanket.options("reporter",
  				BlanketReporter.bind(null, iLinesOfContext, iThreshold, fnGetTestedModules));
  		});
  	}
  
  	//**********************************************************************************************
  	// Code for tracking "Uncaught (in promise)" for sap.ui.base.SyncPromise inside QUnit tests
  	// and for checking isolated code coverage (that is, each "class" by its corresponding test)
  	//**********************************************************************************************
  	var bInfo,
  		sClassName = "sap.ui.base.SyncPromise",
  		mFileName2InitialHits = {},
  		sFilter,
  		fnModule,
  		iNo = 0,
  		sTestId,
  		mUncaughtById = {},
  		mUncaughtPromise2Reason = new Map();
  
  	/**
  	 * Check isolated line/branch coverage for the given test.
  	 *
  	 * @param {object} oTest - QUnit's test environment
  	 * @param {object} assert - QUnit's object with the assertion methods
  	 */
  	function checkIsolatedCoverage(oTest, assert) {
  		var aBranchesWithUnchangedHits,
  			aHits = _$blanket[oTest.$currentFileName],
  			aInitialHits = mFileName2InitialHits[oTest.$currentFileName],
  			aLinesWithUnchangedHits;
  
  		if (oTest.$oldHits) {
  			aLinesWithUnchangedHits = Object.keys(aHits).filter(function (iLine) {
  				return !(aInitialHits && aInitialHits[iLine])
  					&& aHits[iLine] === oTest.$oldHits[iLine];
  			});
  			assert.notOk(aLinesWithUnchangedHits.length,
  				"Some lines have not been covered by this module in isolation: "
  					+ aLinesWithUnchangedHits);
  		}
  		if (oTest.$oldBranchTracking) {
  			aBranchesWithUnchangedHits = Object.keys(aHits.branchTracking).filter(function (i) {
  				return aHits.branchTracking[i].falsy === oTest.$oldBranchTracking[i].falsy
  					|| aHits.branchTracking[i].truthy === oTest.$oldBranchTracking[i].truthy;
  			});
  			assert.notOk(aBranchesWithUnchangedHits.length,
  				"Some branches have not been fully covered by this module in isolation: "
  					+ aBranchesWithUnchangedHits);
  		}
  	}
  
  	/**
  	 * Check for uncaught errors in sync promises and provide an appropriate report to the given
  	 * optional reporter.
  	 *
  	 * @param {function} [fnReporter]
  	 *   Optional reporter to receive a report
  	 */
  	function checkUncaught(fnReporter) {
  		var sId,
  			iLength = Object.keys(mUncaughtById).length
  				+ (mUncaughtPromise2Reason ? mUncaughtPromise2Reason.size : 0),
  			sMessage = "Uncaught (in promise): " + iLength + " times\n",
  			oPromise,
  			vReason,
  			oResult,
  			itValues;
  
  		if (iLength) {
  			for (sId in mUncaughtById) {
  				oPromise = mUncaughtById[sId];
  				if (oPromise.getResult() && oPromise.getResult().stack) {
  					sMessage += oPromise.getResult().stack;
  				} else {
  					sMessage += oPromise.getResult();
  				}
  				if (oPromise.$error.stack) {
  					sMessage += "\n>>> SyncPromise rejected with above reason...\n"
  						+ oPromise.$error.stack.split("\n").slice(2).join("\n"); // hide listener
  				}
  				sMessage += "\n\n";
  			}
  			mUncaughtById = {};
  
  			//TODO for (let vReason of mUncaughtPromise2Reason.values()) {...}
  			if (mUncaughtPromise2Reason && mUncaughtPromise2Reason.size) {
  				itValues = mUncaughtPromise2Reason.values();
  				for (;;) {
  					oResult = itValues.next();
  					if (oResult.done) {
  						break;
  					}
  					vReason = oResult.value;
  					sMessage += (vReason && vReason.stack || vReason) + "\n\n";
  				}
  				mUncaughtPromise2Reason.clear();
  			}
  
  			if (fnReporter) {
  				fnReporter(sMessage);
  			} else if (bInfo) {
  				jQuery.sap.log.info("Clearing " + iLength + " uncaught promises", sMessage,
  					sClassName);
  			}
  		}
  	}
  
  	if (oScript.getAttribute("data-uncaught-in-promise") !== "true") {
  		/*
  		 * Listener for "unhandledrejection" events to keep track of "Uncaught (in promise)".
  		 */
  		window.addEventListener("unhandledrejection", function (oEvent) {
  			if (oEvent.reason && oEvent.reason.$uncaughtInPromise) { // ignore exceptional cases
  				return;
  			}
  
  			if (mUncaughtPromise2Reason) {
  				mUncaughtPromise2Reason.set(oEvent.promise, oEvent.reason);
  				oEvent.preventDefault(); // do not report on console
  			} else { // QUnit already done
  				alert("Uncaught (in promise) " + oEvent.reason);
  			}
  		});
  
  		/*
  		 * Listener for "rejectionhandled" events to keep track of "Uncaught (in promise)".
  		 */
  		window.addEventListener("rejectionhandled", function (oEvent) {
  			if (mUncaughtPromise2Reason) {
  				mUncaughtPromise2Reason.delete(oEvent.promise);
  			}
  		});
  	}
  
  	/**
  	 * Listener for sync promises which become (un)caught.
  	 *
  	 * @param {sap.ui.base.SyncPromise} oPromise
  	 *   A sync promise
  	 * @param {boolean} bCaught
  	 *   Tells whether the given sync promise became caught
  	 */
  	function listener(oPromise, bCaught) {
  		if (bCaught) {
  			delete mUncaughtById[oPromise.$id];
  			if (bInfo) {
  				jQuery.sap.log.info("Promise " + oPromise.$id + " caught",
  					Object.keys(mUncaughtById), sClassName);
  			}
  			return;
  		}
  
  		oPromise.$id = iNo++;
  		oPromise.$error = new Error();
  		mUncaughtById[oPromise.$id] = oPromise;
  		if (bInfo) {
  			jQuery.sap.log.info("Promise " + oPromise.$id + " rejected with "
  				+ oPromise.getResult(), Object.keys(mUncaughtById), sClassName);
  		}
  	}
  
  	/**
  	 * Wrapper for <code>QUnit.module</code> to check for uncaught errors in sync promises and
  	 * for 100% isolated test coverage.
  	 *
  	 * @param {string} sTitle
  	 *   The module's title
  	 * @param {object} [mHooks]
  	 *   Optional map of hooks, e.g. "beforeEach"
  	 */
  	function module(sTitle, mHooks) {
  		var fnAfter, fnAfterEach, fnBefore, fnBeforeEach;
  
  		mHooks = mHooks || {};
  		fnAfter = mHooks.after || function () {};
  		fnAfterEach = mHooks.afterEach || function () {};
  		fnBefore = mHooks.before || function () {};
  		fnBeforeEach = mHooks.beforeEach || function () {};
  
  		mHooks.after = function (assert) {
  			if (window.blanket && !sFilter && !sTestId && !this.__ignoreIsolatedCoverage__
  					&& iThreshold >= 100 && !assert.test.module.stats.bad) {
  				checkIsolatedCoverage(this, assert);
  			}
  
  			return fnAfter.apply(this, arguments);
  		};
  
  		mHooks.afterEach = function (assert) {
  			var fnCheckUncaught = checkUncaught.bind(null, assert.ok.bind(assert, false));
  
  			function error(oError) {
  				fnCheckUncaught();
  				throw oError;
  			}
  
  			function success(vResult) {
  				if (vResult && typeof vResult.then === "function") {
  					return vResult.then(success, error);
  				}
  				fnCheckUncaught();
  				return vResult;
  			}
  
  			try {
  				return success(fnAfterEach.apply(this, arguments));
  			} catch (oError) {
  				error(oError);
  			}
  		};
  
  		mHooks.before = function (assert) {
  			var aHits;
  
  			this.$currentFileName = jQuery.sap.getResourceName(assert.test.module.name);
  			aHits = window.blanket && _$blanket[this.$currentFileName];
  			if (aHits) {
  				this.$oldHits = aHits.slice();
  				if (aHits.branchTracking) {
  					this.$oldBranchTracking = JSON.parse(
  						JSON.stringify(aHits.branchTracking, ["falsy", "truthy"]));
  				}
  				aHits.warnings.forEach(function (sMessage) {
  					assert.ok(false, sMessage);
  				});
  			}
  
  			return fnBefore.apply(this, arguments);
  		};
  
  		mHooks.beforeEach = function (assert) {
  			checkUncaught(); // cleans up what happened before
  			return fnBeforeEach.apply(this, arguments);
  		};
  
  		fnModule(sTitle, mHooks);
  	}
  
  	if (QUnit.module !== module) {
  		fnModule = QUnit.module.bind(QUnit);
  		QUnit.module = module;
  		sap.ui.require([
  			"sap/base/Log",
  			"sap/base/util/UriParameters",
  			"sap/ui/base/SyncPromise"
  		], function (Log, UriParameters, SyncPromise) {
  			var oUriParameters = UriParameters.fromQuery(window.location.search);
  
  			bInfo = Log.isLoggable(Log.Level.INFO, sClassName);
  			sFilter = oUriParameters.get("filter");
  			sTestId = oUriParameters.get("testId");
  			SyncPromise.listener = listener;
  		});
  
  		// allow easier module selection: larger list, one click selection
  		QUnit.begin(function () {
  			var sFileName, aHits;
  
  			jQuery("#qunit-modulefilter-dropdown-list").css("max-height", "none");
  
  			jQuery("#qunit-modulefilter-dropdown").on("click", function (oMouseEvent) {
  				if (oMouseEvent.target.tagName === "LABEL") {
  					setTimeout(function () {
  						// click on label instead of checkbox triggers "Apply" automatically
  						jQuery("#qunit-modulefilter-actions").children().first().trigger("click");
  					});
  				}
  			});
  
  			if (window.blanket) {
  				// remember which lines have been covered initially, at load time
  				for (sFileName in _$blanket) {
  					aHits = _$blanket[sFileName];
  					mFileName2InitialHits[sFileName] = aHits.slice();
  				}
  				// Note: for SyncPromise, a lot of lines are already covered!
  			}
  		});
  
  		QUnit.done(function () {
  			mUncaughtPromise2Reason = null; // no use to keep track anymore
  		});
  	}
  }());
  //TODO add tooltips to highlighting to explain rules
  
  