UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

921 lines (845 loc) 31.6 kB
/*! * 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);