/* global createHttpBackend: false, createMockXhr: false, MockXhr: false */ 'use strict'; describe('$httpBackend', function() { var $backend, $browser, callbacks, xhr, fakeDocument, callback; beforeEach(inject(function($injector) { callbacks = {counter: 0}; $browser = $injector.get('$browser'); fakeDocument = { $$scripts: [], createElement: jasmine.createSpy('createElement').andCallFake(function() { // Return a proper script element... return document.createElement(arguments[0]); }), body: { appendChild: jasmine.createSpy('body.appendChild').andCallFake(function(script) { fakeDocument.$$scripts.push(script); }), removeChild: jasmine.createSpy('body.removeChild').andCallFake(function(script) { var index = fakeDocument.$$scripts.indexOf(script); if (index != -1) { fakeDocument.$$scripts.splice(index, 1); } }) } }; $backend = createHttpBackend($browser, createMockXhr, $browser.defer, callbacks, fakeDocument); callback = jasmine.createSpy('done'); })); it('should do basics - open async xhr and send data', function() { $backend('GET', '/some-url', 'some-data', noop); xhr = MockXhr.$$lastInstance; expect(xhr.$$method).toBe('GET'); expect(xhr.$$url).toBe('/some-url'); expect(xhr.$$data).toBe('some-data'); expect(xhr.$$async).toBe(true); }); it('should pass null to send if no body is set', function() { $backend('GET', '/some-url', null, noop); xhr = MockXhr.$$lastInstance; expect(xhr.$$data).toBe(null); }); it('should call completion function with xhr.statusText if present', function() { callback.andCallFake(function(status, response, headers, statusText) { expect(statusText).toBe('OK'); }); $backend('GET', '/some-url', null, callback); xhr = MockXhr.$$lastInstance; xhr.statusText = 'OK'; xhr.onload(); expect(callback).toHaveBeenCalledOnce(); }); it('should call completion function with empty string if not present', function() { callback.andCallFake(function(status, response, headers, statusText) { expect(statusText).toBe(''); }); $backend('GET', '/some-url', null, callback); xhr = MockXhr.$$lastInstance; xhr.onload(); expect(callback).toHaveBeenCalledOnce(); }); it('should normalize IE\'s 1223 status code into 204', function() { callback.andCallFake(function(status) { expect(status).toBe(204); }); $backend('GET', 'URL', null, callback); xhr = MockXhr.$$lastInstance; xhr.status = 1223; xhr.onload(); expect(callback).toHaveBeenCalledOnce(); }); it('should set only the requested headers', function() { $backend('POST', 'URL', null, noop, {'X-header1': 'value1', 'X-header2': 'value2'}); xhr = MockXhr.$$lastInstance; expect(xhr.$$reqHeaders).toEqual({ 'X-header1': 'value1', 'X-header2': 'value2' }); }); it('should set requested headers even if they have falsy values', function() { $backend('POST', 'URL', null, noop, { 'X-header1': 0, 'X-header2': '', 'X-header3': false, 'X-header4': undefined }); xhr = MockXhr.$$lastInstance; expect(xhr.$$reqHeaders).toEqual({ 'X-header1': 0, 'X-header2': '', 'X-header3': false }); }); it('should not try to read response data when request is aborted', function() { callback.andCallFake(function(status, response, headers) { expect(status).toBe(-1); expect(response).toBe(null); expect(headers).toBe(null); }); $backend('GET', '/url', null, callback, {}, 2000); xhr = MockXhr.$$lastInstance; spyOn(xhr, 'abort'); $browser.defer.flush(); expect(xhr.abort).toHaveBeenCalledOnce(); xhr.status = 0; xhr.onabort(); expect(callback).toHaveBeenCalledOnce(); }); it('should abort request on timeout', function() { callback.andCallFake(function(status, response) { expect(status).toBe(-1); }); $backend('GET', '/url', null, callback, {}, 2000); xhr = MockXhr.$$lastInstance; spyOn(xhr, 'abort'); expect($browser.deferredFns[0].time).toBe(2000); $browser.defer.flush(); expect(xhr.abort).toHaveBeenCalledOnce(); xhr.status = 0; xhr.onabort(); expect(callback).toHaveBeenCalledOnce(); }); it('should abort request on timeout promise resolution', inject(function($timeout) { callback.andCallFake(function(status, response) { expect(status).toBe(-1); }); $backend('GET', '/url', null, callback, {}, $timeout(noop, 2000)); xhr = MockXhr.$$lastInstance; spyOn(xhr, 'abort'); $timeout.flush(); expect(xhr.abort).toHaveBeenCalledOnce(); xhr.status = 0; xhr.onabort(); expect(callback).toHaveBeenCalledOnce(); })); it('should not abort resolved request on timeout promise resolution', inject(function($timeout) { callback.andCallFake(function(status, response) { expect(status).toBe(200); }); $backend('GET', '/url', null, callback, {}, $timeout(noop, 2000)); xhr = MockXhr.$$lastInstance; spyOn(xhr, 'abort'); xhr.status = 200; xhr.onload(); expect(callback).toHaveBeenCalledOnce(); $timeout.flush(); expect(xhr.abort).not.toHaveBeenCalled(); })); it('should cancel timeout on completion', function() { callback.andCallFake(function(status, response) { expect(status).toBe(200); }); $backend('GET', '/url', null, callback, {}, 2000); xhr = MockXhr.$$lastInstance; spyOn(xhr, 'abort'); expect($browser.deferredFns[0].time).toBe(2000); xhr.status = 200; xhr.onload(); expect(callback).toHaveBeenCalledOnce(); expect($browser.deferredFns.length).toBe(0); expect(xhr.abort).not.toHaveBeenCalled(); }); it('should set withCredentials', function() { $backend('GET', '/some.url', null, callback, {}, null, true); expect(MockXhr.$$lastInstance.withCredentials).toBe(true); }); describe('responseType', function() { it('should set responseType and return xhr.response', function() { $backend('GET', '/whatever', null, callback, {}, null, null, 'blob'); var xhrInstance = MockXhr.$$lastInstance; expect(xhrInstance.responseType).toBe('blob'); callback.andCallFake(function(status, response) { expect(response).toBe(xhrInstance.response); }); xhrInstance.response = {some: 'object'}; xhrInstance.onload(); expect(callback).toHaveBeenCalledOnce(); }); it('should read responseText if response was not defined', function() { // old browsers like IE9, don't support responseType, so they always respond with responseText $backend('GET', '/whatever', null, callback, {}, null, null, 'blob'); var xhrInstance = MockXhr.$$lastInstance; var responseText = '{"some": "object"}'; expect(xhrInstance.responseType).toBe('blob'); callback.andCallFake(function(status, response) { expect(response).toBe(responseText); }); xhrInstance.responseText = responseText; xhrInstance.onload(); expect(callback).toHaveBeenCalledOnce(); }); }); describe('JSONP', function() { var SCRIPT_URL = /([^\?]*)\?cb=angular\.callbacks\.(.*)/; it('should add script tag for JSONP request', function() { callback.andCallFake(function(status, response) { expect(status).toBe(200); expect(response).toBe('some-data'); }); $backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, callback); expect(fakeDocument.$$scripts.length).toBe(1); var script = fakeDocument.$$scripts.shift(), url = script.src.match(SCRIPT_URL); expect(url[1]).toBe('http://example.org/path'); callbacks[url[2]]('some-data'); browserTrigger(script, "load"); expect(callback).toHaveBeenCalledOnce(); }); it('should clean up the callback and remove the script', function() { $backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, callback); expect(fakeDocument.$$scripts.length).toBe(1); var script = fakeDocument.$$scripts.shift(), callbackId = script.src.match(SCRIPT_URL)[2]; callbacks[callbackId]('some-data'); browserTrigger(script, "load"); expect(callbacks[callbackId]).toBe(angular.noop); expect(fakeDocument.body.removeChild).toHaveBeenCalledOnceWith(script); }); it('should set url to current location if not specified or empty string', function() { $backend('JSONP', undefined, null, callback); expect(fakeDocument.$$scripts[0].src).toBe($browser.url()); fakeDocument.$$scripts.shift(); $backend('JSONP', '', null, callback); expect(fakeDocument.$$scripts[0].src).toBe($browser.url()); }); it('should abort request on timeout and replace callback with noop', function() { callback.andCallFake(function(status, response) { expect(status).toBe(-1); }); $backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, callback, null, 2000); expect(fakeDocument.$$scripts.length).toBe(1); expect($browser.deferredFns[0].time).toBe(2000); var script = fakeDocument.$$scripts.shift(), callbackId = script.src.match(SCRIPT_URL)[2]; $browser.defer.flush(); expect(fakeDocument.$$scripts.length).toBe(0); expect(callback).toHaveBeenCalledOnce(); expect(callbacks[callbackId]).toBe(angular.noop); }); // TODO(vojta): test whether it fires "async-start" // TODO(vojta): test whether it fires "async-end" on both success and error }); describe('protocols that return 0 status code', function() { function respond(status, content) { xhr = MockXhr.$$lastInstance; xhr.status = status; xhr.responseText = content; xhr.onload(); } it('should convert 0 to 200 if content and file protocol', function() { $backend = createHttpBackend($browser, createMockXhr); $backend('GET', 'file:///whatever/index.html', null, callback); respond(0, 'SOME CONTENT'); expect(callback).toHaveBeenCalled(); expect(callback.mostRecentCall.args[0]).toBe(200); }); it('should convert 0 to 200 if content for protocols other than file', function() { $backend = createHttpBackend($browser, createMockXhr); $backend('GET', 'someProtocol:///whatever/index.html', null, callback); respond(0, 'SOME CONTENT'); expect(callback).toHaveBeenCalled(); expect(callback.mostRecentCall.args[0]).toBe(200); }); it('should convert 0 to 404 if no content and file protocol', function() { $backend = createHttpBackend($browser, createMockXhr); $backend('GET', 'file:///whatever/index.html', null, callback); respond(0, ''); expect(callback).toHaveBeenCalled(); expect(callback.mostRecentCall.args[0]).toBe(404); }); it('should not convert 0 to 404 if no content for protocols other than file', function() { $backend = createHttpBackend($browser, createMockXhr); $backend('GET', 'someProtocol:///whatever/index.html', null, callback); respond(0, ''); expect(callback).toHaveBeenCalled(); expect(callback.mostRecentCall.args[0]).toBe(0); }); it('should convert 0 to 404 if no content - relative url', function() { /* global urlParsingNode: true */ var originalUrlParsingNode = urlParsingNode; //temporarily overriding the DOM element to pretend that the test runs origin with file:// protocol urlParsingNode = { hash: "#/C:/", host: "", hostname: "", href: "file:///C:/base#!/C:/foo", pathname: "/C:/foo", port: "", protocol: "file:", search: "", setAttribute: angular.noop }; try { $backend = createHttpBackend($browser, createMockXhr); $backend('GET', '/whatever/index.html', null, callback); respond(0, ''); expect(callback).toHaveBeenCalled(); expect(callback.mostRecentCall.args[0]).toBe(404); } finally { urlParsingNode = originalUrlParsingNode; } }); it('should return original backend status code if different from 0', function() { $backend = createHttpBackend($browser, createMockXhr); // request to http:// $backend('POST', 'http://rest_api/create_whatever', null, callback); respond(201, ''); expect(callback).toHaveBeenCalled(); expect(callback.mostRecentCall.args[0]).toBe(201); // request to file:// $backend('POST', 'file://rest_api/create_whatever', null, callback); respond(201, ''); expect(callback).toHaveBeenCalled(); expect(callback.mostRecentCall.args[0]).toBe(201); // request to file:// with HTTP status >= 300 $backend('POST', 'file://rest_api/create_whatever', null, callback); respond(503, ''); expect(callback).toHaveBeenCalled(); expect(callback.mostRecentCall.args[0]).toBe(503); }); }); });