From e1210e53158790023ab52a15207762f2e7b2e917 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 May 2015 19:26:58 +0200 Subject: [PATCH] jasmine ajax defs --- jasmine-ajax/jasmine-ajax-tests.ts | 1818 ++++++++++++++++++ jasmine-ajax/jasmine-ajax-tests.ts.tscparams | 1 + jasmine-ajax/jasmine-ajax.d.ts | 77 + 3 files changed, 1896 insertions(+) create mode 100644 jasmine-ajax/jasmine-ajax-tests.ts create mode 100644 jasmine-ajax/jasmine-ajax-tests.ts.tscparams create mode 100644 jasmine-ajax/jasmine-ajax.d.ts diff --git a/jasmine-ajax/jasmine-ajax-tests.ts b/jasmine-ajax/jasmine-ajax-tests.ts new file mode 100644 index 000000000..b5134d2cf --- /dev/null +++ b/jasmine-ajax/jasmine-ajax-tests.ts @@ -0,0 +1,1818 @@ +/** + * Tests adapted from: https://github.com/jasmine/jasmine-ajax + * By Louis Grignon + */ + +/// +/// + +describe('StubTracker', function() { + beforeEach(function() { + var Constructor = getJasmineRequireObj().AjaxStubTracker(); + this.tracker = new Constructor(); + }); + + it('finds nothing if no stubs are added', function() { + expect(this.tracker.findStub()).toBeUndefined(); + }); + + it('finds an added stub', function() { + var stub = { matches: function() { return true; } }; + this.tracker.addStub(stub); + + expect(this.tracker.findStub()).toBe(stub); + }); + + it('skips an added stub that does not match', function() { + var stub = { matches: function() { return false; } }; + this.tracker.addStub(stub); + + expect(this.tracker.findStub()).toBeUndefined(); + }); + + it('passes url, data, and method to the stub', function() { + var stub = { matches: jasmine.createSpy('matches') }; + this.tracker.addStub(stub); + + this.tracker.findStub('url', 'data', 'method'); + + expect(stub.matches).toHaveBeenCalledWith('url', 'data', 'method'); + }); + + it('can clear out all stubs', function() { + var stub = { matches: jasmine.createSpy('matches') }; + this.tracker.addStub(stub); + + this.tracker.findStub(); + + expect(stub.matches).toHaveBeenCalled(); + + this.tracker.reset(); + stub.matches.calls.reset(); + + this.tracker.findStub(); + + expect(stub.matches).not.toHaveBeenCalled(); + }); + + it('uses the most recently added stub that matches', function() { + var stub1 = { matches: function() { return true; } }; + var stub2 = { matches: function() { return true; } }; + var stub3 = { matches: function() { return false; } }; + + this.tracker.addStub(stub1); + this.tracker.addStub(stub2); + this.tracker.addStub(stub3); + + expect(this.tracker.findStub()).toBe(stub2); + }); +}); + +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 }; } }; + var eventBus = this.fakeEventBus = { + addEventListener: jasmine.createSpy('addEventListener'), + trigger: jasmine.createSpy('trigger'), + removeEventListener: jasmine.createSpy('removeEventListener') + }; + this.eventBusFactory = function() { + return eventBus; + }; + this.fakeGlobal = { + XMLHttpRequest: function() { + this.extraAttribute = 'my cool attribute'; + }, + DOMParser: window['DOMParser'], + ActiveXObject: window['ActiveXObject'] + }; + this.FakeRequest = getJasmineRequireObj().AjaxFakeRequest(this.eventBusFactory)(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(this.eventBusFactory)(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(); + }); + + it('has an initial ready state of 0 (uninitialized)', function() { + expect(this.request.readyState).toBe(0); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalled(); + }); + + it('has a ready state of 1 (open) when opened', function() { + this.request.open(); + + expect(this.request.readyState).toBe(1); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('readystatechange'); + }); + + it('has a ready state of 0 (uninitialized) when aborted', function() { + this.request.open(); + this.fakeEventBus.trigger.calls.reset(); + + this.request.abort(); + + expect(this.request.readyState).toBe(0); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('readystatechange'); + }); + + it('has a ready state of 1 (sent) when sent', function() { + this.request.open(); + this.fakeEventBus.trigger.calls.reset(); + + this.request.send(); + + expect(this.request.readyState).toBe(1); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('loadstart'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('readystatechange'); + }); + + it('has a ready state of 4 (loaded) when timed out', function() { + this.request.open(); + this.request.send(); + this.fakeEventBus.trigger.calls.reset(); + + jasmine.clock().install(); + this.request.responseTimeout(); + jasmine.clock().uninstall(); + + expect(this.request.readyState).toBe(4); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('readystatechange', 'timeout'); + }); + + it('has a ready state of 4 (loaded) when network erroring', function() { + this.request.open(); + this.request.send(); + this.fakeEventBus.trigger.calls.reset(); + + this.request.responseError(); + + expect(this.request.readyState).toBe(4); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('readystatechange'); + }); + + it('has a ready state of 4 (loaded) when responding', function() { + this.request.open(); + this.request.send(); + this.fakeEventBus.trigger.calls.reset(); + + this.request.respondWith({}); + + expect(this.request.readyState).toBe(4); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('readystatechange'); + }); + + it('has a ready state of 2, then 4 (loaded) when responding', function() { + this.request.open(); + this.request.send(); + this.fakeEventBus.trigger.calls.reset(); + + var request = this.request; + var events = []; + var headers = [ + { name: 'X-Header', value: 'foo' } + ]; + + this.fakeEventBus.trigger.and.callFake(function(event) { + if (event === 'readystatechange') { + events.push({ + readyState: request.readyState, + status: request.status, + statusText: request.statusText, + responseHeaders: request.responseHeaders + }); + } + }); + + this.request.respondWith({ + status: 200, + statusText: 'OK', + responseHeaders: headers + }); + + expect(this.request.readyState).toBe(4); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('readystatechange'); + expect(events.length).toBe(2); + expect(events).toEqual([ + { readyState: 2, status: 200, statusText: 'OK', responseHeaders: headers }, + { readyState: 4, status: 200, statusText: 'OK', responseHeaders: headers } + ]); + }); + + it('throws an error when timing out a request that has completed', function() { + this.request.open(); + this.request.send(); + this.request.respondWith({}); + 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.respondWith({}); + var request = this.request; + + expect(function() { + request.respondWith({}); + }).toThrowError('FakeXMLHttpRequest already completed'); + }); + + it('throws an error when erroring a request that has completed', function() { + this.request.open(); + this.request.send(); + this.request.respondWith({}); + var request = this.request; + + expect(function() { + request.responseError({}); + }).toThrowError('FakeXMLHttpRequest already completed'); + }); + }); + + it('registers on-style callback with the event bus', function() { + this.request = new this.FakeRequest(); + + expect(this.fakeEventBus.addEventListener).toHaveBeenCalledWith('readystatechange', jasmine.any(Function)); + expect(this.fakeEventBus.addEventListener).toHaveBeenCalledWith('loadstart', jasmine.any(Function)); + expect(this.fakeEventBus.addEventListener).toHaveBeenCalledWith('progress', jasmine.any(Function)); + expect(this.fakeEventBus.addEventListener).toHaveBeenCalledWith('abort', jasmine.any(Function)); + expect(this.fakeEventBus.addEventListener).toHaveBeenCalledWith('error', jasmine.any(Function)); + expect(this.fakeEventBus.addEventListener).toHaveBeenCalledWith('load', jasmine.any(Function)); + expect(this.fakeEventBus.addEventListener).toHaveBeenCalledWith('timeout', jasmine.any(Function)); + expect(this.fakeEventBus.addEventListener).toHaveBeenCalledWith('loadend', jasmine.any(Function)); + + this.request.onreadystatechange = jasmine.createSpy('readystatechange'); + this.request.onloadstart = jasmine.createSpy('loadstart'); + this.request.onprogress = jasmine.createSpy('progress'); + this.request.onabort = jasmine.createSpy('abort'); + this.request.onerror = jasmine.createSpy('error'); + this.request.onload = jasmine.createSpy('load'); + this.request.ontimeout = jasmine.createSpy('timeout'); + this.request.onloadend = jasmine.createSpy('loadend'); + + var args = this.fakeEventBus.addEventListener.calls.allArgs(); + for (var i = 0; i < args.length; i++) { + var eventName = args[i][0], + busCallback = args[i][1]; + + busCallback(); + expect(this.request['on' + eventName]).toHaveBeenCalled(); + } + }); + + it('delegates addEventListener to the eventBus', function() { + this.request = new this.FakeRequest(); + + this.request.addEventListener('foo', 'bar'); + + expect(this.fakeEventBus.addEventListener).toHaveBeenCalledWith('foo', 'bar'); + }); + + it('delegates removeEventListener to the eventBus', function() { + this.request = new this.FakeRequest(); + + this.request.removeEventListener('foo', 'bar'); + + expect(this.fakeEventBus.removeEventListener).toHaveBeenCalledWith('foo', 'bar'); + }); + + describe('triggering progress events', function() { + beforeEach(function() { + this.request = new this.FakeRequest(); + }); + + it('should not trigger any events to start', function() { + this.request.open(); + + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('readystatechange'); + }); + + it('should trigger loadstart when sent', function() { + this.request.open(); + + this.fakeEventBus.trigger.calls.reset(); + + this.request.send(); + + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('loadstart'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('readystatechange'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('progress'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('abort'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('error'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('load'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('timeout'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('loadend'); + }); + + it('should trigger abort, progress, loadend when aborted', function() { + this.request.open(); + this.request.send(); + + this.fakeEventBus.trigger.calls.reset(); + + this.request.abort(); + + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('readystatechange'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('loadstart'); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('progress'); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('abort'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('error'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('load'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('timeout'); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('loadend'); + }); + + it('should trigger error, progress, loadend when network error', function() { + this.request.open(); + this.request.send(); + + this.fakeEventBus.trigger.calls.reset(); + + this.request.responseError(); + + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('loadstart'); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('readystatechange'); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('progress'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('abort'); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('error'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('load'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('timeout'); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('loadend'); + }); + + it('should trigger timeout, progress, loadend when timing out', function() { + this.request.open(); + this.request.send(); + + this.fakeEventBus.trigger.calls.reset(); + + jasmine.clock().install(); + this.request.responseTimeout(); + jasmine.clock().uninstall(); + + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('loadstart'); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('readystatechange', 'timeout'); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('progress'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('abort'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('error'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('load'); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('timeout'); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('loadend'); + }); + + it('should trigger load, progress, loadend when responding', function() { + this.request.open(); + this.request.send(); + + this.fakeEventBus.trigger.calls.reset(); + + this.request.respondWith({ status: 200 }); + + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('loadstart'); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('readystatechange'); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('progress'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('abort'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('error'); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('load'); + expect(this.fakeEventBus.trigger).not.toHaveBeenCalledWith('timeout'); + expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('loadend'); + }); + }); + + 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.respondWith({ 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.respondWith({ 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.respondWith({ 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.respondWith({ status: 200 }); + + expect(request.responseText).toBe(''); + }); + + it('retrieves individual response headers', function() { + var request = new this.FakeRequest(); + request.open(); + request.send(); + + request.respondWith({ + 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.respondWith({ + 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.respondWith({ + 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.respondWith({ status: 200, responseHeaders: { 'X-Foo': 'bar' } }); + request2.respondWith({ status: 200, responseHeaders: { 'X-Baz': 'quux' } }); + + expect(request1.getAllResponseHeaders()).toBe("X-Foo: bar\r\n"); + expect(request2.getAllResponseHeaders()).toBe("X-Baz: quux\r\n"); + }); + + it('retrieves all response headers', function() { + var request = new this.FakeRequest(); + request.open(); + request.send(); + + request.respondWith({ + 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\r\n"); + }); + + 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.respondWith({ status: 200, contentType: 'text/plain' }); + + expect(request.getResponseHeader('content-type')).toBe('text/plain'); + expect(request.getAllResponseHeaders()).toBe("Content-Type: text/plain\r\n"); + }); + + 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.respondWith({ status: 200 }); + + expect(request.getResponseHeader('content-type')).toBe('application/json'); + expect(request.getAllResponseHeaders()).toBe("Content-Type: application/json\r\n"); + }); + + it('has no responseXML by default', function() { + var request = new this.FakeRequest(); + request.open(); + request.send(); + + request.respondWith({ status: 200 }); + + expect(request.responseXML).toBeNull(); + }); + + it('parses a text/xml document into responseXML', function() { + var request = new this.FakeRequest(); + request.open(); + request.send(); + + request.respondWith({ status: 200, contentType: 'text/xml', responseText: '' }); + + if (typeof window['Document'] !== 'undefined') { + expect(request.responseXML instanceof window['Document']).toBe(true); + expect(request.response instanceof window['Document']).toBe(true); + } else { + // IE 8 + expect(request.responseXML instanceof window['ActiveXObject']).toBe(true); + expect(request.response instanceof window['ActiveXObject']).toBe(true); + } + }); + + it('parses an application/xml document into responseXML', function() { + var request = new this.FakeRequest(); + request.open(); + request.send(); + + request.respondWith({ status: 200, contentType: 'application/xml', responseText: '' }); + + if (typeof window['Document'] !== 'undefined') { + expect(request.responseXML instanceof window['Document']).toBe(true); + expect(request.response instanceof window['Document']).toBe(true); + } else { + // IE 8 + expect(request.responseXML instanceof window['ActiveXObject']).toBe(true); + expect(request.response instanceof window['ActiveXObject']).toBe(true); + } + }); + + it('parses a custom blah+xml document into responseXML', function() { + var request = new this.FakeRequest(); + request.open(); + request.send(); + + request.respondWith({ status: 200, contentType: 'application/text+xml', responseText: '' }); + + if (typeof window['Document'] !== 'undefined') { + expect(request.responseXML instanceof window['Document']).toBe(true); + expect(request.response instanceof window['Document']).toBe(true); + } else { + // IE 8 + expect(request.responseXML instanceof window['ActiveXObject']).toBe(true); + expect(request.response instanceof window['ActiveXObject']).toBe(true); + } + }); + + it('defaults the response attribute to the responseText', function() { + var request = new this.FakeRequest(); + request.open(); + request.send(); + + request.respondWith({ status: 200, responseText: 'foo' }); + + expect(request.response).toEqual('foo'); + }); + + it('has a text response when the responseType is blank', function() { + var request = new this.FakeRequest(); + request.open(); + request.send(); + + request.respondWith({ status: 200, responseText: 'foo', responseType: '' }); + + expect(request.response).toEqual('foo'); + }); + + it('has a text response when the responseType is text', function() { + var request = new this.FakeRequest(); + request.open(); + request.send(); + + request.respondWith({ status: 200, responseText: 'foo', responseType: 'text' }); + + expect(request.response).toEqual('foo'); + }); +}); + + +describe("Jasmine Mock Ajax (for toplevel)", function() { + var request, anotherRequest, response; + var success, error, complete; + var client, onreadystatechange; + var sharedContext: any = {}; + var fakeGlobal, mockAjax; + + beforeEach(function() { + var fakeXMLHttpRequest = jasmine.createSpy('realFakeXMLHttpRequest'); + fakeGlobal = { + XMLHttpRequest: fakeXMLHttpRequest, + DOMParser: window['DOMParser'], + ActiveXObject: window['ActiveXObject'] + }; + mockAjax = new MockAjax(fakeGlobal); + mockAjax.install(); + + success = jasmine.createSpy("onSuccess"); + error = jasmine.createSpy("onFailure"); + complete = jasmine.createSpy("onComplete"); + + onreadystatechange = function() { + if (this.readyState === (this.DONE || 4)) { // IE 8 doesn't support DONE + if (this.status === 200) { + success(this.responseText, this.textStatus, this); + } else { + error(this, this.textStatus, ''); + } + + complete(this, this.textStatus); + } + }; + }); + + describe("when making a request", function () { + beforeEach(function() { + client = new fakeGlobal.XMLHttpRequest(); + client.onreadystatechange = onreadystatechange; + client.open("GET", "example.com/someApi"); + client.send(); + request = mockAjax.requests.mostRecent(); + }); + + it("should store URL and transport", function() { + expect(request.url).toEqual("example.com/someApi"); + }); + + it("should queue the request", function() { + expect(mockAjax.requests.count()).toEqual(1); + }); + + it("should allow access to the queued request", function() { + expect(mockAjax.requests.first()).toEqual(request); + }); + + it("should allow access to the queued request via index", function() { + expect(mockAjax.requests.at(0)).toEqual(request); + }); + + describe("and then another request", function () { + beforeEach(function() { + client = new fakeGlobal.XMLHttpRequest(); + client.onreadystatechange = onreadystatechange; + client.open("GET", "example.com/someApi"); + client.send(); + + anotherRequest = mockAjax.requests.mostRecent(); + }); + + it("should queue the next request", function() { + expect(mockAjax.requests.count()).toEqual(2); + }); + + it("should allow access to the other queued request", function() { + expect(mockAjax.requests.first()).toEqual(request); + expect(mockAjax.requests.mostRecent()).toEqual(anotherRequest); + }); + }); + + describe("mockAjax.requests.mostRecent()", function () { + + describe("when there is one request queued", function () { + it("should return the request", function() { + expect(mockAjax.requests.mostRecent()).toEqual(request); + }); + }); + + describe("when there is more than one request", function () { + beforeEach(function() { + client = new fakeGlobal.XMLHttpRequest(); + client.onreadystatechange = onreadystatechange; + client.open("GET", "example.com/someApi"); + client.send(); + anotherRequest = mockAjax.requests.mostRecent(); + }); + + it("should return the most recent request", function() { + expect(mockAjax.requests.mostRecent()).toEqual(anotherRequest); + }); + }); + + describe("when there are no requests", function () { + beforeEach(function() { + mockAjax.requests.reset(); + }); + + it("should return null", function() { + expect(mockAjax.requests.mostRecent()).toBeUndefined(); + }); + }); + }); + + describe("clearAjaxRequests()", function () { + beforeEach(function() { + mockAjax.requests.reset(); + }); + + it("should remove all requests", function() { + expect(mockAjax.requests.count()).toEqual(0); + expect(mockAjax.requests.mostRecent()).toBeUndefined(); + }); + }); + }); + + describe("when simulating a response with request.response", function () { + describe("and the response is Success", function () { + beforeEach(function() { + client = new fakeGlobal.XMLHttpRequest(); + client.onreadystatechange = onreadystatechange; + client.open("GET", "example.com/someApi"); + client.setRequestHeader("Content-Type", "text/plain"); + client.send(); + + request = mockAjax.requests.mostRecent(); + response = {status: 200, statusText: "OK", contentType: "text/html", responseText: "OK!"}; + request.respondWith(response); + + sharedContext.responseCallback = success; + sharedContext.status = response.status; + sharedContext.statusText = response.statusText; + sharedContext.contentType = response.contentType; + sharedContext.responseText = response.responseText; + sharedContext.responseType = response.responseType; + }); + + it("should call the success handler", function() { + expect(success).toHaveBeenCalled(); + }); + + it("should not call the failure handler", function() { + expect(error).not.toHaveBeenCalled(); + }); + + it("should call the complete handler", function() { + expect(complete).toHaveBeenCalled(); + }); + + sharedAjaxResponseBehaviorForZepto_Success(sharedContext); + }); + + describe("and the response is Success, but with JSON", function () { + beforeEach(function() { + client = new fakeGlobal.XMLHttpRequest(); + client.onreadystatechange = onreadystatechange; + client.open("GET", "example.com/someApi"); + client.setRequestHeader("Content-Type", "application/json"); + client.send(); + + request = mockAjax.requests.mostRecent(); + var responseObject = {status: 200, statusText: "OK", contentType: "application/json", responseText: '{"foo":"bar"}', responseType: "json"}; + + request.respondWith(responseObject); + + sharedContext.responseCallback = success; + sharedContext.status = responseObject.status; + sharedContext.statusText = responseObject.statusText; + sharedContext.contentType = responseObject.contentType; + sharedContext.responseText = responseObject.responseText; + sharedContext.responseType = responseObject.responseType; + + response = success.calls.mostRecent().args[2]; + }); + + it("should call the success handler", function() { + expect(success).toHaveBeenCalled(); + }); + + it("should not call the failure handler", function() { + expect(error).not.toHaveBeenCalled(); + }); + + it("should call the complete handler", function() { + expect(complete).toHaveBeenCalled(); + }); + + it("should return a JavaScript object for XHR2 response", function() { + var responseText = sharedContext.responseText; + expect(success.calls.mostRecent().args[0]).toEqual(responseText); + + expect(response.responseText).toEqual(responseText); + expect(response.response).toEqual({foo: "bar"}); + }); + + sharedAjaxResponseBehaviorForZepto_Success(sharedContext); + }); + + describe("and the response is Success, and response is overriden", function () { + beforeEach(function() { + client = new fakeGlobal.XMLHttpRequest(); + client.onreadystatechange = onreadystatechange; + client.open("GET", "example.com/someApi"); + client.setRequestHeader("Content-Type", "application/json"); + client.send(); + + request = mockAjax.requests.mostRecent(); + var responseObject = {status: 200, statusText: "OK", contentType: "application/json", responseText: '{"foo":"bar"}', responseType: 'json'}; + + request.respondWith(responseObject); + + sharedContext.responseCallback = success; + sharedContext.status = responseObject.status; + sharedContext.statusText = responseObject.statusText; + sharedContext.contentType = responseObject.contentType; + sharedContext.responseText = responseObject.responseText; + sharedContext.responseType = responseObject.responseType; + + response = success.calls.mostRecent().args[2]; + }); + + it("should return the provided override for the XHR2 response", function() { + var responseText = sharedContext.responseText; + + expect(response.responseText).toEqual(responseText); + expect(response.response).toEqual({foo: "bar"}); + }); + + sharedAjaxResponseBehaviorForZepto_Success(sharedContext); + }); + + describe("response with unique header names using an object", function () { + beforeEach(function () { + client = new fakeGlobal.XMLHttpRequest(); + client.onreadystatechange = onreadystatechange; + client.open("GET", "example.com"); + client.send(); + + request = mockAjax.requests.mostRecent(); + var responseObject = {status: 200, statusText: "OK", responseText: '["foo"]', responseHeaders: { + 'X-Header1': 'header 1 value', + 'X-Header2': 'header 2 value', + 'X-Header3': 'header 3 value' + }}; + request.respondWith(responseObject); + response = success.calls.mostRecent().args[2]; + }); + + it("getResponseHeader should return the each value", function () { + expect(response.getResponseHeader('X-Header1')).toBe('header 1 value'); + expect(response.getResponseHeader('X-Header2')).toBe('header 2 value'); + expect(response.getResponseHeader('X-Header3')).toBe('header 3 value'); + }); + + it("getAllResponseHeaders should return all values", function () { + expect(response.getAllResponseHeaders()).toBe([ + "X-Header1: header 1 value", + "X-Header2: header 2 value", + "X-Header3: header 3 value" + ].join("\r\n") + "\r\n"); + }); + }); + + describe("response with multiple headers of the same name using an array of objects", function () { + beforeEach(function () { + client = new fakeGlobal.XMLHttpRequest(); + client.onreadystatechange = onreadystatechange; + client.open("GET", "example.com"); + client.send(); + + request = mockAjax.requests.mostRecent(); + var responseObject = {status: 200, statusText: "OK", responseText: '["foo"]', responseHeaders: [ + { name: 'X-Header', value: 'header value 1' }, + { name: 'X-Header', value: 'header value 2' } + ]}; + request.respondWith(responseObject); + response = success.calls.mostRecent().args[2]; + }); + + it("getResponseHeader should return all values comma separated", function () { + expect(response.getResponseHeader('X-Header')).toBe('header value 1, header value 2'); + }); + + it("getAllResponseHeaders should return all values", function () { + expect(response.getAllResponseHeaders()).toBe([ + "X-Header: header value 1", + "X-Header: header value 2" + ].join("\r\n") + "\r\n"); + }); + }); + + describe("the content type defaults to application/json", function () { + beforeEach(function() { + client = new fakeGlobal.XMLHttpRequest(); + client.onreadystatechange = onreadystatechange; + client.open("GET", "example.com/someApi"); + client.setRequestHeader("Content-Type", "application/json"); + client.send(); + + request = mockAjax.requests.mostRecent(); + response = {status: 200, statusText: "OK", responseText: '{"foo": "valid JSON, dammit."}', responseType: 'json'}; + request.respondWith(response); + + sharedContext.responseCallback = success; + sharedContext.status = response.status; + sharedContext.statusText = response.statusText; + sharedContext.contentType = "application/json"; + sharedContext.responseType = response.responseType; + sharedContext.responseText = response.responseText; + }); + + it("should call the success handler", function() { + expect(success).toHaveBeenCalled(); + }); + + it("should not call the failure handler", function() { + expect(error).not.toHaveBeenCalled(); + }); + + it("should call the complete handler", function() { + expect(complete).toHaveBeenCalled(); + }); + + sharedAjaxResponseBehaviorForZepto_Success(sharedContext); + }); + + describe("and the status/response code is 0", function () { + beforeEach(function() { + client = new fakeGlobal.XMLHttpRequest(); + client.onreadystatechange = onreadystatechange; + client.open("GET", "example.com/someApi"); + client.setRequestHeader("Content-Type", "text/plain"); + client.send(); + + request = mockAjax.requests.mostRecent(); + response = {status: 0, statusText: "ABORT", responseText: '{"foo": "whoops!"}'}; + request.respondWith(response); + + sharedContext.responseCallback = error; + sharedContext.status = 0; + sharedContext.statusText = response.statusText; + sharedContext.contentType = 'application/json'; + sharedContext.responseText = response.responseText; + sharedContext.responseType = response.responseType; + }); + + it("should call the success handler", function() { + expect(success).not.toHaveBeenCalled(); + }); + + it("should not call the failure handler", function() { + expect(error).toHaveBeenCalled(); + }); + + it("should call the complete handler", function() { + expect(complete).toHaveBeenCalled(); + }); + + sharedAjaxResponseBehaviorForZepto_Failure(sharedContext); + }); + }); + + describe("and the response is error", function () { + beforeEach(function() { + client = new fakeGlobal.XMLHttpRequest(); + client.onreadystatechange = onreadystatechange; + client.open("GET", "example.com/someApi"); + client.setRequestHeader("Content-Type", "text/plain"); + client.send(); + + request = mockAjax.requests.mostRecent(); + response = {status: 500, statusText: "SERVER ERROR", contentType: "text/html", responseText: "(._){"}; + request.respondWith(response); + + sharedContext.responseCallback = error; + sharedContext.status = response.status; + sharedContext.statusText = response.statusText; + sharedContext.contentType = response.contentType; + sharedContext.responseText = response.responseText; + sharedContext.responseType = response.responseType; + }); + + it("should not call the success handler", function() { + expect(success).not.toHaveBeenCalled(); + }); + + it("should call the failure handler", function() { + expect(error).toHaveBeenCalled(); + }); + + it("should call the complete handler", function() { + expect(complete).toHaveBeenCalled(); + }); + + sharedAjaxResponseBehaviorForZepto_Failure(sharedContext); + }); + + describe('when simulating a response with request.responseTimeout', function() { + beforeEach(function() { + jasmine.clock().install(); + + client = new fakeGlobal.XMLHttpRequest(); + client.onreadystatechange = onreadystatechange; + client.open("GET", "example.com/someApi"); + client.setRequestHeader("Content-Type", "text/plain"); + client.send(); + + request = mockAjax.requests.mostRecent(); + response = {contentType: "text/html", response: "(._){response", responseText: "(._){", responseType: "text"}; + request.responseTimeout(response); + + sharedContext.responseCallback = error; + sharedContext.status = response.status; + sharedContext.statusText = response.statusText; + sharedContext.contentType = response.contentType; + sharedContext.responseText = response.responseText; + sharedContext.responseType = response.responseType; + }); + + afterEach(function() { + jasmine.clock().uninstall(); + }); + + it("should not call the success handler", function() { + expect(success).not.toHaveBeenCalled(); + }); + + it("should call the failure handler", function() { + expect(error).toHaveBeenCalled(); + }); + + it("should call the complete handler", function() { + expect(complete).toHaveBeenCalled(); + }); + }); +}); + + +function sharedAjaxResponseBehaviorForZepto_Success(context) { + describe("the success response", function () { + var xhr; + beforeEach(function() { + xhr = context.responseCallback.calls.mostRecent().args[2]; + }); + + it("should have the expected status code", function() { + expect(xhr.status).toEqual(context.status); + }); + + it("should have the expected content type", function() { + expect(xhr.getResponseHeader('Content-Type')).toEqual(context.contentType); + }); + + it("should have the expected xhr2 response", function() { + var expected = context.response || context.responseType === 'json' ? JSON.parse(context.responseText) : context.responseText; + expect(xhr.response).toEqual(expected); + }); + + it("should have the expected response text", function() { + expect(xhr.responseText).toEqual(context.responseText); + }); + + it("should have the expected status text", function() { + expect(xhr.statusText).toEqual(context.statusText); + }); + }); +} + +function sharedAjaxResponseBehaviorForZepto_Failure(context) { + describe("the failure response", function () { + var xhr; + beforeEach(function() { + xhr = context.responseCallback.calls.mostRecent().args[0]; + }); + + it("should have the expected status code", function() { + expect(xhr.status).toEqual(context.status); + }); + + it("should have the expected content type", function() { + expect(xhr.getResponseHeader('Content-Type')).toEqual(context.contentType); + }); + + it("should have the expected xhr2 response", function() { + var expected = context.response || xhr.responseType === 'json' ? JSON.parse(xhr.responseText) : xhr.responseText; + expect(xhr.response).toEqual(expected); + }); + + it("should have the expected response text", function() { + expect(xhr.responseText).toEqual(context.responseText); + }); + + it("should have the expected status text", function() { + expect(xhr.statusText).toEqual(context.statusText); + }); + }); +} + +describe('ParamParser', function() { + beforeEach(function() { + var Constructor = getJasmineRequireObj().AjaxParamParser(); + expect(Constructor).toEqual(jasmine.any(Function)); + this.parser = new Constructor(); + }); + + 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'); + + expect(parsed).toEqual({ + '3 stooges': ['shemp', 'larry & moe & curly'], + 'some=thing': ['else entirely'] + }); + }); + + it('should detect and parse json', function() { + var data = { + foo: 'bar', + baz: ['q', 'u', 'u', 'x'], + nested: { + object: { + containing: 'stuff' + } + } + }, + parser = this.parser.findParser({ contentType: function() { return 'application/json'; } }), + parsed = parser.parse(JSON.stringify(data)); + + expect(parsed).toEqual(data); + }); + + it('should parse json with further qualifiers on content-type', function() { + var data = { + foo: 'bar', + baz: ['q', 'u', 'u', 'x'], + nested: { + object: { + containing: 'stuff' + } + } + }, + parser = this.parser.findParser({ contentType: function() { return 'application/json; charset=utf-8'; } }), + parsed = parser.parse(JSON.stringify(data)); + + expect(parsed).toEqual(data); + }); + + it('should have custom parsers take precedence', function() { + var custom = { + test: jasmine.createSpy('test').and.returnValue(true), + parse: jasmine.createSpy('parse').and.returnValue('parsedFormat') + }; + + this.parser.add(custom); + + var parser = this.parser.findParser({ contentType: function() {} }), + parsed = parser.parse('custom_format'); + + expect(parsed).toEqual('parsedFormat'); + expect(custom.test).toHaveBeenCalled(); + expect(custom.parse).toHaveBeenCalledWith('custom_format'); + }); + + it('should skip custom parsers that do not match', function() { + var custom = { + test: jasmine.createSpy('test').and.returnValue(false), + parse: jasmine.createSpy('parse').and.returnValue('parsedFormat') + }; + + this.parser.add(custom); + + var parser = this.parser.findParser({ contentType: function() {} }), + parsed = parser.parse('custom_format'); + + expect(parsed).toEqual({ custom_format: [ 'undefined' ] }); + expect(custom.test).toHaveBeenCalled(); + expect(custom.parse).not.toHaveBeenCalled(); + }); + + it('removes custom parsers when reset', function() { + var custom = { + test: jasmine.createSpy('test').and.returnValue(true), + parse: jasmine.createSpy('parse').and.returnValue('parsedFormat') + }; + + this.parser.add(custom); + + var parser = this.parser.findParser({ contentType: function() {} }), + parsed = parser.parse('custom_format'); + + expect(parsed).toEqual('parsedFormat'); + + custom.test['calls'].reset(); + custom.parse['calls'].reset(); + + this.parser.reset(); + + parser = this.parser.findParser({ contentType: function() {} }); + parsed = parser.parse('custom_format'); + + expect(parsed).toEqual({ custom_format: [ 'undefined' ] }); + expect(custom.test).not.toHaveBeenCalled(); + expect(custom.parse).not.toHaveBeenCalled(); + }); +}); + +describe('RequestStub', function() { + beforeEach(function() { + this.RequestStub = getJasmineRequireObj().AjaxRequestStub(); + + jasmine.addMatchers({ + toMatchRequest: function() { + return { + compare: function(actual) { + return { + pass: actual.matches.apply(actual, Array.prototype.slice.call(arguments, 1)) + }; + } + }; + } + }); + }); + + it('matches just by exact url', function() { + var stub = new this.RequestStub('www.example.com/foo'); + + expect(stub)['toMatchRequest']('www.example.com/foo'); + }); + + it('does not match if the url differs', function() { + var stub = new this.RequestStub('www.example.com/foo'); + + expect(stub).not['toMatchRequest']('www.example.com/bar'); + }); + + it('matches unordered query params', function() { + var stub = new this.RequestStub('www.example.com?foo=bar&baz=quux'); + + expect(stub)['toMatchRequest']('www.example.com?baz=quux&foo=bar'); + }); + + it('requires all specified query params to be there', function() { + var stub = new this.RequestStub('www.example.com?foo=bar&baz=quux'); + + expect(stub).not['toMatchRequest']('www.example.com?foo=bar'); + }); + + it('can match the url with a RegExp', function() { + var stub = new this.RequestStub(/ba[rz]/); + + expect(stub)['toMatchRequest']('bar'); + expect(stub)['toMatchRequest']('baz'); + expect(stub).not['toMatchRequest']('foo'); + }); + + it('requires the method to match if supplied', function() { + var stub = new this.RequestStub('www.example.com/foo', null, 'POST'); + + expect(stub).not['toMatchRequest']('www.example.com/foo'); + expect(stub).not['toMatchRequest']('www.example.com/foo', null, 'GET'); + expect(stub)['toMatchRequest']('www.example.com/foo', null, 'POST'); + }); + + it('requires the data submitted to match if supplied', function() { + var stub = new this.RequestStub('/foo', 'foo=bar&baz=quux'); + + expect(stub)['toMatchRequest']('/foo', 'baz=quux&foo=bar'); + expect(stub).not['toMatchRequest']('/foo', 'foo=bar'); + }); +}); + +describe('RequestTracker', function() { + beforeEach(function() { + var Constructor = getJasmineRequireObj().AjaxRequestTracker(); + this.tracker = new Constructor(); + }); + + it('tracks the number of times ajax requests are made', function() { + expect(this.tracker.count()).toBe(0); + + this.tracker.track(); + + expect(this.tracker.count()).toBe(1); + }); + + it('simplifies access to the last (most recent) request', function() { + this.tracker.track(); + this.tracker.track('request'); + + expect(this.tracker.mostRecent()).toEqual('request'); + }); + + it('returns a useful falsy value when there is no last (most recent) request', function() { + expect(this.tracker.mostRecent()).toBeFalsy(); + }); + + it('simplifies access to the first (oldest) request', function() { + this.tracker.track('request'); + this.tracker.track(); + + expect(this.tracker.first()).toEqual('request'); + }); + + it('returns a useful falsy value when there is no first (oldest) request', function() { + expect(this.tracker.first()).toBeFalsy(); + }); + + it('allows the requests list to be reset', function() { + this.tracker.track(); + this.tracker.track(); + + expect(this.tracker.count()).toBe(2); + + this.tracker.reset(); + + expect(this.tracker.count()).toBe(0); + }); + + it('allows retrieval of an arbitrary request by index', function() { + this.tracker.track('1'); + this.tracker.track('2'); + this.tracker.track('3'); + + expect(this.tracker.at(1)).toEqual('2'); + }); + + it('allows retrieval of all requests that are for a given url', function() { + this.tracker.track({ url: 'foo' }); + this.tracker.track({ url: 'bar' }); + + expect(this.tracker.filter('bar')).toEqual([{ url: 'bar' }]); + }); + + it('allows retrieval of all requests that match a given RegExp', function() { + this.tracker.track({ url: 'foo' }); + this.tracker.track({ url: 'bar' }); + this.tracker.track({ url: 'baz' }); + + expect(this.tracker.filter(/ba[rz]/)).toEqual([{ url: 'bar' }, { url: 'baz' }]); + }); + + it('allows retrieval of all requests that match based on a function', function() { + this.tracker.track({ url: 'foo' }); + this.tracker.track({ url: 'bar' }); + this.tracker.track({ url: 'baz' }); + + var func = function(request) { + return request.url === 'bar'; + }; + + expect(this.tracker.filter(func)).toEqual([{ url: 'bar' }]); + }); + + it('filters to nothing if no requests have been tracked', function() { + expect(this.tracker.filter('foo')).toEqual([]); + }); +}); + +describe('EventBus', function() { + beforeEach(function() { + this.bus = getJasmineRequireObj().AjaxEventBus()(); + }); + + it('calls an event listener', function() { + var callback = jasmine.createSpy('callback'); + + this.bus.addEventListener('foo', callback); + this.bus.trigger('foo'); + + expect(callback).toHaveBeenCalled(); + }); + + it('calls an event listener with additional arguments', function() { + var callback = jasmine.createSpy('callback'); + + this.bus.addEventListener('foo', callback); + this.bus.trigger('foo', 'bar'); + + expect(callback).toHaveBeenCalledWith('bar'); + }); + + it('only triggers callbacks for the specified event', function() { + var fooCallback = jasmine.createSpy('foo'), + barCallback = jasmine.createSpy('bar'); + + this.bus.addEventListener('foo', fooCallback); + this.bus.addEventListener('bar', barCallback); + + this.bus.trigger('foo'); + + expect(fooCallback).toHaveBeenCalled(); + expect(barCallback).not.toHaveBeenCalled(); + }); + + it('calls all the callbacks for the specified event', function() { + var callback1 = jasmine.createSpy('callback'); + var callback2 = jasmine.createSpy('otherCallback'); + + this.bus.addEventListener('foo', callback1); + this.bus.addEventListener('foo', callback2); + + this.bus.trigger('foo'); + + expect(callback1).toHaveBeenCalled(); + expect(callback2).toHaveBeenCalled(); + }); + + it('works if there are no callbacks for the event', function() { + var bus = this.bus; + expect(function() { + bus.trigger('notActuallyThere'); + }).not.toThrow(); + }); + + it('does not call listeners that have been removed', function() { + var callback = jasmine.createSpy('callback'); + + this.bus.addEventListener('foo', callback); + this.bus.removeEventListener('foo', callback); + this.bus.trigger('foo'); + + expect(callback).not.toHaveBeenCalled(); + }); + + it('only removes the specified callback', function() { + var callback1 = jasmine.createSpy('callback'); + var callback2 = jasmine.createSpy('otherCallback'); + + this.bus.addEventListener('foo', callback1); + this.bus.addEventListener('foo', callback2); + this.bus.removeEventListener('foo', callback2); + + this.bus.trigger('foo'); + + expect(callback1).toHaveBeenCalled(); + expect(callback2).not.toHaveBeenCalled(); + }); +}); + +describe("Webmock style mocking", function() { + var successSpy, errorSpy, response, fakeGlobal, mockAjax; + + var sendRequest = function(fakeGlobal, url?, method?) { + url = url || "http://example.com/someApi"; + method = method || 'GET'; + var xhr = new fakeGlobal.XMLHttpRequest(); + xhr.onreadystatechange = function(args) { + if (this.readyState === (this.DONE || 4)) { // IE 8 doesn't support DONE + response = this; + successSpy(); + } + }; + + xhr.open(method, url); + xhr.send(); + }; + + beforeEach(function() { + successSpy = jasmine.createSpy('success'); + fakeGlobal = {XMLHttpRequest: jasmine.createSpy('realXMLHttpRequest')}; + mockAjax = new MockAjax(fakeGlobal); + mockAjax.install(); + + mockAjax.stubRequest("http://example.com/someApi").andReturn({responseText: "hi!"}); + }); + + it("allows a url to be setup as a stub", function() { + sendRequest(fakeGlobal); + expect(successSpy).toHaveBeenCalled(); + }); + + it("should allow you to clear all the ajax stubs", function() { + mockAjax.stubs.reset(); + sendRequest(fakeGlobal); + expect(successSpy).not.toHaveBeenCalled(); + }); + + it("should set the contentType", function() { + sendRequest(fakeGlobal); + expect(response.getResponseHeader('Content-Type')).toEqual('application/json'); + }); + + it("should set the responseText", function() { + sendRequest(fakeGlobal); + expect(response.responseText).toEqual('hi!'); + }); + + it("should default the status to 200", function() { + sendRequest(fakeGlobal); + expect(response.status).toEqual(200); + }); + + it("should set the responseHeaders", function() { + mockAjax.stubRequest("http://example.com/someApi").andReturn({ + responseText: "hi!", + responseHeaders: [{name: "X-Custom", value: "header value"}] + }); + sendRequest(fakeGlobal); + expect(response.getResponseHeader('X-Custom')).toEqual('header value'); + }); + + describe("with another stub for the same url", function() { + beforeEach(function() { + mockAjax.stubRequest("http://example.com/someApi").andReturn({responseText: "no", status: 403}); + sendRequest(fakeGlobal); + }); + + it("should set the status", function() { + expect(response.status).toEqual(403); + }); + + it("should allow the latest stub to win", function() { + expect(response.responseText).toEqual('no'); + }); + }); +}); + +describe("withMock", function() { + var sendRequest = function(fakeGlobal) { + var xhr = new fakeGlobal.XMLHttpRequest(); + + xhr.open("GET", "http://example.com/someApi"); + xhr.send(); + }; + + it("installs the mock for passed in function, and uninstalls when complete", function() { + var xmlHttpRequest = jasmine.createSpyObj('XMLHttpRequest', ['open', 'send']), + xmlHttpRequestCtor = spyOn(window, 'XMLHttpRequest').and.returnValue(xmlHttpRequest), + fakeGlobal = {XMLHttpRequest: xmlHttpRequestCtor}, + mockAjax = new MockAjax(fakeGlobal); + + mockAjax.withMock(function() { + sendRequest(fakeGlobal); + expect(xmlHttpRequest.open).not.toHaveBeenCalled(); + }); + + sendRequest(fakeGlobal); + expect(xmlHttpRequest.open).toHaveBeenCalled(); + }); + + it("properly uninstalls when the passed in function throws", function() { + var xmlHttpRequest = jasmine.createSpyObj('XMLHttpRequest', ['open', 'send']), + xmlHttpRequestCtor = spyOn(window, 'XMLHttpRequest').and.returnValue(xmlHttpRequest), + fakeGlobal = {XMLHttpRequest: xmlHttpRequestCtor}, + mockAjax = new MockAjax(fakeGlobal); + + expect(function() { + mockAjax.withMock(function() { + throw "error"; + }); + }).toThrow("error"); + + sendRequest(fakeGlobal); + expect(xmlHttpRequest.open).toHaveBeenCalled(); + }); +}); + +describe("mockAjax", function() { + it("throws an error if installed multiple times", function() { + var fakeXmlHttpRequest = jasmine.createSpy('fakeXmlHttpRequest'), + fakeGlobal = { XMLHttpRequest: fakeXmlHttpRequest }, + mockAjax = new MockAjax(fakeGlobal); + + function doubleInstall() { + mockAjax.install(); + mockAjax.install(); + } + + expect(doubleInstall).toThrow(); + }); + + it("does not throw an error if uninstalled between installs", function() { + var fakeXmlHttpRequest = jasmine.createSpy('fakeXmlHttpRequest'), + fakeGlobal = { XMLHttpRequest: fakeXmlHttpRequest }, + mockAjax = new MockAjax(fakeGlobal); + + function sequentialInstalls() { + mockAjax.install(); + mockAjax.uninstall(); + mockAjax.install(); + } + + expect(sequentialInstalls).not.toThrow(); + }); + + it("does not replace XMLHttpRequest until it is installed", function() { + var fakeXmlHttpRequest = jasmine.createSpy('fakeXmlHttpRequest'), + fakeGlobal = { XMLHttpRequest: fakeXmlHttpRequest }, + mockAjax = new MockAjax(fakeGlobal); + + fakeGlobal.XMLHttpRequest('foo'); + expect(fakeXmlHttpRequest).toHaveBeenCalledWith('foo'); + fakeXmlHttpRequest.calls.reset(); + + mockAjax.install(); + fakeGlobal.XMLHttpRequest('foo'); + expect(fakeXmlHttpRequest).not.toHaveBeenCalled(); + }); + + it("replaces the global XMLHttpRequest on uninstall", function() { + var fakeXmlHttpRequest = jasmine.createSpy('fakeXmlHttpRequest'), + fakeGlobal = { XMLHttpRequest: fakeXmlHttpRequest }, + mockAjax = new MockAjax(fakeGlobal); + + mockAjax.install(); + mockAjax.uninstall(); + + fakeGlobal.XMLHttpRequest('foo'); + expect(fakeXmlHttpRequest).toHaveBeenCalledWith('foo'); + }); + + it("clears requests and stubs upon uninstall", function() { + var fakeXmlHttpRequest = jasmine.createSpy('fakeXmlHttpRequest'), + fakeGlobal = { XMLHttpRequest: fakeXmlHttpRequest }, + mockAjax = new MockAjax(fakeGlobal); + + mockAjax.install(); + + mockAjax.requests.track({url: '/testurl'}); + mockAjax.stubRequest('/bobcat'); + + expect(mockAjax.requests.count()).toEqual(1); + expect(mockAjax.stubs.findStub('/bobcat')).toBeDefined(); + + mockAjax.uninstall(); + + expect(mockAjax.requests.count()).toEqual(0); + expect(mockAjax.stubs.findStub('/bobcat')).not.toBeDefined(); + }); + + it("allows the httpRequest to be retrieved", function() { + var fakeXmlHttpRequest = jasmine.createSpy('fakeXmlHttpRequest'), + fakeGlobal = { XMLHttpRequest: fakeXmlHttpRequest }, + mockAjax = new MockAjax(fakeGlobal); + + mockAjax.install(); + var request = new (fakeGlobal.XMLHttpRequest)(); + + expect(mockAjax.requests.count()).toBe(1); + expect(mockAjax.requests.mostRecent()).toBe(request); + }); + + it("allows the httpRequests to be cleared", function() { + var fakeXmlHttpRequest = jasmine.createSpy('fakeXmlHttpRequest'), + fakeGlobal = { XMLHttpRequest: fakeXmlHttpRequest }, + mockAjax = new MockAjax(fakeGlobal); + + mockAjax.install(); + var request = new (fakeGlobal.XMLHttpRequest)(); + + expect(mockAjax.requests.mostRecent()).toBe(request); + mockAjax.requests.reset(); + expect(mockAjax.requests.count()).toBe(0); + }); +}); diff --git a/jasmine-ajax/jasmine-ajax-tests.ts.tscparams b/jasmine-ajax/jasmine-ajax-tests.ts.tscparams new file mode 100644 index 000000000..d3f5a12fa --- /dev/null +++ b/jasmine-ajax/jasmine-ajax-tests.ts.tscparams @@ -0,0 +1 @@ + diff --git a/jasmine-ajax/jasmine-ajax.d.ts b/jasmine-ajax/jasmine-ajax.d.ts new file mode 100644 index 000000000..70ca2f57c --- /dev/null +++ b/jasmine-ajax/jasmine-ajax.d.ts @@ -0,0 +1,77 @@ +// Type definitions for jasmine-ajax 3.1.1 +// Project: https://github.com/jasmine/jasmine-ajax +// Definitions by: Louis Grignon +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +interface JasmineAjaxResponse { + status?: string; + statusText?: string; + responseText?: string; + response?: string; + responseType?: string; + contentType?: string; + responseHeaders?: { [key: string]: string }; +} + +interface JasmineAjaxRequest { + url: string; + respondWith(response: JasmineAjaxResponse): void; +} + +interface JasmineAjaxRequestTracker { + track(request: JasmineAjaxRequest): void; + first(): JasmineAjaxRequest; + count(): number; + reset(): void; + mostRecent(): JasmineAjaxRequest; + at(index: number): JasmineAjaxRequest; + filter(urlToMatch: RegExp): JasmineAjaxRequest[]; + filter(urlToMatch: Function): JasmineAjaxRequest[]; + filter(urlToMatch: string): JasmineAjaxRequest[]; +} + +interface JasmineAjaxRequestStubReturnOptions { + status?: number; + contentType?: string; + response?: string; + responseText?: string; + responseHeaders?: { [key: string]: string }; +} + +interface JasmineAjaxRequestStub { + data?: string; + method?: string; + andReturn(options: JasmineAjaxRequestStubReturnOptions): void; + matches(fullUrl: string, data: string, method: string): boolean; +} + +interface JasmineAjaxStubTracker { + addStub(stub: JasmineAjaxRequestStub): void; + reset(): void; + findStub(url: string, data?: string, method?: string): JasmineAjaxRequestStub; +} + +interface JasmineAjaxParamParser { + test(xhr: XMLHttpRequest): boolean; + parse(paramString: string): any; +} + +declare class MockAjax { + constructor(globals); + + install(): void; + uninstall(): void; + + withMock(closure: () => void): void; + addCustomParamParser(parser: JasmineAjaxParamParser): void; + + stubRequest(url: RegExp, data?: string, method?: string): JasmineAjaxRequestStub; + stubRequest(url: string, data?: string, method?: string): JasmineAjaxRequestStub; + + requests: JasmineAjaxRequestTracker; + stubs: JasmineAjaxStubTracker; +} + +declare module jasmine { + export var Ajax: MockAjax; +}