index.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. "use strict";
  2. var fakeXhr = require("../fake-xhr");
  3. var push = [].push;
  4. var format = require("./format");
  5. var configureLogError = require("../configure-logger");
  6. var pathToRegexp = require("path-to-regexp");
  7. var supportsArrayBuffer = typeof ArrayBuffer !== "undefined";
  8. function responseArray(handler) {
  9. var response = handler;
  10. if (Object.prototype.toString.call(handler) !== "[object Array]") {
  11. response = [200, {}, handler];
  12. }
  13. if (typeof response[2] !== "string") {
  14. if (!supportsArrayBuffer) {
  15. throw new TypeError("Fake server response body should be a string, but was " +
  16. typeof response[2]);
  17. }
  18. else if (!(response[2] instanceof ArrayBuffer)) {
  19. throw new TypeError("Fake server response body should be a string or ArrayBuffer, but was " +
  20. typeof response[2]);
  21. }
  22. }
  23. return response;
  24. }
  25. function getDefaultWindowLocation() {
  26. return { "host": "localhost", "protocol": "http" };
  27. }
  28. function getWindowLocation() {
  29. if (typeof window === "undefined") {
  30. // Fallback
  31. return getDefaultWindowLocation();
  32. }
  33. if (typeof window.location !== "undefined") {
  34. // Browsers place location on window
  35. return window.location;
  36. }
  37. if ((typeof window.window !== "undefined") && (typeof window.window.location !== "undefined")) {
  38. // React Native on Android places location on window.window
  39. return window.window.location;
  40. }
  41. return getDefaultWindowLocation();
  42. }
  43. function matchOne(response, reqMethod, reqUrl) {
  44. var rmeth = response.method;
  45. var matchMethod = !rmeth || rmeth.toLowerCase() === reqMethod.toLowerCase();
  46. var url = response.url;
  47. var matchUrl = !url || url === reqUrl || (typeof url.test === "function" && url.test(reqUrl));
  48. return matchMethod && matchUrl;
  49. }
  50. function match(response, request) {
  51. var wloc = getWindowLocation();
  52. var rCurrLoc = new RegExp("^" + wloc.protocol + "//" + wloc.host);
  53. var requestUrl = request.url;
  54. if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) {
  55. requestUrl = requestUrl.replace(rCurrLoc, "");
  56. }
  57. if (matchOne(response, this.getHTTPMethod(request), requestUrl)) {
  58. if (typeof response.response === "function") {
  59. var ru = response.url;
  60. var args = [request].concat(ru && typeof ru.exec === "function" ? ru.exec(requestUrl).slice(1) : []);
  61. return response.response.apply(response, args);
  62. }
  63. return true;
  64. }
  65. return false;
  66. }
  67. function incrementRequestCount() {
  68. var count = ++this.requestCount;
  69. this.requested = true;
  70. this.requestedOnce = count === 1;
  71. this.requestedTwice = count === 2;
  72. this.requestedThrice = count === 3;
  73. this.firstRequest = this.getRequest(0);
  74. this.secondRequest = this.getRequest(1);
  75. this.thirdRequest = this.getRequest(2);
  76. this.lastRequest = this.getRequest(count - 1);
  77. }
  78. var fakeServer = {
  79. create: function (config) {
  80. var server = Object.create(this);
  81. server.configure(config);
  82. this.xhr = fakeXhr.useFakeXMLHttpRequest();
  83. server.requests = [];
  84. server.requestCount = 0;
  85. server.queue = [];
  86. server.responses = [];
  87. this.xhr.onCreate = function (xhrObj) {
  88. xhrObj.unsafeHeadersEnabled = function () {
  89. return !(server.unsafeHeadersEnabled === false);
  90. };
  91. server.addRequest(xhrObj);
  92. };
  93. return server;
  94. },
  95. configure: function (config) {
  96. var self = this;
  97. var whitelist = {
  98. "autoRespond": true,
  99. "autoRespondAfter": true,
  100. "respondImmediately": true,
  101. "fakeHTTPMethods": true,
  102. "logger": true,
  103. "unsafeHeadersEnabled": true
  104. };
  105. config = config || {};
  106. Object.keys(config).forEach(function (setting) {
  107. if (setting in whitelist) {
  108. self[setting] = config[setting];
  109. }
  110. });
  111. self.logError = configureLogError(config);
  112. },
  113. addRequest: function addRequest(xhrObj) {
  114. var server = this;
  115. push.call(this.requests, xhrObj);
  116. incrementRequestCount.call(this);
  117. xhrObj.onSend = function () {
  118. server.handleRequest(this);
  119. if (server.respondImmediately) {
  120. server.respond();
  121. } else if (server.autoRespond && !server.responding) {
  122. setTimeout(function () {
  123. server.responding = false;
  124. server.respond();
  125. }, server.autoRespondAfter || 10);
  126. server.responding = true;
  127. }
  128. };
  129. },
  130. getHTTPMethod: function getHTTPMethod(request) {
  131. if (this.fakeHTTPMethods && /post/i.test(request.method)) {
  132. var matches = (request.requestBody || "").match(/_method=([^\b;]+)/);
  133. return matches ? matches[1] : request.method;
  134. }
  135. return request.method;
  136. },
  137. handleRequest: function handleRequest(xhr) {
  138. if (xhr.async) {
  139. push.call(this.queue, xhr);
  140. } else {
  141. this.processRequest(xhr);
  142. }
  143. },
  144. logger: function () {
  145. // no-op; override via configure()
  146. },
  147. logError: configureLogError({}),
  148. log: function log(response, request) {
  149. var str;
  150. str = "Request:\n" + format(request) + "\n\n";
  151. str += "Response:\n" + format(response) + "\n\n";
  152. if (typeof this.logger === "function") {
  153. this.logger(str);
  154. }
  155. },
  156. respondWith: function respondWith(method, url, body) {
  157. if (arguments.length === 1 && typeof method !== "function") {
  158. this.response = responseArray(method);
  159. return;
  160. }
  161. if (arguments.length === 1) {
  162. body = method;
  163. url = method = null;
  164. }
  165. if (arguments.length === 2) {
  166. body = url;
  167. url = method;
  168. method = null;
  169. }
  170. push.call(this.responses, {
  171. method: method,
  172. url: typeof url === "string" && url !== "" ? pathToRegexp(url) : url,
  173. response: typeof body === "function" ? body : responseArray(body)
  174. });
  175. },
  176. respond: function respond() {
  177. if (arguments.length > 0) {
  178. this.respondWith.apply(this, arguments);
  179. }
  180. var queue = this.queue || [];
  181. var requests = queue.splice(0, queue.length);
  182. var self = this;
  183. requests.forEach(function (request) {
  184. self.processRequest(request);
  185. });
  186. },
  187. processRequest: function processRequest(request) {
  188. try {
  189. if (request.aborted) {
  190. return;
  191. }
  192. var response = this.response || [404, {}, ""];
  193. if (this.responses) {
  194. for (var l = this.responses.length, i = l - 1; i >= 0; i--) {
  195. if (match.call(this, this.responses[i], request)) {
  196. response = this.responses[i].response;
  197. break;
  198. }
  199. }
  200. }
  201. if (request.readyState !== 4) {
  202. this.log(response, request);
  203. request.respond(response[0], response[1], response[2]);
  204. }
  205. } catch (e) {
  206. this.logError("Fake server request processing", e);
  207. }
  208. },
  209. restore: function restore() {
  210. return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments);
  211. },
  212. getRequest: function getRequest(index) {
  213. return this.requests[index] || null;
  214. },
  215. reset: function reset() {
  216. this.resetBehavior();
  217. this.resetHistory();
  218. },
  219. resetBehavior: function resetBehavior() {
  220. this.responses.length = this.queue.length = 0;
  221. },
  222. resetHistory: function resetHistory() {
  223. this.requests.length = this.requestCount = 0;
  224. this.requestedOnce = this.requestedTwice = this.requestedThrice = this.requested = false;
  225. this.firstRequest = this.secondRequest = this.thirdRequest = this.lastRequest = null;
  226. }
  227. };
  228. module.exports = fakeServer;