├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONDUCT.md ├── LICENSE ├── README.md ├── bower.json ├── fake_xml_http_request.js ├── fake_xml_http_request.js.map ├── index.d.ts ├── karma.conf.js ├── package-lock.json ├── package.json ├── src └── fake-xml-http-request.js └── test ├── aborting_test.js ├── event_listeners_test.js ├── initialization_test.js ├── open_test.js ├── readyStateChange_test.js ├── responding_test.js ├── send_test.js ├── unsafe_headers_test.js └── upload_test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "14" 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # FakeXMLHttpRequest Changelog 2 | 3 | ## 2.1.2 4 | 5 | * [#59](https://github.com/pretenderjs/FakeXMLHttpRequest/pull/59) Add default values for on* properties 6 | * [#58](https://github.com/pretenderjs/FakeXMLHttpRequest/pull/58) (chore) Update npm dependencies and remove deprecated packages 7 | 8 | ## 2.1.1 9 | 10 | * [#53](https://github.com/pretenderjs/FakeXMLHttpRequest/pull/53) Alias responseText to response 11 | * [#48](https://github.com/pretenderjs/FakeXMLHttpRequest/pull/48) Add requestBody and requestHeaders to types declaration 12 | * [#46](https://github.com/pretenderjs/FakeXMLHttpRequest/pull/46) Add `module` attribute 13 | 14 | ## 2.0.1 15 | * [#42](https://github.com/pretenderjs/FakeXMLHttpRequest/pull/42) Adds better supported for aborted fetches 16 | * [#43](https://github.com/pretenderjs/FakeXMLHttpRequest/pull/43) Adds `url` to responses for better Fetch 17 | 18 | ## 1.4.0 19 | 20 | * [#23](https://github.com/pretenderjs/FakeXMLHttpRequest/pull/23) Adds an `overrideMimeType` method. 21 | -------------------------------------------------------------------------------- /CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviors that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This Code of Conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 38 | reported by contacting a project maintainer at trek.glowacki@gmail.com. All 39 | complaints will be reviewed and investigated and will result in a response that 40 | is deemed necessary and appropriate to the circumstances. Maintainers are 41 | obligated to maintain confidentiality with regard to the reporter of an 42 | incident. 43 | 44 | 45 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 46 | version 1.3.0, available at 47 | [http://contributor-covenant.org/version/1/3/0/][version] 48 | 49 | [homepage]: http://contributor-covenant.org 50 | [version]: http://contributor-covenant.org/version/1/3/0/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Trek Glowacki and contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FakeXMLHttpRequest [![Build Status](https://travis-ci.org/pretenderjs/FakeXMLHttpRequest.png?branch=master)](https://travis-ci.org/pretenderjs/FakeXMLHttpRequest) [![npm version](https://badge.fury.io/js/fake-xml-http-request.svg)](https://badge.fury.io/js/fake-xml-http-request) 2 | 3 | This library provide a fake XMLHttpRequest object for testing browser-based 4 | libraries. It is partially extracted (and in many places simplified) from 5 | [Sinon.JS](http://sinonjs.org/) and attempts to match the behavior of 6 | [XMLHttpRequest specification](http://www.w3.org/TR/XMLHttpRequest/). 7 | 8 | ## Why not just use Sinon.JS? 9 | 10 | Sinon includes much more than _just_ a fake XHR object which is useful in 11 | situations where you may not need mocks, spies, stubs, or fake servers. 12 | 13 | ## How to use it 14 | 15 | In addition to matching the native XMLHttpRequest's API, FakeXMLHttpRequest 16 | adds a `respond` function that takes three arguments: a HTTP response status 17 | number, a headers object, and a text response body: 18 | 19 | ```javascript 20 | // simulate successful response 21 | import FakeXMLHttpRequest from 'fake-xml-http-request'; 22 | 23 | let xhr = new FakeXMLHttpRequest(); 24 | xhr.respond(200, { 'Content-Type': 'application/json' }, '{"key":"value"}'); 25 | xhr.status; // 200 26 | xhr.statusText; // "OK" 27 | xhr.responseText; // '{"key":"value"}' 28 | 29 | // simulate failed response 30 | xhr = new FakeXMLHttpRequest(); 31 | xhr.abort(); 32 | ``` 33 | 34 | There is no mechanism for swapping the native XMLHttpRequest or for 35 | recording, finding, or playing back requests. Libraries using FakeXMLHttpRequest 36 | should provide this behavior. 37 | 38 | ## Testing 39 | 40 | Tests are written in [QUnit](http://qunitjs.com/) and run through the 41 | [Karma test runner](http://karma-runner.github.io/0.10/index.html). 42 | 43 | Run with: 44 | 45 | ``` 46 | karma start 47 | ``` 48 | 49 | ## Code of Conduct 50 | 51 | In order to have a more open and welcoming community this project adheres to a [code of conduct](CONDUCT.md) adapted from the [contributor covenant](http://contributor-covenant.org/). 52 | 53 | Please adhere to this code of conduct in any interactions you have with this project's community. If you encounter someone violating these terms, please let a maintainer (@trek) know and we will address it as soon as possible. 54 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fake-xml-http-request", 3 | "version": "2.1.2", 4 | "main": [ 5 | "./fake_xml_http_request.js" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /fake_xml_http_request.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.FakeXMLHttpRequest = factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | /** 8 | * Minimal Event interface implementation 9 | * 10 | * Original implementation by Sven Fuchs: https://gist.github.com/995028 11 | * Modifications and tests by Christian Johansen. 12 | * 13 | * @author Sven Fuchs (svenfuchs@artweb-design.de) 14 | * @author Christian Johansen (christian@cjohansen.no) 15 | * @license BSD 16 | * 17 | * Copyright (c) 2011 Sven Fuchs, Christian Johansen 18 | */ 19 | 20 | var _Event = function Event(type, bubbles, cancelable, target) { 21 | this.type = type; 22 | this.bubbles = bubbles; 23 | this.cancelable = cancelable; 24 | this.target = target; 25 | }; 26 | 27 | _Event.prototype = { 28 | stopPropagation: function () {}, 29 | preventDefault: function () { 30 | this.defaultPrevented = true; 31 | } 32 | }; 33 | 34 | /* 35 | Used to set the statusText property of an xhr object 36 | */ 37 | var httpStatusCodes = { 38 | 100: "Continue", 39 | 101: "Switching Protocols", 40 | 200: "OK", 41 | 201: "Created", 42 | 202: "Accepted", 43 | 203: "Non-Authoritative Information", 44 | 204: "No Content", 45 | 205: "Reset Content", 46 | 206: "Partial Content", 47 | 300: "Multiple Choice", 48 | 301: "Moved Permanently", 49 | 302: "Found", 50 | 303: "See Other", 51 | 304: "Not Modified", 52 | 305: "Use Proxy", 53 | 307: "Temporary Redirect", 54 | 400: "Bad Request", 55 | 401: "Unauthorized", 56 | 402: "Payment Required", 57 | 403: "Forbidden", 58 | 404: "Not Found", 59 | 405: "Method Not Allowed", 60 | 406: "Not Acceptable", 61 | 407: "Proxy Authentication Required", 62 | 408: "Request Timeout", 63 | 409: "Conflict", 64 | 410: "Gone", 65 | 411: "Length Required", 66 | 412: "Precondition Failed", 67 | 413: "Request Entity Too Large", 68 | 414: "Request-URI Too Long", 69 | 415: "Unsupported Media Type", 70 | 416: "Requested Range Not Satisfiable", 71 | 417: "Expectation Failed", 72 | 422: "Unprocessable Entity", 73 | 500: "Internal Server Error", 74 | 501: "Not Implemented", 75 | 502: "Bad Gateway", 76 | 503: "Service Unavailable", 77 | 504: "Gateway Timeout", 78 | 505: "HTTP Version Not Supported" 79 | }; 80 | 81 | 82 | /* 83 | Cross-browser XML parsing. Used to turn 84 | XML responses into Document objects 85 | Borrowed from JSpec 86 | */ 87 | function parseXML(text) { 88 | var xmlDoc; 89 | 90 | if (typeof DOMParser != "undefined") { 91 | var parser = new DOMParser(); 92 | xmlDoc = parser.parseFromString(text, "text/xml"); 93 | } else { 94 | xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); 95 | xmlDoc.async = "false"; 96 | xmlDoc.loadXML(text); 97 | } 98 | 99 | return xmlDoc; 100 | } 101 | 102 | /* 103 | Without mocking, the native XMLHttpRequest object will throw 104 | an error when attempting to set these headers. We match this behavior. 105 | */ 106 | var unsafeHeaders = { 107 | "Accept-Charset": true, 108 | "Accept-Encoding": true, 109 | "Connection": true, 110 | "Content-Length": true, 111 | "Cookie": true, 112 | "Cookie2": true, 113 | "Content-Transfer-Encoding": true, 114 | "Date": true, 115 | "Expect": true, 116 | "Host": true, 117 | "Keep-Alive": true, 118 | "Referer": true, 119 | "TE": true, 120 | "Trailer": true, 121 | "Transfer-Encoding": true, 122 | "Upgrade": true, 123 | "User-Agent": true, 124 | "Via": true 125 | }; 126 | 127 | /* 128 | Adds an "event" onto the fake xhr object 129 | that just calls the same-named method. This is 130 | in case a library adds callbacks for these events. 131 | */ 132 | function _addEventListener(eventName, xhr){ 133 | xhr.addEventListener(eventName, function (event) { 134 | var listener = xhr["on" + eventName]; 135 | 136 | if (listener && typeof listener == "function") { 137 | listener.call(event.target, event); 138 | } 139 | }); 140 | } 141 | 142 | function EventedObject() { 143 | this._eventListeners = {}; 144 | var events = ["loadstart", "progress", "load", "abort", "loadend"]; 145 | for (var i = events.length - 1; i >= 0; i--) { 146 | _addEventListener(events[i], this); 147 | } 148 | } 149 | EventedObject.prototype = { 150 | /* 151 | Duplicates the behavior of native XMLHttpRequest's addEventListener function 152 | */ 153 | addEventListener: function addEventListener(event, listener) { 154 | this._eventListeners[event] = this._eventListeners[event] || []; 155 | this._eventListeners[event].push(listener); 156 | }, 157 | 158 | /* 159 | Duplicates the behavior of native XMLHttpRequest's removeEventListener function 160 | */ 161 | removeEventListener: function removeEventListener(event, listener) { 162 | var listeners = this._eventListeners[event] || []; 163 | 164 | for (var i = 0, l = listeners.length; i < l; ++i) { 165 | if (listeners[i] == listener) { 166 | return listeners.splice(i, 1); 167 | } 168 | } 169 | }, 170 | 171 | /* 172 | Duplicates the behavior of native XMLHttpRequest's dispatchEvent function 173 | */ 174 | dispatchEvent: function dispatchEvent(event) { 175 | var type = event.type; 176 | var listeners = this._eventListeners[type] || []; 177 | 178 | for (var i = 0; i < listeners.length; i++) { 179 | if (typeof listeners[i] == "function") { 180 | listeners[i].call(this, event); 181 | } else { 182 | listeners[i].handleEvent(event); 183 | } 184 | } 185 | 186 | return !!event.defaultPrevented; 187 | }, 188 | 189 | /* 190 | Triggers an `onprogress` event with the given parameters. 191 | */ 192 | _progress: function _progress(lengthComputable, loaded, total) { 193 | var event = new _Event('progress'); 194 | event.target = this; 195 | event.lengthComputable = lengthComputable; 196 | event.loaded = loaded; 197 | event.total = total; 198 | this.dispatchEvent(event); 199 | } 200 | }; 201 | 202 | /* 203 | Constructor for a fake window.XMLHttpRequest 204 | */ 205 | function FakeXMLHttpRequest() { 206 | EventedObject.call(this); 207 | this.readyState = FakeXMLHttpRequest.UNSENT; 208 | this.requestHeaders = {}; 209 | this.requestBody = null; 210 | this.status = 0; 211 | this.statusText = ""; 212 | this.upload = new EventedObject(); 213 | this.onabort= null; 214 | this.onerror= null; 215 | this.onload= null; 216 | this.onloadend= null; 217 | this.onloadstart= null; 218 | this.onprogress= null; 219 | this.onreadystatechange= null; 220 | this.ontimeout= null; 221 | } 222 | 223 | FakeXMLHttpRequest.prototype = new EventedObject(); 224 | 225 | // These status codes are available on the native XMLHttpRequest 226 | // object, so we match that here in case a library is relying on them. 227 | FakeXMLHttpRequest.UNSENT = 0; 228 | FakeXMLHttpRequest.OPENED = 1; 229 | FakeXMLHttpRequest.HEADERS_RECEIVED = 2; 230 | FakeXMLHttpRequest.LOADING = 3; 231 | FakeXMLHttpRequest.DONE = 4; 232 | 233 | var FakeXMLHttpRequestProto = { 234 | UNSENT: 0, 235 | OPENED: 1, 236 | HEADERS_RECEIVED: 2, 237 | LOADING: 3, 238 | DONE: 4, 239 | async: true, 240 | withCredentials: false, 241 | 242 | /* 243 | Duplicates the behavior of native XMLHttpRequest's open function 244 | */ 245 | open: function open(method, url, async, username, password) { 246 | this.method = method; 247 | this.url = url; 248 | this.async = typeof async == "boolean" ? async : true; 249 | this.username = username; 250 | this.password = password; 251 | this.responseText = null; 252 | this.response = this.responseText; 253 | this.responseXML = null; 254 | this.responseURL = url; 255 | this.requestHeaders = {}; 256 | this.sendFlag = false; 257 | this._readyStateChange(FakeXMLHttpRequest.OPENED); 258 | }, 259 | 260 | /* 261 | Duplicates the behavior of native XMLHttpRequest's setRequestHeader function 262 | */ 263 | setRequestHeader: function setRequestHeader(header, value) { 264 | verifyState(this); 265 | 266 | if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) { 267 | throw new Error("Refused to set unsafe header \"" + header + "\""); 268 | } 269 | 270 | if (this.requestHeaders[header]) { 271 | this.requestHeaders[header] += "," + value; 272 | } else { 273 | this.requestHeaders[header] = value; 274 | } 275 | }, 276 | 277 | /* 278 | Duplicates the behavior of native XMLHttpRequest's send function 279 | */ 280 | send: function send(data) { 281 | verifyState(this); 282 | 283 | if (!/^(get|head)$/i.test(this.method)) { 284 | var hasContentTypeHeader = false; 285 | 286 | Object.keys(this.requestHeaders).forEach(function (key) { 287 | if (key.toLowerCase() === 'content-type') { 288 | hasContentTypeHeader = true; 289 | } 290 | }); 291 | 292 | if (!hasContentTypeHeader && !(data || '').toString().match('FormData')) { 293 | this.requestHeaders["Content-Type"] = "text/plain;charset=UTF-8"; 294 | } 295 | 296 | this.requestBody = data; 297 | } 298 | 299 | this.errorFlag = false; 300 | this.sendFlag = this.async; 301 | this._readyStateChange(FakeXMLHttpRequest.OPENED); 302 | 303 | if (typeof this.onSend == "function") { 304 | this.onSend(this); 305 | } 306 | 307 | this.dispatchEvent(new _Event("loadstart", false, false, this)); 308 | }, 309 | 310 | /* 311 | Duplicates the behavior of native XMLHttpRequest's abort function 312 | */ 313 | abort: function abort() { 314 | this.aborted = true; 315 | this.responseText = null; 316 | this.response = this.responseText; 317 | this.errorFlag = true; 318 | this.requestHeaders = {}; 319 | 320 | this.dispatchEvent(new _Event("abort", false, false, this)); 321 | 322 | if (this.readyState > FakeXMLHttpRequest.UNSENT && this.sendFlag) { 323 | this._readyStateChange(FakeXMLHttpRequest.UNSENT); 324 | this.sendFlag = false; 325 | } 326 | 327 | if (typeof this.onerror === "function") { 328 | this.onerror(); 329 | } 330 | }, 331 | 332 | /* 333 | Duplicates the behavior of native XMLHttpRequest's getResponseHeader function 334 | */ 335 | getResponseHeader: function getResponseHeader(header) { 336 | if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { 337 | return null; 338 | } 339 | 340 | if (/^Set-Cookie2?$/i.test(header)) { 341 | return null; 342 | } 343 | 344 | header = header.toLowerCase(); 345 | 346 | for (var h in this.responseHeaders) { 347 | if (h.toLowerCase() == header) { 348 | return this.responseHeaders[h]; 349 | } 350 | } 351 | 352 | return null; 353 | }, 354 | 355 | /* 356 | Duplicates the behavior of native XMLHttpRequest's getAllResponseHeaders function 357 | */ 358 | getAllResponseHeaders: function getAllResponseHeaders() { 359 | if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { 360 | return ""; 361 | } 362 | 363 | var headers = ""; 364 | 365 | for (var header in this.responseHeaders) { 366 | if (this.responseHeaders.hasOwnProperty(header) && !/^Set-Cookie2?$/i.test(header)) { 367 | headers += header + ": " + this.responseHeaders[header] + "\r\n"; 368 | } 369 | } 370 | 371 | return headers; 372 | }, 373 | 374 | /* 375 | Duplicates the behavior of native XMLHttpRequest's overrideMimeType function 376 | */ 377 | overrideMimeType: function overrideMimeType(mimeType) { 378 | if (typeof mimeType === "string") { 379 | this.forceMimeType = mimeType.toLowerCase(); 380 | } 381 | }, 382 | 383 | 384 | /* 385 | Places a FakeXMLHttpRequest object into the passed 386 | state. 387 | */ 388 | _readyStateChange: function _readyStateChange(state) { 389 | this.readyState = state; 390 | 391 | if (typeof this.onreadystatechange == "function") { 392 | this.onreadystatechange(new _Event("readystatechange")); 393 | } 394 | 395 | this.dispatchEvent(new _Event("readystatechange")); 396 | 397 | if (this.readyState == FakeXMLHttpRequest.DONE) { 398 | this.dispatchEvent(new _Event("load", false, false, this)); 399 | } 400 | if (this.readyState == FakeXMLHttpRequest.UNSENT || this.readyState == FakeXMLHttpRequest.DONE) { 401 | this.dispatchEvent(new _Event("loadend", false, false, this)); 402 | } 403 | }, 404 | 405 | 406 | /* 407 | Sets the FakeXMLHttpRequest object's response headers and 408 | places the object into readyState 2 409 | */ 410 | _setResponseHeaders: function _setResponseHeaders(headers) { 411 | this.responseHeaders = {}; 412 | 413 | for (var header in headers) { 414 | if (headers.hasOwnProperty(header)) { 415 | this.responseHeaders[header] = headers[header]; 416 | } 417 | } 418 | 419 | if (this.forceMimeType) { 420 | this.responseHeaders['Content-Type'] = this.forceMimeType; 421 | } 422 | 423 | if (this.async) { 424 | this._readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED); 425 | } else { 426 | this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED; 427 | } 428 | }, 429 | 430 | /* 431 | Sets the FakeXMLHttpRequest object's response body and 432 | if body text is XML, sets responseXML to parsed document 433 | object 434 | */ 435 | _setResponseBody: function _setResponseBody(body) { 436 | verifyRequestSent(this); 437 | verifyHeadersReceived(this); 438 | verifyResponseBodyType(body); 439 | 440 | var chunkSize = this.chunkSize || 10; 441 | var index = 0; 442 | this.responseText = ""; 443 | this.response = this.responseText; 444 | 445 | do { 446 | if (this.async) { 447 | this._readyStateChange(FakeXMLHttpRequest.LOADING); 448 | } 449 | 450 | this.responseText += body.substring(index, index + chunkSize); 451 | this.response = this.responseText; 452 | index += chunkSize; 453 | } while (index < body.length); 454 | 455 | var type = this.getResponseHeader("Content-Type"); 456 | 457 | if (this.responseText && (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) { 458 | try { 459 | this.responseXML = parseXML(this.responseText); 460 | } catch (e) { 461 | // Unable to parse XML - no biggie 462 | } 463 | } 464 | 465 | if (this.async) { 466 | this._readyStateChange(FakeXMLHttpRequest.DONE); 467 | } else { 468 | this.readyState = FakeXMLHttpRequest.DONE; 469 | } 470 | }, 471 | 472 | /* 473 | Forces a response on to the FakeXMLHttpRequest object. 474 | 475 | This is the public API for faking responses. This function 476 | takes a number status, headers object, and string body: 477 | 478 | ``` 479 | xhr.respond(404, {Content-Type: 'text/plain'}, "Sorry. This object was not found.") 480 | 481 | ``` 482 | */ 483 | respond: function respond(status, headers, body) { 484 | this._setResponseHeaders(headers || {}); 485 | this.status = typeof status == "number" ? status : 200; 486 | this.statusText = httpStatusCodes[this.status]; 487 | this._setResponseBody(body || ""); 488 | } 489 | }; 490 | 491 | for (var property in FakeXMLHttpRequestProto) { 492 | FakeXMLHttpRequest.prototype[property] = FakeXMLHttpRequestProto[property]; 493 | } 494 | 495 | function verifyState(xhr) { 496 | if (xhr.readyState !== FakeXMLHttpRequest.OPENED) { 497 | throw new Error("INVALID_STATE_ERR"); 498 | } 499 | 500 | if (xhr.sendFlag) { 501 | throw new Error("INVALID_STATE_ERR"); 502 | } 503 | } 504 | 505 | 506 | function verifyRequestSent(xhr) { 507 | if (xhr.readyState == FakeXMLHttpRequest.DONE) { 508 | throw new Error("Request done"); 509 | } 510 | } 511 | 512 | function verifyHeadersReceived(xhr) { 513 | if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) { 514 | throw new Error("No headers received"); 515 | } 516 | } 517 | 518 | function verifyResponseBodyType(body) { 519 | if (typeof body != "string") { 520 | var error = new Error("Attempted to respond to fake XMLHttpRequest with " + 521 | body + ", which is not a string."); 522 | error.name = "InvalidBodyException"; 523 | throw error; 524 | } 525 | } 526 | 527 | return FakeXMLHttpRequest; 528 | 529 | }))); 530 | //# sourceMappingURL=fake_xml_http_request.js.map 531 | -------------------------------------------------------------------------------- /fake_xml_http_request.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"fake_xml_http_request.js","sources":["src/fake-xml-http-request.js"],"sourcesContent":["/**\n * Minimal Event interface implementation\n *\n * Original implementation by Sven Fuchs: https://gist.github.com/995028\n * Modifications and tests by Christian Johansen.\n *\n * @author Sven Fuchs (svenfuchs@artweb-design.de)\n * @author Christian Johansen (christian@cjohansen.no)\n * @license BSD\n *\n * Copyright (c) 2011 Sven Fuchs, Christian Johansen\n */\n\nvar _Event = function Event(type, bubbles, cancelable, target) {\n this.type = type;\n this.bubbles = bubbles;\n this.cancelable = cancelable;\n this.target = target;\n};\n\n_Event.prototype = {\n stopPropagation: function () {},\n preventDefault: function () {\n this.defaultPrevented = true;\n }\n};\n\n/*\n Used to set the statusText property of an xhr object\n*/\nvar httpStatusCodes = {\n 100: \"Continue\",\n 101: \"Switching Protocols\",\n 200: \"OK\",\n 201: \"Created\",\n 202: \"Accepted\",\n 203: \"Non-Authoritative Information\",\n 204: \"No Content\",\n 205: \"Reset Content\",\n 206: \"Partial Content\",\n 300: \"Multiple Choice\",\n 301: \"Moved Permanently\",\n 302: \"Found\",\n 303: \"See Other\",\n 304: \"Not Modified\",\n 305: \"Use Proxy\",\n 307: \"Temporary Redirect\",\n 400: \"Bad Request\",\n 401: \"Unauthorized\",\n 402: \"Payment Required\",\n 403: \"Forbidden\",\n 404: \"Not Found\",\n 405: \"Method Not Allowed\",\n 406: \"Not Acceptable\",\n 407: \"Proxy Authentication Required\",\n 408: \"Request Timeout\",\n 409: \"Conflict\",\n 410: \"Gone\",\n 411: \"Length Required\",\n 412: \"Precondition Failed\",\n 413: \"Request Entity Too Large\",\n 414: \"Request-URI Too Long\",\n 415: \"Unsupported Media Type\",\n 416: \"Requested Range Not Satisfiable\",\n 417: \"Expectation Failed\",\n 422: \"Unprocessable Entity\",\n 500: \"Internal Server Error\",\n 501: \"Not Implemented\",\n 502: \"Bad Gateway\",\n 503: \"Service Unavailable\",\n 504: \"Gateway Timeout\",\n 505: \"HTTP Version Not Supported\"\n};\n\n\n/*\n Cross-browser XML parsing. Used to turn\n XML responses into Document objects\n Borrowed from JSpec\n*/\nfunction parseXML(text) {\n var xmlDoc;\n\n if (typeof DOMParser != \"undefined\") {\n var parser = new DOMParser();\n xmlDoc = parser.parseFromString(text, \"text/xml\");\n } else {\n xmlDoc = new ActiveXObject(\"Microsoft.XMLDOM\");\n xmlDoc.async = \"false\";\n xmlDoc.loadXML(text);\n }\n\n return xmlDoc;\n}\n\n/*\n Without mocking, the native XMLHttpRequest object will throw\n an error when attempting to set these headers. We match this behavior.\n*/\nvar unsafeHeaders = {\n \"Accept-Charset\": true,\n \"Accept-Encoding\": true,\n \"Connection\": true,\n \"Content-Length\": true,\n \"Cookie\": true,\n \"Cookie2\": true,\n \"Content-Transfer-Encoding\": true,\n \"Date\": true,\n \"Expect\": true,\n \"Host\": true,\n \"Keep-Alive\": true,\n \"Referer\": true,\n \"TE\": true,\n \"Trailer\": true,\n \"Transfer-Encoding\": true,\n \"Upgrade\": true,\n \"User-Agent\": true,\n \"Via\": true\n};\n\n/*\n Adds an \"event\" onto the fake xhr object\n that just calls the same-named method. This is\n in case a library adds callbacks for these events.\n*/\nfunction _addEventListener(eventName, xhr){\n xhr.addEventListener(eventName, function (event) {\n var listener = xhr[\"on\" + eventName];\n\n if (listener && typeof listener == \"function\") {\n listener.call(event.target, event);\n }\n });\n}\n\nfunction EventedObject() {\n this._eventListeners = {};\n var events = [\"loadstart\", \"progress\", \"load\", \"abort\", \"loadend\"];\n for (var i = events.length - 1; i >= 0; i--) {\n _addEventListener(events[i], this);\n }\n};\n\nEventedObject.prototype = {\n /*\n Duplicates the behavior of native XMLHttpRequest's addEventListener function\n */\n addEventListener: function addEventListener(event, listener) {\n this._eventListeners[event] = this._eventListeners[event] || [];\n this._eventListeners[event].push(listener);\n },\n\n /*\n Duplicates the behavior of native XMLHttpRequest's removeEventListener function\n */\n removeEventListener: function removeEventListener(event, listener) {\n var listeners = this._eventListeners[event] || [];\n\n for (var i = 0, l = listeners.length; i < l; ++i) {\n if (listeners[i] == listener) {\n return listeners.splice(i, 1);\n }\n }\n },\n\n /*\n Duplicates the behavior of native XMLHttpRequest's dispatchEvent function\n */\n dispatchEvent: function dispatchEvent(event) {\n var type = event.type;\n var listeners = this._eventListeners[type] || [];\n\n for (var i = 0; i < listeners.length; i++) {\n if (typeof listeners[i] == \"function\") {\n listeners[i].call(this, event);\n } else {\n listeners[i].handleEvent(event);\n }\n }\n\n return !!event.defaultPrevented;\n },\n\n /*\n Triggers an `onprogress` event with the given parameters.\n */\n _progress: function _progress(lengthComputable, loaded, total) {\n var event = new _Event('progress');\n event.target = this;\n event.lengthComputable = lengthComputable;\n event.loaded = loaded;\n event.total = total;\n this.dispatchEvent(event);\n }\n}\n\n/*\n Constructor for a fake window.XMLHttpRequest\n*/\nfunction FakeXMLHttpRequest() {\n EventedObject.call(this);\n this.readyState = FakeXMLHttpRequest.UNSENT;\n this.requestHeaders = {};\n this.requestBody = null;\n this.status = 0;\n this.statusText = \"\";\n this.upload = new EventedObject();\n this.onabort= null;\n this.onerror= null;\n this.onload= null;\n this.onloadend= null;\n this.onloadstart= null;\n this.onprogress= null;\n this.onreadystatechange= null;\n this.ontimeout= null;\n}\n\nFakeXMLHttpRequest.prototype = new EventedObject();\n\n// These status codes are available on the native XMLHttpRequest\n// object, so we match that here in case a library is relying on them.\nFakeXMLHttpRequest.UNSENT = 0;\nFakeXMLHttpRequest.OPENED = 1;\nFakeXMLHttpRequest.HEADERS_RECEIVED = 2;\nFakeXMLHttpRequest.LOADING = 3;\nFakeXMLHttpRequest.DONE = 4;\n\nvar FakeXMLHttpRequestProto = {\n UNSENT: 0,\n OPENED: 1,\n HEADERS_RECEIVED: 2,\n LOADING: 3,\n DONE: 4,\n async: true,\n withCredentials: false,\n\n /*\n Duplicates the behavior of native XMLHttpRequest's open function\n */\n open: function open(method, url, async, username, password) {\n this.method = method;\n this.url = url;\n this.async = typeof async == \"boolean\" ? async : true;\n this.username = username;\n this.password = password;\n this.responseText = null;\n this.response = this.responseText;\n this.responseXML = null;\n this.responseURL = url;\n this.requestHeaders = {};\n this.sendFlag = false;\n this._readyStateChange(FakeXMLHttpRequest.OPENED);\n },\n\n /*\n Duplicates the behavior of native XMLHttpRequest's setRequestHeader function\n */\n setRequestHeader: function setRequestHeader(header, value) {\n verifyState(this);\n\n if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) {\n throw new Error(\"Refused to set unsafe header \\\"\" + header + \"\\\"\");\n }\n\n if (this.requestHeaders[header]) {\n this.requestHeaders[header] += \",\" + value;\n } else {\n this.requestHeaders[header] = value;\n }\n },\n\n /*\n Duplicates the behavior of native XMLHttpRequest's send function\n */\n send: function send(data) {\n verifyState(this);\n\n if (!/^(get|head)$/i.test(this.method)) {\n var hasContentTypeHeader = false\n\n Object.keys(this.requestHeaders).forEach(function (key) {\n if (key.toLowerCase() === 'content-type') {\n hasContentTypeHeader = true;\n }\n });\n\n if (!hasContentTypeHeader && !(data || '').toString().match('FormData')) {\n this.requestHeaders[\"Content-Type\"] = \"text/plain;charset=UTF-8\";\n }\n\n this.requestBody = data;\n }\n\n this.errorFlag = false;\n this.sendFlag = this.async;\n this._readyStateChange(FakeXMLHttpRequest.OPENED);\n\n if (typeof this.onSend == \"function\") {\n this.onSend(this);\n }\n\n this.dispatchEvent(new _Event(\"loadstart\", false, false, this));\n },\n\n /*\n Duplicates the behavior of native XMLHttpRequest's abort function\n */\n abort: function abort() {\n this.aborted = true;\n this.responseText = null;\n this.response = this.responseText;\n this.errorFlag = true;\n this.requestHeaders = {};\n\n this.dispatchEvent(new _Event(\"abort\", false, false, this));\n\n if (this.readyState > FakeXMLHttpRequest.UNSENT && this.sendFlag) {\n this._readyStateChange(FakeXMLHttpRequest.UNSENT);\n this.sendFlag = false;\n }\n\n if (typeof this.onerror === \"function\") {\n this.onerror();\n }\n },\n\n /*\n Duplicates the behavior of native XMLHttpRequest's getResponseHeader function\n */\n getResponseHeader: function getResponseHeader(header) {\n if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {\n return null;\n }\n\n if (/^Set-Cookie2?$/i.test(header)) {\n return null;\n }\n\n header = header.toLowerCase();\n\n for (var h in this.responseHeaders) {\n if (h.toLowerCase() == header) {\n return this.responseHeaders[h];\n }\n }\n\n return null;\n },\n\n /*\n Duplicates the behavior of native XMLHttpRequest's getAllResponseHeaders function\n */\n getAllResponseHeaders: function getAllResponseHeaders() {\n if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {\n return \"\";\n }\n\n var headers = \"\";\n\n for (var header in this.responseHeaders) {\n if (this.responseHeaders.hasOwnProperty(header) && !/^Set-Cookie2?$/i.test(header)) {\n headers += header + \": \" + this.responseHeaders[header] + \"\\r\\n\";\n }\n }\n\n return headers;\n },\n\n /*\n Duplicates the behavior of native XMLHttpRequest's overrideMimeType function\n */\n overrideMimeType: function overrideMimeType(mimeType) {\n if (typeof mimeType === \"string\") {\n this.forceMimeType = mimeType.toLowerCase();\n }\n },\n\n\n /*\n Places a FakeXMLHttpRequest object into the passed\n state.\n */\n _readyStateChange: function _readyStateChange(state) {\n this.readyState = state;\n\n if (typeof this.onreadystatechange == \"function\") {\n this.onreadystatechange(new _Event(\"readystatechange\"));\n }\n\n this.dispatchEvent(new _Event(\"readystatechange\"));\n\n if (this.readyState == FakeXMLHttpRequest.DONE) {\n this.dispatchEvent(new _Event(\"load\", false, false, this));\n }\n if (this.readyState == FakeXMLHttpRequest.UNSENT || this.readyState == FakeXMLHttpRequest.DONE) {\n this.dispatchEvent(new _Event(\"loadend\", false, false, this));\n }\n },\n\n\n /*\n Sets the FakeXMLHttpRequest object's response headers and\n places the object into readyState 2\n */\n _setResponseHeaders: function _setResponseHeaders(headers) {\n this.responseHeaders = {};\n\n for (var header in headers) {\n if (headers.hasOwnProperty(header)) {\n this.responseHeaders[header] = headers[header];\n }\n }\n\n if (this.forceMimeType) {\n this.responseHeaders['Content-Type'] = this.forceMimeType;\n }\n\n if (this.async) {\n this._readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED);\n } else {\n this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED;\n }\n },\n\n /*\n Sets the FakeXMLHttpRequest object's response body and\n if body text is XML, sets responseXML to parsed document\n object\n */\n _setResponseBody: function _setResponseBody(body) {\n verifyRequestSent(this);\n verifyHeadersReceived(this);\n verifyResponseBodyType(body);\n\n var chunkSize = this.chunkSize || 10;\n var index = 0;\n this.responseText = \"\";\n this.response = this.responseText;\n\n do {\n if (this.async) {\n this._readyStateChange(FakeXMLHttpRequest.LOADING);\n }\n\n this.responseText += body.substring(index, index + chunkSize);\n this.response = this.responseText;\n index += chunkSize;\n } while (index < body.length);\n\n var type = this.getResponseHeader(\"Content-Type\");\n\n if (this.responseText && (!type || /(text\\/xml)|(application\\/xml)|(\\+xml)/.test(type))) {\n try {\n this.responseXML = parseXML(this.responseText);\n } catch (e) {\n // Unable to parse XML - no biggie\n }\n }\n\n if (this.async) {\n this._readyStateChange(FakeXMLHttpRequest.DONE);\n } else {\n this.readyState = FakeXMLHttpRequest.DONE;\n }\n },\n\n /*\n Forces a response on to the FakeXMLHttpRequest object.\n\n This is the public API for faking responses. This function\n takes a number status, headers object, and string body:\n\n ```\n xhr.respond(404, {Content-Type: 'text/plain'}, \"Sorry. This object was not found.\")\n\n ```\n */\n respond: function respond(status, headers, body) {\n this._setResponseHeaders(headers || {});\n this.status = typeof status == \"number\" ? status : 200;\n this.statusText = httpStatusCodes[this.status];\n this._setResponseBody(body || \"\");\n }\n};\n\nfor (var property in FakeXMLHttpRequestProto) {\n FakeXMLHttpRequest.prototype[property] = FakeXMLHttpRequestProto[property];\n}\n\nfunction verifyState(xhr) {\n if (xhr.readyState !== FakeXMLHttpRequest.OPENED) {\n throw new Error(\"INVALID_STATE_ERR\");\n }\n\n if (xhr.sendFlag) {\n throw new Error(\"INVALID_STATE_ERR\");\n }\n}\n\n\nfunction verifyRequestSent(xhr) {\n if (xhr.readyState == FakeXMLHttpRequest.DONE) {\n throw new Error(\"Request done\");\n }\n}\n\nfunction verifyHeadersReceived(xhr) {\n if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) {\n throw new Error(\"No headers received\");\n }\n}\n\nfunction verifyResponseBodyType(body) {\n if (typeof body != \"string\") {\n var error = new Error(\"Attempted to respond to fake XMLHttpRequest with \" +\n body + \", which is not a string.\");\n error.name = \"InvalidBodyException\";\n throw error;\n }\n}\nexport default FakeXMLHttpRequest;\n"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export default class FakeXMLHttpRequest extends XMLHttpRequest { 2 | requestBody: string; 3 | 4 | requestHeaders: {[k: string]: string}; 5 | /* 6 | Forces a response on to the FakeXMLHttpRequest object. 7 | 8 | This is the public API for faking responses. This function 9 | takes a number status, headers object, and string body: 10 | 11 | ``` 12 | xhr.respond(404, {Content-Type: 'text/plain'}, "Sorry. This object was not found.") 13 | ``` 14 | */ 15 | respond( 16 | statusCode: number, 17 | headersObject?: { 18 | [k: string]: string; 19 | }, 20 | bodyText?: string 21 | ): void; 22 | } 23 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Mon Mar 08 2021 12:58:46 GMT-0800 (Pacific Standard Time) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['qunit'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | './fake_xml_http_request.js', 19 | 'test/**/*_test.js' 20 | ], 21 | 22 | 23 | // list of files / patterns to exclude 24 | exclude: [ 25 | ], 26 | 27 | 28 | // preprocess matching files before serving them to the browser 29 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 30 | preprocessors: { 31 | }, 32 | 33 | 34 | // test results reporter to use 35 | // possible values: 'dots', 'progress' 36 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 37 | reporters: ['progress'], 38 | 39 | 40 | // web server port 41 | port: 9876, 42 | 43 | 44 | // enable / disable colors in the output (reporters and logs) 45 | colors: true, 46 | 47 | 48 | // level of logging 49 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 50 | logLevel: config.LOG_INFO, 51 | 52 | 53 | // enable / disable watching file and executing tests whenever any file changes 54 | autoWatch: true, 55 | 56 | 57 | // start these browsers 58 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 59 | browsers: ['Chrome', 'PhantomJS'], 60 | 61 | 62 | // Continuous Integration mode 63 | // if true, Karma captures browsers, runs the tests and exits 64 | singleRun: false, 65 | 66 | // Concurrency level 67 | // how many browser should be started simultaneous 68 | concurrency: Infinity 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fake-xml-http-request", 3 | "version": "2.1.2", 4 | "description": "test infrastructure for a fake XMLHttpRequest object", 5 | "main": "fake_xml_http_request.js", 6 | "module": "./src/fake-xml-http-request.js", 7 | "repository": "https://github.com/trek/FakeXMLHttpRequest.git", 8 | "scripts": { 9 | "start": "karma start", 10 | "build": "rollup src/fake-xml-http-request.js --file fake_xml_http_request.js --format umd --name 'FakeXMLHttpRequest' --sourcemap true", 11 | "test": "npm run build && karma start --single-run --browsers PhantomJS" 12 | }, 13 | "author": { 14 | "name": "Trek Glowacki" 15 | }, 16 | "homepage": "https://github.com/trek/FakeXMLHttpRequest", 17 | "license": "MIT", 18 | "devDependencies": { 19 | "karma": "^6.1.1", 20 | "karma-chrome-launcher": "^3.1.0", 21 | "karma-firefox-launcher": "^2.1.0", 22 | "karma-phantomjs-launcher": "^1.0.4", 23 | "karma-qunit": "^4.1.2", 24 | "qunit": "^2.14.0", 25 | "rollup": "^2.40.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/fake-xml-http-request.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Minimal Event interface implementation 3 | * 4 | * Original implementation by Sven Fuchs: https://gist.github.com/995028 5 | * Modifications and tests by Christian Johansen. 6 | * 7 | * @author Sven Fuchs (svenfuchs@artweb-design.de) 8 | * @author Christian Johansen (christian@cjohansen.no) 9 | * @license BSD 10 | * 11 | * Copyright (c) 2011 Sven Fuchs, Christian Johansen 12 | */ 13 | 14 | var _Event = function Event(type, bubbles, cancelable, target) { 15 | this.type = type; 16 | this.bubbles = bubbles; 17 | this.cancelable = cancelable; 18 | this.target = target; 19 | }; 20 | 21 | _Event.prototype = { 22 | stopPropagation: function () {}, 23 | preventDefault: function () { 24 | this.defaultPrevented = true; 25 | } 26 | }; 27 | 28 | /* 29 | Used to set the statusText property of an xhr object 30 | */ 31 | var httpStatusCodes = { 32 | 100: "Continue", 33 | 101: "Switching Protocols", 34 | 200: "OK", 35 | 201: "Created", 36 | 202: "Accepted", 37 | 203: "Non-Authoritative Information", 38 | 204: "No Content", 39 | 205: "Reset Content", 40 | 206: "Partial Content", 41 | 300: "Multiple Choice", 42 | 301: "Moved Permanently", 43 | 302: "Found", 44 | 303: "See Other", 45 | 304: "Not Modified", 46 | 305: "Use Proxy", 47 | 307: "Temporary Redirect", 48 | 400: "Bad Request", 49 | 401: "Unauthorized", 50 | 402: "Payment Required", 51 | 403: "Forbidden", 52 | 404: "Not Found", 53 | 405: "Method Not Allowed", 54 | 406: "Not Acceptable", 55 | 407: "Proxy Authentication Required", 56 | 408: "Request Timeout", 57 | 409: "Conflict", 58 | 410: "Gone", 59 | 411: "Length Required", 60 | 412: "Precondition Failed", 61 | 413: "Request Entity Too Large", 62 | 414: "Request-URI Too Long", 63 | 415: "Unsupported Media Type", 64 | 416: "Requested Range Not Satisfiable", 65 | 417: "Expectation Failed", 66 | 422: "Unprocessable Entity", 67 | 500: "Internal Server Error", 68 | 501: "Not Implemented", 69 | 502: "Bad Gateway", 70 | 503: "Service Unavailable", 71 | 504: "Gateway Timeout", 72 | 505: "HTTP Version Not Supported" 73 | }; 74 | 75 | 76 | /* 77 | Cross-browser XML parsing. Used to turn 78 | XML responses into Document objects 79 | Borrowed from JSpec 80 | */ 81 | function parseXML(text) { 82 | var xmlDoc; 83 | 84 | if (typeof DOMParser != "undefined") { 85 | var parser = new DOMParser(); 86 | xmlDoc = parser.parseFromString(text, "text/xml"); 87 | } else { 88 | xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); 89 | xmlDoc.async = "false"; 90 | xmlDoc.loadXML(text); 91 | } 92 | 93 | return xmlDoc; 94 | } 95 | 96 | /* 97 | Without mocking, the native XMLHttpRequest object will throw 98 | an error when attempting to set these headers. We match this behavior. 99 | */ 100 | var unsafeHeaders = { 101 | "Accept-Charset": true, 102 | "Accept-Encoding": true, 103 | "Connection": true, 104 | "Content-Length": true, 105 | "Cookie": true, 106 | "Cookie2": true, 107 | "Content-Transfer-Encoding": true, 108 | "Date": true, 109 | "Expect": true, 110 | "Host": true, 111 | "Keep-Alive": true, 112 | "Referer": true, 113 | "TE": true, 114 | "Trailer": true, 115 | "Transfer-Encoding": true, 116 | "Upgrade": true, 117 | "User-Agent": true, 118 | "Via": true 119 | }; 120 | 121 | /* 122 | Adds an "event" onto the fake xhr object 123 | that just calls the same-named method. This is 124 | in case a library adds callbacks for these events. 125 | */ 126 | function _addEventListener(eventName, xhr){ 127 | xhr.addEventListener(eventName, function (event) { 128 | var listener = xhr["on" + eventName]; 129 | 130 | if (listener && typeof listener == "function") { 131 | listener.call(event.target, event); 132 | } 133 | }); 134 | } 135 | 136 | function EventedObject() { 137 | this._eventListeners = {}; 138 | var events = ["loadstart", "progress", "load", "abort", "loadend"]; 139 | for (var i = events.length - 1; i >= 0; i--) { 140 | _addEventListener(events[i], this); 141 | } 142 | }; 143 | 144 | EventedObject.prototype = { 145 | /* 146 | Duplicates the behavior of native XMLHttpRequest's addEventListener function 147 | */ 148 | addEventListener: function addEventListener(event, listener) { 149 | this._eventListeners[event] = this._eventListeners[event] || []; 150 | this._eventListeners[event].push(listener); 151 | }, 152 | 153 | /* 154 | Duplicates the behavior of native XMLHttpRequest's removeEventListener function 155 | */ 156 | removeEventListener: function removeEventListener(event, listener) { 157 | var listeners = this._eventListeners[event] || []; 158 | 159 | for (var i = 0, l = listeners.length; i < l; ++i) { 160 | if (listeners[i] == listener) { 161 | return listeners.splice(i, 1); 162 | } 163 | } 164 | }, 165 | 166 | /* 167 | Duplicates the behavior of native XMLHttpRequest's dispatchEvent function 168 | */ 169 | dispatchEvent: function dispatchEvent(event) { 170 | var type = event.type; 171 | var listeners = this._eventListeners[type] || []; 172 | 173 | for (var i = 0; i < listeners.length; i++) { 174 | if (typeof listeners[i] == "function") { 175 | listeners[i].call(this, event); 176 | } else { 177 | listeners[i].handleEvent(event); 178 | } 179 | } 180 | 181 | return !!event.defaultPrevented; 182 | }, 183 | 184 | /* 185 | Triggers an `onprogress` event with the given parameters. 186 | */ 187 | _progress: function _progress(lengthComputable, loaded, total) { 188 | var event = new _Event('progress'); 189 | event.target = this; 190 | event.lengthComputable = lengthComputable; 191 | event.loaded = loaded; 192 | event.total = total; 193 | this.dispatchEvent(event); 194 | } 195 | } 196 | 197 | /* 198 | Constructor for a fake window.XMLHttpRequest 199 | */ 200 | function FakeXMLHttpRequest() { 201 | EventedObject.call(this); 202 | this.readyState = FakeXMLHttpRequest.UNSENT; 203 | this.requestHeaders = {}; 204 | this.requestBody = null; 205 | this.status = 0; 206 | this.statusText = ""; 207 | this.upload = new EventedObject(); 208 | this.onabort= null; 209 | this.onerror= null; 210 | this.onload= null; 211 | this.onloadend= null; 212 | this.onloadstart= null; 213 | this.onprogress= null; 214 | this.onreadystatechange= null; 215 | this.ontimeout= null; 216 | } 217 | 218 | FakeXMLHttpRequest.prototype = new EventedObject(); 219 | 220 | // These status codes are available on the native XMLHttpRequest 221 | // object, so we match that here in case a library is relying on them. 222 | FakeXMLHttpRequest.UNSENT = 0; 223 | FakeXMLHttpRequest.OPENED = 1; 224 | FakeXMLHttpRequest.HEADERS_RECEIVED = 2; 225 | FakeXMLHttpRequest.LOADING = 3; 226 | FakeXMLHttpRequest.DONE = 4; 227 | 228 | var FakeXMLHttpRequestProto = { 229 | UNSENT: 0, 230 | OPENED: 1, 231 | HEADERS_RECEIVED: 2, 232 | LOADING: 3, 233 | DONE: 4, 234 | async: true, 235 | withCredentials: false, 236 | 237 | /* 238 | Duplicates the behavior of native XMLHttpRequest's open function 239 | */ 240 | open: function open(method, url, async, username, password) { 241 | this.method = method; 242 | this.url = url; 243 | this.async = typeof async == "boolean" ? async : true; 244 | this.username = username; 245 | this.password = password; 246 | this.responseText = null; 247 | this.response = this.responseText; 248 | this.responseXML = null; 249 | this.responseURL = url; 250 | this.requestHeaders = {}; 251 | this.sendFlag = false; 252 | this._readyStateChange(FakeXMLHttpRequest.OPENED); 253 | }, 254 | 255 | /* 256 | Duplicates the behavior of native XMLHttpRequest's setRequestHeader function 257 | */ 258 | setRequestHeader: function setRequestHeader(header, value) { 259 | verifyState(this); 260 | 261 | if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) { 262 | throw new Error("Refused to set unsafe header \"" + header + "\""); 263 | } 264 | 265 | if (this.requestHeaders[header]) { 266 | this.requestHeaders[header] += "," + value; 267 | } else { 268 | this.requestHeaders[header] = value; 269 | } 270 | }, 271 | 272 | /* 273 | Duplicates the behavior of native XMLHttpRequest's send function 274 | */ 275 | send: function send(data) { 276 | verifyState(this); 277 | 278 | if (!/^(get|head)$/i.test(this.method)) { 279 | var hasContentTypeHeader = false 280 | 281 | Object.keys(this.requestHeaders).forEach(function (key) { 282 | if (key.toLowerCase() === 'content-type') { 283 | hasContentTypeHeader = true; 284 | } 285 | }); 286 | 287 | if (!hasContentTypeHeader && !(data || '').toString().match('FormData')) { 288 | this.requestHeaders["Content-Type"] = "text/plain;charset=UTF-8"; 289 | } 290 | 291 | this.requestBody = data; 292 | } 293 | 294 | this.errorFlag = false; 295 | this.sendFlag = this.async; 296 | this._readyStateChange(FakeXMLHttpRequest.OPENED); 297 | 298 | if (typeof this.onSend == "function") { 299 | this.onSend(this); 300 | } 301 | 302 | this.dispatchEvent(new _Event("loadstart", false, false, this)); 303 | }, 304 | 305 | /* 306 | Duplicates the behavior of native XMLHttpRequest's abort function 307 | */ 308 | abort: function abort() { 309 | this.aborted = true; 310 | this.responseText = null; 311 | this.response = this.responseText; 312 | this.errorFlag = true; 313 | this.requestHeaders = {}; 314 | 315 | this.dispatchEvent(new _Event("abort", false, false, this)); 316 | 317 | if (this.readyState > FakeXMLHttpRequest.UNSENT && this.sendFlag) { 318 | this._readyStateChange(FakeXMLHttpRequest.UNSENT); 319 | this.sendFlag = false; 320 | } 321 | 322 | if (typeof this.onerror === "function") { 323 | this.onerror(); 324 | } 325 | }, 326 | 327 | /* 328 | Duplicates the behavior of native XMLHttpRequest's getResponseHeader function 329 | */ 330 | getResponseHeader: function getResponseHeader(header) { 331 | if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { 332 | return null; 333 | } 334 | 335 | if (/^Set-Cookie2?$/i.test(header)) { 336 | return null; 337 | } 338 | 339 | header = header.toLowerCase(); 340 | 341 | for (var h in this.responseHeaders) { 342 | if (h.toLowerCase() == header) { 343 | return this.responseHeaders[h]; 344 | } 345 | } 346 | 347 | return null; 348 | }, 349 | 350 | /* 351 | Duplicates the behavior of native XMLHttpRequest's getAllResponseHeaders function 352 | */ 353 | getAllResponseHeaders: function getAllResponseHeaders() { 354 | if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { 355 | return ""; 356 | } 357 | 358 | var headers = ""; 359 | 360 | for (var header in this.responseHeaders) { 361 | if (this.responseHeaders.hasOwnProperty(header) && !/^Set-Cookie2?$/i.test(header)) { 362 | headers += header + ": " + this.responseHeaders[header] + "\r\n"; 363 | } 364 | } 365 | 366 | return headers; 367 | }, 368 | 369 | /* 370 | Duplicates the behavior of native XMLHttpRequest's overrideMimeType function 371 | */ 372 | overrideMimeType: function overrideMimeType(mimeType) { 373 | if (typeof mimeType === "string") { 374 | this.forceMimeType = mimeType.toLowerCase(); 375 | } 376 | }, 377 | 378 | 379 | /* 380 | Places a FakeXMLHttpRequest object into the passed 381 | state. 382 | */ 383 | _readyStateChange: function _readyStateChange(state) { 384 | this.readyState = state; 385 | 386 | if (typeof this.onreadystatechange == "function") { 387 | this.onreadystatechange(new _Event("readystatechange")); 388 | } 389 | 390 | this.dispatchEvent(new _Event("readystatechange")); 391 | 392 | if (this.readyState == FakeXMLHttpRequest.DONE) { 393 | this.dispatchEvent(new _Event("load", false, false, this)); 394 | } 395 | if (this.readyState == FakeXMLHttpRequest.UNSENT || this.readyState == FakeXMLHttpRequest.DONE) { 396 | this.dispatchEvent(new _Event("loadend", false, false, this)); 397 | } 398 | }, 399 | 400 | 401 | /* 402 | Sets the FakeXMLHttpRequest object's response headers and 403 | places the object into readyState 2 404 | */ 405 | _setResponseHeaders: function _setResponseHeaders(headers) { 406 | this.responseHeaders = {}; 407 | 408 | for (var header in headers) { 409 | if (headers.hasOwnProperty(header)) { 410 | this.responseHeaders[header] = headers[header]; 411 | } 412 | } 413 | 414 | if (this.forceMimeType) { 415 | this.responseHeaders['Content-Type'] = this.forceMimeType; 416 | } 417 | 418 | if (this.async) { 419 | this._readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED); 420 | } else { 421 | this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED; 422 | } 423 | }, 424 | 425 | /* 426 | Sets the FakeXMLHttpRequest object's response body and 427 | if body text is XML, sets responseXML to parsed document 428 | object 429 | */ 430 | _setResponseBody: function _setResponseBody(body) { 431 | verifyRequestSent(this); 432 | verifyHeadersReceived(this); 433 | verifyResponseBodyType(body); 434 | 435 | var chunkSize = this.chunkSize || 10; 436 | var index = 0; 437 | this.responseText = ""; 438 | this.response = this.responseText; 439 | 440 | do { 441 | if (this.async) { 442 | this._readyStateChange(FakeXMLHttpRequest.LOADING); 443 | } 444 | 445 | this.responseText += body.substring(index, index + chunkSize); 446 | this.response = this.responseText; 447 | index += chunkSize; 448 | } while (index < body.length); 449 | 450 | var type = this.getResponseHeader("Content-Type"); 451 | 452 | if (this.responseText && (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) { 453 | try { 454 | this.responseXML = parseXML(this.responseText); 455 | } catch (e) { 456 | // Unable to parse XML - no biggie 457 | } 458 | } 459 | 460 | if (this.async) { 461 | this._readyStateChange(FakeXMLHttpRequest.DONE); 462 | } else { 463 | this.readyState = FakeXMLHttpRequest.DONE; 464 | } 465 | }, 466 | 467 | /* 468 | Forces a response on to the FakeXMLHttpRequest object. 469 | 470 | This is the public API for faking responses. This function 471 | takes a number status, headers object, and string body: 472 | 473 | ``` 474 | xhr.respond(404, {Content-Type: 'text/plain'}, "Sorry. This object was not found.") 475 | 476 | ``` 477 | */ 478 | respond: function respond(status, headers, body) { 479 | this._setResponseHeaders(headers || {}); 480 | this.status = typeof status == "number" ? status : 200; 481 | this.statusText = httpStatusCodes[this.status]; 482 | this._setResponseBody(body || ""); 483 | } 484 | }; 485 | 486 | for (var property in FakeXMLHttpRequestProto) { 487 | FakeXMLHttpRequest.prototype[property] = FakeXMLHttpRequestProto[property]; 488 | } 489 | 490 | function verifyState(xhr) { 491 | if (xhr.readyState !== FakeXMLHttpRequest.OPENED) { 492 | throw new Error("INVALID_STATE_ERR"); 493 | } 494 | 495 | if (xhr.sendFlag) { 496 | throw new Error("INVALID_STATE_ERR"); 497 | } 498 | } 499 | 500 | 501 | function verifyRequestSent(xhr) { 502 | if (xhr.readyState == FakeXMLHttpRequest.DONE) { 503 | throw new Error("Request done"); 504 | } 505 | } 506 | 507 | function verifyHeadersReceived(xhr) { 508 | if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) { 509 | throw new Error("No headers received"); 510 | } 511 | } 512 | 513 | function verifyResponseBodyType(body) { 514 | if (typeof body != "string") { 515 | var error = new Error("Attempted to respond to fake XMLHttpRequest with " + 516 | body + ", which is not a string."); 517 | error.name = "InvalidBodyException"; 518 | throw error; 519 | } 520 | } 521 | export default FakeXMLHttpRequest; 522 | -------------------------------------------------------------------------------- /test/aborting_test.js: -------------------------------------------------------------------------------- 1 | var xhr; 2 | QUnit.module( "aborting", { 3 | beforeEach: function( assert ) { 4 | xhr = new FakeXMLHttpRequest(); 5 | }, 6 | afterEach: function( assert ) { 7 | window.xhr = undefined; 8 | } 9 | } ); 10 | 11 | QUnit.test( "sets aborted to true", function( assert ) { 12 | xhr.abort(); 13 | assert.equal( xhr.aborted, true ); 14 | } ); 15 | 16 | QUnit.test( "sets responseText to null", function( assert ) { 17 | xhr.abort(); 18 | assert.equal( xhr.responseText, null ); 19 | } ); 20 | 21 | QUnit.test( "sets response to null", function( assert ) { 22 | xhr.abort(); 23 | assert.equal( xhr.response, null ); 24 | } ); 25 | 26 | QUnit.test( "sets errorFlag to true", function( assert ) { 27 | xhr.abort(); 28 | assert.equal( xhr.errorFlag, true ); 29 | } ); 30 | 31 | QUnit.test( "sets requestHeaders to {}", function( assert ) { 32 | xhr.abort(); 33 | assert.deepEqual( xhr.requestHeaders, {} ); 34 | } ); 35 | 36 | QUnit.test( "sets readyState to 0", function( assert ) { 37 | xhr.abort(); 38 | assert.equal( xhr.readyState, 0 ); 39 | } ); 40 | 41 | QUnit.test( "calls the abort event", function( assert ) { 42 | var wasCalled = false; 43 | xhr.addEventListener( "abort", function() { 44 | wasCalled = true; 45 | } ); 46 | 47 | xhr.abort(); 48 | 49 | assert.ok( wasCalled ); 50 | } ); 51 | 52 | QUnit.test( "calls the onerror event", function( assert ) { 53 | var wasCalled = false; 54 | xhr.onerror = function() { 55 | wasCalled = true; 56 | }; 57 | 58 | xhr.abort(); 59 | 60 | assert.ok( wasCalled ); 61 | } ); 62 | 63 | QUnit.test( "does not call the onload event", function( assert ) { 64 | var wasCalled = false; 65 | xhr.onload = function() { 66 | wasCalled = true; 67 | }; 68 | 69 | xhr.open( "POST", "/" ); 70 | xhr.send( "data" ); 71 | 72 | xhr.abort(); 73 | 74 | assert.notOk( wasCalled ); 75 | } ); 76 | 77 | QUnit.test( "calls the loadend event", function( assert ) { 78 | var wasCalled = false; 79 | xhr.onloadend = function() { 80 | wasCalled = true; 81 | }; 82 | 83 | xhr.open( "POST", "/" ); 84 | xhr.send( "data" ); 85 | 86 | xhr.abort(); 87 | 88 | assert.ok( wasCalled ); 89 | } ); 90 | -------------------------------------------------------------------------------- /test/event_listeners_test.js: -------------------------------------------------------------------------------- 1 | var xhr; 2 | QUnit.module( "event listeners", { 3 | beforeEach: function( assert ) { 4 | xhr = new FakeXMLHttpRequest(); 5 | }, 6 | afterEach: function( assert ) { 7 | window.xhr = undefined; 8 | } 9 | } ); 10 | 11 | QUnit.test( "adding a listener", function( assert ) { 12 | var wasCalled = false; 13 | xhr.addEventListener( "somethingHappened", function() { 14 | wasCalled = true; 15 | } ); 16 | 17 | xhr.dispatchEvent( { type: "somethingHappened" } ); 18 | 19 | assert.ok( wasCalled, "the listener was called" ); 20 | } ); 21 | 22 | QUnit.test( "removing a listener", function( assert ) { 23 | var wasCalled = false; 24 | var listener = xhr.addEventListener( "somethingHappened", function() { 25 | wasCalled = true; 26 | } ); 27 | 28 | xhr.dispatchEvent( { type: "somethingHappened" } ); 29 | 30 | assert.ok( wasCalled, "the listener was called" ); 31 | } ); 32 | -------------------------------------------------------------------------------- /test/initialization_test.js: -------------------------------------------------------------------------------- 1 | var xhr; 2 | QUnit.module( "FakeXMLHttpRequest construction", { 3 | beforeEach: function( assert ) { 4 | xhr = new FakeXMLHttpRequest(); 5 | }, 6 | afterEach: function( assert ) { 7 | xhr = undefined; 8 | } 9 | } ); 10 | 11 | QUnit.test( "readyState is 0", function( assert ) { 12 | assert.equal( xhr.readyState, 0 ); 13 | } ); 14 | 15 | QUnit.test( "requestHeaders are {}", function( assert ) { 16 | assert.deepEqual( xhr.requestHeaders, {} ); 17 | } ); 18 | 19 | QUnit.test( "requestBody is null", function( assert ) { 20 | assert.equal( xhr.requestBody, null ); 21 | } ); 22 | 23 | QUnit.test( "status is 0", function( assert ) { 24 | assert.equal( xhr.status, 0 ); 25 | } ); 26 | 27 | QUnit.test( "statusText is empty", function( assert ) { 28 | assert.equal( xhr.status, "" ); 29 | } ); 30 | 31 | QUnit.test( "withCredentials is false", function( assert ) { 32 | assert.equal( xhr.withCredentials, false ); 33 | } ); 34 | 35 | QUnit.test( "onabort is null", function( assert ) { 36 | assert.equal( xhr.onabort, null ); 37 | }); 38 | 39 | QUnit.test( "onerror is null", function( assert ) { 40 | assert.equal( xhr.onerror, null ); 41 | }); 42 | 43 | QUnit.test( "onload is null", function( assert ) { 44 | assert.equal( xhr.onload, null ); 45 | }); 46 | 47 | QUnit.test( "onloadend is null", function( assert ) { 48 | assert.equal( xhr.onloadend, null ); 49 | }); 50 | 51 | QUnit.test( "onloadstart is null", function( assert ) { 52 | assert.equal( xhr.onloadstart, null ); 53 | }); 54 | 55 | QUnit.test( "onprogress is null", function( assert ) { 56 | assert.equal( xhr.onprogress, null ); 57 | }); 58 | 59 | QUnit.test( "onreadystatechange is null", function( assert ) { 60 | assert.equal( xhr.onreadystatechange, null ); 61 | }); 62 | 63 | QUnit.test( "ontimeout is null", function( assert ) { 64 | assert.equal( xhr.ontimeout, null ); 65 | }); 66 | -------------------------------------------------------------------------------- /test/open_test.js: -------------------------------------------------------------------------------- 1 | var xhr; 2 | QUnit.module( "open", { 3 | beforeEach: function( assert ) { 4 | xhr = new FakeXMLHttpRequest(); 5 | }, 6 | afterEach: function( assert ) { 7 | xhr = undefined; 8 | } 9 | } ); 10 | 11 | QUnit.test( "open sets the method property", function( assert ) { 12 | xhr.open( "get", "/some/url" ); 13 | assert.equal( xhr.method, "get" ); 14 | } ); 15 | 16 | QUnit.test( "open sets the url property", function( assert ) { 17 | xhr.open( "get", "/some/url" ); 18 | assert.equal( xhr.url, "/some/url" ); 19 | } ); 20 | 21 | QUnit.test( "open sets the async property", function( assert ) { 22 | xhr.open( "get", "/some/url", false ); 23 | assert.equal( xhr.async, false ); 24 | } ); 25 | 26 | QUnit.test( "open sets the async property to true if a boolean isn't passed", function( assert ) { 27 | xhr.open( "get", "/some/url", "whatisthisidontevent" ); 28 | assert.equal( xhr.url, "/some/url", false ); 29 | } ); 30 | 31 | QUnit.test( "open sets the username property", function( assert ) { 32 | xhr.open( "get", "/some/url", true, "johndoe" ); 33 | assert.equal( xhr.username, "johndoe" ); 34 | } ); 35 | 36 | QUnit.test( "open sets the password property", function( assert ) { 37 | xhr.open( "get", "/some/url", true, "johndoe", "password" ); 38 | assert.equal( xhr.password, "password" ); 39 | } ); 40 | 41 | QUnit.test( "initializes the responseText as null", function( assert ) { 42 | xhr.open( "get", "/some/url" ); 43 | assert.equal( xhr.responseText, null ); 44 | } ); 45 | 46 | QUnit.test( "initializes the response as null", function( assert ) { 47 | xhr.open( "get", "/some/url" ); 48 | assert.equal( xhr.response, null ); 49 | } ); 50 | 51 | QUnit.test( "initializes the responseXML as null", function( assert ) { 52 | xhr.open( "get", "/some/url" ); 53 | assert.equal( xhr.responseXML, null ); 54 | } ); 55 | 56 | QUnit.test( "initializes the responseURL as the opened url", function( assert ) { 57 | xhr.open( "get", "/some/url" ); 58 | assert.equal( xhr.responseURL, "/some/url" ); 59 | } ); 60 | 61 | QUnit.test( "initializes the requestHeaders property as empty object", function( assert ) { 62 | xhr.open( "get", "/some/url" ); 63 | assert.deepEqual( xhr.requestHeaders, {} ); 64 | 65 | } ); 66 | 67 | QUnit.test( "open sets the ready state to 1", function( assert ) { 68 | xhr.open( "get", "/some/url" ); 69 | assert.equal( xhr.readyState, 1 ); 70 | } ); 71 | 72 | QUnit.test( "triggers the onreadystatechange event with OPENED readyState", function( assert ) { 73 | var readyState = null; 74 | 75 | xhr.onreadystatechange = function() { 76 | readyState = this.readyState; 77 | }; 78 | 79 | xhr.open( "get", "/some/url" ); 80 | 81 | assert.equal( readyState, FakeXMLHttpRequest.OPENED ); 82 | } ); 83 | -------------------------------------------------------------------------------- /test/readyStateChange_test.js: -------------------------------------------------------------------------------- 1 | var xhr, xmlDocumentConstructor; 2 | QUnit.module( "readyState handling", { 3 | beforeEach: function( assert ) { 4 | xhr = new FakeXMLHttpRequest(); 5 | }, 6 | afterEach: function( assert ) { 7 | xhr = undefined; 8 | } 9 | } ); 10 | 11 | QUnit.test( "does not call onload and loadend if readyState not UNSENT or DONE", function( assert ) { 12 | var wasCalled = 0; 13 | 14 | xhr.onload = function() { 15 | wasCalled += 1; 16 | }; 17 | xhr.onloadend = function( ev ) { 18 | wasCalled += 1; 19 | }; 20 | 21 | [ 22 | FakeXMLHttpRequest.OPENED, 23 | FakeXMLHttpRequest.HEADERS_RECEIVED, 24 | FakeXMLHttpRequest.LOADING 25 | ].forEach( function( state ) { 26 | xhr._readyStateChange( state ); 27 | } ); 28 | 29 | assert.strictEqual( wasCalled, 0 ); 30 | } ); 31 | -------------------------------------------------------------------------------- /test/responding_test.js: -------------------------------------------------------------------------------- 1 | var xhr, xmlDocumentConstructor; 2 | QUnit.module( "responding", { 3 | beforeEach: function( assert ) { 4 | xhr = new FakeXMLHttpRequest(); 5 | xmlDocumentConstructor = makeXMLDocument().constructor; 6 | }, 7 | afterEach: function( assert ) { 8 | xhr = undefined; 9 | xmlDocumentConstructor = undefined; 10 | } 11 | } ); 12 | 13 | // Different browsers report different constructors for XML Documents. 14 | // Chrome 45.0.2454 and Firefox 40.0.0 report `XMLDocument`, 15 | // PhantomJS 1.9.8 reports `Document`. 16 | // Make a dummy xml document to determine what constructor to 17 | // compare against in the tests below. 18 | // This function is taken from `parseXML` in the src/ 19 | function makeXMLDocument() { 20 | var xmlDoc, text = "xml"; 21 | 22 | if ( typeof DOMParser != "undefined" ) { 23 | var parser = new DOMParser(); 24 | xmlDoc = parser.parseFromString( text, "text/xml" ); 25 | } else { 26 | xmlDoc = new ActiveXObject( "Microsoft.XMLDOM" ); 27 | xmlDoc.async = "false"; 28 | xmlDoc.loadXML( text ); 29 | } 30 | 31 | return xmlDoc; 32 | } 33 | 34 | QUnit.test( "defaults responseHeaders to {} if not passed", function( assert ) { 35 | xhr.respond( 200 ); 36 | assert.deepEqual( xhr.responseHeaders, {} ); 37 | } ); 38 | 39 | QUnit.test( "sets responseHeaders", function( assert ) { 40 | xhr.respond( 200, { "Content-Type":"application/json" } ); 41 | assert.deepEqual( xhr.responseHeaders, { "Content-Type":"application/json" } ); 42 | } ); 43 | 44 | QUnit.test( "sets body", function( assert ) { 45 | xhr.respond( 200, { "Content-Type":"application/json" }, JSON.stringify( { a: "key" } ) ); 46 | assert.equal( xhr.responseText, '{"a":"key"}' ); 47 | assert.equal( xhr.response, '{"a":"key"}' ); 48 | } ); 49 | 50 | QUnit.test( "parses the body if it's XML and no content-type is set", function( assert ) { 51 | xhr.respond( 200, {}, "value" ); 52 | assert.equal( xhr.responseXML.constructor, xmlDocumentConstructor ); 53 | } ); 54 | 55 | QUnit.test( "parses the body if it's XML and xml content type is set", function( assert ) { 56 | xhr.respond( 200, { "Content-Type":"application/xml" }, "value" ); 57 | assert.equal( xhr.responseXML.constructor, xmlDocumentConstructor ); 58 | } ); 59 | 60 | QUnit.test( "does not parse the body if it's XML and another content type is set", function( assert ) { 61 | xhr.respond( 200, { "Content-Type":"application/json" }, "value" ); 62 | assert.equal( xhr.responseXML, undefined ); 63 | } ); 64 | 65 | QUnit.test( "calls the onload callback once", function( assert ) { 66 | var wasCalled = 0; 67 | 68 | xhr.onload = function( ev ) { 69 | wasCalled += 1; 70 | }; 71 | 72 | xhr.respond( 200, {}, "" ); 73 | 74 | assert.strictEqual( wasCalled, 1 ); 75 | } ); 76 | 77 | QUnit.test( "calls the onloadend callback once", function( assert ) { 78 | var wasCalled = 0; 79 | 80 | xhr.onloadend = function( ev ) { 81 | wasCalled += 1; 82 | }; 83 | 84 | xhr.respond( 200, {}, "" ); 85 | 86 | assert.strictEqual( wasCalled, 1 ); 87 | } ); 88 | 89 | QUnit.test( "passes event target as context to onload", function( assert ) { 90 | var context; 91 | var event; 92 | 93 | xhr.onload = function( ev ) { 94 | event = ev; 95 | context = this; 96 | }; 97 | 98 | xhr.respond( 200, {}, "" ); 99 | 100 | assert.deepEqual( context, event.target ); 101 | } ); 102 | 103 | QUnit.test( "calls onreadystatechange for each state change", function( assert ) { 104 | var states = []; 105 | 106 | xhr.onreadystatechange = function() { 107 | states.push( this.readyState ); 108 | }; 109 | 110 | xhr.open( "get", "/some/url" ); 111 | 112 | xhr.respond( 200, {}, "" ); 113 | 114 | var expectedStates = [ 115 | FakeXMLHttpRequest.OPENED, 116 | FakeXMLHttpRequest.HEADERS_RECEIVED, 117 | FakeXMLHttpRequest.LOADING, 118 | FakeXMLHttpRequest.DONE 119 | ]; 120 | assert.deepEqual( states, expectedStates ); 121 | } ); 122 | 123 | QUnit.test( "passes event to onreadystatechange", function( assert ) { 124 | var event = null; 125 | xhr.onreadystatechange = function( e ) { 126 | event = e; 127 | }; 128 | xhr.open( "get", "/some/url" ); 129 | xhr.respond( 200, {}, "" ); 130 | 131 | assert.ok( event && event.type === "readystatechange", 132 | 'passes event with type "readystatechange"' ); 133 | } ); 134 | 135 | QUnit.test( "overrideMimeType overrides content-type responseHeader", function( assert ) { 136 | xhr.overrideMimeType( "text/plain" ); 137 | xhr.respond( 200, { "Content-Type":"application/json" } ); 138 | assert.deepEqual( xhr.responseHeaders, { "Content-Type":"text/plain" } ); 139 | } ); 140 | 141 | QUnit.test( "parses the body if it's XML and overrideMimeType is set to xml", function( assert ) { 142 | xhr.overrideMimeType( "application/xml" ); 143 | xhr.respond( 200, { "Content-Type":"text/plain" }, "value" ); 144 | assert.equal( xhr.responseXML.constructor, xmlDocumentConstructor ); 145 | } ); 146 | 147 | QUnit.test( "does not parse the body if it's XML and overrideMimeType is set to another content type", function( assert ) { 148 | xhr.overrideMimeType( "text/plain" ); 149 | xhr.respond( 200, { "Content-Type":"application/xml" }, "value" ); 150 | assert.equal( xhr.responseXML, undefined ); 151 | } ); 152 | -------------------------------------------------------------------------------- /test/send_test.js: -------------------------------------------------------------------------------- 1 | var xhr; 2 | QUnit.module( "send", { 3 | beforeEach: function( assert ) { 4 | xhr = new FakeXMLHttpRequest(); 5 | }, 6 | afterEach: function( assert ) { 7 | xhr = undefined; 8 | } 9 | } ); 10 | 11 | QUnit.test( "sets Content-Type header for non-GET/HEAD requests if not set", function( assert ) { 12 | xhr.open( "POST", "/" ); 13 | xhr.send( "data" ); 14 | assert.equal( xhr.requestHeaders[ "Content-Type" ], "text/plain;charset=UTF-8", 15 | "sets content-type when not set" ); 16 | } ); 17 | 18 | QUnit.test( "does not change Content-Type if explicitly set for non-GET/HEAD using key Content-TYpe", function( assert ) { 19 | xhr.open( "POST", "/" ); 20 | xhr.setRequestHeader( "Content-Type", "application/json" ); 21 | xhr.send( "data" ); 22 | assert.equal( xhr.requestHeaders[ "Content-Type" ], "application/json", 23 | "does not change existing content-type header" ); 24 | } ); 25 | 26 | QUnit.test( "does not set Content-Type if content-type explicitly set for non-GET/HEAD", function( assert ) { 27 | xhr.open( "POST", "/" ); 28 | xhr.setRequestHeader( "content-type", "application/json" ); 29 | xhr.send( "data" ); 30 | assert.equal( xhr.requestHeaders[ "content-type" ], "application/json", 31 | "does not change existing content-type header" ); 32 | assert.equal( xhr.requestHeaders[ "Content-Type" ], undefined, 33 | "does not add Content-Type header" ); 34 | } ); 35 | 36 | QUnit.test( "does not set Content-Type if CoNtEnT-tYpE explicitly set for non-GET/HEAD", function( assert ) { 37 | xhr.open( "POST", "/" ); 38 | xhr.setRequestHeader( "CoNtEnT-tYpE", "application/json" ); 39 | xhr.send( "data" ); 40 | assert.equal( xhr.requestHeaders[ "CoNtEnT-tYpE" ], "application/json", 41 | "does not change existing content-type header" ); 42 | assert.equal( xhr.requestHeaders[ "Content-Type" ], undefined, 43 | "does not add Content-Type header" ); 44 | } ); 45 | 46 | QUnit.test( "does not set Content-Type if data is FormData object", function( assert ) { 47 | xhr.open( "POST", "/" ); 48 | xhr.send( new FormData() ); 49 | assert.equal( xhr.requestHeaders[ "Content-Type" ], null, 50 | "does not set content-type header for FormData POSTs" ); 51 | } ); 52 | -------------------------------------------------------------------------------- /test/unsafe_headers_test.js: -------------------------------------------------------------------------------- 1 | var xhr; 2 | QUnit.module( "setting unsafe header mirrors browser behavior and throws", { 3 | beforeEach: function( assert ) { 4 | xhr = new FakeXMLHttpRequest(); 5 | xhr.open( "GET", "/" ); 6 | }, 7 | afterEach: function( assert ) { 8 | window.xhr = undefined; 9 | } 10 | } ); 11 | 12 | QUnit.test( "Accept-Charset", function( assert ) { 13 | assert.throws( function() { 14 | xhr.setRequestHeader( "Accept-Charset", "..." ); 15 | }, /Refused to set unsafe header.*Accept\-Charset/ ); 16 | } ); 17 | 18 | QUnit.test( "Accept-Encoding", function( assert ) { 19 | assert.throws( function() { 20 | xhr.setRequestHeader( "Accept-Encoding", "..." ); 21 | }, /Refused to set unsafe header.*Accept\-Encoding/ ); 22 | } ); 23 | 24 | QUnit.test( "Connection", function( assert ) { 25 | assert.throws( function() { 26 | xhr.setRequestHeader( "Connection", "..." ); 27 | }, /Refused to set unsafe header.*Connection/ ); 28 | } ); 29 | 30 | QUnit.test( "Content-Length", function( assert ) { 31 | assert.throws( function() { 32 | xhr.setRequestHeader( "Content\-Length", "..." ); 33 | }, /Refused to set unsafe header.*Content\-Length/ ); 34 | } ); 35 | 36 | QUnit.test( "Cookie", function( assert ) { 37 | assert.throws( function() { 38 | xhr.setRequestHeader( "Cookie", "..." ); 39 | }, /Refused to set unsafe header.*Cookie/ ); 40 | } ); 41 | 42 | QUnit.test( "Cookie2", function( assert ) { 43 | assert.throws( function() { 44 | xhr.setRequestHeader( "Cookie2", "..." ); 45 | }, /Refused to set unsafe header.*Cookie2/ ); 46 | } ); 47 | 48 | QUnit.test( "Content-Transfer-Encoding", function( assert ) { 49 | assert.throws( function() { 50 | xhr.setRequestHeader( "Content-Transfer-Encoding", "..." ); 51 | }, /Refused to set unsafe header.*Content\-Transfer\-Encoding/ ); 52 | } ); 53 | 54 | QUnit.test( "Date", function( assert ) { 55 | assert.throws( function() { 56 | xhr.setRequestHeader( "Date", "..." ); 57 | }, /Refused to set unsafe header.*Date/ ); 58 | } ); 59 | 60 | QUnit.test( "Expect", function( assert ) { 61 | assert.throws( function() { 62 | xhr.setRequestHeader( "Expect", "..." ); 63 | }, /Refused to set unsafe header.*Expect/ ); 64 | } ); 65 | 66 | QUnit.test( "Host", function( assert ) { 67 | assert.throws( function() { 68 | xhr.setRequestHeader( "Host", "..." ); 69 | }, /Refused to set unsafe header.*Host/ ); 70 | } ); 71 | 72 | QUnit.test( "Keep-Alive", function( assert ) { 73 | assert.throws( function() { 74 | xhr.setRequestHeader( "Keep-Alive", "..." ); 75 | }, /Refused to set unsafe header.*Keep-Alive/ ); 76 | } ); 77 | 78 | QUnit.test( "Referer", function( assert ) { 79 | assert.throws( function() { 80 | xhr.setRequestHeader( "Referer", "..." ); 81 | }, /Refused to set unsafe header.*Referer/ ); 82 | } ); 83 | 84 | QUnit.test( "TE", function( assert ) { 85 | assert.throws( function() { 86 | xhr.setRequestHeader( "TE", "..." ); 87 | }, /Refused to set unsafe header.*TE/ ); 88 | } ); 89 | 90 | QUnit.test( "Trailer", function( assert ) { 91 | assert.throws( function() { 92 | xhr.setRequestHeader( "Trailer", "..." ); 93 | }, /Refused to set unsafe header.*Trailer/ ); 94 | } ); 95 | 96 | QUnit.test( "Transfer-Encoding", function( assert ) { 97 | assert.throws( function() { 98 | xhr.setRequestHeader( "Transfer-Encoding", "..." ); 99 | }, /Refused to set unsafe header.*Transfer\-Encoding/ ); 100 | } ); 101 | 102 | QUnit.test( "Upgrade", function( assert ) { 103 | assert.throws( function() { 104 | xhr.setRequestHeader( "Upgrade", "..." ); 105 | }, /Refused to set unsafe header.*Upgrade/ ); 106 | } ); 107 | 108 | QUnit.test( "User-Agent", function( assert ) { 109 | assert.throws( function() { 110 | xhr.setRequestHeader( "User-Agent", "..." ); 111 | }, /Refused to set unsafe header.*User\-Agent/ ); 112 | } ); 113 | 114 | QUnit.test( "Via", function( assert ) { 115 | assert.throws( function() { 116 | xhr.setRequestHeader( "Via", "..." ); 117 | }, /Refused to set unsafe header.*Via/ ); 118 | } ); 119 | -------------------------------------------------------------------------------- /test/upload_test.js: -------------------------------------------------------------------------------- 1 | var upload; 2 | var xhr; 3 | 4 | QUnit.module( "upload", { 5 | beforeEach: function( assert ) { 6 | xhr = new FakeXMLHttpRequest(); 7 | upload = xhr.upload; 8 | } 9 | } ); 10 | 11 | QUnit.test( "the upload property of a fake xhr is defined", function( assert ) { 12 | assert.ok( upload ); 13 | } ); 14 | 15 | QUnit.test( "_progress triggers the onprogress event", function( assert ) { 16 | var event; 17 | upload.onprogress = function( e ) { 18 | event = e; 19 | }; 20 | upload._progress( true, 10, 100 ); 21 | assert.ok( event, "A progress event was fired" ); 22 | assert.ok( event.lengthComputable, "ProgressEvent.lengthComputable" ); 23 | assert.equal( event.loaded, 10, "ProgressEvent.loaded" ); 24 | assert.equal( event.total, 100, "ProgressEvent.total" ); 25 | } ); 26 | --------------------------------------------------------------------------------