Move specs for the fake XHR to be more unity and less integrationy

This commit is contained in:
slackersoft 2014-08-08 08:06:42 -07:00
parent 06af24b239
commit 85059718ad
8 changed files with 384 additions and 239 deletions

View File

@ -63,7 +63,7 @@ getJasmineRequireObj().AjaxFakeRequest = function() {
return false;
}
function fakeRequest(requestTracker, stubTracker, paramParser) {
function fakeRequest(global, requestTracker, stubTracker, paramParser) {
function FakeXMLHttpRequest() {
requestTracker.track(this);
this.requestHeaders = {};
@ -100,7 +100,7 @@ getJasmineRequireObj().AjaxFakeRequest = function() {
}
var iePropertiesThatCannotBeCopied = ['responseBody', 'responseText', 'responseXML', 'status', 'statusText', 'responseTimeout'];
extend(FakeXMLHttpRequest.prototype, new window.XMLHttpRequest(), iePropertiesThatCannotBeCopied);
extend(FakeXMLHttpRequest.prototype, new global.XMLHttpRequest(), iePropertiesThatCannotBeCopied);
extend(FakeXMLHttpRequest.prototype, {
open: function() {
this.method = arguments[0];
@ -226,7 +226,7 @@ getJasmineRequireObj().MockAjax = function($ajax) {
stubTracker = new $ajax.StubTracker(),
paramParser = new $ajax.ParamParser(),
realAjaxFunction = global.XMLHttpRequest,
mockAjaxFunction = $ajax.fakeRequest(requestTracker, stubTracker, paramParser);
mockAjaxFunction = $ajax.fakeRequest(global, requestTracker, stubTracker, paramParser);
this.install = function() {
global.XMLHttpRequest = mockAjaxFunction;
@ -395,7 +395,6 @@ getJasmineRequireObj().AjaxRequestTracker = function() {
};
this.filter = function(url_to_match) {
if (requests.length === 0) { return []; }
var matching_requests = [];
for (var i = 0; i < requests.length; i++) {

View File

@ -1,229 +0,0 @@
describe("FakeXMLHttpRequest", function() {
var xhr;
var xhr2;
var mockAjax;
beforeEach(function() {
var realXMLHttpRequest = {someOtherProperty: 'someValue'},
realXMLHttpRequestCtor = spyOn(window, 'XMLHttpRequest').and.returnValue(realXMLHttpRequest),
fakeGlobal = {XMLHttpRequest: realXMLHttpRequestCtor};
mockAjax = new window.MockAjax(fakeGlobal);
mockAjax.install();
xhr = new fakeGlobal.XMLHttpRequest();
xhr2 = new fakeGlobal.XMLHttpRequest();
});
function objectKeys(obj) {
var keys = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
keys.push(key);
}
}
return keys;
}
it("should have an initial readyState of 0 (uninitialized)", function() {
expect(xhr.readyState).toEqual(0);
});
describe("when setting request headers", function() {
beforeEach(function() {
xhr.setRequestHeader('X-Header-1', 'one');
});
it("should make the request headers available", function() {
expect(objectKeys(xhr.requestHeaders).length).toEqual(1);
expect(xhr.requestHeaders['X-Header-1']).toEqual('one');
});
it('should combine request headers with the same header name', function() {
xhr.setRequestHeader('X-Header-1', 'two');
expect(objectKeys(xhr.requestHeaders).length).toEqual(1);
expect(xhr.requestHeaders['X-Header-1']).toEqual('one, two');
});
describe("when setting headers on another xhr object", function() {
beforeEach(function() {
xhr2.setRequestHeader('X-Header-2', 'two');
});
it("should make the only its request headers available", function() {
expect(objectKeys(xhr2.requestHeaders).length).toEqual(1);
expect(xhr2.requestHeaders['X-Header-2']).toEqual('two');
});
it("should not modify any other xhr objects", function() {
expect(objectKeys(xhr.requestHeaders).length).toEqual(1);
expect(xhr.requestHeaders['X-Header-1']).toEqual('one');
});
});
});
describe("when opened", function() {
beforeEach(function() {
spyOn(xhr, 'onreadystatechange');
xhr.open("GET", "http://example.com");
});
it("should have a readyState of 1 (open)", function() {
expect(xhr.readyState).toEqual(1);
expect(xhr.onreadystatechange).toHaveBeenCalled();
});
describe("when sent", function() {
it("should have a readyState of 2 (sent)", function() {
xhr.onreadystatechange.calls.reset();
xhr.send(null);
expect(xhr.readyState).toEqual(2);
expect(xhr.onreadystatechange).toHaveBeenCalled();
});
});
describe("when a response comes in", function() {
it("should have a readyState of 4 (loaded)", function() {
xhr.onreadystatechange.calls.reset();
xhr.response({status: 200});
expect(xhr.readyState).toEqual(4);
expect(xhr.onreadystatechange).toHaveBeenCalled();
});
describe("when a second response comes in", function() {
it("should throw an error", function() {
xhr.onreadystatechange.calls.reset();
xhr.response({status: 200});
expect(function() { xhr.response({status: 200}); }).toThrowError('FakeXMLHttpRequest already completed');
});
});
describe("when a second response comes in as a timout", function() {
it("should throw an error", function() {
xhr.onreadystatechange.calls.reset();
xhr.response({status: 200});
expect(function() { xhr.responseTimeout(); }).toThrowError('FakeXMLHttpRequest already completed');
});
});
});
describe("when aborted", function() {
it("should have a readyState of 0 (uninitialized)", function() {
xhr.onreadystatechange.calls.reset();
xhr.abort();
expect(xhr.readyState).toEqual(0);
expect(xhr.onreadystatechange).toHaveBeenCalled();
expect(xhr.status).toEqual(0);
expect(xhr.statusText).toEqual("abort");
});
});
});
describe("when opened with a username/password", function() {
beforeEach(function() {
xhr.open("GET", "http://example.com", true, "username", "password");
});
it("should store the username", function() {
expect(xhr.username).toEqual("username");
});
it("should store the password", function() {
expect(xhr.password).toEqual("password");
});
});
describe("data", function() {
beforeEach(function() {
xhr.open("POST", "http://example.com?this=that");
});
it("should be an empty object if no params were sent", function() {
xhr.send();
expect(xhr.data()).toEqual({});
});
it("should return request params as a hash of arrays", function() {
xhr.send('3+stooges=shemp&3+stooges=larry%20%26%20moe%20%26%20curly&some%3Dthing=else+entirely');
var data = xhr.data();
expect(data['3 stooges'].length).toEqual(2);
expect(data['3 stooges'][0]).toEqual('shemp');
expect(data['3 stooges'][1]).toEqual('larry & moe & curly');
expect(data['some=thing']).toEqual(['else entirely']);
});
it("should parse json when the content type is appropriate", function() {
var data = {
foo: 'bar',
baz: ['q', 'u', 'u', 'x'],
nested: {
object: {
containing: 'stuff'
}
}
};
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(data));
expect(xhr.data()).toEqual(data);
});
it("should use a custom parser and clear custom parsers when uninstalled", function() {
var custom = {
test: jasmine.createSpy('test').and.returnValue(true),
parse: jasmine.createSpy('parse').and.returnValue('parsedFormat')
};
mockAjax.addCustomParamParser(custom);
xhr.send('custom_format');
expect(xhr.data()).toBe('parsedFormat');
mockAjax.uninstall();
mockAjax.install();
xhr.send('custom_format');
expect(xhr.data()).toEqual({custom_format: [ 'undefined' ]});
});
});
describe("contentType", function() {
it("gets the Content-Type", function() {
xhr.setRequestHeader('Content-Type', 'something');
expect(xhr.contentType()).toEqual('something');
});
it("gets the content-type case-insensitively", function() {
xhr.setRequestHeader('content-Type', 'some other thing');
expect(xhr.contentType()).toEqual('some other thing');
});
});
describe("getResponseHeader", function() {
it("gets a response header case-insensitively", function() {
xhr.send();
xhr.response({
status: 200,
responseHeaders: {
'X-Foo': 'Bar'
}
});
expect(xhr.getResponseHeader('x-foo')).toBe('Bar');
});
});
describe("overriding mime type", function() {
it('has null override by default', function() {
expect(xhr.overriddenMimeType).toBeNull();
});
it('records the override', function() {
xhr.overrideMimeType('text/plain; charset: utf-8');
expect(xhr.overriddenMimeType).toBe('text/plain; charset: utf-8');
});
});
describe("when a fake XMLHttpRequest is created", function() {
it("inherits the properties of the real XMLHttpRequest object", function() {
expect(xhr.someOtherProperty).toBe('someValue');
});
});
});

375
spec/fakeRequestSpec.js Normal file
View File

@ -0,0 +1,375 @@
describe('FakeRequest', function() {
beforeEach(function() {
this.requestTracker = { track: jasmine.createSpy('trackRequest') };
this.stubTracker = { findStub: function() {} };
var parserInstance = this.parserInstance = jasmine.createSpy('parse');
this.paramParser = { findParser: function() { return { parse: parserInstance }; } };
this.fakeGlobal = {
XMLHttpRequest: function() {
this.extraAttribute = 'my cool attribute';
}
};
this.FakeRequest = getJasmineRequireObj().AjaxFakeRequest()(this.fakeGlobal, this.requestTracker, this.stubTracker, this.paramParser);
});
it('extends from the global XMLHttpRequest', function() {
var request = new this.FakeRequest();
expect(request.extraAttribute).toEqual('my cool attribute');
});
it('skips XMLHttpRequest attributes that IE does not want copied', function() {
// use real window here so it will correctly go red on IE if it breaks
var FakeRequest = getJasmineRequireObj().AjaxFakeRequest()(window, this.requestTracker, this.stubTracker, this.paramParser);
var request = new FakeRequest();
expect(request.responseBody).toBeUndefined();
expect(request.responseXML).toBeUndefined();
expect(request.statusText).toBeUndefined();
});
it('tracks the request', function() {
var request = new this.FakeRequest();
expect(this.requestTracker.track).toHaveBeenCalledWith(request);
});
it('has default request headers and override mime type', function() {
var request = new this.FakeRequest();
expect(request.requestHeaders).toEqual({});
expect(request.overriddenMimeType).toBeNull();
});
it('saves request information when opened', function() {
var request = new this.FakeRequest();
request.open('METHOD', 'URL', 'ignore_async', 'USERNAME', 'PASSWORD');
expect(request.method).toEqual('METHOD');
expect(request.url).toEqual('URL');
expect(request.username).toEqual('USERNAME');
expect(request.password).toEqual('PASSWORD');
});
it('saves an override mime type', function() {
var request = new this.FakeRequest();
request.overrideMimeType('application/text; charset: utf-8');
expect(request.overriddenMimeType).toBe('application/text; charset: utf-8');
});
it('saves request headers', function() {
var request = new this.FakeRequest();
request.setRequestHeader('X-Header-1', 'value1');
request.setRequestHeader('X-Header-2', 'value2');
expect(request.requestHeaders).toEqual({
'X-Header-1': 'value1',
'X-Header-2': 'value2'
});
});
it('combines request headers with the same header name', function() {
var request = new this.FakeRequest();
request.setRequestHeader('X-Header', 'value1');
request.setRequestHeader('X-Header', 'value2');
expect(request.requestHeaders['X-Header']).toEqual('value1, value2');
});
it('finds the content-type request header', function() {
var request = new this.FakeRequest();
request.setRequestHeader('ContEnt-tYPe', 'application/text+xml');
expect(request.contentType()).toEqual('application/text+xml');
});
describe('managing readyState', function() {
beforeEach(function() {
this.request = new this.FakeRequest();
this.request.onreadystatechange = jasmine.createSpy('onreadystatechange');
});
it('has an initial ready state of 0 (uninitialized)', function() {
expect(this.request.readyState).toBe(0);
expect(this.request.onreadystatechange).not.toHaveBeenCalled();
});
it('has a ready state of 1 (open) when opened', function() {
this.request.open();
expect(this.request.readyState).toBe(1);
expect(this.request.onreadystatechange).toHaveBeenCalled();
});
it('has a ready state of 0 (uninitialized) when aborted', function() {
this.request.open();
this.request.onreadystatechange.calls.reset();
this.request.abort();
expect(this.request.readyState).toBe(0);
expect(this.request.onreadystatechange).toHaveBeenCalled();
});
it('has a ready state of 2 (sent) when sent', function() {
this.request.open();
this.request.onreadystatechange.calls.reset();
this.request.send();
expect(this.request.readyState).toBe(2);
expect(this.request.onreadystatechange).toHaveBeenCalled();
});
it('has a ready state of 4 (loaded) when timed out', function() {
this.request.open();
this.request.send();
this.request.onreadystatechange.calls.reset();
jasmine.clock().install();
this.request.responseTimeout();
jasmine.clock().uninstall();
expect(this.request.readyState).toBe(4);
expect(this.request.onreadystatechange).toHaveBeenCalledWith('timeout');
});
it('has a ready state of 4 (loaded) when responding', function() {
this.request.open();
this.request.send();
this.request.onreadystatechange.calls.reset();
this.request.onload = jasmine.createSpy('onload');
this.request.response({});
expect(this.request.readyState).toBe(4);
expect(this.request.onreadystatechange).toHaveBeenCalled();
expect(this.request.onload).toHaveBeenCalled();
});
it('throws an error when timing out a request that has completed', function() {
this.request.open();
this.request.send();
this.request.response({});
var request = this.request;
expect(function() {
request.responseTimeout();
}).toThrowError('FakeXMLHttpRequest already completed');
});
it('throws an error when responding to a request that has completed', function() {
this.request.open();
this.request.send();
this.request.response({});
var request = this.request;
expect(function() {
request.response({});
}).toThrowError('FakeXMLHttpRequest already completed');
});
});
it('ticks the jasmine clock on timeout', function() {
var clock = { tick: jasmine.createSpy('tick') };
spyOn(jasmine, 'clock').and.returnValue(clock);
var request = new this.FakeRequest();
request.open();
request.send();
request.responseTimeout();
expect(clock.tick).toHaveBeenCalledWith(30000);
});
it('has an initial status of null', function() {
var request = new this.FakeRequest();
expect(request.status).toBeNull();
});
it('has an aborted status', function() {
var request = new this.FakeRequest();
request.abort();
expect(request.status).toBe(0);
expect(request.statusText).toBe('abort');
});
it('has a status from the response', function() {
var request = new this.FakeRequest();
request.open();
request.send();
request.response({ status: 200 });
expect(request.status).toBe(200);
expect(request.statusText).toBe('');
});
it('has a statusText from the response', function() {
var request = new this.FakeRequest();
request.open();
request.send();
request.response({ status: 200, statusText: 'OK' });
expect(request.status).toBe(200);
expect(request.statusText).toBe('OK');
});
it('saves off any data sent to the server', function() {
var request = new this.FakeRequest();
request.open();
request.send('foo=bar&baz=quux');
expect(request.params).toBe('foo=bar&baz=quux');
});
it('parses data sent to the server', function() {
var request = new this.FakeRequest();
request.open();
request.send('foo=bar&baz=quux');
this.parserInstance.and.returnValue('parsed');
expect(request.data()).toBe('parsed');
});
it('skips parsing if no data was sent', function() {
var request = new this.FakeRequest();
request.open();
request.send();
expect(request.data()).toEqual({});
expect(this.parserInstance).not.toHaveBeenCalled();
});
it('saves responseText', function() {
var request = new this.FakeRequest();
request.open();
request.send();
request.response({ status: 200, responseText: 'foobar' });
expect(request.responseText).toBe('foobar');
});
it('defaults responseText if none is given', function() {
var request = new this.FakeRequest();
request.open();
request.send();
request.response({ status: 200 });
expect(request.responseText).toBe('');
});
it('retrieves individual response headers', function() {
var request = new this.FakeRequest();
request.open();
request.send();
request.response({
status: 200,
responseHeaders: {
'X-Header': 'foo'
}
});
expect(request.getResponseHeader('X-Header')).toBe('foo');
});
it('retrieves individual response headers case-insensitively', function() {
var request = new this.FakeRequest();
request.open();
request.send();
request.response({
status: 200,
responseHeaders: {
'X-Header': 'foo'
}
});
expect(request.getResponseHeader('x-header')).toBe('foo');
});
it('retrieves a combined response header', function() {
var request = new this.FakeRequest();
request.open();
request.send();
request.response({
status: 200,
responseHeaders: [
{ name: 'X-Header', value: 'foo' },
{ name: 'X-Header', value: 'bar' }
]
});
expect(request.getResponseHeader('x-header')).toBe('foo, bar');
});
it("doesn't pollute the response headers of other XHRs", function() {
var request1 = new this.FakeRequest();
request1.open();
request1.send();
var request2 = new this.FakeRequest();
request2.open();
request2.send();
request1.response({ status: 200, responseHeaders: { 'X-Foo': 'bar' } });
request2.response({ status: 200, responseHeaders: { 'X-Baz': 'quux' } });
expect(request1.getAllResponseHeaders()).toBe('X-Foo: bar');
expect(request2.getAllResponseHeaders()).toBe('X-Baz: quux');
});
it('retrieves all response headers', function() {
var request = new this.FakeRequest();
request.open();
request.send();
request.response({
status: 200,
responseHeaders: [
{ name: 'X-Header-1', value: 'foo' },
{ name: 'X-Header-2', value: 'bar' },
{ name: 'X-Header-1', value: 'baz' }
]
});
expect(request.getAllResponseHeaders()).toBe("X-Header-1: foo\r\nX-Header-2: bar\r\nX-Header-1: baz");
});
it('sets the content-type header to the specified contentType when no other headers are supplied', function() {
var request = new this.FakeRequest();
request.open();
request.send();
request.response({ status: 200, contentType: 'text/plain' });
expect(request.getResponseHeader('content-type')).toBe('text/plain');
expect(request.getAllResponseHeaders()).toBe('Content-Type: text/plain');
});
it('sets a default content-type header if no contentType and headers are supplied', function() {
var request = new this.FakeRequest();
request.open();
request.send();
request.response({ status: 200 });
expect(request.getResponseHeader('content-type')).toBe('application/json');
expect(request.getAllResponseHeaders()).toBe('Content-Type: application/json');
});
});

View File

@ -7,7 +7,7 @@ describe('ParamParser', function() {
it('has a default parser', function() {
var parser = this.parser.findParser({ contentType: function() {} }),
parsed = parser.parse('3+stooges=shemp&3+stooges=larry%20%26%20moe%20%26%20curly&some%3Dthing=else+entirely')
parsed = parser.parse('3+stooges=shemp&3+stooges=larry%20%26%20moe%20%26%20curly&some%3Dthing=else+entirely');
expect(parsed).toEqual({
'3 stooges': ['shemp', 'larry & moe & curly'],

View File

@ -9,7 +9,7 @@ spec_dir:
- spec
spec_files:
- '*[sS]pec.js'
- '**/*[sS]pec.js'
helpers:
- helpers/spec-helper.js

View File

@ -7,7 +7,7 @@ spec_dir:
- spec
spec_files:
- '*-spec.js'
- '**/*-[Ss]pec.js'
helpers:
- helpers/spec-helper.js

View File

@ -18,7 +18,7 @@ getJasmineRequireObj().AjaxFakeRequest = function() {
return false;
}
function fakeRequest(requestTracker, stubTracker, paramParser) {
function fakeRequest(global, requestTracker, stubTracker, paramParser) {
function FakeXMLHttpRequest() {
requestTracker.track(this);
this.requestHeaders = {};
@ -55,7 +55,7 @@ getJasmineRequireObj().AjaxFakeRequest = function() {
}
var iePropertiesThatCannotBeCopied = ['responseBody', 'responseText', 'responseXML', 'status', 'statusText', 'responseTimeout'];
extend(FakeXMLHttpRequest.prototype, new window.XMLHttpRequest(), iePropertiesThatCannotBeCopied);
extend(FakeXMLHttpRequest.prototype, new global.XMLHttpRequest(), iePropertiesThatCannotBeCopied);
extend(FakeXMLHttpRequest.prototype, {
open: function() {
this.method = arguments[0];

View File

@ -4,7 +4,7 @@ getJasmineRequireObj().MockAjax = function($ajax) {
stubTracker = new $ajax.StubTracker(),
paramParser = new $ajax.ParamParser(),
realAjaxFunction = global.XMLHttpRequest,
mockAjaxFunction = $ajax.fakeRequest(requestTracker, stubTracker, paramParser);
mockAjaxFunction = $ajax.fakeRequest(global, requestTracker, stubTracker, paramParser);
this.install = function() {
global.XMLHttpRequest = mockAjaxFunction;