diff --git a/app/assets/javascripts/discourse/package.json b/app/assets/javascripts/discourse/package.json index 99aaf019354..1f66aa4e7f7 100644 --- a/app/assets/javascripts/discourse/package.json +++ b/app/assets/javascripts/discourse/package.json @@ -54,7 +54,7 @@ "message-bus-client": "^3.3.0", "mousetrap": "^1.6.5", "mousetrap-global-bind": "^1.1.0", - "pretender": "^3.4.3", + "pretender": "^3.4.7", "pretty-text": "^1.0.0", "qunit": "^2.14.0", "qunit-dom": "^1.6.0", diff --git a/app/assets/javascripts/yarn.lock b/app/assets/javascripts/yarn.lock index 76e24fe1e24..236a56ce41c 100644 --- a/app/assets/javascripts/yarn.lock +++ b/app/assets/javascripts/yarn.lock @@ -5935,7 +5935,7 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fake-xml-http-request@^2.1.1: +fake-xml-http-request@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/fake-xml-http-request/-/fake-xml-http-request-2.1.2.tgz#f1786720cae50bbb46273035a0173414f3e85e74" integrity sha512-HaFMBi7r+oEC9iJNpc3bvcW7Z7iLmM26hPDmlb0mFwyANSsOQAtJxbdWsXITKOzZUyMYK0zYCv3h5yDj9TsiXg== @@ -9347,12 +9347,12 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -pretender@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/pretender/-/pretender-3.4.3.tgz#a3b4160516007075d29127262f3a0063d19896e9" - integrity sha512-AlbkBly9R8KR+R0sTCJ/ToOeEoUMtt52QVCetui5zoSmeLOU3S8oobFsyPLm1O2txR6t58qDNysqPnA1vVi8Hg== +pretender@^3.4.7: + version "3.4.7" + resolved "https://registry.yarnpkg.com/pretender/-/pretender-3.4.7.tgz#34a2ae2d1fc9db440a990d50e6c0f5481d8755fc" + integrity sha512-jkPAvt1BfRi0RKamweJdEcnjkeu7Es8yix3bJ+KgBC5VpG/Ln4JE3hYN6vJym4qprm8Xo5adhWpm3HCoft1dOw== dependencies: - fake-xml-http-request "^2.1.1" + fake-xml-http-request "^2.1.2" route-recognizer "^0.3.3" printf@^0.6.1: diff --git a/lib/tasks/javascript.rake b/lib/tasks/javascript.rake index 510a1ff0b78..2914940a788 100644 --- a/lib/tasks/javascript.rake +++ b/lib/tasks/javascript.rake @@ -188,7 +188,10 @@ def dependencies source: 'qunit/qunit/qunit.js' }, { - source: 'pretender/pretender.js' + source: 'pretender/dist/pretender.js' + }, + { + source: 'fake-xml-http-request/fake_xml_http_request.js' }, { source: 'sinon/pkg/sinon.js' diff --git a/package.json b/package.json index 39a7c9eeff5..abcdfe65f98 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "chrome-launcher": "^0.12.0", "chrome-remote-interface": "^0.25", "lodash-cli": "https://github.com/lodash-archive/lodash-cli.git", - "pretender": "^1.6", + "pretender": "^3.4.7", "puppeteer": "1.20", "qunit": "2.8.0", "route-recognizer": "^0.3.3", diff --git a/vendor/assets/javascripts/fake_xml_http_request.js b/vendor/assets/javascripts/fake_xml_http_request.js index 0284c65fca9..3537cc2a6fc 100644 --- a/vendor/assets/javascripts/fake_xml_http_request.js +++ b/vendor/assets/javascripts/fake_xml_http_request.js @@ -1,480 +1,530 @@ -(function(undefined){ -/** - * Minimal Event interface implementation - * - * Original implementation by Sven Fuchs: https://gist.github.com/995028 - * Modifications and tests by Christian Johansen. - * - * @author Sven Fuchs (svenfuchs@artweb-design.de) - * @author Christian Johansen (christian@cjohansen.no) - * @license BSD - * - * Copyright (c) 2011 Sven Fuchs, Christian Johansen - */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.FakeXMLHttpRequest = factory()); +}(this, (function () { 'use strict'; -var _Event = function Event(type, bubbles, cancelable, target) { - this.type = type; - this.bubbles = bubbles; - this.cancelable = cancelable; - this.target = target; -}; + /** + * Minimal Event interface implementation + * + * Original implementation by Sven Fuchs: https://gist.github.com/995028 + * Modifications and tests by Christian Johansen. + * + * @author Sven Fuchs (svenfuchs@artweb-design.de) + * @author Christian Johansen (christian@cjohansen.no) + * @license BSD + * + * Copyright (c) 2011 Sven Fuchs, Christian Johansen + */ -_Event.prototype = { - stopPropagation: function () {}, - preventDefault: function () { - this.defaultPrevented = true; - } -}; + var _Event = function Event(type, bubbles, cancelable, target) { + this.type = type; + this.bubbles = bubbles; + this.cancelable = cancelable; + this.target = target; + }; -/* - Used to set the statusText property of an xhr object -*/ -var httpStatusCodes = { - 100: "Continue", - 101: "Switching Protocols", - 200: "OK", - 201: "Created", - 202: "Accepted", - 203: "Non-Authoritative Information", - 204: "No Content", - 205: "Reset Content", - 206: "Partial Content", - 300: "Multiple Choice", - 301: "Moved Permanently", - 302: "Found", - 303: "See Other", - 304: "Not Modified", - 305: "Use Proxy", - 307: "Temporary Redirect", - 400: "Bad Request", - 401: "Unauthorized", - 402: "Payment Required", - 403: "Forbidden", - 404: "Not Found", - 405: "Method Not Allowed", - 406: "Not Acceptable", - 407: "Proxy Authentication Required", - 408: "Request Timeout", - 409: "Conflict", - 410: "Gone", - 411: "Length Required", - 412: "Precondition Failed", - 413: "Request Entity Too Large", - 414: "Request-URI Too Long", - 415: "Unsupported Media Type", - 416: "Requested Range Not Satisfiable", - 417: "Expectation Failed", - 422: "Unprocessable Entity", - 500: "Internal Server Error", - 501: "Not Implemented", - 502: "Bad Gateway", - 503: "Service Unavailable", - 504: "Gateway Timeout", - 505: "HTTP Version Not Supported" -}; - - -/* - Cross-browser XML parsing. Used to turn - XML responses into Document objects - Borrowed from JSpec -*/ -function parseXML(text) { - var xmlDoc; - - if (typeof DOMParser != "undefined") { - var parser = new DOMParser(); - xmlDoc = parser.parseFromString(text, "text/xml"); - } else { - xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); - xmlDoc.async = "false"; - xmlDoc.loadXML(text); - } - - return xmlDoc; -} - -/* - Without mocking, the native XMLHttpRequest object will throw - an error when attempting to set these headers. We match this behavior. -*/ -var unsafeHeaders = { - "Accept-Charset": true, - "Accept-Encoding": true, - "Connection": true, - "Content-Length": true, - "Cookie": true, - "Cookie2": true, - "Content-Transfer-Encoding": true, - "Date": true, - "Expect": true, - "Host": true, - "Keep-Alive": true, - "Referer": true, - "TE": true, - "Trailer": true, - "Transfer-Encoding": true, - "Upgrade": true, - "User-Agent": true, - "Via": true -}; - -/* - Adds an "event" onto the fake xhr object - that just calls the same-named method. This is - in case a library adds callbacks for these events. -*/ -function _addEventListener(eventName, xhr){ - xhr.addEventListener(eventName, function (event) { - var listener = xhr["on" + eventName]; - - if (listener && typeof listener == "function") { - listener(event); + _Event.prototype = { + stopPropagation: function () {}, + preventDefault: function () { + this.defaultPrevented = true; } - }); -} - -/* - Constructor for a fake window.XMLHttpRequest -*/ -function FakeXMLHttpRequest() { - this.readyState = FakeXMLHttpRequest.UNSENT; - this.requestHeaders = {}; - this.requestBody = null; - this.status = 0; - this.statusText = ""; - - this._eventListeners = {}; - var events = ["loadstart", "load", "abort", "loadend"]; - for (var i = events.length - 1; i >= 0; i--) { - _addEventListener(events[i], this); - } -} - - -// These status codes are available on the native XMLHttpRequest -// object, so we match that here in case a library is relying on them. -FakeXMLHttpRequest.UNSENT = 0; -FakeXMLHttpRequest.OPENED = 1; -FakeXMLHttpRequest.HEADERS_RECEIVED = 2; -FakeXMLHttpRequest.LOADING = 3; -FakeXMLHttpRequest.DONE = 4; - -FakeXMLHttpRequest.prototype = { - UNSENT: 0, - OPENED: 1, - HEADERS_RECEIVED: 2, - LOADING: 3, - DONE: 4, - async: true, + }; /* - Duplicates the behavior of native XMLHttpRequest's open function + Used to set the statusText property of an xhr object */ - open: function open(method, url, async, username, password) { - this.method = method; - this.url = url; - this.async = typeof async == "boolean" ? async : true; - this.username = username; - this.password = password; - this.responseText = null; - this.responseXML = null; - this.requestHeaders = {}; - this.sendFlag = false; - this._readyStateChange(FakeXMLHttpRequest.OPENED); - }, + var httpStatusCodes = { + 100: "Continue", + 101: "Switching Protocols", + 200: "OK", + 201: "Created", + 202: "Accepted", + 203: "Non-Authoritative Information", + 204: "No Content", + 205: "Reset Content", + 206: "Partial Content", + 300: "Multiple Choice", + 301: "Moved Permanently", + 302: "Found", + 303: "See Other", + 304: "Not Modified", + 305: "Use Proxy", + 307: "Temporary Redirect", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Request Entity Too Large", + 414: "Request-URI Too Long", + 415: "Unsupported Media Type", + 416: "Requested Range Not Satisfiable", + 417: "Expectation Failed", + 422: "Unprocessable Entity", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported" + }; + /* - Duplicates the behavior of native XMLHttpRequest's addEventListener function + Cross-browser XML parsing. Used to turn + XML responses into Document objects + Borrowed from JSpec */ - addEventListener: function addEventListener(event, listener) { - this._eventListeners[event] = this._eventListeners[event] || []; - this._eventListeners[event].push(listener); - }, + function parseXML(text) { + var xmlDoc; - /* - Duplicates the behavior of native XMLHttpRequest's removeEventListener function - */ - removeEventListener: function removeEventListener(event, listener) { - var listeners = this._eventListeners[event] || []; - - for (var i = 0, l = listeners.length; i < l; ++i) { - if (listeners[i] == listener) { - return listeners.splice(i, 1); - } - } - }, - - /* - Duplicates the behavior of native XMLHttpRequest's dispatchEvent function - */ - dispatchEvent: function dispatchEvent(event) { - var type = event.type; - var listeners = this._eventListeners[type] || []; - - for (var i = 0; i < listeners.length; i++) { - if (typeof listeners[i] == "function") { - listeners[i].call(this, event); - } else { - listeners[i].handleEvent(event); - } - } - - return !!event.defaultPrevented; - }, - - /* - Duplicates the behavior of native XMLHttpRequest's setRequestHeader function - */ - setRequestHeader: function setRequestHeader(header, value) { - verifyState(this); - - if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) { - throw new Error("Refused to set unsafe header \"" + header + "\""); - } - - if (this.requestHeaders[header]) { - this.requestHeaders[header] += "," + value; + if (typeof DOMParser != "undefined") { + var parser = new DOMParser(); + xmlDoc = parser.parseFromString(text, "text/xml"); } else { - this.requestHeaders[header] = value; + xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); + xmlDoc.async = "false"; + xmlDoc.loadXML(text); } - }, + + return xmlDoc; + } /* - Duplicates the behavior of native XMLHttpRequest's send function + Without mocking, the native XMLHttpRequest object will throw + an error when attempting to set these headers. We match this behavior. */ - send: function send(data) { - verifyState(this); + var unsafeHeaders = { + "Accept-Charset": true, + "Accept-Encoding": true, + "Connection": true, + "Content-Length": true, + "Cookie": true, + "Cookie2": true, + "Content-Transfer-Encoding": true, + "Date": true, + "Expect": true, + "Host": true, + "Keep-Alive": true, + "Referer": true, + "TE": true, + "Trailer": true, + "Transfer-Encoding": true, + "Upgrade": true, + "User-Agent": true, + "Via": true + }; - if (!/^(get|head)$/i.test(this.method)) { - if (this.requestHeaders["Content-Type"]) { - var value = this.requestHeaders["Content-Type"].split(";"); - this.requestHeaders["Content-Type"] = value[0] + ";charset=utf-8"; - } else { - this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8"; + /* + Adds an "event" onto the fake xhr object + that just calls the same-named method. This is + in case a library adds callbacks for these events. + */ + function _addEventListener(eventName, xhr){ + xhr.addEventListener(eventName, function (event) { + var listener = xhr["on" + eventName]; + + if (listener && typeof listener == "function") { + listener.call(event.target, event); + } + }); + } + + function EventedObject() { + this._eventListeners = {}; + var events = ["loadstart", "progress", "load", "abort", "loadend"]; + for (var i = events.length - 1; i >= 0; i--) { + _addEventListener(events[i], this); + } + } + EventedObject.prototype = { + /* + Duplicates the behavior of native XMLHttpRequest's addEventListener function + */ + addEventListener: function addEventListener(event, listener) { + this._eventListeners[event] = this._eventListeners[event] || []; + this._eventListeners[event].push(listener); + }, + + /* + Duplicates the behavior of native XMLHttpRequest's removeEventListener function + */ + removeEventListener: function removeEventListener(event, listener) { + var listeners = this._eventListeners[event] || []; + + for (var i = 0, l = listeners.length; i < l; ++i) { + if (listeners[i] == listener) { + return listeners.splice(i, 1); + } + } + }, + + /* + Duplicates the behavior of native XMLHttpRequest's dispatchEvent function + */ + dispatchEvent: function dispatchEvent(event) { + var type = event.type; + var listeners = this._eventListeners[type] || []; + + for (var i = 0; i < listeners.length; i++) { + if (typeof listeners[i] == "function") { + listeners[i].call(this, event); + } else { + listeners[i].handleEvent(event); + } } - this.requestBody = data; + return !!event.defaultPrevented; + }, + + /* + Triggers an `onprogress` event with the given parameters. + */ + _progress: function _progress(lengthComputable, loaded, total) { + var event = new _Event('progress'); + event.target = this; + event.lengthComputable = lengthComputable; + event.loaded = loaded; + event.total = total; + this.dispatchEvent(event); } - - this.errorFlag = false; - this.sendFlag = this.async; - this._readyStateChange(FakeXMLHttpRequest.OPENED); - - if (typeof this.onSend == "function") { - this.onSend(this); - } - - this.dispatchEvent(new _Event("loadstart", false, false, this)); - }, + }; /* - Duplicates the behavior of native XMLHttpRequest's abort function + Constructor for a fake window.XMLHttpRequest */ - abort: function abort() { - this.aborted = true; - this.responseText = null; - this.errorFlag = true; - this.requestHeaders = {}; - - if (this.readyState > FakeXMLHttpRequest.UNSENT && this.sendFlag) { - this._readyStateChange(FakeXMLHttpRequest.DONE); - this.sendFlag = false; - } - + function FakeXMLHttpRequest() { + EventedObject.call(this); this.readyState = FakeXMLHttpRequest.UNSENT; + this.requestHeaders = {}; + this.requestBody = null; + this.status = 0; + this.statusText = ""; + this.upload = new EventedObject(); + this.onabort= null; + this.onerror= null; + this.onload= null; + this.onloadend= null; + this.onloadstart= null; + this.onprogress= null; + this.onreadystatechange= null; + this.ontimeout= null; + } - this.dispatchEvent(new _Event("abort", false, false, this)); - if (typeof this.onerror === "function") { + FakeXMLHttpRequest.prototype = new EventedObject(); + + // These status codes are available on the native XMLHttpRequest + // object, so we match that here in case a library is relying on them. + FakeXMLHttpRequest.UNSENT = 0; + FakeXMLHttpRequest.OPENED = 1; + FakeXMLHttpRequest.HEADERS_RECEIVED = 2; + FakeXMLHttpRequest.LOADING = 3; + FakeXMLHttpRequest.DONE = 4; + + var FakeXMLHttpRequestProto = { + UNSENT: 0, + OPENED: 1, + HEADERS_RECEIVED: 2, + LOADING: 3, + DONE: 4, + async: true, + withCredentials: false, + + /* + Duplicates the behavior of native XMLHttpRequest's open function + */ + open: function open(method, url, async, username, password) { + this.method = method; + this.url = url; + this.async = typeof async == "boolean" ? async : true; + this.username = username; + this.password = password; + this.responseText = null; + this.response = this.responseText; + this.responseXML = null; + this.responseURL = url; + this.requestHeaders = {}; + this.sendFlag = false; + this._readyStateChange(FakeXMLHttpRequest.OPENED); + }, + + /* + Duplicates the behavior of native XMLHttpRequest's setRequestHeader function + */ + setRequestHeader: function setRequestHeader(header, value) { + verifyState(this); + + if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) { + throw new Error("Refused to set unsafe header \"" + header + "\""); + } + + if (this.requestHeaders[header]) { + this.requestHeaders[header] += "," + value; + } else { + this.requestHeaders[header] = value; + } + }, + + /* + Duplicates the behavior of native XMLHttpRequest's send function + */ + send: function send(data) { + verifyState(this); + + if (!/^(get|head)$/i.test(this.method)) { + var hasContentTypeHeader = false; + + Object.keys(this.requestHeaders).forEach(function (key) { + if (key.toLowerCase() === 'content-type') { + hasContentTypeHeader = true; + } + }); + + if (!hasContentTypeHeader && !(data || '').toString().match('FormData')) { + this.requestHeaders["Content-Type"] = "text/plain;charset=UTF-8"; + } + + this.requestBody = data; + } + + this.errorFlag = false; + this.sendFlag = this.async; + this._readyStateChange(FakeXMLHttpRequest.OPENED); + + if (typeof this.onSend == "function") { + this.onSend(this); + } + + this.dispatchEvent(new _Event("loadstart", false, false, this)); + }, + + /* + Duplicates the behavior of native XMLHttpRequest's abort function + */ + abort: function abort() { + this.aborted = true; + this.responseText = null; + this.response = this.responseText; + this.errorFlag = true; + this.requestHeaders = {}; + + this.dispatchEvent(new _Event("abort", false, false, this)); + + if (this.readyState > FakeXMLHttpRequest.UNSENT && this.sendFlag) { + this._readyStateChange(FakeXMLHttpRequest.UNSENT); + this.sendFlag = false; + } + + if (typeof this.onerror === "function") { this.onerror(); - } - }, + } + }, + + /* + Duplicates the behavior of native XMLHttpRequest's getResponseHeader function + */ + getResponseHeader: function getResponseHeader(header) { + if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { + return null; + } + + if (/^Set-Cookie2?$/i.test(header)) { + return null; + } + + header = header.toLowerCase(); + + for (var h in this.responseHeaders) { + if (h.toLowerCase() == header) { + return this.responseHeaders[h]; + } + } - /* - Duplicates the behavior of native XMLHttpRequest's getResponseHeader function - */ - getResponseHeader: function getResponseHeader(header) { - if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { return null; - } + }, - if (/^Set-Cookie2?$/i.test(header)) { - return null; - } - - header = header.toLowerCase(); - - for (var h in this.responseHeaders) { - if (h.toLowerCase() == header) { - return this.responseHeaders[h]; + /* + Duplicates the behavior of native XMLHttpRequest's getAllResponseHeaders function + */ + getAllResponseHeaders: function getAllResponseHeaders() { + if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { + return ""; } - } - return null; - }, + var headers = ""; - /* - Duplicates the behavior of native XMLHttpRequest's getAllResponseHeaders function - */ - getAllResponseHeaders: function getAllResponseHeaders() { - if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { - return ""; - } - - var headers = ""; - - for (var header in this.responseHeaders) { - if (this.responseHeaders.hasOwnProperty(header) && !/^Set-Cookie2?$/i.test(header)) { - headers += header + ": " + this.responseHeaders[header] + "\r\n"; + for (var header in this.responseHeaders) { + if (this.responseHeaders.hasOwnProperty(header) && !/^Set-Cookie2?$/i.test(header)) { + headers += header + ": " + this.responseHeaders[header] + "\r\n"; + } } - } - return headers; - }, + return headers; + }, - /* - Places a FakeXMLHttpRequest object into the passed - state. - */ - _readyStateChange: function _readyStateChange(state) { - this.readyState = state; - - if (typeof this.onreadystatechange == "function") { - this.onreadystatechange(); - } - - this.dispatchEvent(new _Event("readystatechange")); - - if (this.readyState == FakeXMLHttpRequest.DONE) { - this.dispatchEvent(new _Event("load", false, false, this)); - this.dispatchEvent(new _Event("loadend", false, false, this)); - } - }, - - - /* - Sets the FakeXMLHttpRequest object's response headers and - places the object into readyState 2 - */ - _setResponseHeaders: function _setResponseHeaders(headers) { - this.responseHeaders = {}; - - for (var header in headers) { - if (headers.hasOwnProperty(header)) { - this.responseHeaders[header] = headers[header]; + /* + Duplicates the behavior of native XMLHttpRequest's overrideMimeType function + */ + overrideMimeType: function overrideMimeType(mimeType) { + if (typeof mimeType === "string") { + this.forceMimeType = mimeType.toLowerCase(); } - } - - if (this.async) { - this._readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED); - } else { - this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED; - } - }, + }, + /* + Places a FakeXMLHttpRequest object into the passed + state. + */ + _readyStateChange: function _readyStateChange(state) { + this.readyState = state; - /* - Sets the FakeXMLHttpRequest object's response body and - if body text is XML, sets responseXML to parsed document - object - */ - _setResponseBody: function _setResponseBody(body) { - verifyRequestSent(this); - verifyHeadersReceived(this); - verifyResponseBodyType(body); + if (typeof this.onreadystatechange == "function") { + this.onreadystatechange(new _Event("readystatechange")); + } - var chunkSize = this.chunkSize || 10; - var index = 0; - this.responseText = ""; + this.dispatchEvent(new _Event("readystatechange")); + + if (this.readyState == FakeXMLHttpRequest.DONE) { + this.dispatchEvent(new _Event("load", false, false, this)); + } + if (this.readyState == FakeXMLHttpRequest.UNSENT || this.readyState == FakeXMLHttpRequest.DONE) { + this.dispatchEvent(new _Event("loadend", false, false, this)); + } + }, + + + /* + Sets the FakeXMLHttpRequest object's response headers and + places the object into readyState 2 + */ + _setResponseHeaders: function _setResponseHeaders(headers) { + this.responseHeaders = {}; + + for (var header in headers) { + if (headers.hasOwnProperty(header)) { + this.responseHeaders[header] = headers[header]; + } + } + + if (this.forceMimeType) { + this.responseHeaders['Content-Type'] = this.forceMimeType; + } - do { if (this.async) { - this._readyStateChange(FakeXMLHttpRequest.LOADING); + this._readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED); + } else { + this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED; + } + }, + + /* + Sets the FakeXMLHttpRequest object's response body and + if body text is XML, sets responseXML to parsed document + object + */ + _setResponseBody: function _setResponseBody(body) { + verifyRequestSent(this); + verifyHeadersReceived(this); + verifyResponseBodyType(body); + + var chunkSize = this.chunkSize || 10; + var index = 0; + this.responseText = ""; + this.response = this.responseText; + + do { + if (this.async) { + this._readyStateChange(FakeXMLHttpRequest.LOADING); + } + + this.responseText += body.substring(index, index + chunkSize); + this.response = this.responseText; + index += chunkSize; + } while (index < body.length); + + var type = this.getResponseHeader("Content-Type"); + + if (this.responseText && (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) { + try { + this.responseXML = parseXML(this.responseText); + } catch (e) { + // Unable to parse XML - no biggie + } } - this.responseText += body.substring(index, index + chunkSize); - index += chunkSize; - } while (index < body.length); - - var type = this.getResponseHeader("Content-Type"); - - if (this.responseText && (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) { - try { - this.responseXML = parseXML(this.responseText); - } catch (e) { - // Unable to parse XML - no biggie + if (this.async) { + this._readyStateChange(FakeXMLHttpRequest.DONE); + } else { + this.readyState = FakeXMLHttpRequest.DONE; } + }, + + /* + Forces a response on to the FakeXMLHttpRequest object. + + This is the public API for faking responses. This function + takes a number status, headers object, and string body: + + ``` + xhr.respond(404, {Content-Type: 'text/plain'}, "Sorry. This object was not found.") + + ``` + */ + respond: function respond(status, headers, body) { + this._setResponseHeaders(headers || {}); + this.status = typeof status == "number" ? status : 200; + this.statusText = httpStatusCodes[this.status]; + this._setResponseBody(body || ""); } + }; - if (this.async) { - this._readyStateChange(FakeXMLHttpRequest.DONE); - } else { - this.readyState = FakeXMLHttpRequest.DONE; - } - }, - - /* - Forces a response on to the FakeXMLHttpRequest object. - - This is the public API for faking responses. This function - takes a number status, headers object, and string body: - - ``` - xhr.respond(404, {Content-Type: 'text/plain'}, "Sorry. This object was not found.") - - ``` - */ - respond: function respond(status, headers, body) { - this._setResponseHeaders(headers || {}); - this.status = typeof status == "number" ? status : 200; - this.statusText = httpStatusCodes[this.status]; - this._setResponseBody(body || ""); - if (typeof this.onload === "function"){ - this.onload(); - } - } -}; - -function verifyState(xhr) { - if (xhr.readyState !== FakeXMLHttpRequest.OPENED) { - throw new Error("INVALID_STATE_ERR"); + for (var property in FakeXMLHttpRequestProto) { + FakeXMLHttpRequest.prototype[property] = FakeXMLHttpRequestProto[property]; } - if (xhr.sendFlag) { - throw new Error("INVALID_STATE_ERR"); + function verifyState(xhr) { + if (xhr.readyState !== FakeXMLHttpRequest.OPENED) { + throw new Error("INVALID_STATE_ERR"); + } + + if (xhr.sendFlag) { + throw new Error("INVALID_STATE_ERR"); + } } -} -function verifyRequestSent(xhr) { - if (xhr.readyState == FakeXMLHttpRequest.DONE) { - throw new Error("Request done"); - } -} + function verifyRequestSent(xhr) { + if (xhr.readyState == FakeXMLHttpRequest.DONE) { + throw new Error("Request done"); + } + } -function verifyHeadersReceived(xhr) { - if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) { - throw new Error("No headers received"); - } -} + function verifyHeadersReceived(xhr) { + if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) { + throw new Error("No headers received"); + } + } -function verifyResponseBodyType(body) { - if (typeof body != "string") { - var error = new Error("Attempted to respond to fake XMLHttpRequest with " + - body + ", which is not a string."); - error.name = "InvalidBodyException"; - throw error; - } -} + function verifyResponseBodyType(body) { + if (typeof body != "string") { + var error = new Error("Attempted to respond to fake XMLHttpRequest with " + + body + ", which is not a string."); + error.name = "InvalidBodyException"; + throw error; + } + } -if (typeof module !== 'undefined' && module.exports) { - module.exports = FakeXMLHttpRequest; -} else if (typeof define === 'function' && define.amd) { - define(function() { return FakeXMLHttpRequest; }); -} else if (typeof window !== 'undefined') { - window.FakeXMLHttpRequest = FakeXMLHttpRequest; -} else if (this) { - this.FakeXMLHttpRequest = FakeXMLHttpRequest; -} -})(); + return FakeXMLHttpRequest; + +}))); +//# sourceMappingURL=fake_xml_http_request.js.map diff --git a/vendor/assets/javascripts/pretender.js b/vendor/assets/javascripts/pretender.js index f489fc7ca37..a715b72fd50 100644 --- a/vendor/assets/javascripts/pretender.js +++ b/vendor/assets/javascripts/pretender.js @@ -1,487 +1,1850 @@ -(function(self) { -'use strict'; - -var appearsBrowserified = typeof self !== 'undefined' && - typeof process !== 'undefined' && - Object.prototype.toString.call(process) === '[object Object]'; - -var RouteRecognizer = appearsBrowserified ? require('route-recognizer') : self.RouteRecognizer; -var FakeXMLHttpRequest = appearsBrowserified ? require('fake-xml-http-request') : self.FakeXMLHttpRequest; - -/** - * parseURL - decompose a URL into its parts - * @param {String} url a URL - * @return {Object} parts of the URL, including the following - * - * 'https://www.yahoo.com:1234/mypage?test=yes#abc' - * - * { - * host: 'www.yahoo.com:1234', - * protocol: 'https:', - * search: '?test=yes', - * hash: '#abc', - * href: 'https://www.yahoo.com:1234/mypage?test=yes#abc', - * pathname: '/mypage', - * fullpath: '/mypage?test=yes' - * } - */ -function parseURL(url) { - // TODO: something for when document isn't present... #yolo - var anchor = document.createElement('a'); - anchor.href = url; - - if (!anchor.host) { - anchor.href = anchor.href; // IE: load the host and protocol +var Pretender = (function(self) { + function getModuleDefault(module) { + return module.default || module; } - var pathname = anchor.pathname; - if (pathname.charAt(0) !== '/') { - pathname = '/' + pathname; // IE: prepend leading slash + var appearsBrowserified = + typeof self !== 'undefined' && + typeof process !== 'undefined' && + (Object.prototype.toString.call(process) === '[object Object]' || + Object.prototype.toString.call(process) === '[object process]'); + + var RouteRecognizer = appearsBrowserified + ? getModuleDefault(require('route-recognizer')) + : self.RouteRecognizer; + var FakeXMLHttpRequest = appearsBrowserified + ? getModuleDefault(require('fake-xml-http-request')) + : self.FakeXMLHttpRequest; + + +var Pretender = (function (RouteRecognizer, FakeXMLHttpRequest) { + 'use strict'; + + RouteRecognizer = RouteRecognizer && Object.prototype.hasOwnProperty.call(RouteRecognizer, 'default') ? RouteRecognizer['default'] : RouteRecognizer; + FakeXMLHttpRequest = FakeXMLHttpRequest && Object.prototype.hasOwnProperty.call(FakeXMLHttpRequest, 'default') ? FakeXMLHttpRequest['default'] : FakeXMLHttpRequest; + + var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + + /** + * Check if we're required to add a port number. + * + * @see https://url.spec.whatwg.org/#default-port + * @param {Number|String} port Port number we need to check + * @param {String} protocol Protocol we need to check against. + * @returns {Boolean} Is it a default port for the given protocol + * @api private + */ + var requiresPort = function required(port, protocol) { + protocol = protocol.split(':')[0]; + port = +port; + + if (!port) return false; + + switch (protocol) { + case 'http': + case 'ws': + return port !== 80; + + case 'https': + case 'wss': + return port !== 443; + + case 'ftp': + return port !== 21; + + case 'gopher': + return port !== 70; + + case 'file': + return false; + } + + return port !== 0; + }; + + var has = Object.prototype.hasOwnProperty + , undef; + + /** + * Decode a URI encoded string. + * + * @param {String} input The URI encoded string. + * @returns {String|Null} The decoded string. + * @api private + */ + function decode(input) { + try { + return decodeURIComponent(input.replace(/\+/g, ' ')); + } catch (e) { + return null; + } + } + + /** + * Simple query string parser. + * + * @param {String} query The query string that needs to be parsed. + * @returns {Object} + * @api public + */ + function querystring(query) { + var parser = /([^=?&]+)=?([^&]*)/g + , result = {} + , part; + + while (part = parser.exec(query)) { + var key = decode(part[1]) + , value = decode(part[2]); + + // + // Prevent overriding of existing properties. This ensures that build-in + // methods like `toString` or __proto__ are not overriden by malicious + // querystrings. + // + // In the case if failed decoding, we want to omit the key/value pairs + // from the result. + // + if (key === null || value === null || key in result) continue; + result[key] = value; + } + + return result; + } + + /** + * Transform a query string to an object. + * + * @param {Object} obj Object that should be transformed. + * @param {String} prefix Optional prefix. + * @returns {String} + * @api public + */ + function querystringify(obj, prefix) { + prefix = prefix || ''; + + var pairs = [] + , value + , key; + + // + // Optionally prefix with a '?' if needed + // + if ('string' !== typeof prefix) prefix = '?'; + + for (key in obj) { + if (has.call(obj, key)) { + value = obj[key]; + + // + // Edge cases where we actually want to encode the value to an empty + // string instead of the stringified value. + // + if (!value && (value === null || value === undef || isNaN(value))) { + value = ''; + } + + key = encodeURIComponent(key); + value = encodeURIComponent(value); + + // + // If we failed to encode the strings, we should bail out as we don't + // want to add invalid strings to the query. + // + if (key === null || value === null) continue; + pairs.push(key +'='+ value); + } + } + + return pairs.length ? prefix + pairs.join('&') : ''; + } + + // + // Expose the module. + // + var stringify = querystringify; + var parse = querystring; + + var querystringify_1 = { + stringify: stringify, + parse: parse + }; + + var slashes = /^[A-Za-z][A-Za-z0-9+-.]*:\/\// + , protocolre = /^([a-z][a-z0-9.+-]*:)?(\/\/)?([\\/]+)?([\S\s]*)/i + , windowsDriveLetter = /^[a-zA-Z]:/ + , whitespace = '[\\x09\\x0A\\x0B\\x0C\\x0D\\x20\\xA0\\u1680\\u180E\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200A\\u202F\\u205F\\u3000\\u2028\\u2029\\uFEFF]' + , left = new RegExp('^'+ whitespace +'+'); + + /** + * Trim a given string. + * + * @param {String} str String to trim. + * @public + */ + function trimLeft(str) { + return (str ? str : '').toString().replace(left, ''); + } + + /** + * These are the parse rules for the URL parser, it informs the parser + * about: + * + * 0. The char it Needs to parse, if it's a string it should be done using + * indexOf, RegExp using exec and NaN means set as current value. + * 1. The property we should set when parsing this value. + * 2. Indication if it's backwards or forward parsing, when set as number it's + * the value of extra chars that should be split off. + * 3. Inherit from location if non existing in the parser. + * 4. `toLowerCase` the resulting value. + */ + var rules = [ + ['#', 'hash'], // Extract from the back. + ['?', 'query'], // Extract from the back. + function sanitize(address, url) { // Sanitize what is left of the address + return isSpecial(url.protocol) ? address.replace(/\\/g, '/') : address; + }, + ['/', 'pathname'], // Extract from the back. + ['@', 'auth', 1], // Extract from the front. + [NaN, 'host', undefined, 1, 1], // Set left over value. + [/:(\d+)$/, 'port', undefined, 1], // RegExp the back. + [NaN, 'hostname', undefined, 1, 1] // Set left over. + ]; + + /** + * These properties should not be copied or inherited from. This is only needed + * for all non blob URL's as a blob URL does not include a hash, only the + * origin. + * + * @type {Object} + * @private + */ + var ignore = { hash: 1, query: 1 }; + + /** + * The location object differs when your code is loaded through a normal page, + * Worker or through a worker using a blob. And with the blobble begins the + * trouble as the location object will contain the URL of the blob, not the + * location of the page where our code is loaded in. The actual origin is + * encoded in the `pathname` so we can thankfully generate a good "default" + * location from it so we can generate proper relative URL's again. + * + * @param {Object|String} loc Optional default location object. + * @returns {Object} lolcation object. + * @public + */ + function lolcation(loc) { + var globalVar; + + if (typeof window !== 'undefined') globalVar = window; + else if (typeof commonjsGlobal !== 'undefined') globalVar = commonjsGlobal; + else if (typeof self !== 'undefined') globalVar = self; + else globalVar = {}; + + var location = globalVar.location || {}; + loc = loc || location; + + var finaldestination = {} + , type = typeof loc + , key; + + if ('blob:' === loc.protocol) { + finaldestination = new Url(unescape(loc.pathname), {}); + } else if ('string' === type) { + finaldestination = new Url(loc, {}); + for (key in ignore) delete finaldestination[key]; + } else if ('object' === type) { + for (key in loc) { + if (key in ignore) continue; + finaldestination[key] = loc[key]; + } + + if (finaldestination.slashes === undefined) { + finaldestination.slashes = slashes.test(loc.href); + } + } + + return finaldestination; + } + + /** + * Check whether a protocol scheme is special. + * + * @param {String} The protocol scheme of the URL + * @return {Boolean} `true` if the protocol scheme is special, else `false` + * @private + */ + function isSpecial(scheme) { + return ( + scheme === 'file:' || + scheme === 'ftp:' || + scheme === 'http:' || + scheme === 'https:' || + scheme === 'ws:' || + scheme === 'wss:' + ); + } + + /** + * @typedef ProtocolExtract + * @type Object + * @property {String} protocol Protocol matched in the URL, in lowercase. + * @property {Boolean} slashes `true` if protocol is followed by "//", else `false`. + * @property {String} rest Rest of the URL that is not part of the protocol. + */ + + /** + * Extract protocol information from a URL with/without double slash ("//"). + * + * @param {String} address URL we want to extract from. + * @param {Object} location + * @return {ProtocolExtract} Extracted information. + * @private + */ + function extractProtocol(address, location) { + address = trimLeft(address); + location = location || {}; + + var match = protocolre.exec(address); + var protocol = match[1] ? match[1].toLowerCase() : ''; + var forwardSlashes = !!match[2]; + var otherSlashes = !!match[3]; + var slashesCount = 0; + var rest; + + if (forwardSlashes) { + if (otherSlashes) { + rest = match[2] + match[3] + match[4]; + slashesCount = match[2].length + match[3].length; + } else { + rest = match[2] + match[4]; + slashesCount = match[2].length; + } + } else { + if (otherSlashes) { + rest = match[3] + match[4]; + slashesCount = match[3].length; + } else { + rest = match[4]; + } + } + + if (protocol === 'file:') { + if (slashesCount >= 2) { + rest = rest.slice(2); + } + } else if (isSpecial(protocol)) { + rest = match[4]; + } else if (protocol) { + if (forwardSlashes) { + rest = rest.slice(2); + } + } else if (slashesCount >= 2 && isSpecial(location.protocol)) { + rest = match[4]; + } + + return { + protocol: protocol, + slashes: forwardSlashes || isSpecial(protocol), + slashesCount: slashesCount, + rest: rest + }; + } + + /** + * Resolve a relative URL pathname against a base URL pathname. + * + * @param {String} relative Pathname of the relative URL. + * @param {String} base Pathname of the base URL. + * @return {String} Resolved pathname. + * @private + */ + function resolve(relative, base) { + if (relative === '') return base; + + var path = (base || '/').split('/').slice(0, -1).concat(relative.split('/')) + , i = path.length + , last = path[i - 1] + , unshift = false + , up = 0; + + while (i--) { + if (path[i] === '.') { + path.splice(i, 1); + } else if (path[i] === '..') { + path.splice(i, 1); + up++; + } else if (up) { + if (i === 0) unshift = true; + path.splice(i, 1); + up--; + } + } + + if (unshift) path.unshift(''); + if (last === '.' || last === '..') path.push(''); + + return path.join('/'); + } + + /** + * The actual URL instance. Instead of returning an object we've opted-in to + * create an actual constructor as it's much more memory efficient and + * faster and it pleases my OCD. + * + * It is worth noting that we should not use `URL` as class name to prevent + * clashes with the global URL instance that got introduced in browsers. + * + * @constructor + * @param {String} address URL we want to parse. + * @param {Object|String} [location] Location defaults for relative paths. + * @param {Boolean|Function} [parser] Parser for the query string. + * @private + */ + function Url(address, location, parser) { + address = trimLeft(address); + + if (!(this instanceof Url)) { + return new Url(address, location, parser); + } + + var relative, extracted, parse, instruction, index, key + , instructions = rules.slice() + , type = typeof location + , url = this + , i = 0; + + // + // The following if statements allows this module two have compatibility with + // 2 different API: + // + // 1. Node.js's `url.parse` api which accepts a URL, boolean as arguments + // where the boolean indicates that the query string should also be parsed. + // + // 2. The `URL` interface of the browser which accepts a URL, object as + // arguments. The supplied object will be used as default values / fall-back + // for relative paths. + // + if ('object' !== type && 'string' !== type) { + parser = location; + location = null; + } + + if (parser && 'function' !== typeof parser) parser = querystringify_1.parse; + + location = lolcation(location); + + // + // Extract protocol information before running the instructions. + // + extracted = extractProtocol(address || '', location); + relative = !extracted.protocol && !extracted.slashes; + url.slashes = extracted.slashes || relative && location.slashes; + url.protocol = extracted.protocol || location.protocol || ''; + address = extracted.rest; + + // + // When the authority component is absent the URL starts with a path + // component. + // + if ( + extracted.protocol === 'file:' && ( + extracted.slashesCount !== 2 || windowsDriveLetter.test(address)) || + (!extracted.slashes && + (extracted.protocol || + extracted.slashesCount < 2 || + !isSpecial(url.protocol))) + ) { + instructions[3] = [/(.*)/, 'pathname']; + } + + for (; i < instructions.length; i++) { + instruction = instructions[i]; + + if (typeof instruction === 'function') { + address = instruction(address, url); + continue; + } + + parse = instruction[0]; + key = instruction[1]; + + if (parse !== parse) { + url[key] = address; + } else if ('string' === typeof parse) { + if (~(index = address.indexOf(parse))) { + if ('number' === typeof instruction[2]) { + url[key] = address.slice(0, index); + address = address.slice(index + instruction[2]); + } else { + url[key] = address.slice(index); + address = address.slice(0, index); + } + } + } else if ((index = parse.exec(address))) { + url[key] = index[1]; + address = address.slice(0, index.index); + } + + url[key] = url[key] || ( + relative && instruction[3] ? location[key] || '' : '' + ); + + // + // Hostname, host and protocol should be lowercased so they can be used to + // create a proper `origin`. + // + if (instruction[4]) url[key] = url[key].toLowerCase(); + } + + // + // Also parse the supplied query string in to an object. If we're supplied + // with a custom parser as function use that instead of the default build-in + // parser. + // + if (parser) url.query = parser(url.query); + + // + // If the URL is relative, resolve the pathname against the base URL. + // + if ( + relative + && location.slashes + && url.pathname.charAt(0) !== '/' + && (url.pathname !== '' || location.pathname !== '') + ) { + url.pathname = resolve(url.pathname, location.pathname); + } + + // + // Default to a / for pathname if none exists. This normalizes the URL + // to always have a / + // + if (url.pathname.charAt(0) !== '/' && isSpecial(url.protocol)) { + url.pathname = '/' + url.pathname; + } + + // + // We should not add port numbers if they are already the default port number + // for a given protocol. As the host also contains the port number we're going + // override it with the hostname which contains no port number. + // + if (!requiresPort(url.port, url.protocol)) { + url.host = url.hostname; + url.port = ''; + } + + // + // Parse down the `auth` for the username and password. + // + url.username = url.password = ''; + if (url.auth) { + instruction = url.auth.split(':'); + url.username = instruction[0] || ''; + url.password = instruction[1] || ''; + } + + url.origin = url.protocol !== 'file:' && isSpecial(url.protocol) && url.host + ? url.protocol +'//'+ url.host + : 'null'; + + // + // The href is just the compiled result. + // + url.href = url.toString(); + } + + /** + * This is convenience method for changing properties in the URL instance to + * insure that they all propagate correctly. + * + * @param {String} part Property we need to adjust. + * @param {Mixed} value The newly assigned value. + * @param {Boolean|Function} fn When setting the query, it will be the function + * used to parse the query. + * When setting the protocol, double slash will be + * removed from the final url if it is true. + * @returns {URL} URL instance for chaining. + * @public + */ + function set(part, value, fn) { + var url = this; + + switch (part) { + case 'query': + if ('string' === typeof value && value.length) { + value = (fn || querystringify_1.parse)(value); + } + + url[part] = value; + break; + + case 'port': + url[part] = value; + + if (!requiresPort(value, url.protocol)) { + url.host = url.hostname; + url[part] = ''; + } else if (value) { + url.host = url.hostname +':'+ value; + } + + break; + + case 'hostname': + url[part] = value; + + if (url.port) value += ':'+ url.port; + url.host = value; + break; + + case 'host': + url[part] = value; + + if (/:\d+$/.test(value)) { + value = value.split(':'); + url.port = value.pop(); + url.hostname = value.join(':'); + } else { + url.hostname = value; + url.port = ''; + } + + break; + + case 'protocol': + url.protocol = value.toLowerCase(); + url.slashes = !fn; + break; + + case 'pathname': + case 'hash': + if (value) { + var char = part === 'pathname' ? '/' : '#'; + url[part] = value.charAt(0) !== char ? char + value : value; + } else { + url[part] = value; + } + break; + + default: + url[part] = value; + } + + for (var i = 0; i < rules.length; i++) { + var ins = rules[i]; + + if (ins[4]) url[ins[1]] = url[ins[1]].toLowerCase(); + } + + url.origin = url.protocol !== 'file:' && isSpecial(url.protocol) && url.host + ? url.protocol +'//'+ url.host + : 'null'; + + url.href = url.toString(); + + return url; + } + + /** + * Transform the properties back in to a valid and full URL string. + * + * @param {Function} stringify Optional query stringify function. + * @returns {String} Compiled version of the URL. + * @public + */ + function toString(stringify) { + if (!stringify || 'function' !== typeof stringify) stringify = querystringify_1.stringify; + + var query + , url = this + , protocol = url.protocol; + + if (protocol && protocol.charAt(protocol.length - 1) !== ':') protocol += ':'; + + var result = protocol + (url.slashes || isSpecial(url.protocol) ? '//' : ''); + + if (url.username) { + result += url.username; + if (url.password) result += ':'+ url.password; + result += '@'; + } + + result += url.host + url.pathname; + + query = 'object' === typeof url.query ? stringify(url.query) : url.query; + if (query) result += '?' !== query.charAt(0) ? '?'+ query : query; + + if (url.hash) result += url.hash; + + return result; + } + + Url.prototype = { set: set, toString: toString }; + + // + // Expose the URL parser and some additional properties that might be useful for + // others or testing. + // + Url.extractProtocol = extractProtocol; + Url.location = lolcation; + Url.trimLeft = trimLeft; + Url.qs = querystringify_1; + + var urlParse = Url; + + /** + * parseURL - decompose a URL into its parts + * @param {String} url a URL + * @return {Object} parts of the URL, including the following + * + * 'https://www.yahoo.com:1234/mypage?test=yes#abc' + * + * { + * host: 'www.yahoo.com:1234', + * protocol: 'https:', + * search: '?test=yes', + * hash: '#abc', + * href: 'https://www.yahoo.com:1234/mypage?test=yes#abc', + * pathname: '/mypage', + * fullpath: '/mypage?test=yes' + * } + */ + function parseURL(url) { + var parsedUrl = new urlParse(url); + if (!parsedUrl.host) { + // eslint-disable-next-line no-self-assign + parsedUrl.href = parsedUrl.href; // IE: load the host and protocol + } + var pathname = parsedUrl.pathname; + if (pathname.charAt(0) !== '/') { + pathname = '/' + pathname; // IE: prepend leading slash + } + var host = parsedUrl.host; + if (parsedUrl.port === '80' || parsedUrl.port === '443') { + host = parsedUrl.hostname; // IE: remove default port + } + return { + host: host, + protocol: parsedUrl.protocol, + search: parsedUrl.query, + hash: parsedUrl.hash, + href: parsedUrl.href, + pathname: pathname, + fullpath: pathname + (parsedUrl.query || '') + (parsedUrl.hash || '') + }; + } + + /** + * Registry + * + * A registry is a map of HTTP verbs to route recognizers. + */ + var Registry = /** @class */ (function () { + function Registry( /* host */) { + // Herein we keep track of RouteRecognizer instances + // keyed by HTTP method. Feel free to add more as needed. + this.verbs = { + GET: new RouteRecognizer(), + PUT: new RouteRecognizer(), + POST: new RouteRecognizer(), + DELETE: new RouteRecognizer(), + PATCH: new RouteRecognizer(), + HEAD: new RouteRecognizer(), + OPTIONS: new RouteRecognizer() + }; + } + return Registry; + }()); + + /** + * Hosts + * + * a map of hosts to Registries, ultimately allowing + * a per-host-and-port, per HTTP verb lookup of RouteRecognizers + */ + var Hosts = /** @class */ (function () { + function Hosts() { + this.registries = {}; + } + /** + * Hosts#forURL - retrieve a map of HTTP verbs to RouteRecognizers + * for a given URL + * + * @param {String} url a URL + * @return {Registry} a map of HTTP verbs to RouteRecognizers + * corresponding to the provided URL's + * hostname and port + */ + Hosts.prototype.forURL = function (url) { + var host = parseURL(url).host; + var registry = this.registries[host]; + if (registry === undefined) { + registry = (this.registries[host] = new Registry( /*host*/)); + } + return registry.verbs; + }; + return Hosts; + }()); + + var global$1 = + (typeof globalThis !== 'undefined' && globalThis) || + (typeof self !== 'undefined' && self) || + (typeof global$1 !== 'undefined' && global$1); + + var support = { + searchParams: 'URLSearchParams' in global$1, + iterable: 'Symbol' in global$1 && 'iterator' in Symbol, + blob: + 'FileReader' in global$1 && + 'Blob' in global$1 && + (function() { + try { + new Blob(); + return true + } catch (e) { + return false + } + })(), + formData: 'FormData' in global$1, + arrayBuffer: 'ArrayBuffer' in global$1 + }; + + function isDataView(obj) { + return obj && DataView.prototype.isPrototypeOf(obj) + } + + if (support.arrayBuffer) { + var viewClasses = [ + '[object Int8Array]', + '[object Uint8Array]', + '[object Uint8ClampedArray]', + '[object Int16Array]', + '[object Uint16Array]', + '[object Int32Array]', + '[object Uint32Array]', + '[object Float32Array]', + '[object Float64Array]' + ]; + + var isArrayBufferView = + ArrayBuffer.isView || + function(obj) { + return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1 + }; + } + + function normalizeName(name) { + if (typeof name !== 'string') { + name = String(name); + } + if (/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(name) || name === '') { + throw new TypeError('Invalid character in header field name: "' + name + '"') + } + return name.toLowerCase() + } + + function normalizeValue(value) { + if (typeof value !== 'string') { + value = String(value); + } + return value + } + + // Build a destructive iterator for the value list + function iteratorFor(items) { + var iterator = { + next: function() { + var value = items.shift(); + return {done: value === undefined, value: value} + } + }; + + if (support.iterable) { + iterator[Symbol.iterator] = function() { + return iterator + }; + } + + return iterator + } + + function Headers(headers) { + this.map = {}; + + if (headers instanceof Headers) { + headers.forEach(function(value, name) { + this.append(name, value); + }, this); + } else if (Array.isArray(headers)) { + headers.forEach(function(header) { + this.append(header[0], header[1]); + }, this); + } else if (headers) { + Object.getOwnPropertyNames(headers).forEach(function(name) { + this.append(name, headers[name]); + }, this); + } + } + + Headers.prototype.append = function(name, value) { + name = normalizeName(name); + value = normalizeValue(value); + var oldValue = this.map[name]; + this.map[name] = oldValue ? oldValue + ', ' + value : value; + }; + + Headers.prototype['delete'] = function(name) { + delete this.map[normalizeName(name)]; + }; + + Headers.prototype.get = function(name) { + name = normalizeName(name); + return this.has(name) ? this.map[name] : null + }; + + Headers.prototype.has = function(name) { + return this.map.hasOwnProperty(normalizeName(name)) + }; + + Headers.prototype.set = function(name, value) { + this.map[normalizeName(name)] = normalizeValue(value); + }; + + Headers.prototype.forEach = function(callback, thisArg) { + for (var name in this.map) { + if (this.map.hasOwnProperty(name)) { + callback.call(thisArg, this.map[name], name, this); + } + } + }; + + Headers.prototype.keys = function() { + var items = []; + this.forEach(function(value, name) { + items.push(name); + }); + return iteratorFor(items) + }; + + Headers.prototype.values = function() { + var items = []; + this.forEach(function(value) { + items.push(value); + }); + return iteratorFor(items) + }; + + Headers.prototype.entries = function() { + var items = []; + this.forEach(function(value, name) { + items.push([name, value]); + }); + return iteratorFor(items) + }; + + if (support.iterable) { + Headers.prototype[Symbol.iterator] = Headers.prototype.entries; + } + + function consumed(body) { + if (body.bodyUsed) { + return Promise.reject(new TypeError('Already read')) + } + body.bodyUsed = true; + } + + function fileReaderReady(reader) { + return new Promise(function(resolve, reject) { + reader.onload = function() { + resolve(reader.result); + }; + reader.onerror = function() { + reject(reader.error); + }; + }) + } + + function readBlobAsArrayBuffer(blob) { + var reader = new FileReader(); + var promise = fileReaderReady(reader); + reader.readAsArrayBuffer(blob); + return promise + } + + function readBlobAsText(blob) { + var reader = new FileReader(); + var promise = fileReaderReady(reader); + reader.readAsText(blob); + return promise + } + + function readArrayBufferAsText(buf) { + var view = new Uint8Array(buf); + var chars = new Array(view.length); + + for (var i = 0; i < view.length; i++) { + chars[i] = String.fromCharCode(view[i]); + } + return chars.join('') + } + + function bufferClone(buf) { + if (buf.slice) { + return buf.slice(0) + } else { + var view = new Uint8Array(buf.byteLength); + view.set(new Uint8Array(buf)); + return view.buffer + } + } + + function Body() { + this.bodyUsed = false; + + this._initBody = function(body) { + /* + fetch-mock wraps the Response object in an ES6 Proxy to + provide useful test harness features such as flush. However, on + ES5 browsers without fetch or Proxy support pollyfills must be used; + the proxy-pollyfill is unable to proxy an attribute unless it exists + on the object before the Proxy is created. This change ensures + Response.bodyUsed exists on the instance, while maintaining the + semantic of setting Request.bodyUsed in the constructor before + _initBody is called. + */ + this.bodyUsed = this.bodyUsed; + this._bodyInit = body; + if (!body) { + this._bodyText = ''; + } else if (typeof body === 'string') { + this._bodyText = body; + } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { + this._bodyBlob = body; + } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { + this._bodyFormData = body; + } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { + this._bodyText = body.toString(); + } else if (support.arrayBuffer && support.blob && isDataView(body)) { + this._bodyArrayBuffer = bufferClone(body.buffer); + // IE 10-11 can't handle a DataView body. + this._bodyInit = new Blob([this._bodyArrayBuffer]); + } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { + this._bodyArrayBuffer = bufferClone(body); + } else { + this._bodyText = body = Object.prototype.toString.call(body); + } + + if (!this.headers.get('content-type')) { + if (typeof body === 'string') { + this.headers.set('content-type', 'text/plain;charset=UTF-8'); + } else if (this._bodyBlob && this._bodyBlob.type) { + this.headers.set('content-type', this._bodyBlob.type); + } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { + this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8'); + } + } + }; + + if (support.blob) { + this.blob = function() { + var rejected = consumed(this); + if (rejected) { + return rejected + } + + if (this._bodyBlob) { + return Promise.resolve(this._bodyBlob) + } else if (this._bodyArrayBuffer) { + return Promise.resolve(new Blob([this._bodyArrayBuffer])) + } else if (this._bodyFormData) { + throw new Error('could not read FormData body as blob') + } else { + return Promise.resolve(new Blob([this._bodyText])) + } + }; + + this.arrayBuffer = function() { + if (this._bodyArrayBuffer) { + var isConsumed = consumed(this); + if (isConsumed) { + return isConsumed + } + if (ArrayBuffer.isView(this._bodyArrayBuffer)) { + return Promise.resolve( + this._bodyArrayBuffer.buffer.slice( + this._bodyArrayBuffer.byteOffset, + this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength + ) + ) + } else { + return Promise.resolve(this._bodyArrayBuffer) + } + } else { + return this.blob().then(readBlobAsArrayBuffer) + } + }; + } + + this.text = function() { + var rejected = consumed(this); + if (rejected) { + return rejected + } + + if (this._bodyBlob) { + return readBlobAsText(this._bodyBlob) + } else if (this._bodyArrayBuffer) { + return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer)) + } else if (this._bodyFormData) { + throw new Error('could not read FormData body as text') + } else { + return Promise.resolve(this._bodyText) + } + }; + + if (support.formData) { + this.formData = function() { + return this.text().then(decode$1) + }; + } + + this.json = function() { + return this.text().then(JSON.parse) + }; + + return this + } + + // HTTP methods whose capitalization should be normalized + var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']; + + function normalizeMethod(method) { + var upcased = method.toUpperCase(); + return methods.indexOf(upcased) > -1 ? upcased : method + } + + function Request(input, options) { + if (!(this instanceof Request)) { + throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.') + } + + options = options || {}; + var body = options.body; + + if (input instanceof Request) { + if (input.bodyUsed) { + throw new TypeError('Already read') + } + this.url = input.url; + this.credentials = input.credentials; + if (!options.headers) { + this.headers = new Headers(input.headers); + } + this.method = input.method; + this.mode = input.mode; + this.signal = input.signal; + if (!body && input._bodyInit != null) { + body = input._bodyInit; + input.bodyUsed = true; + } + } else { + this.url = String(input); + } + + this.credentials = options.credentials || this.credentials || 'same-origin'; + if (options.headers || !this.headers) { + this.headers = new Headers(options.headers); + } + this.method = normalizeMethod(options.method || this.method || 'GET'); + this.mode = options.mode || this.mode || null; + this.signal = options.signal || this.signal; + this.referrer = null; + + if ((this.method === 'GET' || this.method === 'HEAD') && body) { + throw new TypeError('Body not allowed for GET or HEAD requests') + } + this._initBody(body); + + if (this.method === 'GET' || this.method === 'HEAD') { + if (options.cache === 'no-store' || options.cache === 'no-cache') { + // Search for a '_' parameter in the query string + var reParamSearch = /([?&])_=[^&]*/; + if (reParamSearch.test(this.url)) { + // If it already exists then set the value with the current time + this.url = this.url.replace(reParamSearch, '$1_=' + new Date().getTime()); + } else { + // Otherwise add a new '_' parameter to the end with the current time + var reQueryString = /\?/; + this.url += (reQueryString.test(this.url) ? '&' : '?') + '_=' + new Date().getTime(); + } + } + } + } + + Request.prototype.clone = function() { + return new Request(this, {body: this._bodyInit}) + }; + + function decode$1(body) { + var form = new FormData(); + body + .trim() + .split('&') + .forEach(function(bytes) { + if (bytes) { + var split = bytes.split('='); + var name = split.shift().replace(/\+/g, ' '); + var value = split.join('=').replace(/\+/g, ' '); + form.append(decodeURIComponent(name), decodeURIComponent(value)); + } + }); + return form + } + + function parseHeaders(rawHeaders) { + var headers = new Headers(); + // Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space + // https://tools.ietf.org/html/rfc7230#section-3.2 + var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' '); + // Avoiding split via regex to work around a common IE11 bug with the core-js 3.6.0 regex polyfill + // https://github.com/github/fetch/issues/748 + // https://github.com/zloirock/core-js/issues/751 + preProcessedHeaders + .split('\r') + .map(function(header) { + return header.indexOf('\n') === 0 ? header.substr(1, header.length) : header + }) + .forEach(function(line) { + var parts = line.split(':'); + var key = parts.shift().trim(); + if (key) { + var value = parts.join(':').trim(); + headers.append(key, value); + } + }); + return headers + } + + Body.call(Request.prototype); + + function Response(bodyInit, options) { + if (!(this instanceof Response)) { + throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.') + } + if (!options) { + options = {}; + } + + this.type = 'default'; + this.status = options.status === undefined ? 200 : options.status; + this.ok = this.status >= 200 && this.status < 300; + this.statusText = options.statusText === undefined ? '' : '' + options.statusText; + this.headers = new Headers(options.headers); + this.url = options.url || ''; + this._initBody(bodyInit); + } + + Body.call(Response.prototype); + + Response.prototype.clone = function() { + return new Response(this._bodyInit, { + status: this.status, + statusText: this.statusText, + headers: new Headers(this.headers), + url: this.url + }) + }; + + Response.error = function() { + var response = new Response(null, {status: 0, statusText: ''}); + response.type = 'error'; + return response + }; + + var redirectStatuses = [301, 302, 303, 307, 308]; + + Response.redirect = function(url, status) { + if (redirectStatuses.indexOf(status) === -1) { + throw new RangeError('Invalid status code') + } + + return new Response(null, {status: status, headers: {location: url}}) + }; + + var DOMException = global$1.DOMException; + try { + new DOMException(); + } catch (err) { + DOMException = function(message, name) { + this.message = message; + this.name = name; + var error = Error(message); + this.stack = error.stack; + }; + DOMException.prototype = Object.create(Error.prototype); + DOMException.prototype.constructor = DOMException; + } + + function fetch(input, init) { + return new Promise(function(resolve, reject) { + var request = new Request(input, init); + + if (request.signal && request.signal.aborted) { + return reject(new DOMException('Aborted', 'AbortError')) + } + + var xhr = new XMLHttpRequest(); + + function abortXhr() { + xhr.abort(); + } + + xhr.onload = function() { + var options = { + status: xhr.status, + statusText: xhr.statusText, + headers: parseHeaders(xhr.getAllResponseHeaders() || '') + }; + options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL'); + var body = 'response' in xhr ? xhr.response : xhr.responseText; + setTimeout(function() { + resolve(new Response(body, options)); + }, 0); + }; + + xhr.onerror = function() { + setTimeout(function() { + reject(new TypeError('Network request failed')); + }, 0); + }; + + xhr.ontimeout = function() { + setTimeout(function() { + reject(new TypeError('Network request failed')); + }, 0); + }; + + xhr.onabort = function() { + setTimeout(function() { + reject(new DOMException('Aborted', 'AbortError')); + }, 0); + }; + + function fixUrl(url) { + try { + return url === '' && global$1.location.href ? global$1.location.href : url + } catch (e) { + return url + } + } + + xhr.open(request.method, fixUrl(request.url), true); + + if (request.credentials === 'include') { + xhr.withCredentials = true; + } else if (request.credentials === 'omit') { + xhr.withCredentials = false; + } + + if ('responseType' in xhr) { + if (support.blob) { + xhr.responseType = 'blob'; + } else if ( + support.arrayBuffer && + request.headers.get('Content-Type') && + request.headers.get('Content-Type').indexOf('application/octet-stream') !== -1 + ) { + xhr.responseType = 'arraybuffer'; + } + } + + if (init && typeof init.headers === 'object' && !(init.headers instanceof Headers)) { + Object.getOwnPropertyNames(init.headers).forEach(function(name) { + xhr.setRequestHeader(name, normalizeValue(init.headers[name])); + }); + } else { + request.headers.forEach(function(value, name) { + xhr.setRequestHeader(name, value); + }); + } + + if (request.signal) { + request.signal.addEventListener('abort', abortXhr); + + xhr.onreadystatechange = function() { + // DONE (success or failure) + if (xhr.readyState === 4) { + request.signal.removeEventListener('abort', abortXhr); + } + }; + } + + xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit); + }) + } + + fetch.polyfill = true; + + if (!global$1.fetch) { + global$1.fetch = fetch; + global$1.Headers = Headers; + global$1.Request = Request; + global$1.Response = Response; + } + + var FakeFetch = /*#__PURE__*/Object.freeze({ + __proto__: null, + Headers: Headers, + Request: Request, + Response: Response, + get DOMException () { return DOMException; }, + fetch: fetch + }); + + function createPassthrough(fakeXHR, nativeXMLHttpRequest) { + // event types to handle on the xhr + var evts = ['error', 'timeout', 'abort', 'readystatechange']; + // event types to handle on the xhr.upload + var uploadEvents = []; + // properties to copy from the native xhr to fake xhr + var lifecycleProps = [ + 'readyState', + 'responseText', + 'response', + 'responseXML', + 'responseURL', + 'status', + 'statusText', + ]; + var xhr = (fakeXHR._passthroughRequest = new nativeXMLHttpRequest()); + xhr.open(fakeXHR.method, fakeXHR.url, fakeXHR.async, fakeXHR.username, fakeXHR.password); + if (fakeXHR.responseType === 'arraybuffer') { + lifecycleProps = ['readyState', 'response', 'status', 'statusText']; + xhr.responseType = fakeXHR.responseType; + } + // use onload if the browser supports it + if ('onload' in xhr) { + evts.push('load'); + } + // add progress event for async calls + // avoid using progress events for sync calls, they will hang https://bugs.webkit.org/show_bug.cgi?id=40996. + if (fakeXHR.async && fakeXHR.responseType !== 'arraybuffer') { + evts.push('progress'); + uploadEvents.push('progress'); + } + // update `propertyNames` properties from `fromXHR` to `toXHR` + function copyLifecycleProperties(propertyNames, fromXHR, toXHR) { + for (var i = 0; i < propertyNames.length; i++) { + var prop = propertyNames[i]; + if (prop in fromXHR) { + toXHR[prop] = fromXHR[prop]; + } + } + } + // fire fake event on `eventable` + function dispatchEvent(eventable, eventType, event) { + eventable.dispatchEvent(event); + if (eventable['on' + eventType]) { + eventable['on' + eventType](event); + } + } + // set the on- handler on the native xhr for the given eventType + function createHandler(eventType) { + xhr['on' + eventType] = function (event) { + copyLifecycleProperties(lifecycleProps, xhr, fakeXHR); + dispatchEvent(fakeXHR, eventType, event); + }; + } + // set the on- handler on the native xhr's `upload` property for + // the given eventType + function createUploadHandler(eventType) { + if (xhr.upload && fakeXHR.upload && fakeXHR.upload['on' + eventType]) { + xhr.upload['on' + eventType] = function (event) { + dispatchEvent(fakeXHR.upload, eventType, event); + }; + } + } + var i; + for (i = 0; i < evts.length; i++) { + createHandler(evts[i]); + } + for (i = 0; i < uploadEvents.length; i++) { + createUploadHandler(uploadEvents[i]); + } + if (fakeXHR.async) { + xhr.timeout = fakeXHR.timeout; + xhr.withCredentials = fakeXHR.withCredentials; + } + // XMLHttpRequest.timeout default initializes to 0, and is not allowed to be used for + // synchronous XMLHttpRequests requests in a document environment. However, when a XHR + // polyfill does not sets the timeout value, it will throw in React Native environment. + // TODO: + // synchronous XHR is deprecated, make async the default as XMLHttpRequest.open(), + // and throw error if sync XHR has timeout not 0 + if (!xhr.timeout && xhr.timeout !== 0) { + xhr.timeout = 0; // default XMLHttpRequest timeout + } + for (var h in fakeXHR.requestHeaders) { + xhr.setRequestHeader(h, fakeXHR.requestHeaders[h]); + } + return xhr; + } + + function interceptor(ctx) { + function FakeRequest() { + // super() + FakeXMLHttpRequest.call(this); + } + FakeRequest.prototype = Object.create(FakeXMLHttpRequest.prototype); + FakeRequest.prototype.constructor = FakeRequest; + // extend + FakeRequest.prototype.send = function send() { + this.sendArguments = arguments; + if (!ctx.pretender.running) { + throw new Error('You shut down a Pretender instance while there was a pending request. ' + + 'That request just tried to complete. Check to see if you accidentally shut down ' + + 'a pretender earlier than you intended to'); + } + FakeXMLHttpRequest.prototype.send.apply(this, arguments); + if (ctx.pretender.checkPassthrough(this)) { + this.passthrough(); + } + else { + ctx.pretender.handleRequest(this); + } + }; + FakeRequest.prototype.passthrough = function passthrough() { + if (!this.sendArguments) { + throw new Error('You attempted to passthrough a FakeRequest that was never sent. ' + + 'Call `.send()` on the original request first'); + } + var xhr = createPassthrough(this, ctx.pretender._nativeXMLHttpRequest); + xhr.send.apply(xhr, this.sendArguments); + return xhr; + }; + FakeRequest.prototype._passthroughCheck = function (method, args) { + if (this._passthroughRequest) { + return this._passthroughRequest[method].apply(this._passthroughRequest, args); + } + return FakeXMLHttpRequest.prototype[method].apply(this, args); + }; + FakeRequest.prototype.abort = function abort() { + return this._passthroughCheck('abort', arguments); + }; + FakeRequest.prototype.getResponseHeader = function getResponseHeader() { + return this._passthroughCheck('getResponseHeader', arguments); + }; + FakeRequest.prototype.getAllResponseHeaders = function getAllResponseHeaders() { + return this._passthroughCheck('getAllResponseHeaders', arguments); + }; + if (ctx.pretender._nativeXMLHttpRequest.prototype._passthroughCheck) { + // eslint-disable-next-line no-console + console.warn('You created a second Pretender instance while there was already one running. ' + + 'Running two Pretender servers at once will lead to unexpected results and will ' + + 'be removed entirely in a future major version.' + + 'Please call .shutdown() on your instances when you no longer need them to respond.'); + } + return FakeRequest; + } + + var NoopArray = /** @class */ (function () { + function NoopArray() { + this.length = 0; + } + NoopArray.prototype.push = function () { + var _items = []; + for (var _i = 0; _i < arguments.length; _i++) { + _items[_i] = arguments[_i]; + } + return 0; + }; + return NoopArray; + }()); + function scheduleProgressEvent(request, startTime, totalTime) { + var totalSize = 0; + var body = request.requestBody; + if (body) { + if (body instanceof FormData) { + body.forEach(function (value) { + if (value instanceof File) { + totalSize += value.size; + } + else { + totalSize += value.length; + } + }); + } + else { + // Support Blob, BufferSource, USVString, ArrayBufferView + totalSize = body.byteLength || body.size || body.length || 0; + } + } + setTimeout(function () { + if (!request.aborted && !request.status) { + var elapsedTime = new Date().getTime() - startTime.getTime(); + var progressTransmitted = totalTime <= 0 ? 0 : (elapsedTime / totalTime) * totalSize; + // ProgressEvent expects loaded, total + // https://xhr.spec.whatwg.org/#interface-progressevent + request.upload._progress(true, progressTransmitted, totalSize); + request._progress(true, progressTransmitted, totalSize); + scheduleProgressEvent(request, startTime, totalTime); + } + else if (request.status) { + // we're done, send a final progress event with loaded === total + request.upload._progress(true, totalSize, totalSize); + request._progress(true, totalSize, totalSize); + } + }, 50); + } + function isArray(array) { + return Object.prototype.toString.call(array) === '[object Array]'; + } + var PASSTHROUGH = {}; + function verbify(verb) { + return function (path, handler, async) { + return this.register(verb, path, handler, async); + }; + } + var Pretender = /** @class */ (function () { + function Pretender() { + var _this = this; + this.hosts = new Hosts(); + this.handlers = []; + this.get = verbify('GET'); + this.post = verbify('POST'); + this.put = verbify('PUT'); + this.delete = verbify('DELETE'); + this.patch = verbify('PATCH'); + this.head = verbify('HEAD'); + this.options = verbify('OPTIONS'); + this.passthrough = PASSTHROUGH; + var lastArg = arguments[arguments.length - 1]; + var options = typeof lastArg === 'object' ? lastArg : null; + var shouldNotTrack = options && options.trackRequests === false; + this.handledRequests = shouldNotTrack ? new NoopArray() : []; + this.passthroughRequests = shouldNotTrack ? new NoopArray() : []; + this.unhandledRequests = shouldNotTrack ? new NoopArray() : []; + this.requestReferences = []; + this.forcePassthrough = options && options.forcePassthrough === true; + this.disableUnhandled = options && options.disableUnhandled === true; + // reference the native XMLHttpRequest object so + // it can be restored later + this._nativeXMLHttpRequest = self.XMLHttpRequest; + this.running = false; + var ctx = { pretender: this }; + this.ctx = ctx; + // capture xhr requests, channeling them into + // the route map. + self.XMLHttpRequest = interceptor(ctx); + // polyfill fetch when xhr is ready + this._fetchProps = FakeFetch + ? ['fetch', 'Headers', 'Request', 'Response'] + : []; + this._fetchProps.forEach(function (name) { + _this['_native' + name] = self[name]; + self[name] = FakeFetch[name]; + }, this); + // 'start' the server + this.running = true; + // trigger the route map DSL. + var argLength = options ? arguments.length - 1 : arguments.length; + for (var i = 0; i < argLength; i++) { + this.map(arguments[i]); + } + } + Pretender.prototype.map = function (maps) { + maps.call(this); + }; + Pretender.prototype.register = function (verb, url, handler, async) { + if (!handler) { + throw new Error('The function you tried passing to Pretender to handle ' + + verb + + ' ' + + url + + ' is undefined or missing.'); + } + var handlerInstance = handler; + handlerInstance.numberOfCalls = 0; + handlerInstance.async = async; + this.handlers.push(handlerInstance); + var registry = this.hosts.forURL(url)[verb]; + registry.add([ + { + path: parseURL(url).fullpath, + handler: handlerInstance, + }, + ]); + return handlerInstance; + }; + Pretender.prototype.checkPassthrough = function (request) { + var verb = request.method.toUpperCase(); + var path = parseURL(request.url).fullpath; + var recognized = this.hosts.forURL(request.url)[verb].recognize(path); + var match = recognized && recognized[0]; + if ((match && match.handler === PASSTHROUGH) || this.forcePassthrough) { + this.passthroughRequests.push(request); + this.passthroughRequest(verb, path, request); + return true; + } + return false; + }; + Pretender.prototype.handleRequest = function (request) { + var verb = request.method.toUpperCase(); + var path = request.url; + var handler = this._handlerFor(verb, path, request); + if (handler) { + handler.handler.numberOfCalls++; + var async_1 = handler.handler.async; + this.handledRequests.push(request); + var pretender_1 = this; + var _handleRequest_1 = function (statusHeadersAndBody) { + if (!isArray(statusHeadersAndBody)) { + var note = 'Remember to `return [status, headers, body];` in your route handler.'; + throw new Error('Nothing returned by handler for ' + path + '. ' + note); + } + var status = statusHeadersAndBody[0]; + var headers = pretender_1.prepareHeaders(statusHeadersAndBody[1]); + var body = pretender_1.prepareBody(statusHeadersAndBody[2], headers); + pretender_1.handleResponse(request, async_1, function () { + request.respond(status, headers, body); + pretender_1.handledRequest(verb, path, request); + }); + }; + try { + var result = handler.handler(request); + if (result && typeof result.then === 'function') { + // `result` is a promise, resolve it + result.then(function (resolvedResult) { + _handleRequest_1(resolvedResult); + }); + } + else { + _handleRequest_1(result); + } + } + catch (error) { + this.erroredRequest(verb, path, request, error); + this.resolve(request); + } + } + else { + if (!this.disableUnhandled) { + this.unhandledRequests.push(request); + this.unhandledRequest(verb, path, request); + } + } + }; + Pretender.prototype.handleResponse = function (request, strategy, callback) { + var delay = typeof strategy === 'function' ? strategy() : strategy; + delay = typeof delay === 'boolean' || typeof delay === 'number' ? delay : 0; + if (delay === false) { + callback(); + } + else { + var pretender_2 = this; + pretender_2.requestReferences.push({ + request: request, + callback: callback, + }); + if (delay !== true) { + scheduleProgressEvent(request, new Date(), delay); + setTimeout(function () { + pretender_2.resolve(request); + }, delay); + } + } + }; + Pretender.prototype.resolve = function (request) { + for (var i = 0, len = this.requestReferences.length; i < len; i++) { + var res = this.requestReferences[i]; + if (res.request === request) { + res.callback(); + this.requestReferences.splice(i, 1); + break; + } + } + }; + Pretender.prototype.requiresManualResolution = function (verb, path) { + var handler = this._handlerFor(verb.toUpperCase(), path, {}); + if (!handler) { + return false; + } + var async = handler.handler.async; + return typeof async === 'function' ? async() === true : async === true; + }; + Pretender.prototype.prepareBody = function (body, _headers) { + return body; + }; + Pretender.prototype.prepareHeaders = function (headers) { + return headers; + }; + Pretender.prototype.handledRequest = function (_verb, _path, _request) { + /* no-op */ + }; + Pretender.prototype.passthroughRequest = function (_verb, _path, _request) { + /* no-op */ + }; + Pretender.prototype.unhandledRequest = function (verb, path, _request) { + throw new Error('Pretender intercepted ' + + verb + + ' ' + + path + + ' but no handler was defined for this type of request'); + }; + Pretender.prototype.erroredRequest = function (verb, path, _request, error) { + error.message = + 'Pretender intercepted ' + + verb + + ' ' + + path + + ' but encountered an error: ' + + error.message; + throw error; + }; + Pretender.prototype.shutdown = function () { + var _this = this; + self.XMLHttpRequest = this._nativeXMLHttpRequest; + this._fetchProps.forEach(function (name) { + self[name] = _this['_native' + name]; + }, this); + this.ctx.pretender = undefined; + // 'stop' the server + this.running = false; + }; + Pretender.prototype._handlerFor = function (verb, url, request) { + var registry = this.hosts.forURL(url)[verb]; + var matches = registry.recognize(parseURL(url).fullpath); + var match = matches ? matches[0] : null; + if (match) { + request.params = match.params; + request.queryParams = matches.queryParams; + } + return match; + }; + Pretender.parseURL = parseURL; + Pretender.Hosts = Hosts; + Pretender.Registry = Registry; + return Pretender; + }()); + + Pretender.parseURL = parseURL; + Pretender.Hosts = Hosts; + Pretender.Registry = Registry; + + return Pretender; + +}(RouteRecognizer, FakeXMLHttpRequest)); + + + if (typeof module === 'object') { + module.exports = Pretender; + } else if (typeof define !== 'undefined') { + define('pretender', [], function() { + return Pretender; + }); } - var host = anchor.host; - if (anchor.port === '80' || anchor.port === '443') { - host = anchor.hostname; // IE: remove default port - } + self.Pretender = Pretender; - return { - host: host, - protocol: anchor.protocol, - search: anchor.search, - hash: anchor.hash, - href: anchor.href, - pathname: pathname, - fullpath: pathname + (anchor.search || '') + (anchor.hash || '') - }; -} - - -/** - * Registry - * - * A registry is a map of HTTP verbs to route recognizers. - */ - -function Registry(/* host */) { - // Herein we keep track of RouteRecognizer instances - // keyed by HTTP method. Feel free to add more as needed. - this.verbs = { - GET: new RouteRecognizer(), - PUT: new RouteRecognizer(), - POST: new RouteRecognizer(), - DELETE: new RouteRecognizer(), - PATCH: new RouteRecognizer(), - HEAD: new RouteRecognizer(), - OPTIONS: new RouteRecognizer() - }; -} - -/** - * Hosts - * - * a map of hosts to Registries, ultimately allowing - * a per-host-and-port, per HTTP verb lookup of RouteRecognizers - */ -function Hosts() { - this._registries = {}; -} - -/** - * Hosts#forURL - retrieve a map of HTTP verbs to RouteRecognizers - * for a given URL - * - * @param {String} url a URL - * @return {Registry} a map of HTTP verbs to RouteRecognizers - * corresponding to the provided URL's - * hostname and port - */ -Hosts.prototype.forURL = function(url) { - var host = parseURL(url).host; - var registry = this._registries[host]; - - if (registry === undefined) { - registry = (this._registries[host] = new Registry(host)); - } - - return registry.verbs; -}; - - -function Pretender(/* routeMap1, routeMap2, ..., options*/) { - this.hosts = new Hosts(); - - var lastArg = arguments[arguments.length - 1]; - var options = typeof lastArg === 'object' ? lastArg : null; - var shouldNotTrack = options && (options.trackRequests === false); - var noopArray = { push: function() {}, length: 0 }; - - this.handlers = []; - this.handledRequests = shouldNotTrack ? noopArray: []; - this.passthroughRequests = shouldNotTrack ? noopArray: []; - this.unhandledRequests = shouldNotTrack ? noopArray: []; - this.requestReferences = []; - this.forcePassthrough = options && (options.forcePassthrough === true); - this.disableUnhandled = options && (options.disableUnhandled === true); - - // reference the native XMLHttpRequest object so - // it can be restored later - this._nativeXMLHttpRequest = self.XMLHttpRequest; - this.running = false; - var ctx = { pretender: this }; - this.ctx = ctx; - - // capture xhr requests, channeling them into - // the route map. - self.XMLHttpRequest = interceptor(ctx); - - // 'start' the server - this.running = true; - - // trigger the route map DSL. - var argLength = options ? arguments.length - 1 : arguments.length; - for (var i = 0; i < argLength; i++) { - this.map(arguments[i]); - } -} - -function interceptor(ctx) { - function FakeRequest() { - // super() - FakeXMLHttpRequest.call(this); - } - FakeRequest.prototype = Object.create(FakeXMLHttpRequest.prototype); - FakeRequest.prototype.constructor = FakeRequest; - - // extend - FakeRequest.prototype.send = function send() { - if (!ctx.pretender.running) { - throw new Error('You shut down a Pretender instance while there was a pending request. ' + - 'That request just tried to complete. Check to see if you accidentally shut down ' + - 'a pretender earlier than you intended to'); - } - - FakeXMLHttpRequest.prototype.send.apply(this, arguments); - - if (ctx.pretender.checkPassthrough(this)) { - var xhr = createPassthrough(this); - xhr.send.apply(xhr, arguments); - } else { - ctx.pretender.handleRequest(this); - } - }; - - - function createPassthrough(fakeXHR) { - // event types to handle on the xhr - var evts = ['error', 'timeout', 'abort', 'readystatechange']; - - // event types to handle on the xhr.upload - var uploadEvents = []; - - // properties to copy from the native xhr to fake xhr - var lifecycleProps = ['readyState', 'responseText', 'responseXML', 'status', 'statusText']; - - var xhr = fakeXHR._passthroughRequest = new ctx.pretender._nativeXMLHttpRequest(); - xhr.open(fakeXHR.method, fakeXHR.url, fakeXHR.async, fakeXHR.username, fakeXHR.password); - - if (fakeXHR.responseType === 'arraybuffer') { - lifecycleProps = ['readyState', 'response', 'status', 'statusText']; - xhr.responseType = fakeXHR.responseType; - } - - // use onload if the browser supports it - if ('onload' in xhr) { - evts.push('load'); - } - - // add progress event for async calls - // avoid using progress events for sync calls, they will hang https://bugs.webkit.org/show_bug.cgi?id=40996. - if (fakeXHR.async && fakeXHR.responseType !== 'arraybuffer') { - evts.push('progress'); - uploadEvents.push('progress'); - } - - // update `propertyNames` properties from `fromXHR` to `toXHR` - function copyLifecycleProperties(propertyNames, fromXHR, toXHR) { - for (var i = 0; i < propertyNames.length; i++) { - var prop = propertyNames[i]; - if (prop in fromXHR) { - toXHR[prop] = fromXHR[prop]; - } - } - } - - // fire fake event on `eventable` - function dispatchEvent(eventable, eventType, event) { - eventable.dispatchEvent(event); - if (eventable['on' + eventType]) { - eventable['on' + eventType](event); - } - } - - // set the on- handler on the native xhr for the given eventType - function createHandler(eventType) { - xhr['on' + eventType] = function(event) { - copyLifecycleProperties(lifecycleProps, xhr, fakeXHR); - dispatchEvent(fakeXHR, eventType, event); - }; - } - - // set the on- handler on the native xhr's `upload` property for - // the given eventType - function createUploadHandler(eventType) { - if (xhr.upload) { - xhr.upload['on' + eventType] = function(event) { - dispatchEvent(fakeXHR.upload, eventType, event); - }; - } - } - - var i; - for (i = 0; i < evts.length; i++) { - createHandler(evts[i]); - } - for (i = 0; i < uploadEvents.length; i++) { - createUploadHandler(uploadEvents[i]); - } - - if (fakeXHR.async) { - xhr.timeout = fakeXHR.timeout; - xhr.withCredentials = fakeXHR.withCredentials; - } - for (var h in fakeXHR.requestHeaders) { - xhr.setRequestHeader(h, fakeXHR.requestHeaders[h]); - } - return xhr; - } - - FakeRequest.prototype._passthroughCheck = function(method, args) { - if (this._passthroughRequest) { - return this._passthroughRequest[method].apply(this._passthroughRequest, args); - } - return FakeXMLHttpRequest.prototype[method].apply(this, args); - }; - - FakeRequest.prototype.abort = function abort() { - return this._passthroughCheck('abort', arguments); - }; - - FakeRequest.prototype.getResponseHeader = function getResponseHeader() { - return this._passthroughCheck('getResponseHeader', arguments); - }; - - FakeRequest.prototype.getAllResponseHeaders = function getAllResponseHeaders() { - return this._passthroughCheck('getAllResponseHeaders', arguments); - }; - - if (ctx.pretender._nativeXMLHttpRequest.prototype._passthroughCheck) { - console.warn('You created a second Pretender instance while there was already one running. ' + - 'Running two Pretender servers at once will lead to unexpected results and will ' + - 'be removed entirely in a future major version.' + - 'Please call .shutdown() on your instances when you no longer need them to respond.'); - } - return FakeRequest; -} - -function verbify(verb) { - return function(path, handler, async) { - return this.register(verb, path, handler, async); - }; -} - -function scheduleProgressEvent(request, startTime, totalTime) { - setTimeout(function() { - if (!request.aborted && !request.status) { - var ellapsedTime = new Date().getTime() - startTime.getTime(); - request.upload._progress(true, ellapsedTime, totalTime); - request._progress(true, ellapsedTime, totalTime); - scheduleProgressEvent(request, startTime, totalTime); - } - }, 50); -} - -function isArray(array) { - return Object.prototype.toString.call(array) === '[object Array]'; -} - -var PASSTHROUGH = {}; - -Pretender.prototype = { - get: verbify('GET'), - post: verbify('POST'), - put: verbify('PUT'), - 'delete': verbify('DELETE'), - patch: verbify('PATCH'), - head: verbify('HEAD'), - options: verbify('OPTIONS'), - map: function(maps) { - maps.call(this); - }, - register: function register(verb, url, handler, async) { - if (!handler) { - throw new Error('The function you tried passing to Pretender to handle ' + - verb + ' ' + url + ' is undefined or missing.'); - } - - handler.numberOfCalls = 0; - handler.async = async; - this.handlers.push(handler); - - var registry = this.hosts.forURL(url)[verb]; - - registry.add([{ - path: parseURL(url).fullpath, - handler: handler - }]); - - return handler; - }, - passthrough: PASSTHROUGH, - checkPassthrough: function checkPassthrough(request) { - var verb = request.method.toUpperCase(); - var path = parseURL(request.url).fullpath; - var recognized = this.hosts.forURL(request.url)[verb].recognize(path); - var match = recognized && recognized[0]; - - if ((match && match.handler === PASSTHROUGH) || this.forcePassthrough) { - this.passthroughRequests.push(request); - this.passthroughRequest(verb, path, request); - return true; - } - - return false; - }, - handleRequest: function handleRequest(request) { - var verb = request.method.toUpperCase(); - var path = request.url; - - var handler = this._handlerFor(verb, path, request); - - if (handler) { - handler.handler.numberOfCalls++; - var async = handler.handler.async; - this.handledRequests.push(request); - - var pretender = this; - - var _handleRequest = function(statusHeadersAndBody) { - if (!isArray(statusHeadersAndBody)) { - var note = 'Remember to `return [status, headers, body];` in your route handler.'; - throw new Error('Nothing returned by handler for ' + path + '. ' + note); - } - - var status = statusHeadersAndBody[0], - headers = pretender.prepareHeaders(statusHeadersAndBody[1]), - body = pretender.prepareBody(statusHeadersAndBody[2], headers); - - pretender.handleResponse(request, async, function() { - request.respond(status, headers, body); - pretender.handledRequest(verb, path, request); - }); - }; - - try { - var result = handler.handler(request); - if (result && typeof result.then === 'function') { - // `result` is a promise, resolve it - result.then(function(resolvedResult) { - _handleRequest(resolvedResult); - }); - } else { - _handleRequest(result); - } - } catch (error) { - this.erroredRequest(verb, path, request, error); - this.resolve(request); - } - } else { - if (!this.disableUnhandled) { - this.unhandledRequests.push(request); - this.unhandledRequest(verb, path, request); - } - } - }, - handleResponse: function handleResponse(request, strategy, callback) { - var delay = typeof strategy === 'function' ? strategy() : strategy; - delay = typeof delay === 'boolean' || typeof delay === 'number' ? delay : 0; - - if (delay === false) { - callback(); - } else { - var pretender = this; - pretender.requestReferences.push({ - request: request, - callback: callback - }); - - if (delay !== true) { - scheduleProgressEvent(request, new Date(), delay); - setTimeout(function() { - pretender.resolve(request); - }, delay); - } - } - }, - resolve: function resolve(request) { - for (var i = 0, len = this.requestReferences.length; i < len; i++) { - var res = this.requestReferences[i]; - if (res.request === request) { - res.callback(); - this.requestReferences.splice(i, 1); - break; - } - } - }, - requiresManualResolution: function(verb, path) { - var handler = this._handlerFor(verb.toUpperCase(), path, {}); - if (!handler) { return false; } - - var async = handler.handler.async; - return typeof async === 'function' ? async() === true : async === true; - }, - prepareBody: function(body) { return body; }, - prepareHeaders: function(headers) { return headers; }, - handledRequest: function(/* verb, path, request */) { /* no-op */}, - passthroughRequest: function(/* verb, path, request */) { /* no-op */}, - unhandledRequest: function(verb, path/*, request */) { - throw new Error('Pretender intercepted ' + verb + ' ' + - path + ' but no handler was defined for this type of request'); - }, - erroredRequest: function(verb, path, request, error) { - error.message = 'Pretender intercepted ' + verb + ' ' + - path + ' but encountered an error: ' + error.message; - throw error; - }, - _handlerFor: function(verb, url, request) { - var registry = this.hosts.forURL(url)[verb]; - var matches = registry.recognize(parseURL(url).fullpath); - - var match = matches ? matches[0] : null; - if (match) { - request.params = match.params; - request.queryParams = matches.queryParams; - } - - return match; - }, - shutdown: function shutdown() { - self.XMLHttpRequest = this._nativeXMLHttpRequest; - this.ctx.pretender = undefined; - // 'stop' the server - this.running = false; - } -}; - -Pretender.parseURL = parseURL; -Pretender.Hosts = Hosts; -Pretender.Registry = Registry; - -if (typeof module === 'object') { - module.exports = Pretender; -} else if (typeof define !== 'undefined') { - define('pretender', [], function() { - return Pretender; - }); -} -self.Pretender = Pretender; -}(self)); + return Pretender; +})(self); diff --git a/yarn.lock b/yarn.lock index 3c1e5fcca78..e787964b31a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1932,10 +1932,10 @@ extract-zip@^1.6.6: mkdirp "^0.5.4" yauzl "^2.10.0" -fake-xml-http-request@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/fake-xml-http-request/-/fake-xml-http-request-1.6.0.tgz#bd0ac79ae3e2660098282048a12c730a6f64d550" - integrity sha512-99XPwwSg89BfzPuv4XCpZxn3EbauMCgAQCxq9MzrvS6DFD73OON6AnUTicL4A0HZtYMBwCZBWVnRqGjZDgQkTg== +fake-xml-http-request@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/fake-xml-http-request/-/fake-xml-http-request-2.1.2.tgz#f1786720cae50bbb46273035a0173414f3e85e74" + integrity sha512-HaFMBi7r+oEC9iJNpc3bvcW7Z7iLmM26hPDmlb0mFwyANSsOQAtJxbdWsXITKOzZUyMYK0zYCv3h5yDj9TsiXg== fast-deep-equal@^3.1.1: version "3.1.3" @@ -3527,12 +3527,12 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -pretender@^1.6: - version "1.6.1" - resolved "https://registry.yarnpkg.com/pretender/-/pretender-1.6.1.tgz#77d1e42ac8c6b298f5cd43534a87645df035db8c" - integrity sha1-d9HkKsjGspj1zUNTSodkXfA124w= +pretender@^3.4.7: + version "3.4.7" + resolved "https://registry.yarnpkg.com/pretender/-/pretender-3.4.7.tgz#34a2ae2d1fc9db440a990d50e6c0f5481d8755fc" + integrity sha512-jkPAvt1BfRi0RKamweJdEcnjkeu7Es8yix3bJ+KgBC5VpG/Ln4JE3hYN6vJym4qprm8Xo5adhWpm3HCoft1dOw== dependencies: - fake-xml-http-request "^1.6.0" + fake-xml-http-request "^2.1.2" route-recognizer "^0.3.3" prettier@2.2.1, prettier@^2.0.4: