@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
317 lines (274 loc) • 9.86 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2026 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
/*
* IMPORTANT: This is a private module, its API must not be used and is subject to change.
* Code other than the Core tests must not yet introduce dependencies to this module.
*/
sap.ui.define([
"sap/base/util/isPlainObject",
"sap/base/util/merge",
"./config"
], function(isPlainObject, merge, DEFAULT_CONFIG) {
"use strict";
// ---- helpers ----
/**
* Searches for the first occurrence of an attribute with
* the given name and if found, returns its value.
*
* Name must not contain characters that have a meaning in CSS selectors (no escaping).
*
* @param {string} name Name of the attribute to search for
* @returns {string} Value of the attribute or <code>null</code>
*/
function getAttribute(name) {
var tag = document.querySelector("[" + name + "]");
return tag ? tag.getAttribute(name) : null;
}
function getDefaultSuiteName() {
var sName = sap.ui.loader._.guessResourceName(window.location.href);
// special handling for karma runner: paths starting with /base/test/ should be /base/test-resources/
if ( sName == null && window.location.pathname.startsWith("/base/test/") ) {
const altPath = window.location.origin + window.location.pathname.replace("/base/test/", "/base/test-resources/");
sName = sap.ui.loader._.guessResourceName(altPath);
}
return sName ? sName.replace(/\.html$/, "") : null;
}
/**
* Execute the given callback once the DOM is ready (which might already be the case).
*
* @returns {Promise<void>} Returns a promise that settles after DOM became ready
*/
function whenDOMReady() {
return new Promise(function(resolve) {
function onLoaded() {
document.removeEventListener( "DOMContentLoaded", onLoaded, false );
resolve();
}
if ( document.readyState === 'loading' ) {
document.addEventListener( "DOMContentLoaded", onLoaded, false );
} else {
resolve();
}
});
}
/**
* Adds a stylesheet to the head.
*
* sap/ui/dom/includeStylesheet requires additional modules, so we don't use it here.
* @param {string} resourceName UI5 resource name of the stylesheet (not a URL!)
*/
function addStylesheet(resourceName) {
var oLink = document.createElement("link");
oLink.rel = "stylesheet";
oLink.href = sap.ui.require.toUrl(resourceName);
document.head.appendChild(oLink);
}
/**
* Very basic HTML escaping, not bullet proof.
*
* @param {string} str HTML string to encode
* @returns {string} Encoded HTML string.
*/
function encode(str) {
return str.replace(/&/g, "&").replace(/</g, "<");
}
// assume the document.baseURI to be constant
const [baseOrigin, baseURL] = (() => {
const url = new URL(document.baseURI);
return [url.origin, url.origin + url.pathname];
})();
function createEffectivePageURL(sUrl, oParams, sName) {
// check for ui5 scheme
if (sUrl.startsWith("ui5:")) {
// check for authority
if (!sUrl.startsWith("ui5://")) {
throw new TypeError(`Test '${sName}': Page URLs using the 'ui5' protocol must be absolute. Relative and server absolute URLs are reserved for future use.`);
}
const sNoScheme = sUrl.slice(6 /* "ui5://".length */);
sUrl = sap.ui.require.toUrl(sNoScheme);
} else {
// not a ui5:// URL
// in the context of the test starter, it then by convention is relative to the UI5 baseUrl (parent of "resources/")
sUrl = sap.ui.require.toUrl("") + "/../" + sUrl;
}
const url = new URL(sUrl, baseURL);
// add URL parameters if given
if ( oParams != null ) {
if ( typeof oParams !== "object" ) {
throw new TypeError(`Test '${sName}': URL parameters must be an object.`);
}
const urlParams = url.searchParams;
for (const name in oParams) {
if ( Object.hasOwn(oParams, name) ) {
const value = oParams[name];
if ( Array.isArray(value) ) {
value.forEach((singleValue) => urlParams.append(name, singleValue));
} else {
urlParams.append(name, value);
}
}
}
}
// for same origin URLs, return a URL w/o origin, otherwise the full URL
return url.origin === baseOrigin ? url.pathname + url.search + url.hash : url.href;
}
// ---- Suite Configuration ----
function normalize(oTestConfig) {
if ( oTestConfig && typeof oTestConfig === "object" ) {
if ( oTestConfig.qunit === null || oTestConfig.qunit === false ) {
oTestConfig.qunit = {
version: null
};
} else if ( typeof oTestConfig.qunit === "number" || oTestConfig.qunit === "edge" ) {
oTestConfig.qunit = {
version: oTestConfig.qunit
};
} else if ( typeof oTestConfig.qunit !== "object" ) {
oTestConfig.qunit = {};
}
if ( oTestConfig.sinon === null || oTestConfig.sinon === false ) {
oTestConfig.sinon = {
version: null
};
} else if ( typeof oTestConfig.sinon === "number" || oTestConfig.sinon === "edge" ) {
oTestConfig.sinon = {
version: oTestConfig.sinon
};
} else if ( typeof oTestConfig.sinon !== "object" ) {
oTestConfig.sinon = {};
}
} else {
oTestConfig = null;
}
return oTestConfig;
}
function mergeWithDefaults(oSuiteConfig, sTestSuite) {
function resolvePlaceholders(str, name) {
return str == null ? str : str.replace(/\{suite\}/g, sTestSuite).replace(/\{name\}/g, name);
}
var sModulePrefix = sTestSuite.slice(0, sTestSuite.lastIndexOf('/') + 1);
function resolvePackage(sModule) {
return sModule == null ? sModule : sModule.replace(/^\.\//, sModulePrefix);
}
// first merge the static defaults and the defaults of the suite
var oSuiteDefaults = merge({}, DEFAULT_CONFIG, normalize(oSuiteConfig.defaults));
// then merge each test config with the test specific defaults and the suite defaults
Object.keys(oSuiteConfig.tests).forEach(function(name) {
var oTestConfig = normalize(oSuiteConfig.tests[name]),
oTestDefaults = {
name: name
};
const mergeConfigObjects = (...aConfigs) => {
const oResult = merge({}, aConfigs.shift());
while (aConfigs.length) {
const oTmp = aConfigs.shift();
for (const sKey in oTmp) {
oResult[sKey] = isPlainObject(oResult[sKey]) ? mergeConfigObjects(oResult[sKey], oTmp[sKey]) : oTmp[sKey];
}
}
return oResult;
};
oTestConfig = mergeConfigObjects(oSuiteDefaults, oTestDefaults, oTestConfig);
if ( Array.isArray(oTestConfig.module) ) {
oTestConfig.module = oTestConfig.module.map(function(sModule) {
return resolvePackage(resolvePlaceholders(sModule, name));
});
} else {
oTestConfig.module = resolvePackage(resolvePlaceholders(oTestConfig.module, name));
}
oTestConfig.beforeBootstrap = resolvePackage(resolvePlaceholders(oTestConfig.beforeBootstrap, name));
oTestConfig.page = createEffectivePageURL(resolvePlaceholders(oTestConfig.page, name), oTestConfig.searchParams || oTestConfig.uriParams, name);
oTestConfig.title = resolvePlaceholders(oTestConfig.title, name);
oSuiteConfig.tests[name] = oTestConfig;
});
oSuiteConfig.sortedTests =
Object.keys(oSuiteConfig.tests)
.sort(function(a, b) {
var groupA = oSuiteConfig.tests[a].group || "";
var groupB = oSuiteConfig.tests[b].group || "";
if ( groupA !== groupB ) {
return groupA < groupB ? -1 : 1;
}
a = a.toUpperCase();
b = b.toUpperCase();
if (a === b) {
return 0;
}
return a < b ? -1 : 1;
})
.map(function(name) {
return oSuiteConfig.tests[name];
});
return oSuiteConfig;
}
/**
* Pattern to validate testsuite names.
*
* The pattern is so restrictive to limit the locations where code will be loaded from.
*/
var VALID_TESTSUITE = /^test-resources\/([a-zA-Z_$\-][a-zA-Z_$0-9\-\.]*\/)*testsuite(?:\.[a-z][a-z0-9\-]*)*\.qunit$/;
//var VALID_TEST = /^([a-zA-Z_$\-][a-zA-Z_$0-9\-]*\/)*[a-zA-Z_$\-][a-zA-Z_$0-9\-]*$/;
/**
* Loads and normalized a test suite configuration.
*
* After loading, the configuration for the individual tests is merged with
* the defaults that are also stored in the configuration module and it is merged
* with the static defaults above.
*
* In addition to the merged configuration, an alphabetically sorted
* array of the tests is built and stored in property <code>sortedTests</code>
* (sorted by 'group' asc, 'name' asc).
*
* @param {string} sTestSuite resource name of the test suite, usually starting with 'test-resources/'
* @returns {Promise<object>} A promise on the normalized configuration object.
*/
function getSuiteConfig(sTestSuite) {
return new Promise(function(resolve, reject) {
if ( !sTestSuite ) {
throw new TypeError("No test suite specified");
}
if ( !VALID_TESTSUITE.test(sTestSuite) ) {
throw new TypeError("Invalid test suite name");
}
sap.ui.require([sTestSuite], function(oSuiteConfig) {
try {
resolve( mergeWithDefaults(oSuiteConfig, sTestSuite) );
} catch (oErr) {
reject(oErr);
}
}, function(oErr) {
reject(oErr);
});
});
}
function registerResourceRoots(oScriptTag) {
const sResourceRoots = getAttribute("data-sap-ui-resource-roots");
if (!sResourceRoots) {
return;
}
const oResourceRoots = JSON.parse(sResourceRoots);
const paths = {};
for (const n in oResourceRoots) {
paths[n.replace(/\./g, "/")] = oResourceRoots[n] || ".";
}
sap.ui.loader.config({ paths });
}
sap.ui.loader.config({
paths: {
'test-resources': sap.ui.require.toUrl("") + "/../test-resources/"
}
});
return {
defaultConfig: DEFAULT_CONFIG,
addStylesheet: addStylesheet,
encode: encode,
getAttribute: getAttribute,
getDefaultSuiteName: getDefaultSuiteName,
getSuiteConfig: getSuiteConfig,
whenDOMReady: whenDOMReady,
registerResourceRoots: registerResourceRoots
};
});