@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
921 lines (845 loc) • 31.6 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
sap.ui.define([
"jquery.sap.sjax",
"sap/base/Log",
"sap/base/util/merge",
"sap/base/util/UriParameters",
"sap/ui/thirdparty/URI",
"sap/ui/core/Core" // provides sap.ui.getCore()
], function (jQuery, Log, merge, UriParameters, URI) {
"use strict";
/*global QUnit, sinon */
// Note: The dependency to Sinon.JS has been omitted deliberately. Most test files load it via
// <script> anyway and declaring the dependency would cause it to be loaded twice.
var rBatch = /\/\$batch($|\?)/,
rContentId = /(?:^|\r\n)Content-Id\s*:\s*(\S+)/i,
rHeaderLine = /^(.*)?:\s*(.*)$/,
sJson = "application/json;charset=UTF-8;IEEE754Compatible=true",
mMessageForPath = {}, // a cache for files, see useFakeServer
sMimeHeaders = "\r\nContent-Type: application/http\r\n"
+ "Content-Transfer-Encoding: binary\r\n",
rMultipartHeader = /^Content-Type:\s*multipart\/mixed;\s*boundary=/i,
oUriParameters = UriParameters.fromQuery(window.location.search),
sAutoRespondAfter = oUriParameters.get("autoRespondAfter"),
sRealOData = oUriParameters.get("realOData"),
rRequestKey = /^(\S+) (\S+)$/,
rRequestLine = /^(GET|DELETE|MERGE|PATCH|POST) (\S+) HTTP\/1\.1$/,
mData = {},
rODataHeaders = /^(OData-Version|DataServiceVersion)$/,
bProxy = sRealOData === "true" || sRealOData === "proxy",
bRealOData = bProxy || sRealOData === "direct",
iRequestCount = 0,
bSupportAssistant = oUriParameters.get("supportAssistant") === "true",
TestUtils;
if (bRealOData) {
document.title = document.title + " (real OData)";
}
/**
* Checks that the actual value deeply contains the expected value, ignoring additional
* properties.
*
* @param {object} oActual
* the actual value to be tested
* @param {object|RegExp} oExpected
* the expected value which needs to be contained structurally (as a subset) within the
* actual value, or a regular expression which must match the actual string(!) value
* @param {string} sPath
* path to the values under investigation
* @throws {Error}
* in case the actual value does not deeply contain the expected value; the error message
* provides a proof of this
*/
function deeplyContains(oActual, oExpected, sPath) {
var sActualType = QUnit.objectType(oActual),
sExpectedType = QUnit.objectType(oExpected),
sName;
if (sActualType === "string" && sExpectedType === "regexp") {
if (!oExpected.test(oActual)) {
throw new Error(sPath + ": actual value " + oActual
+ " does not match expected regular expression " + oExpected);
}
return;
}
if (sActualType !== sExpectedType) {
throw new Error(sPath + ": actual type " + sActualType
+ " does not match expected type " + sExpectedType);
}
if (sActualType === "array") {
if (oActual.length < oExpected.length) {
throw new Error(sPath
+ ": array length: " + oActual.length + " < " + oExpected.length);
}
}
if (sActualType === "array" || sActualType === "object") {
for (sName in oExpected) {
deeplyContains(oActual[sName], oExpected[sName],
sPath === "/" ? sPath + sName : sPath + "/" + sName);
}
} else if (oActual !== oExpected) {
throw new Error(sPath + ": actual value " + oActual
+ " does not match expected value " + oExpected);
}
}
/**
* Pushes a QUnit test which succeeds if and only if a call to {@link deeplyContains} succeeds
* as indicated via <code>bExpectSuccess</code>.
*
* @param {object} oActual
* the actual value to be tested
* @param {object} oExpected
* the expected value which needs to be contained structurally (as a subset) within the
* actual value
* @param {string} sMessage
* message text
* @param {boolean} bExpectSuccess
* whether {@link deeplyContains} is expected to succeed
*/
function pushDeeplyContains(oActual, oExpected, sMessage, bExpectSuccess) {
try {
deeplyContains(oActual, oExpected, "/");
QUnit.assert.pushResult({
result: bExpectSuccess,
actual:oActual,
expected : oExpected,
message : sMessage
});
} catch (ex) {
QUnit.assert.pushResult({
result : !bExpectSuccess,
actual : oActual,
expected : oExpected,
message : (sMessage || "") + " failed because of " + ex.message
});
}
}
/**
* @classdesc
* A collection of functions that support QUnit testing.
*
* @namespace sap.ui.test.TestUtils
* @since 1.27.1
*/
TestUtils = /** @lends sap.ui.test.TestUtils */ {
/**
* If the UI5 core is dirty, the function returns a promise that waits until the rendering
* is finished.
*
* @returns {Promise|undefined}
* An optional promise that is resolved when the UI5 core is no longer dirty
*/
awaitRendering : function () {
if (sap.ui.getCore().getUIDirty()) {
return new Promise(function (resolve) {
function check() {
if (sap.ui.getCore().getUIDirty()) {
setTimeout(check, 1);
} else {
resolve();
}
}
check();
});
}
},
/**
* Companion to <code>QUnit.deepEqual</code> which only tests for the existence of expected
* properties, not the absence of others.
*
* <b>BEWARE:</b> We assume both values to be JS object literals, basically!
*
* @param {object} oActual
* the actual value to be tested
* @param {object} oExpected
* the expected value which needs to be contained structurally (as a subset) within the
* actual value
* @param {string} [sMessage]
* message text
*/
deepContains : function (oActual, oExpected, sMessage) {
pushDeeplyContains(oActual, oExpected, sMessage, true);
},
/**
* Companion to <code>QUnit.notDeepEqual</code> and {@link #deepContains}.
*
* @param {object} oActual
* the actual value to be tested
* @param {object} oExpected
* the expected value which needs to be NOT contained structurally (as a subset) within
* the actual value
* @param {string} [sMessage]
* message text
*/
notDeepContains : function (oActual, oExpected, sMessage) {
pushDeeplyContains(oActual, oExpected, sMessage, false);
},
/**
* Activates a sinon fake server in the given sandbox. The fake server responds to those
* requests given in the fixture, and to all DELETE, PATCH and POST requests regardless
* of the path. It is automatically restored when the sandbox is restored.
*
* The function uses <a href="http://sinonjs.org/docs/">Sinon.js</a> and expects that it
* has been loaded.
*
* POST requests ending on "/$batch" are handled automatically. They are expected to be
* multipart-mime requests where each part is a DELETE, GET, PATCH, MERGE or POST request.
* The response has a multipart-mime message containing responses to these inner requests.
* If an inner request is not a DELETE, a PATCH or a POST and it is not found in the
* fixture, or its message is not JSON, it is responded with an error code.
* The batch itself is always responded with code 200.
*
* "$batch" requests with an OData change set are supported, too. For each request in the
* change set a response is searched in the fixture. As long as all responses are success
* responses (code less than 400) a change set response is returned. Otherwise the first
* error message is the response for the whole change set.
*
* All other POST requests with no matching response in the fixture are responded with code
* 200, the body is simply echoed.
*
* DELETE and PATCH requests with no matching response in the fixture are responded with
* code 204 ("No Content").
*
* Direct HEAD requests with no matching response in the fixture are responded with code 200
* and no content.
*
* The headers "OData-Version" and "DataServiceVersion" are copied from the request to the
* response unless specified in the fixture.
*
* @param {object} oSandbox
* A Sinon sandbox as created using <code>sinon.sandbox.create()</code>
* @param {string} sBase
* The base path for <code>source</code> values in the fixture. The path must be in the
* project's test folder, typically it should start with "sap".
* Example: <code>"sap/ui/core/qunit/model"</code>
* @param {map} mFixture
* The fixture. Each key represents a method and a URL to respond to, in the form
* "METHOD URL". The method "GET" may be omitted. The value is an array or single response
* object that may have the following properties:
* <ul>
* <li> {number} <code>code</code>: The response code (<code>200</code> if not given)
* <li> {map} <code>headers</code>: A map of headers to set in the response
* <li> {RegExp|function} <code>ifMatch</code>: A filter to select the response. If not
* given, all requests match. The first match in the list wins. A regular expression
* is matched against the request body. A function is called with a request object
* having properties method, url, requestHeaders and requestBody; it must return
* truthy to indicate a match.
* <li> {object|string} <code>message</code>: The response message, either as a string
* or as an object which is serialized via <code>JSON.stringify</code> (the header
* <code>Content-Type</code> will be set appropriately in this case)
* <li> {string} <code>source</code>: The path of a file relative to <code>sBase</code>
* to be used for the response message. It will be read synchronously in advance. In
* this case the header <code>Content-Type</code> is determined from the source name's
* extension unless specified. This has precedence over <code>message</code>.
* </ul>
* @param {object[]} [aRegExps]
* An array containing regular expressions in the regExp property and the corresponding
* response(s) objects in the response property. If no match for a request was found in
* the normal fixture, the regular expressions are checked. The response object looks
* exactly the same as in the fixture and may additionally contain a method
* <code>buildResponse(aMatch, oResponse)</code> which gets passed the match object and
* the response to allow modification before sending.
*/
useFakeServer : function (oSandbox, sBase, mFixture, aRegExps) {
// a map from "method path" incl. service URL to a list of response objects with
// properties code, headers, ifMatch and message
var aRegexpResponses, mUrlToResponses;
/*
* OData batch handler
*
* @param {string} sServiceBase
* the service base URL
* @param {object} oRequest
* the Sinon request object
*/
function batch(sServiceBase, oRequest) {
var oMultipart = multipart(sServiceBase, oRequest.requestBody),
mODataHeaders = getODataHeaders(oRequest);
iRequestCount += 1;
oRequest.respond(200,
jQuery.extend({}, mODataHeaders, {
"Content-Type" : "multipart/mixed;boundary=" + oMultipart.boundary
}),
formatMultipart(oMultipart, mODataHeaders));
}
/*
* Builds a responses from <code>mFixture</code>. Reads the source synchronously and
* caches it.
* @returns {object} a resource object with code, headers, ifMatch and message
*/
function buildResponse(oFixtureResponse) {
var oResponse = {
buildResponse : oFixtureResponse.buildResponse,
code : oFixtureResponse.code || 200,
headers : oFixtureResponse.headers || {},
ifMatch : oFixtureResponse.ifMatch
};
if (oFixtureResponse.source) {
oResponse.message = readMessage(sBase + oFixtureResponse.source);
oResponse.headers["Content-Type"] = oResponse.headers["Content-Type"]
|| contentType(oFixtureResponse.source);
} else if (typeof oFixtureResponse.message === "object") {
oResponse.headers["Content-Type"] = sJson;
oResponse.message = JSON.stringify(oFixtureResponse.message);
} else {
oResponse.message = oFixtureResponse.message;
}
return oResponse;
}
/*
* Builds the responses from <code>mFixture</code>. Reads the sources synchronously and
* caches them.
* @returns {map}
* a map from "method path" (incl. service URL) to a list of response objects (with
* properties code, headers, ifMatch and message)
*/
function buildResponses() {
var oFixtureResponse,
sUrl,
mUrls = {};
for (sUrl in mFixture) {
oFixtureResponse = mFixture[sUrl];
if (!sUrl.includes(" ")) {
sUrl = "GET " + sUrl;
}
if (Array.isArray(oFixtureResponse)) {
mUrls[sUrl] = oFixtureResponse.map(buildResponse);
} else {
mUrls[sUrl] = [buildResponse(oFixtureResponse)];
}
}
return mUrls;
}
// calculates the content type from the given resource name
function contentType(sName) {
if (/\.xml$/.test(sName)) {
return "application/xml";
}
if (/\.json$/.test(sName)) {
return sJson;
}
return "application/x-octet-stream";
}
// Logs and returns a response for the given error
function error(iCode, oRequest, sMessage) {
Log.error(oRequest.requestLine, sMessage, "sap.ui.test.TestUtils");
return {
code : iCode,
headers : {"Content-Type" : "text/plain"},
message : sMessage
};
}
// returns the first line (containing method and url)
function firstLine(sText) {
return sText.slice(0, sText.indexOf("\r\n"));
}
/*
* Formats a multipart object into the message body.
*
* @param {object} oMultipart The multipart object with boundary and parts
* @param {map} mODataHeaders The OData headers to copy into the response parts
*/
function formatMultipart(oMultipart, mODataHeaders) {
var aResponseParts = [""];
oMultipart.parts.forEach(function (oPart) {
aResponseParts.push(oPart.boundary
? "\r\nContent-Type: multipart/mixed;boundary=" + oPart.boundary
+ "\r\n\r\n" + formatMultipart(oPart, mODataHeaders)
: formatResponse(oPart, mODataHeaders));
});
aResponseParts.push("--\r\n");
return aResponseParts.join("--" + oMultipart.boundary);
}
/*
* Formats the response to be inserted into the batch
*
* @param {object} oResponse The response with code, contentId, headers, message
* @param {map} mODataHeaders The OData headers from the batch to copy into the response
* @returns {string} The response to be inserted into the batch
*/
function formatResponse(oResponse, mODataHeaders) {
var mHeaders = jQuery.extend({}, mODataHeaders, oResponse.headers);
// Note: datajs expects a space after the response code
return sMimeHeaders
+ (oResponse.contentId ? "Content-ID: " + oResponse.contentId + "\r\n" : "")
+ "\r\nHTTP/1.1 " + oResponse.code + " \r\n"
+ Object.keys(mHeaders).map(function (sHeader) {
return sHeader + ": " + mHeaders[sHeader];
}).join("\r\n")
+ "\r\n\r\n" + (oResponse.message || "") + "\r\n";
}
/**
* Gets matching responses to the URL and request method from the fixture. First, checks
* if a matching response is in the <code>mUrlToResponse</code> map. If that's not the
* case, it goes on to check the regular expressions for a match.
* @param {string} sMethod The request method
* @param {string} sUrl The URL of the request
* @returns {object} An object with the properties <code>responses</code> and
* <code>match</code>
*/
function getMatchingResponse(sMethod, sUrl) {
var aMatches, aMatchingResponses,
sRequestLine = sMethod + " " + sUrl;
if (mUrlToResponses[sRequestLine]) {
return {
responses: mUrlToResponses[sRequestLine]
};
}
if (!aRegexpResponses) {
return undefined;
}
aMatches = [];
aMatchingResponses = aRegexpResponses.filter(function (oResponse) {
var aMatch = sRequestLine.match(oResponse.regExp);
if (aMatch) {
aMatches.push(aMatch);
}
return aMatch;
});
if (aMatchingResponses.length > 1) {
Log.warning("Multiple matches found for " + sRequestLine, undefined,
"sap.ui.test.TestUtils");
return undefined;
}
return aMatchingResponses.length ? {
responses : aMatchingResponses[0].response,
match : aMatches[0]
} : undefined;
}
/*
* Returns a map with only the OData headers that have to be copied to the response
*
* @param {object} oRequest The request to take the headers from
*/
function getODataHeaders(oRequest) {
var sKey,
mODataHeaders = {};
for (sKey in oRequest.requestHeaders) {
if (rODataHeaders.test(sKey)) {
mODataHeaders[sKey] = oRequest.requestHeaders[sKey];
}
}
return mODataHeaders;
}
/*
* Determines the matching response for the request. Returns an error response if no
* match was found.
*
* @param {object} oRequest The Sinon request object
* @param {string} [sContentId] The content ID
*/
function getResponseFromFixture(oRequest, sContentId) {
var oMatch = getMatchingResponse(oRequest.method, oRequest.url),
oResponse,
aResponses = oMatch && oMatch.responses;
aResponses = (aResponses || []).filter(function (oResponse) {
if (typeof oResponse.ifMatch === "function") {
return oResponse.ifMatch(oRequest);
}
return !oResponse.ifMatch || oResponse.ifMatch.test(oRequest.requestBody);
});
if (aResponses.length) {
oResponse = aResponses[0];
if (typeof oResponse.buildResponse === "function") {
oResponse = merge({}, oResponse);
oResponse.buildResponse(oMatch.match, oResponse);
}
} else {
switch (oRequest.method) {
case "HEAD":
oResponse = {code : 200};
break;
case "DELETE":
case "MERGE":
case "PATCH":
oResponse = {
code : 204
};
break;
case "POST":
oResponse = {
code : 200,
headers : {"Content-Type" : sJson},
message : oRequest.requestBody
};
break;
// no default
}
}
if (oResponse) {
Log.info(oRequest.method + " " + oRequest.url,
// Note: JSON.stringify(oRequest.requestHeaders) outputs too much for now
'{"If-Match":' + JSON.stringify(oRequest.requestHeaders["If-Match"]) + '}',
"sap.ui.test.TestUtils");
} else {
oResponse = error(404, oRequest, "No mock data found");
}
oResponse.headers = jQuery.extend({}, getODataHeaders(oRequest), oResponse.headers);
if (sContentId && oResponse.code < 300) {
oResponse.contentId = sContentId;
}
return oResponse;
}
/*
* Processes a multipart message (body or change set)
*
* @param {string} sServiceBase The service base URL
* @param {string} sBody The body
* @returns {object} An object with the properties boundary and parts
*/
function multipart(sServiceBase, sBody) {
var sBoundary;
// skip preamble consisting of whitespace (as sent by datajs)
sBody = sBody.replace(/^\s+/, "");
sBoundary = firstLine(sBody);
return {
boundary : firstLine(sBody).slice(2),
parts : sBody.split(sBoundary).slice(1, -1).map(function (sRequestPart) {
var aFailures, sFirstLine, aMatch, oMultipart, oRequest, iRequestStart;
sRequestPart = sRequestPart.slice(2);
sFirstLine = firstLine(sRequestPart);
if (rMultipartHeader.test(sFirstLine)) {
oMultipart = multipart(sServiceBase,
sRequestPart.slice(sFirstLine.length + 4));
aFailures = oMultipart.parts.filter(function (oPart) {
return oPart.code >= 300;
});
return aFailures.length ? aFailures[0] : oMultipart;
}
iRequestStart = sRequestPart.indexOf("\r\n\r\n") + 4;
oRequest = parseRequest(sServiceBase, sRequestPart.slice(iRequestStart));
aMatch = rContentId.exec(sRequestPart.slice(0, iRequestStart));
return getResponseFromFixture(oRequest, aMatch && aMatch[1]);
})
};
}
// Parses the request string of a batch into an object matching the Sinon request object
function parseRequest(sServiceBase, sRequest) {
var iBodySeparator = sRequest.indexOf("\r\n\r\n"),
aLines,
aMatches,
oRequest = {requestHeaders : {}};
oRequest.requestBody = sRequest.slice(iBodySeparator + 4, sRequest.length - 2);
sRequest = sRequest.slice(0, iBodySeparator);
aLines = sRequest.split("\r\n");
oRequest.requestLine = aLines.shift();
aMatches = rRequestLine.exec(oRequest.requestLine);
if (aMatches) {
oRequest.method = aMatches[1];
oRequest.url = sServiceBase + aMatches[2];
aLines.forEach(function (sLine) {
var aMatches = rHeaderLine.exec(sLine);
if (aMatches) {
oRequest.requestHeaders[aMatches[1]] = aMatches[2];
}
});
}
return oRequest;
}
// POST handler which recognizes a $batch
function post(oRequest) {
var sUrl = oRequest.url;
if (rBatch.test(sUrl)) {
batch(sUrl.slice(0, sUrl.indexOf("/$batch") + 1), oRequest);
} else {
respondFromFixture(oRequest);
}
}
/*
* Reads and caches the source for the given path.
*/
function readMessage(sPath) {
var sMessage = mMessageForPath[sPath];
if (!sMessage) {
jQuery.ajax({
async : false,
url : sPath,
dataType : "text",
success : function (sBody) {
sMessage = sBody;
}
});
if (!sMessage) {
throw new Error(sPath + ": resource not found");
}
mMessageForPath[sPath] = sMessage;
}
return sMessage;
}
/*
* Searches the response in the fixture and responds.
*
* @param {object} oRequest The Sinon request object
*/
function respondFromFixture(oRequest) {
var oResponse = getResponseFromFixture(oRequest);
iRequestCount += 1;
oRequest.respond(oResponse.code, oResponse.headers, oResponse.message);
}
function setupServer() {
var fnRestore, oServer;
// build the fixture
mUrlToResponses = buildResponses();
if (aRegExps) {
aRegexpResponses = aRegExps.map(function (oRegExpFixture) {
return {
regExp : oRegExpFixture.regExp,
response : Array.isArray(oRegExpFixture.response)
? oRegExpFixture.response.map(buildResponse)
: [buildResponse(oRegExpFixture.response)]
};
});
}
// set up the fake server
oServer = sinon.fakeServer.create();
oSandbox.add(oServer);
oServer.autoRespond = true;
if (sAutoRespondAfter) {
oServer.autoRespondAfter = parseInt(sAutoRespondAfter);
}
// Send all requests except $batch through respondFromFixture
oServer.respondWith("GET", /./, respondFromFixture);
oServer.respondWith("DELETE", /./, respondFromFixture);
oServer.respondWith("HEAD", /./, respondFromFixture);
oServer.respondWith("PATCH", /./, respondFromFixture);
oServer.respondWith("MERGE", /./, respondFromFixture);
oServer.respondWith("POST", /./, post);
// wrap oServer.restore to also clear the filter
fnRestore = oServer.restore;
oServer.restore = function () {
sinon.FakeXMLHttpRequest.filters = []; // no API to clear the filter
fnRestore.apply(this, arguments); // call the original restore
};
// Set up a filter so that other requests (e.g. from jQuery.sap.require) go through.
// This filter fetches all DELETE, all POST (incl. $batch) and the selected GET
// requests.
sinon.xhr.supportsCORS = jQuery.support.cors;
sinon.FakeXMLHttpRequest.useFilters = true;
sinon.FakeXMLHttpRequest.addFilter(function (sMethod, sUrl) {
// must return true if the request is NOT processed by the fake server
return sMethod !== "DELETE" && sMethod !== "HEAD" && sMethod !== "MERGE"
&& sMethod !== "PATCH" && sMethod !== "POST"
&& !getMatchingResponse(sMethod, sUrl);
});
}
// ensure to always search the fake data in test-resources, remove cache buster token
sBase = sap.ui.require.toUrl(sBase)
.replace(/(^|\/)resources\/(~[-a-zA-Z0-9_.]*~\/)?/, "$1test-resources/") + "/";
setupServer();
},
/**
* If a test is wrapped by this function, you can test that locale-dependent texts are
* created as expected, but avoid checking against the real message text. The function
* ensures that every message retrieved using
* <code>sap.ui.getCore().getLibraryResourceBundle().getText()</code> consists of the key
* followed by all parameters referenced in the bundle's text in order of their numbers.
*
* The function uses <a href="http://sinonjs.org/docs/">Sinon.js</a> and expects that it
* has been loaded. It creates a <a href="http://sinonjs.org/docs/#sandbox">Sinon
* sandbox</a> which is available as <code>this</code> in the code under test.
*
* <b>Example</b>:
*
* In the message bundle a message looks like this:
* <pre>
* EnterNumber=Enter a number with scale {1} and precision {0}.
* </pre>
* This leads to the following results:
* <table>
* <tr><th>Call</th><th>Result</th></tr>
* <tr><td><code>getText("EnterNumber", [10])</code></td>
* <td>EnterNumber 10 {1}</td></tr>
* <tr><td><code>getText("EnterNumber", [10, 3])</code></td>
* <td>EnterNumber 10 3</td></tr>
* <tr><td><code>getText("EnterNumber", [10, 3, "foo"])</code></td>
* <td>EnterNumber 10 3</td></tr>
* </table>
*
* <b>Usage</b>:
* <pre>
* QUnit.test("parse error", function (assert) {
* sap.ui.test.TestUtils.withNormalizedMessages(function () {
* var oType = new sap.ui.model.odata.type.Decimal({},
* {constraints : {precision : 10, scale : 3});
*
* assert.throws(function () {
* oType.parseValue("-123.4567", "string");
* }, /EnterNumber 10 3/);
* });
* });
* </pre>
* @param {function} fnCodeUnderTest
* the code under test
* @since 1.27.1
*/
withNormalizedMessages : function (fnCodeUnderTest) {
var oSandbox = sinon.sandbox.create();
try {
var oCore = sap.ui.getCore(),
fnGetBundle = oCore.getLibraryResourceBundle;
oSandbox.stub(oCore, "getLibraryResourceBundle").returns({
getText : function (sKey, aArgs) {
var sResult = sKey,
sText = fnGetBundle.call(oCore).getText(sKey),
i;
for (i = 0; i < 10; i += 1) {
if (sText.indexOf("{" + i + "}") >= 0) {
sResult += " " + (i >= aArgs.length ? "{" + i + "}" : aArgs[i]);
}
}
return sResult;
}
});
fnCodeUnderTest.apply(this);
} finally {
oSandbox.verifyAndRestore();
}
},
/**
* @returns {boolean}
* <code>true</code> if the real OData service is used.
*/
isRealOData : function () {
return bRealOData;
},
/**
* @returns {boolean}
* <code>true</code> if the support assistant shall be used.
*/
isSupportAssistant : function () {
return bSupportAssistant;
},
/**
* Returns the realOData query parameter so that it can be forwarded to an embedded test
*
* @returns {string}
* the realOData query parameter or "" if none was given
*/
getRealOData : function () {
return sRealOData ? "&realOData=" + sRealOData : "";
},
/**
* Returns the number of server requests within {@link .useFakeServer} since the latest
* {@link #resetRequestCount}.
*
* @returns {number} The number of requests
*/
getRequestCount : function () {
return iRequestCount;
},
/**
* Adjusts the given absolute path so that (in case of "realOData=proxy" or
* "realOData=true") the request is passed through the SimpleProxyServlet.
*
* @param {string} sAbsolutePath
* some absolute path
* @returns {string}
* the absolute path transformed in a way that invokes a proxy, but still absolute,
* with query parameters preserved
*/
proxy : function (sAbsolutePath) {
var sProxyUrl, iQueryPos;
if (!bProxy) {
return sAbsolutePath;
}
iQueryPos = sAbsolutePath.indexOf("?");
sProxyUrl = sap.ui.require.toUrl("sap/ui").replace("resources/sap/ui", "proxy");
return new URI(sProxyUrl + sAbsolutePath, document.baseURI).pathname().toString()
+ (iQueryPos >= 0 ? sAbsolutePath.slice(iQueryPos) : "");
},
/**
* Resets the counter for server requests within {@link .useFakeServer}.
*/
resetRequestCount : function () {
iRequestCount = 0;
},
/**
* Returns the value which has been stored with the given key using {@link #setData} and
* resets it.
*
* @param {string} sKey
* The key
* @returns {object}
* The value
*/
retrieveData : function (sKey) {
var vValue = mData[sKey];
delete mData[sKey];
return vValue;
},
/**
* Stores the given value under the given key so that it can be used by a test at a later
* point in time.
*
* @param {string} sKey
* The key
* @param {object} vValue
* The value
*/
setData : function (sKey, vValue) {
mData[sKey] = vValue;
},
/**
* Sets up the fake server for OData responses unless real OData responses are requested.
*
* The behavior is controlled by the request property "realOData". If the property has any
* of the following values, the fake server is <i>not</i> set up.
* <ul>
* <li> "realOData=proxy" (or "realOData=true"): The test must be part of the UI5 Java
* Servlet. Set the system property "com.sap.ui5.proxy.REMOTE_LOCATION" to a server
* containing the Gateway test service.
* <li> "realOData=direct": The test and the Gateway service must be reachable via the
* same host. This can be reached either by deploying the test code to the Gateway host
* or by using a reverse proxy like the SAP Web Dispatcher.
* </ul>
*
* @param {object} oSandbox
* a Sinon sandbox as created using <code>sinon.sandbox.create()</code>
* @param {map} mFixture
* the fixture for {@link sap.ui.test.TestUtils.useFakeServer}.
* @param {string} [sSourceBase="sap/ui/core/qunit/odata/v4/data"]
* The base path for <code>source</code> values in the fixture. The path must be in the
* project's test folder, typically it should start with "sap".
* Example: <code>"sap/ui/core/qunit/model"</code>
* @param {string} [sFilterBase="/"]
* A base path for relative filter URLs in <code>mFixture</code>.
* @param {object[]} [aRegExps]
* The regular expression array for {@link sap.ui.test.TestUtils.useFakeServer}
*
* @see #.isRealOData
* @see #.proxy
*/
setupODataV4Server : function (oSandbox, mFixture, sSourceBase, sFilterBase, aRegExps) {
var mResultingFixture = {};
if (bRealOData) {
return;
}
if (!sFilterBase) {
sFilterBase = "/";
} else if (sFilterBase.slice(-1) !== "/") {
sFilterBase += "/";
}
Object.keys(mFixture).forEach(function (sRequest) {
var aMatches = rRequestKey.exec(sRequest),
sMethod,
sUrl;
if (aMatches) {
sMethod = aMatches[1] || "GET";
sUrl = aMatches[2];
} else {
sMethod = "GET";
sUrl = sRequest;
}
if (!sUrl.startsWith("/")) {
sUrl = sFilterBase + sUrl;
}
mResultingFixture[sMethod + " " + sUrl] = mFixture[sRequest];
});
TestUtils.useFakeServer(oSandbox, sSourceBase || "sap/ui/core/qunit/odata/v4/data",
mResultingFixture, aRegExps);
}
};
return TestUtils;
}, /* bExport= */ true);