function shouldThrow(assert, fnAct, expectedError) {
		var done = assert.async();
		var oldOnError = window.onerror;

		function checkAndRestore(msg, qualifier) {
			window.removeEventListener("error", onError);
			window.removeEventListener("unhandledrejection", onUnhandledRejection);
			window.onerror = oldOnError;
			assert.ok(expectedError.test(msg), "message of " + qualifier + " should match the expected message");
			done();
		}

		function onError(e) {
			e.stopImmediatePropagation();
			e.preventDefault();
			checkAndRestore(e.message, "uncaught global error");
		}

		function onUnhandledRejection(e) {
			e.stopImmediatePropagation();
			e.preventDefault();
			checkAndRestore(e.reason && e.reason.message, "unhandled rejection reason");
		}

		window.onerror = null; // deactivate qunit global error handler
		window.addEventListener("error", onError);
		window.addEventListener("unhandledrejection", onUnhandledRejection);

		try {
			fnAct();
		} catch (err) {
			checkAndRestore(err && err.message, "synchronously thrown error");
		}
	}
*************************************************
	// module loading (require/declare/define)
	//****************************************************

	QUnit.module("load modules", {
		beforeEach: function() {
			this.server = sinon.fakeServer.create();
			this.server.autoRespond = true;
		},
		afterEach: function() {
			this.server.restore();
			jQuery.sap.unloadResources('my/first/module.js', false, true, true);
			jQuery.sap.unloadResources('my/second/module.js', false, true, true);
			delete window.my;
		}
	});

	QUnit.test("jQuery.sap.require, basic", function (assert) {

		this.server.respondWith(/first/, 'jQuery.sap.declare("my.first.module");' +
				'jQuery.sap.require("my.second.module");' +
				'my.first.module = "1st";');
		this.server.respondWith(/second/, 'jQuery.sap.declare("my.second.module");' +
				'my.second.module = "2nd";');

		assert.equal(ObjectPath.get("my.first.module"), undefined, "global name for module 'first' should be undefined");
		assert.ok(!sap.ui.require("my/first/module"), "module 'first' should not be declared");
		assert.equal(ObjectPath.get("my.second.module"), undefined, "global name for module 'second' should be undefined");
		assert.ok(!sap.ui.require("my/second/module"), "module 'second' should not be declared");

		jQuery.sap.require("my.first.module");
		assert.equal(ObjectPath.get("my.first.module"), "1st", "require of 'first' should have loaded 'first'");
		assert.ok(sap.ui.require("my/first/module"), "module 'first' should be declared");
		assert.equal(ObjectPath.get("my.second.module"), "2nd", "require of 'first' should have loaded 'second' (transitively)");
		assert.ok(sap.ui.require("my/second/module"), "module 'first' should be declared");

		jQuery.sap.require("my.second.module");
		assert.equal(ObjectPath.get("my.first.module"), "1st", "addtl. require of 'second' shouldn't touch globals");
		assert.equal(ObjectPath.get("my.second.module"), "2nd", "addtl. require of 'second' shouldn't touch globals");

		jQuery.sap.require("my.first.module");
		assert.equal(ObjectPath.get("my.first.module"), "1st", "addtl. require of 'first' shouldn't touch globals");
		assert.equal(ObjectPath.get("my.second.module"), "2nd", "addtl. require of 'first' shouldn't touch globals");

	});

	QUnit.test("jQuery.sap.require, mismatch in jQuery.sap.declare", function (assert) {

		const SCENARIO = "mismatchOfDeclare";
		const MODULE_A = `${SCENARIO}/moduleA`;
		const MODULE_B = `${SCENARIO}/moduleB`;
		const MODULE_C = `${SCENARIO}/moduleC`;
		const toDot = (mod) => mod.replace(/\//g, "."); // create dot notation

		// 'moduleA' has a dependency to 'moduleB', but 'moduleB' declares itself as 'moduleC'
		// After loading 'moduleA', 'moduleB' and 'moduleC' both should be READY. This is loader specific behavior.
		//
		// As 'moduleB/C' only writes to the global name 'moduleC', 'moduleB' has an export of `undefined`.
		// This is module specific behavior, not related to the loader.
		this.server.respondWith(/moduleA/, `
jQuery.sap.declare("${toDot(MODULE_A)}");
jQuery.sap.require("${toDot(MODULE_B)}");
${toDot(MODULE_A)} = "A";
`);
		this.server.respondWith(/moduleB/, `
jQuery.sap.declare("${toDot(MODULE_C)}");
${toDot(MODULE_C)} = "C";
`);

		assert.equal(ObjectPath.get(`${toDot(MODULE_A)}`), undefined, "global property for 'moduleA' should be undefined");
		assert.ok(!sap.ui.require(MODULE_A), "'moduleA' should not be declared");
		assert.equal(ObjectPath.get(`${toDot(MODULE_B)}`), undefined, "global property for 'moduleB' should be undefined");
		assert.ok(!sap.ui.require(MODULE_B), "'moduleB' should not be declared");
		assert.equal(ObjectPath.get(`${toDot(MODULE_B)}`), undefined, "global property for 'moduleC' should be undefined");
		assert.ok(!sap.ui.require(MODULE_C), "'moduleC' should not be declared");

		jQuery.sap.require(`${toDot(MODULE_A)}`);
		assert.equal(ObjectPath.get(`${toDot(MODULE_A)}`), "A", "require of 'moduleA' should have loaded 'moduleA'");
		assert.ok(sap.ui.require(MODULE_A), "'moduleA' should be declared");
		assert.equal(privateLoaderAPI.getModuleState(MODULE_B + ".js"), 4, "'moduleB' should be READY");
		assert.equal(ObjectPath.get(`${toDot(MODULE_B)}`), undefined, "require of 'moduleA' should have loaded 'moduleB' (transitively)");
		assert.equal(sap.ui.require(MODULE_B), undefined, "'moduleB' should have the expected export");
		assert.equal(privateLoaderAPI.getModuleState(MODULE_C + ".js"), 4, "'moduleC' should be READY");
		assert.equal(ObjectPath.get(`${toDot(MODULE_C)}`), "C", "require of 'moduleA' should have loaded 'moduleC' (transitively)");
		assert.equal(sap.ui.require(MODULE_C), "C", "'moduleC' should have the expected export ('C')");

		const done = assert.async();
		sap.ui.require([
			MODULE_B,
			MODULE_C
		], function(moduleB, moduleC) {
			assert.strictEqual(moduleB, undefined, "export of 'moduleB' should be the expected one (undefined)");
			assert.strictEqual(moduleC, "C", "export of 'moduleC' should be the expected one ('C')");
			done();
		}, function() {
			assert.ok(false, "requring 'moduleB' and 'moduleC' should not fail");
		});
	});

	QUnit.test("jQuery.sap.require, nested + cyclic", function (assert) {

		this.server.respondWith(/first/, 'jQuery.sap.declare("my.first.module");' +
				'jQuery.sap.require("my.second.module");' +
				'my.first.module = "1st";');
		this.server.respondWith(/second/, 'jQuery.sap.declare("my.second.module");' +
				'jQuery.sap.require("my.first.module");' +
				'my.second.module = my.first.module;');

		jQuery.sap.require("my.first.module");

		assert.equal(ObjectPath.get("my.first.module"), "1st", "first module should have been loaded");
		assert.ok(sap.ui.loader._.getModuleState("my/first/module.js") !== 0, "module 'first' should be declared");
		assert.equal(ObjectPath.get("my.second.module"), undefined, "second module should have been loaded");
		assert.ok(sap.ui.loader._.getModuleState("my/second/module.js") !== 0, "module 'second' should be declared");

	});

	QUnit.test("jQuery.sap.require, nested + cyclic (2)", function (assert) {

		this.server.respondWith(/first/, 'jQuery.sap.declare("my.first.module");' +
				'my.first.module = "1st";' +
				'jQuery.sap.require("my.second.module");');
		this.server.respondWith(/second/, 'jQuery.sap.declare("my.second.module");' +
				'jQuery.sap.require("my.first.module");' +
				'my.second.module = my.first.module;');

		jQuery.sap.require("my.first.module");

		assert.equal(ObjectPath.get("my.first.module"), "1st", "first module should have been loaded");
		assert.equal(ObjectPath.get("my.second.module"), "1st", "second module should have been loaded");

	});

	QUnit.test("sap.ui.define (basic)", function (assert) {

		this.server.respondWith(/first/, 'sap.ui.define([], function () {' +
				'return {my : "firstmodule"}' +
				'}, /* bExport=