├── .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 [](https://travis-ci.org/pretenderjs/FakeXMLHttpRequest) [](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 |
--------------------------------------------------------------------------------