Skip to content

Instantly share code, notes, and snippets.

@Seteh
Last active February 15, 2016 15:13
Show Gist options
  • Save Seteh/5a3c5386b7e0a1772ccb to your computer and use it in GitHub Desktop.
Save Seteh/5a3c5386b7e0a1772ccb to your computer and use it in GitHub Desktop.
A tool for mocking XMLHttpRequest
(function(global) {
var DummyServer = function() {
var states = {
UNSENT: 0,
OPENED: 1,
HEADERS_RECEIVED: 2,
LOADING: 3,
DONE: 4
};
var statuses = function() {
// NOTE: function as #region
return {
100: 'Continue',
101: 'Switching Protocols',
102: 'Processing',
200: 'OK',
201: 'Created',
202: 'Accepted',
203: 'Non-Authoritative Information',
204: 'No Content',
205: 'Reset Content',
206: 'Partial Content',
207: 'Multi-Status',
300: 'Multiple Choices',
301: 'Moved Permanently',
302: 'Moved Temporarily',
303: 'See Other',
304: 'Not Modified',
305: 'Use Proxy',
307: 'Temporary Redirect',
400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
405: 'Method Not Allowed',
406: 'Not Acceptable',
407: 'Proxy Authentication Required',
408: 'Request Time-out',
409: 'Conflict',
410: 'Gone',
411: 'Length Required',
412: 'Precondition Failed',
413: 'Request Entity Too Large',
414: 'Request-URI Too Large',
415: 'Unsupported Media Type',
416: 'Requested range not satisfiable',
417: 'Expectation Failed',
422: 'Unprocessable Entity',
423: 'Locked',
424: 'Failed Dependency',
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Gateway Time-out',
505: 'HTTP Version not supported',
507: 'Insufficient Storage'
};
}();
function openXhr(xhr) {
xhr.readyState = states.OPENED;
xhr.onreadystatechange();
}
function sendXhr(xhr) {
xhr.sent = true;
xhr.onreadystatechange();
xhr.onsend();
DummyServer.handle(xhr);
}
function abortXhr(xhr) {
xhr.error = true;
xhr.onreadystatechange();
xhr.onabort();
xhr.readyState = states.UNSENT;
}
function recieveXhr(xhr) {
var key,
status = xhr.response.status || 200,
headers = xhr.response.headers || {},
responseText = xhr.response.responseText || "";
for(key in headers) if(headers.hasOwnProperty(key))
xhr.responseHeaders[key] = headers[key];
xhr.status = status;
xhr.statusText = status + " " + statuses[status];
xhr.readyState = states.HEADERS_RECEIVED;
xhr.onprogress();
xhr.onreadystatechange();
xhr.responseText = responseText;
xhr.readyState = states.LOADING;
xhr.onprogress();
xhr.onreadystatechange();
xhr.readyState = states.DONE;
xhr.onreadystatechange();
xhr.onprogress();
xhr.onload();
}
var DummyServer = function() {
var queue = null;
var originalXhr = null;
function on() {
return queue !== null;
}
function setup() {
if(on())
throw new Error("Can't be setuped twice.");
queue = [];
originalXhr = XMLHttpRequest;
global.XMLHttpRequest = DummyXMLHttpRequest;
}
function teardown() {
if(!on())
throw new Error("Teardown before setup.");
global.XMLHttpRequest = originalXhr;
originalXhr = null;
queue = null;
}
function handle(xhr) {
var cb;
if(queue.length) {
cb = queue.shift();
if(cb(xhr) === false) {
abortXhr(xhr);
return;
}
}
recieveXhr(xhr);
}
function enqueue(cb) {
queue.push(cb);
}
return {
setup: setup,
handle: handle,
enqueue: enqueue,
teardown: teardown
};
}();
function DummyXMLHttpRequest() {
this.onsend = function() { };
this.onload = function() { };
this.onabort = function() { };
this.onprogress = function() { };
this.onreadystatechange = function() { };
this.withCredentials = false;
}
DummyXMLHttpRequest.prototype = {
constructor: DummyXMLHttpRequest,
open: function(method, URL, async, userName, password) {
this.url = URL;
this.async = async || true;
this.method = method.toUpperCase();
this.userName = userName;
this.password = password;
this.readyState = -1;
this.error = false;
this.sent = false;
this.requestHeaders = {};
this.responseHeaders = {};
openXhr(this);
},
send: function(content) {
this.requestText = String(content);
sendXhr(this);
},
abort: function() {
this.responseText = "";
this.requestText = null;
abortXhr(this);
},
setRequestHeader: function(label, value) {
if(!this.requestHeaders[label])
this.requestHeaders[label] = value;
else this.requestHeaders[label] = this.requestHeaders[label] + ", " + value;
},
getAllResponseHeaders: function() {
var headers = [],
headerName;
if(this.error)
return null;
if(this.readyState === states.UNSENT || this.readyState === states.OPENED)
throw new Error("INVALID_STATE_ERR");
for(headerName in this.responseHeaders)
headers.push(headerName.charAt(0).toUpperCase() + headerName.substr(1) + ":" + this.responseHeaders[headerName]);
return headers.join("\r\n");
},
getResponseHeader: function(headerName) {
if(this.error)
return null;
if(this.readyState === states.UNSENT || this.readyState === states.OPENED)
throw new Error("INVALID_STATE_ERR");
return this.responseHeaders[headerName.toLowerCase()];
},
overrideMimeType: function(mimeType) { },
setResponse: function(response) {
this.response = response;
}
};
return {
setup: function() {
DummyServer.setup();
},
enqueue: function(cb) {
DummyServer.enqueue(cb);
},
teardown: function() {
DummyServer.teardown();
}
};
}();
global.DummyServer = DummyServer;
})(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment