/** * Tests adapted from: https://github.com/jasmine/jasmine-ajax * By Louis Grignon */ /// declare function getJasmineRequireObj(); describe('StubTracker', () => { beforeEach(() => { const Constructor = getJasmineRequireObj().AjaxStubTracker(); this.tracker = new Constructor(); }); it('finds nothing if no stubs are added', () => { expect(this.tracker.findStub()).toBeUndefined(); }); it('finds an added stub', () => { const stub = { matches: () => true }; this.tracker.addStub(stub); expect(this.tracker.findStub()).toBe(stub); }); it('skips an added stub that does not match', () => { const stub = { matches: () => false }; this.tracker.addStub(stub); expect(this.tracker.findStub()).toBeUndefined(); }); it('passes url, data, and method to the stub', () => { const 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', () => { const 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', () => { const stub1 = { matches: () => true }; const stub2 = { matches: () => false }; const stub3 = { matches: () => false }; this.tracker.addStub(stub1); this.tracker.addStub(stub2); this.tracker.addStub(stub3); expect(this.tracker.findStub()).toBe(stub2); }); }); describe('FakeRequest', () => { beforeEach(() => { this.requestTracker = { track: jasmine.createSpy('trackRequest') }; this.stubTracker = { findStub() { } }; const parserInstance = this.parserInstance = jasmine.createSpy('parse'); this.paramParser = { findParser: () => ({ parse: parserInstance }) }; const eventBus = this.fakeEventBus = { addEventListener: jasmine.createSpy('addEventListener'), trigger: jasmine.createSpy('trigger'), removeEventListener: jasmine.createSpy('removeEventListener') }; this.eventBusFactory = () => { return eventBus; }; this.fakeGlobal = { XMLHttpRequest: () => { 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', () => { const request = new this.FakeRequest(); expect(request.extraAttribute).toEqual('my cool attribute'); }); it('skips XMLHttpRequest attributes that IE does not want copied', () => { // use real window here so it will correctly go red on IE if it breaks const FakeRequest = getJasmineRequireObj().AjaxFakeRequest(this.eventBusFactory)(window, this.requestTracker, this.stubTracker, this.paramParser); const request = new FakeRequest(); expect(request.responseBody).toBeUndefined(); expect(request.responseXML).toBeUndefined(); expect(request.statusText).toBeUndefined(); }); it('tracks the request', () => { const request = new this.FakeRequest(); expect(this.requestTracker.track).toHaveBeenCalledWith(request); }); it('has default request headers and override mime type', () => { const request = new this.FakeRequest(); expect(request.requestHeaders).toEqual({}); expect(request.overriddenMimeType).toBeNull(); }); it('saves request information when opened', () => { const 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', () => { const request = new this.FakeRequest(); request.overrideMimeType('application/text; charset: utf-8'); expect(request.overriddenMimeType).toBe('application/text; charset: utf-8'); }); it('saves request headers', () => { const 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', () => { const 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', () => { const request = new this.FakeRequest(); request.setRequestHeader('ContEnt-tYPe', 'application/text+xml'); expect(request.contentType()).toEqual('application/text+xml'); }); describe('managing readyState', () => { beforeEach(() => { this.request = new this.FakeRequest(); }); it('has an initial ready state of 0 (uninitialized)', () => { expect(this.request.readyState).toBe(0); expect(this.fakeEventBus.trigger).not.toHaveBeenCalled(); }); it('has a ready state of 1 (open) when opened', () => { 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', () => { 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', () => { 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', () => { 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', () => { 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', () => { 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', () => { this.request.open(); this.request.send(); this.fakeEventBus.trigger.calls.reset(); const request = this.request; const events = []; const headers = [ { name: 'X-Header', value: 'foo' } ]; this.fakeEventBus.trigger.and.callFake(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', () => { this.request.open(); this.request.send(); this.request.respondWith({}); const request = this.request; expect(() => { request.responseTimeout(); }).toThrowError('FakeXMLHttpRequest already completed'); }); it('throws an error when responding to a request that has completed', () => { this.request.open(); this.request.send(); this.request.respondWith({}); const request = this.request; expect(() => { request.respondWith({}); }).toThrowError('FakeXMLHttpRequest already completed'); }); it('throws an error when erroring a request that has completed', () => { this.request.open(); this.request.send(); this.request.respondWith({}); const request = this.request; expect(() => { request.responseError({}); }).toThrowError('FakeXMLHttpRequest already completed'); }); }); it('registers on-style callback with the event bus', () => { 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'); const args = this.fakeEventBus.addEventListener.calls.allArgs(); for (const [eventName, busCallback] of args) { busCallback(); expect(this.request['on' + eventName]).toHaveBeenCalled(); } }); it('delegates addEventListener to the eventBus', () => { this.request = new this.FakeRequest(); this.request.addEventListener('foo', 'bar'); expect(this.fakeEventBus.addEventListener).toHaveBeenCalledWith('foo', 'bar'); }); it('delegates removeEventListener to the eventBus', () => { this.request = new this.FakeRequest(); this.request.removeEventListener('foo', 'bar'); expect(this.fakeEventBus.removeEventListener).toHaveBeenCalledWith('foo', 'bar'); }); describe('triggering progress events', () => { beforeEach(() => { this.request = new this.FakeRequest(); }); it('should not trigger any events to start', () => { this.request.open(); expect(this.fakeEventBus.trigger).toHaveBeenCalledWith('readystatechange'); }); it('should trigger loadstart when sent', () => { 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', () => { 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', () => { 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', () => { 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', () => { 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', () => { const clock = { tick: jasmine.createSpy('tick') }; spyOn(jasmine, 'clock').and.returnValue(clock as any); const request = new this.FakeRequest(); request.open(); request.send(); request.responseTimeout(); expect(clock.tick).toHaveBeenCalledWith(30000); }); it('has an initial status of null', () => { const request = new this.FakeRequest(); expect(request.status).toBeNull(); }); it('has an aborted status', () => { const request = new this.FakeRequest(); request.abort(); expect(request.status).toBe(0); expect(request.statusText).toBe('abort'); }); it('has a status from the response', () => { const 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', () => { const 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', () => { const 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', () => { const request = new this.FakeRequest(); request.open(); request.send('foo=bar&baz=quux'); this.parserInstance.and.returnValue({data: 'parsed'}); expect(request.data()).toEqual({data: 'parsed'}); }); it('skips parsing if no data was sent', () => { const request = new this.FakeRequest(); request.open(); request.send(); expect(request.data()).toEqual({}); expect(this.parserInstance).not.toHaveBeenCalled(); }); it('saves responseText', () => { const 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', () => { const request = new this.FakeRequest(); request.open(); request.send(); request.respondWith({ status: 200 }); expect(request.responseText).toBe(''); }); it('retrieves individual response headers', () => { const 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', () => { const 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', () => { const 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", () => { const request1 = new this.FakeRequest(); request1.open(); request1.send(); const 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', () => { const 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', () => { const 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', () => { const 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', () => { const request = new this.FakeRequest(); request.open(); request.send(); request.respondWith({ status: 200 }); expect(request.responseXML).toBeNull(); }); it('parses a text/xml document into responseXML', () => { const 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', () => { const 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', () => { const 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', () => { const 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', () => { const 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', () => { const 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)", () => { // TODO: these are all `any`! let request, anotherRequest, response; // tslint:disable-line one-variable-per-declaration let success, error, complete; // tslint:disable-line one-variable-per-declaration let client, onreadystatechange; // tslint:disable-line one-variable-per-declaration const sharedContext: any = {}; let fakeGlobal, mockAjax; // tslint:disable-line one-variable-per-declaration beforeEach(() => { const 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", () => { beforeEach(() => { 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", () => { expect(request.url).toEqual("example.com/someApi"); }); it("should queue the request", () => { expect(mockAjax.requests.count()).toEqual(1); }); it("should allow access to the queued request", () => { expect(mockAjax.requests.first()).toEqual(request); }); it("should allow access to the queued request via index", () => { expect(mockAjax.requests.at(0)).toEqual(request); }); describe("and then another request", () => { beforeEach(() => { 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", () => { expect(mockAjax.requests.count()).toEqual(2); }); it("should allow access to the other queued request", () => { expect(mockAjax.requests.first()).toEqual(request); expect(mockAjax.requests.mostRecent()).toEqual(anotherRequest); }); }); describe("mockAjax.requests.mostRecent()", () => { describe("when there is one request queued", () => { it("should return the request", () => { expect(mockAjax.requests.mostRecent()).toEqual(request); }); }); describe("when there is more than one request", () => { beforeEach(() => { 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", () => { expect(mockAjax.requests.mostRecent()).toEqual(anotherRequest); }); }); describe("when there are no requests", () => { beforeEach(() => { mockAjax.requests.reset(); }); it("should return null", () => { expect(mockAjax.requests.mostRecent()).toBeUndefined(); }); }); }); describe("clearAjaxRequests()", () => { beforeEach(() => { mockAjax.requests.reset(); }); it("should remove all requests", () => { expect(mockAjax.requests.count()).toEqual(0); expect(mockAjax.requests.mostRecent()).toBeUndefined(); }); }); }); describe("when simulating a response with request.response", () => { describe("and the response is Success", () => { beforeEach(() => { 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!", responseType: "json" }; 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", () => { expect(success).toHaveBeenCalled(); }); it("should not call the failure handler", () => { expect(error).not.toHaveBeenCalled(); }); it("should call the complete handler", () => { expect(complete).toHaveBeenCalled(); }); sharedAjaxResponseBehaviorForZepto_Success(sharedContext); }); describe("and the response is Success, but with JSON", () => { beforeEach(() => { 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(); const 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", () => { expect(success).toHaveBeenCalled(); }); it("should not call the failure handler", () => { expect(error).not.toHaveBeenCalled(); }); it("should call the complete handler", () => { expect(complete).toHaveBeenCalled(); }); it("should return a JavaScript object for XHR2 response", () => { const 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", () => { beforeEach(() => { 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(); const 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", () => { const 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", () => { beforeEach(() => { client = new fakeGlobal.XMLHttpRequest(); client.onreadystatechange = onreadystatechange; client.open("GET", "example.com"); client.send(); request = mockAjax.requests.mostRecent(); const 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", () => { 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", () => { 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", () => { beforeEach(() => { client = new fakeGlobal.XMLHttpRequest(); client.onreadystatechange = onreadystatechange; client.open("GET", "example.com"); client.send(); request = mockAjax.requests.mostRecent(); const 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", () => { expect(response.getResponseHeader('X-Header')).toBe('header value 1, header value 2'); }); it("getAllResponseHeaders should return all values", () => { 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", () => { beforeEach(() => { 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", () => { expect(success).toHaveBeenCalled(); }); it("should not call the failure handler", () => { expect(error).not.toHaveBeenCalled(); }); it("should call the complete handler", () => { expect(complete).toHaveBeenCalled(); }); sharedAjaxResponseBehaviorForZepto_Success(sharedContext); }); describe("and the status/response code is 0", () => { beforeEach(() => { 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!"}', responseType: "json"}; 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", () => { expect(success).not.toHaveBeenCalled(); }); it("should not call the failure handler", () => { expect(error).toHaveBeenCalled(); }); it("should call the complete handler", () => { expect(complete).toHaveBeenCalled(); }); sharedAjaxResponseBehaviorForZepto_Failure(sharedContext); }); }); describe("and the response is error", () => { beforeEach(() => { 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: "(._){", responseType: "json"}; 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", () => { expect(success).not.toHaveBeenCalled(); }); it("should call the failure handler", () => { expect(error).toHaveBeenCalled(); }); it("should call the complete handler", () => { expect(complete).toHaveBeenCalled(); }); sharedAjaxResponseBehaviorForZepto_Failure(sharedContext); }); describe('when simulating a response with request.responseTimeout', () => { beforeEach(() => { 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", status: 200, statusText: 'OK' }; 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(() => { jasmine.clock().uninstall(); }); it("should not call the success handler", () => { expect(success).not.toHaveBeenCalled(); }); it("should call the failure handler", () => { expect(error).toHaveBeenCalled(); }); it("should call the complete handler", () => { expect(complete).toHaveBeenCalled(); }); }); }); function sharedAjaxResponseBehaviorForZepto_Success(context) { describe("the success response", () => { let xhr; beforeEach(() => { xhr = context.responseCallback.calls.mostRecent().args[2]; }); it("should have the expected status code", () => { expect(xhr.status).toEqual(context.status); }); it("should have the expected content type", () => { expect(xhr.getResponseHeader('Content-Type')).toEqual(context.contentType); }); it("should have the expected xhr2 response", () => { const expected = context.response || context.responseType === 'json' ? JSON.parse(context.responseText) : context.responseText; expect(xhr.response).toEqual(expected); }); it("should have the expected response text", () => { expect(xhr.responseText).toEqual(context.responseText); }); it("should have the expected status text", () => { expect(xhr.statusText).toEqual(context.statusText); }); }); } function sharedAjaxResponseBehaviorForZepto_Failure(context) { describe("the failure response", () => { let xhr; beforeEach(() => { xhr = context.responseCallback.calls.mostRecent().args[0]; }); it("should have the expected status code", () => { expect(xhr.status).toEqual(context.status); }); it("should have the expected content type", () => { expect(xhr.getResponseHeader('Content-Type')).toEqual(context.contentType); }); it("should have the expected xhr2 response", () => { const expected = context.response || xhr.responseType === 'json' ? JSON.parse(xhr.responseText) : xhr.responseText; expect(xhr.response).toEqual(expected); }); it("should have the expected response text", () => { expect(xhr.responseText).toEqual(context.responseText); }); it("should have the expected status text", () => { expect(xhr.statusText).toEqual(context.statusText); }); }); } describe('ParamParser', () => { beforeEach(() => { const Constructor = getJasmineRequireObj().AjaxParamParser(); expect(Constructor).toEqual(jasmine.any(Function)); this.parser = new Constructor(); }); it('has a default parser', () => { const parser = this.parser.findParser({ contentType: () => { } }); const 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', () => { const data = { foo: 'bar', baz: ['q', 'u', 'u', 'x'], nested: { object: { containing: 'stuff' } } }; const parser = this.parser.findParser({ contentType: () => 'application/json' }); const parsed = parser.parse(JSON.stringify(data)); expect(parsed).toEqual(data); }); it('should parse json with further qualifiers on content-type', () => { const data = { foo: 'bar', baz: ['q', 'u', 'u', 'x'], nested: { object: { containing: 'stuff' } } }; const parser = this.parser.findParser({ contentType: () => 'application/json; charset=utf-8' }); const parsed = parser.parse(JSON.stringify(data)); expect(parsed).toEqual(data); }); it('should have custom parsers take precedence', () => { const custom = { test: jasmine.createSpy('test').and.returnValue(true), parse: jasmine.createSpy('parse').and.returnValue('parsedFormat') }; this.parser.add(custom); const parser = this.parser.findParser({ contentType: () => { } }); const 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', () => { const custom = { test: jasmine.createSpy('test').and.returnValue(false), parse: jasmine.createSpy('parse').and.returnValue('parsedFormat') }; this.parser.add(custom); const parser = this.parser.findParser({ contentType: () => { } }); const 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', () => { const custom = { test: jasmine.createSpy('test').and.returnValue(true), parse: jasmine.createSpy('parse').and.returnValue('parsedFormat') }; this.parser.add(custom); let parser = this.parser.findParser({ contentType: () => { } }); let 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: () => { } }); parsed = parser.parse('custom_format'); expect(parsed).toEqual({ custom_format: ['undefined'] }); expect(custom.test).not.toHaveBeenCalled(); expect(custom.parse).not.toHaveBeenCalled(); }); }); describe('RequestStub', () => { beforeEach(() => { this.RequestStub = getJasmineRequireObj().AjaxRequestStub(); jasmine.addMatchers({ toMatchRequest(a, b) { return { compare(actual): jasmine.CustomMatcherResult { return { message: '', pass: actual.matches.apply(actual, Array.prototype.slice.call(arguments, 1)) }; } }; } }); }); it('matches just by exact url', () => { const stub = new this.RequestStub('www.example.com/foo'); expect(stub)['toMatchRequest']('www.example.com/foo'); }); it('does not match if the url differs', () => { const stub = new this.RequestStub('www.example.com/foo'); expect(stub).not['toMatchRequest']('www.example.com/bar'); }); it('matches unordered query params', () => { const 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', () => { const 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', () => { const 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', () => { const 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', () => { const stub = new this.RequestStub('/foo', 'foo=bar&baz=quux'); expect(stub)['toMatchRequest']('/foo', 'baz=quux&foo=bar'); expect(stub).not['toMatchRequest']('/foo', 'foo=bar'); }); it('has methods', () => { jasmine.Ajax.stubRequest('/foo').andReturn({ status: 200, contentType: 'application/json', responseText: '{"success": true}', responseHeaders: { 'X-Example': 'a value' }, }); jasmine.Ajax.stubRequest('/bar').andReturn({}); jasmine.Ajax.stubRequest('/baz').andError({ status: 400, statusText: 'Invalid', }); jasmine.Ajax.stubRequest('/foobar').andError({}); jasmine.Ajax.stubRequest('/foobaz').andTimeout(); jasmine.Ajax.stubRequest('/barbaz').andCallFunction((xhr) => { xhr.url === '/barbaz'; xhr.method === 'POST'; xhr.params === {}; xhr.username === 'jane_coder'; xhr.password === '12345'; xhr.requestHeaders === {Accept: 'application/json'}; xhr.data() === {query: 'bananas'}; xhr.respondWith({ status: 200, contentType: 'application/json', responseText: '{"success": true}', responseHeaders: { 'X-Example': 'a value' }, }); xhr.responseTimeout(); xhr.responseError({ status: 400, statusText: 'Invalid', }); }); }); }); describe('RequestTracker', () => { beforeEach(() => { const Constructor = getJasmineRequireObj().AjaxRequestTracker(); this.tracker = new Constructor(); }); it('tracks the number of times ajax requests are made', () => { expect(this.tracker.count()).toBe(0); this.tracker.track(); expect(this.tracker.count()).toBe(1); }); it('simplifies access to the last (most recent) request', () => { 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', () => { expect(this.tracker.mostRecent()).toBeFalsy(); }); it('simplifies access to the first (oldest) request', () => { 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', () => { expect(this.tracker.first()).toBeFalsy(); }); it('allows the requests list to be reset', () => { 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', () => { 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', () => { 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', () => { 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', () => { this.tracker.track({ url: 'foo' }); this.tracker.track({ url: 'bar' }); this.tracker.track({ url: 'baz' }); const func = request => request.url === 'bar'; expect(this.tracker.filter(func)).toEqual([{ url: 'bar' }]); }); it('filters to nothing if no requests have been tracked', () => { expect(this.tracker.filter('foo')).toEqual([]); }); }); describe('EventBus', () => { beforeEach(() => { this.bus = getJasmineRequireObj().AjaxEventBus()(); }); it('calls an event listener', () => { const callback = jasmine.createSpy('callback'); this.bus.addEventListener('foo', callback); this.bus.trigger('foo'); expect(callback).toHaveBeenCalled(); }); it('calls an event listener with additional arguments', () => { const 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', () => { const fooCallback = jasmine.createSpy('foo'); const 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', () => { const callback1 = jasmine.createSpy('callback'); const 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', () => { const bus = this.bus; expect(() => { bus.trigger('notActuallyThere'); }).not.toThrow(); }); it('does not call listeners that have been removed', () => { const 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', () => { const callback1 = jasmine.createSpy('callback'); const 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", () => { let successSpy, response, fakeGlobal, mockAjax; // tslint:disable-line one-variable-per-declaration const sendRequest = function(fakeGlobal, url?, method?) { url = url || "http://example.com/someApi"; method = method || 'GET'; const xhr = new fakeGlobal.XMLHttpRequest(); xhr.onreadystatechange = args => { if (this.readyState === (this.DONE || 4)) { // IE 8 doesn't support DONE response = this; successSpy(); } }; xhr.open(method, url); xhr.send(); }; beforeEach(() => { 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", () => { sendRequest(fakeGlobal); expect(successSpy).toHaveBeenCalled(); }); it("should allow you to clear all the ajax stubs", () => { mockAjax.stubs.reset(); sendRequest(fakeGlobal); expect(successSpy).not.toHaveBeenCalled(); }); it("should set the contentType", () => { sendRequest(fakeGlobal); expect(response.getResponseHeader('Content-Type')).toEqual('application/json'); }); it("should set the responseText", () => { sendRequest(fakeGlobal); expect(response.responseText).toEqual('hi!'); }); it("should default the status to 200", () => { sendRequest(fakeGlobal); expect(response.status).toEqual(200); }); it("should set the responseHeaders", () => { 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", () => { beforeEach(() => { mockAjax.stubRequest("http://example.com/someApi").andReturn({ responseText: "no", status: 403 }); sendRequest(fakeGlobal); }); it("should set the status", () => { expect(response.status).toEqual(403); }); it("should allow the latest stub to win", () => { expect(response.responseText).toEqual('no'); }); }); }); describe("withMock", () => { const sendRequest = fakeGlobal => { const 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", () => { const xmlHttpRequest = jasmine.createSpyObj('XMLHttpRequest', ['open', 'send']); const xmlHttpRequestCtor = spyOn(window as any, 'XMLHttpRequest').and.returnValue(xmlHttpRequest); const fakeGlobal = { XMLHttpRequest: xmlHttpRequestCtor }; const mockAjax = new MockAjax(fakeGlobal); mockAjax.withMock(() => { sendRequest(fakeGlobal); expect(xmlHttpRequest.open).not.toHaveBeenCalled(); }); sendRequest(fakeGlobal); expect(xmlHttpRequest.open).toHaveBeenCalled(); }); it("properly uninstalls when the passed in function throws", () => { const xmlHttpRequest = jasmine.createSpyObj('XMLHttpRequest', ['open', 'send']); const xmlHttpRequestCtor = spyOn(window as any, 'XMLHttpRequest').and.returnValue(xmlHttpRequest); const fakeGlobal = { XMLHttpRequest: xmlHttpRequestCtor }; const mockAjax = new MockAjax(fakeGlobal); expect(() => { mockAjax.withMock(() => { throw "error"; // tslint:disable-line:no-string-throw }); }).toThrow("error"); sendRequest(fakeGlobal); expect(xmlHttpRequest.open).toHaveBeenCalled(); }); }); describe("mockAjax", () => { it("throws an error if installed multiple times", () => { const fakeXmlHttpRequest = jasmine.createSpy('fakeXmlHttpRequest'); const fakeGlobal = { XMLHttpRequest: fakeXmlHttpRequest }; const mockAjax = new MockAjax(fakeGlobal); function doubleInstall() { mockAjax.install(); mockAjax.install(); } expect(doubleInstall).toThrow(); }); it("does not throw an error if uninstalled between installs", () => { const fakeXmlHttpRequest = jasmine.createSpy('fakeXmlHttpRequest'); const fakeGlobal = { XMLHttpRequest: fakeXmlHttpRequest }; const 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", () => { const fakeXmlHttpRequest = jasmine.createSpy('fakeXmlHttpRequest'); const fakeGlobal = { XMLHttpRequest: fakeXmlHttpRequest }; const 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", () => { const fakeXmlHttpRequest = jasmine.createSpy('fakeXmlHttpRequest'); const fakeGlobal = { XMLHttpRequest: fakeXmlHttpRequest }; const mockAjax = new MockAjax(fakeGlobal); mockAjax.install(); mockAjax.uninstall(); fakeGlobal.XMLHttpRequest('foo'); expect(fakeXmlHttpRequest).toHaveBeenCalledWith('foo'); }); it("clears requests and stubs upon uninstall", () => { const fakeXmlHttpRequest = jasmine.createSpy('fakeXmlHttpRequest'); const fakeGlobal = { XMLHttpRequest: fakeXmlHttpRequest }; const 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", () => { const fakeXmlHttpRequest = jasmine.createSpy('fakeXmlHttpRequest'); const fakeGlobal = { XMLHttpRequest: fakeXmlHttpRequest }; const mockAjax = new MockAjax(fakeGlobal); mockAjax.install(); const request = new (fakeGlobal.XMLHttpRequest as any)(); expect(mockAjax.requests.count()).toBe(1); expect(mockAjax.requests.mostRecent()).toBe(request); }); it("allows the httpRequests to be cleared", () => { const fakeXmlHttpRequest = jasmine.createSpy('fakeXmlHttpRequest'); const fakeGlobal = { XMLHttpRequest: fakeXmlHttpRequest }; const mockAjax = new MockAjax(fakeGlobal); mockAjax.install(); const request = new (fakeGlobal.XMLHttpRequest as any)(); expect(mockAjax.requests.mostRecent()).toBe(request); mockAjax.requests.reset(); expect(mockAjax.requests.count()).toBe(0); }); });