mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 07:40:22 +00:00
feat($httpBackend): extract $browser.xhr into separate service
- remove whole $browser.xhr stuff - remove whole mock $browser.xhr stuff - add $httpBackend service + migrate unit tests from $browser - add temporary API to access $browser's outstandingRequests count
This commit is contained in:
parent
540701a8d8
commit
5ad0c7d0e4
5 changed files with 288 additions and 456 deletions
180
src/angular-mocks.js
vendored
180
src/angular-mocks.js
vendored
|
|
@ -57,6 +57,10 @@ angular.module.ngMock.$Browser = function() {
|
|||
self.$$lastUrl = self.$$url; // used by url polling fn
|
||||
self.pollFns = [];
|
||||
|
||||
// TODO(vojta): remove this temporary api
|
||||
self.$$completeOutstandingRequest = noop;
|
||||
self.$$incOutstandingRequestCount = noop;
|
||||
|
||||
|
||||
// register url polling fn
|
||||
|
||||
|
|
@ -73,165 +77,6 @@ angular.module.ngMock.$Browser = function() {
|
|||
return listener;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ngMock.$browser#xhr
|
||||
* @methodOf angular.module.ngMock.$browser
|
||||
*
|
||||
* @description
|
||||
* Generic method for training browser to expect a request in a test and respond to it.
|
||||
*
|
||||
* See also convenience methods for browser training:
|
||||
*
|
||||
* - {@link #xhr.expectGET}
|
||||
* - {@link #xhr.expectPOST}
|
||||
* - {@link #xhr.expectPUT}
|
||||
* - {@link #xhr.expectDELETE}
|
||||
* - {@link #xhr.expectJSON}
|
||||
*
|
||||
* To flush pending requests in tests use
|
||||
* {@link #xhr.flush}.
|
||||
*
|
||||
* @param {string} method Expected HTTP method.
|
||||
* @param {string} url Url path for which a request is expected.
|
||||
* @param {(object|string)=} data Expected body of the (POST) HTTP request.
|
||||
* @param {function(number, *)} callback Callback to call when response is flushed.
|
||||
* @param {object} headers Key-value pairs of expected headers.
|
||||
* @returns {object} Response configuration object. You can call its `respond()` method to
|
||||
* configure what should the browser mock return when the response is
|
||||
* {@link #xhr.flush flushed}.
|
||||
*/
|
||||
self.xhr = function(method, url, data, callback, headers) {
|
||||
headers = headers || {};
|
||||
if (data && angular.isObject(data)) data = angular.toJson(data);
|
||||
if (data && angular.isString(data)) url += "|" + data;
|
||||
var expect = expectations[method] || {};
|
||||
var expectation = expect[url];
|
||||
if (!expectation) {
|
||||
throw new Error("Unexpected request for method '" + method + "' and url '" + url + "'.");
|
||||
}
|
||||
requests.push(function() {
|
||||
angular.forEach(expectation.headers, function(value, key){
|
||||
if (headers[key] !== value) {
|
||||
throw new Error("Missing HTTP request header: " + key + ": " + value);
|
||||
}
|
||||
});
|
||||
callback(expectation.code, expectation.response);
|
||||
});
|
||||
// TODO(vojta): return mock request object
|
||||
};
|
||||
self.xhr.expectations = expectations;
|
||||
self.xhr.requests = requests;
|
||||
self.xhr.expect = function(method, url, data, headers) {
|
||||
if (data && angular.isObject(data)) data = angular.toJson(data);
|
||||
if (data && angular.isString(data)) url += "|" + data;
|
||||
var expect = expectations[method] || (expectations[method] = {});
|
||||
return {
|
||||
respond: function(code, response) {
|
||||
if (!angular.isNumber(code)) {
|
||||
response = code;
|
||||
code = 200;
|
||||
}
|
||||
expect[url] = {code:code, response:response, headers: headers || {}};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ngMock.$browser#xhr.expectGET
|
||||
* @methodOf angular.module.ngMock.$browser
|
||||
*
|
||||
* @description
|
||||
* Trains browser to expect a `GET` request and respond to it.
|
||||
*
|
||||
* @param {string} url Url path for which a request is expected.
|
||||
* @returns {object} Response configuration object. You can call its `respond()` method to
|
||||
* configure what should the browser mock return when the response is
|
||||
* {@link angular.module.ngMock.$browser#xhr.flush flushed}.
|
||||
*/
|
||||
self.xhr.expectGET = angular.bind(self, self.xhr.expect, 'GET');
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ngMock.$browser#xhr.expectPOST
|
||||
* @methodOf angular.module.ngMock.$browser
|
||||
*
|
||||
* @description
|
||||
* Trains browser to expect a `POST` request and respond to it.
|
||||
*
|
||||
* @param {string} url Url path for which a request is expected.
|
||||
* @returns {object} Response configuration object. You can call its `respond()` method to
|
||||
* configure what should the browser mock return when the response is
|
||||
* {@link angular.module.ngMock.$browser#xhr.flush flushed}.
|
||||
*/
|
||||
self.xhr.expectPOST = angular.bind(self, self.xhr.expect, 'POST');
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ngMock.$browser#xhr.expectDELETE
|
||||
* @methodOf angular.module.ngMock.$browser
|
||||
*
|
||||
* @description
|
||||
* Trains browser to expect a `DELETE` request and respond to it.
|
||||
*
|
||||
* @param {string} url Url path for which a request is expected.
|
||||
* @returns {object} Response configuration object. You can call its `respond()` method to
|
||||
* configure what should the browser mock return when the response is
|
||||
* {@link angular.module.ngMock.$browser#xhr.flush flushed}.
|
||||
*/
|
||||
self.xhr.expectDELETE = angular.bind(self, self.xhr.expect, 'DELETE');
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ngMock.$browser#xhr.expectPUT
|
||||
* @methodOf angular.module.ngMock.$browser
|
||||
*
|
||||
* @description
|
||||
* Trains browser to expect a `PUT` request and respond to it.
|
||||
*
|
||||
* @param {string} url Url path for which a request is expected.
|
||||
* @returns {object} Response configuration object. You can call its `respond()` method to
|
||||
* configure what should the browser mock return when the response is
|
||||
* {@link angular.module.ngMock.$browser#xhr.flush flushed}.
|
||||
*/
|
||||
self.xhr.expectPUT = angular.bind(self, self.xhr.expect, 'PUT');
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ngMock.$browser#xhr.expectJSON
|
||||
* @methodOf angular.module.ngMock.$browser
|
||||
*
|
||||
* @description
|
||||
* Trains browser to expect a `JSON` request and respond to it.
|
||||
*
|
||||
* @param {string} url Url path for which a request is expected.
|
||||
* @returns {object} Response configuration object. You can call its `respond()` method to
|
||||
* configure what should the browser mock return when the response is
|
||||
* {@link angular.module.ngMock.$browser#xhr.flush flushed}.
|
||||
*/
|
||||
self.xhr.expectJSON = angular.bind(self, self.xhr.expect, 'JSON');
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ngMock.$browser#xhr.flush
|
||||
* @methodOf angular.module.ngMock.$browser
|
||||
*
|
||||
* @description
|
||||
* Flushes all pending requests and executes xhr callbacks with the trained response as the
|
||||
* argument.
|
||||
*/
|
||||
self.xhr.flush = function() {
|
||||
if (requests.length == 0) {
|
||||
throw new Error("No xhr requests to be flushed!");
|
||||
}
|
||||
|
||||
while(requests.length) {
|
||||
requests.pop()();
|
||||
}
|
||||
};
|
||||
|
||||
self.cookieHash = {};
|
||||
self.lastCookieHash = {};
|
||||
self.deferredFns = [];
|
||||
|
|
@ -871,9 +716,24 @@ function MockHttpExpectation(method, url, data, headers) {
|
|||
|
||||
function MockXhr() {
|
||||
|
||||
// hack for testing $http
|
||||
// hack for testing $http, $httpBackend
|
||||
MockXhr.$$lastInstance = this;
|
||||
|
||||
this.open = function(method, url, async) {
|
||||
this.$$method = method;
|
||||
this.$$url = url;
|
||||
this.$$async = async;
|
||||
this.$$headers = {};
|
||||
};
|
||||
|
||||
this.send = function(data) {
|
||||
this.$$data = data;
|
||||
};
|
||||
|
||||
this.setRequestHeader = function(key, value) {
|
||||
this.$$headers[key] = value;
|
||||
};
|
||||
|
||||
this.getResponseHeader = function(name) {
|
||||
return this.$$headers[name];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,16 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
//////////////////////////////
|
||||
// Browser
|
||||
//////////////////////////////
|
||||
var XHR = window.XMLHttpRequest || function() {
|
||||
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
|
||||
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
|
||||
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
|
||||
throw new Error("This browser does not support XMLHttpRequest.");
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc object
|
||||
* @name angular.module.ng.$browser
|
||||
|
|
@ -33,7 +22,7 @@ var XHR = window.XMLHttpRequest || function() {
|
|||
* @param {object} $log console.log or an object with the same interface.
|
||||
* @param {object} $sniffer $sniffer service
|
||||
*/
|
||||
function Browser(window, document, body, XHR, $log, $sniffer) {
|
||||
function Browser(window, document, body, $log, $sniffer) {
|
||||
var self = this,
|
||||
rawDocument = document[0],
|
||||
location = window.location,
|
||||
|
|
@ -44,13 +33,12 @@ function Browser(window, document, body, XHR, $log, $sniffer) {
|
|||
|
||||
self.isMock = false;
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// XHR API
|
||||
//////////////////////////////////////////////////////////////
|
||||
var idCounter = 0;
|
||||
var outstandingRequestCount = 0;
|
||||
var outstandingRequestCallbacks = [];
|
||||
|
||||
// TODO(vojta): remove this temporary api
|
||||
self.$$completeOutstandingRequest = completeOutstandingRequest;
|
||||
self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
|
||||
|
||||
/**
|
||||
* Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
|
||||
|
|
@ -73,88 +61,6 @@ function Browser(window, document, body, XHR, $log, $sniffer) {
|
|||
}
|
||||
}
|
||||
|
||||
// normalize IE bug (http://bugs.jquery.com/ticket/1450)
|
||||
function fixStatus(status) {
|
||||
return status == 1223 ? 204 : status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ng.$browser#xhr
|
||||
* @methodOf angular.module.ng.$browser
|
||||
*
|
||||
* @param {string} method Requested method (get|post|put|delete|head|json)
|
||||
* @param {string} url Requested url
|
||||
* @param {?string} post Post data to send (null if nothing to post)
|
||||
* @param {function(number, string)} callback Function that will be called on response
|
||||
* @param {object=} header additional HTTP headers to send with XHR.
|
||||
* Standard headers are:
|
||||
* <ul>
|
||||
* <li><tt>Content-Type</tt>: <tt>application/x-www-form-urlencoded</tt></li>
|
||||
* <li><tt>Accept</tt>: <tt>application/json, text/plain, */*</tt></li>
|
||||
* <li><tt>X-Requested-With</tt>: <tt>XMLHttpRequest</tt></li>
|
||||
* </ul>
|
||||
*
|
||||
* @param {number=} timeout Timeout in ms, when the request will be aborted
|
||||
* @returns {XMLHttpRequest|undefined} Raw XMLHttpRequest object or undefined when JSONP method
|
||||
*
|
||||
* @description
|
||||
* Send ajax request
|
||||
*
|
||||
* TODO(vojta): change signature of this method to (method, url, data, headers, callback)
|
||||
*/
|
||||
self.xhr = function(method, url, post, callback, headers, timeout) {
|
||||
outstandingRequestCount ++;
|
||||
if (lowercase(method) == 'jsonp') {
|
||||
var callbackId = ("angular_" + Math.random() + '_' + (idCounter++)).replace(/\d\./, '');
|
||||
window[callbackId] = function(data) {
|
||||
window[callbackId].data = data;
|
||||
};
|
||||
|
||||
var script = self.addJs(url.replace('JSON_CALLBACK', callbackId), function() {
|
||||
if (window[callbackId].data) {
|
||||
completeOutstandingRequest(callback, 200, window[callbackId].data);
|
||||
} else {
|
||||
completeOutstandingRequest(callback, -2);
|
||||
}
|
||||
delete window[callbackId];
|
||||
body[0].removeChild(script);
|
||||
});
|
||||
} else {
|
||||
var xhr = new XHR();
|
||||
xhr.open(method, url, true);
|
||||
forEach(headers, function(value, key) {
|
||||
if (value) xhr.setRequestHeader(key, value);
|
||||
});
|
||||
|
||||
var status;
|
||||
xhr.send(post || '');
|
||||
|
||||
// IE6, IE7 bug - does sync when serving from cache
|
||||
if (xhr.readyState == 4) {
|
||||
setTimeout(function() {
|
||||
completeOutstandingRequest(callback, fixStatus(status || xhr.status), xhr.responseText);
|
||||
}, 0);
|
||||
} else {
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4) {
|
||||
completeOutstandingRequest(callback, fixStatus(status || xhr.status),
|
||||
xhr.responseText);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (timeout > 0) {
|
||||
setTimeout(function() {
|
||||
status = -1;
|
||||
xhr.abort();
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
return xhr;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Note: this method is used only by scenario runner
|
||||
|
|
@ -502,6 +408,6 @@ function Browser(window, document, body, XHR, $log, $sniffer) {
|
|||
function $BrowserProvider(){
|
||||
this.$get = ['$window', '$log', '$sniffer', '$document',
|
||||
function( $window, $log, $sniffer, $document){
|
||||
return new Browser($window, $document, $document.find('body'), XHR, $log, $sniffer);
|
||||
return new Browser($window, $document, $document.find('body'), $log, $sniffer);
|
||||
}];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,86 @@
|
|||
var XHR = window.XMLHttpRequest || function() {
|
||||
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
|
||||
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
|
||||
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
|
||||
throw new Error("This browser does not support XMLHttpRequest.");
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc object
|
||||
* @name angular.module.ng.$httpBackend
|
||||
* @requires $browser
|
||||
* @requires $window
|
||||
* @requires $document
|
||||
*
|
||||
* @description
|
||||
*/
|
||||
function $HttpBackendProvider() {
|
||||
this.$get = ['$browser', function($browser) {
|
||||
return $browser.xhr;
|
||||
this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) {
|
||||
return createHttpBackend($browser, XHR, $browser.defer, $window, $document[0].body);
|
||||
}];
|
||||
}
|
||||
|
||||
function createHttpBackend($browser, XHR, $browserDefer, $window, body) {
|
||||
var idCounter = 0;
|
||||
|
||||
function completeRequest(callback, status, response) {
|
||||
// normalize IE bug (http://bugs.jquery.com/ticket/1450)
|
||||
callback(status == 1223 ? 204 : status, response);
|
||||
$browser.$$completeOutstandingRequest(noop);
|
||||
}
|
||||
|
||||
// TODO(vojta): fix the signature
|
||||
return function(method, url, post, callback, headers, timeout) {
|
||||
$browser.$$incOutstandingRequestCount();
|
||||
|
||||
if (lowercase(method) == 'jsonp') {
|
||||
var callbackId = ('angular_' + Math.random() + '_' + (idCounter++)).replace(/\d\./, '');
|
||||
$window[callbackId] = function(data) {
|
||||
$window[callbackId].data = data;
|
||||
};
|
||||
|
||||
var script = $browser.addJs(url.replace('JSON_CALLBACK', callbackId), null, function() {
|
||||
if ($window[callbackId].data) {
|
||||
completeRequest(callback, 200, $window[callbackId].data);
|
||||
} else {
|
||||
completeRequest(callback, -2);
|
||||
}
|
||||
delete $window[callbackId];
|
||||
body.removeChild(script);
|
||||
});
|
||||
} else {
|
||||
var xhr = new XHR();
|
||||
xhr.open(method, url, true);
|
||||
forEach(headers, function(value, key) {
|
||||
if (value) xhr.setRequestHeader(key, value);
|
||||
});
|
||||
|
||||
var status;
|
||||
xhr.send(post || '');
|
||||
|
||||
// IE6, IE7 bug - does sync when serving from cache
|
||||
if (xhr.readyState == 4) {
|
||||
$browserDefer(function() {
|
||||
completeRequest(callback, status || xhr.status, xhr.responseText);
|
||||
}, 0);
|
||||
} else {
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4) {
|
||||
completeRequest(callback, status || xhr.status, xhr.responseText);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (timeout > 0) {
|
||||
$browserDefer(function() {
|
||||
status = -1;
|
||||
xhr.abort();
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
return xhr;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,34 +48,17 @@ function MockWindow() {
|
|||
|
||||
describe('browser', function() {
|
||||
|
||||
var browser, fakeWindow, xhr, logs, scripts, removedScripts, sniffer;
|
||||
var browser, fakeWindow, logs, scripts, removedScripts, sniffer;
|
||||
|
||||
beforeEach(function() {
|
||||
scripts = [];
|
||||
removedScripts = [];
|
||||
xhr = null;
|
||||
sniffer = {history: true, hashchange: true};
|
||||
fakeWindow = new MockWindow();
|
||||
|
||||
var fakeBody = [{appendChild: function(node){scripts.push(node);},
|
||||
removeChild: function(node){removedScripts.push(node);}}];
|
||||
|
||||
var FakeXhr = function() {
|
||||
xhr = this;
|
||||
this.open = function(method, url, async){
|
||||
xhr.method = method;
|
||||
xhr.url = url;
|
||||
xhr.async = async;
|
||||
xhr.headers = {};
|
||||
};
|
||||
this.setRequestHeader = function(key, value){
|
||||
xhr.headers[key] = value;
|
||||
};
|
||||
this.send = function(post){
|
||||
xhr.post = post;
|
||||
};
|
||||
};
|
||||
|
||||
logs = {log:[], warn:[], info:[], error:[]};
|
||||
|
||||
var fakeLog = {log: function() { logs.log.push(slice.call(arguments)); },
|
||||
|
|
@ -83,8 +66,7 @@ describe('browser', function() {
|
|||
info: function() { logs.info.push(slice.call(arguments)); },
|
||||
error: function() { logs.error.push(slice.call(arguments)); }};
|
||||
|
||||
browser = new Browser(fakeWindow, jqLite(window.document), fakeBody, FakeXhr,
|
||||
fakeLog, sniffer);
|
||||
browser = new Browser(fakeWindow, jqLite(window.document), fakeBody, fakeLog, sniffer);
|
||||
});
|
||||
|
||||
it('should contain cookie cruncher', function() {
|
||||
|
|
@ -97,183 +79,8 @@ describe('browser', function() {
|
|||
browser.notifyWhenNoOutstandingRequests(callback);
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should queue callbacks with outstanding requests', function() {
|
||||
var callback = jasmine.createSpy('callback');
|
||||
browser.xhr('GET', '/url', null, noop);
|
||||
browser.notifyWhenNoOutstandingRequests(callback);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
xhr.readyState = 4;
|
||||
xhr.onreadystatechange();
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('xhr', function() {
|
||||
describe('JSONP', function() {
|
||||
var log;
|
||||
|
||||
function callback(code, data) {
|
||||
log += code + ':' + data + ';';
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
log = "";
|
||||
});
|
||||
|
||||
|
||||
// We don't have unit tests for IE because script.readyState is readOnly.
|
||||
// Instead we run e2e tests on all browsers - see e2e for $http.
|
||||
if (!msie) {
|
||||
|
||||
it('should add script tag for JSONP request', function() {
|
||||
var notify = jasmine.createSpy('notify');
|
||||
browser.xhr('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, callback);
|
||||
browser.notifyWhenNoOutstandingRequests(notify);
|
||||
expect(notify).not.toHaveBeenCalled();
|
||||
expect(scripts.length).toEqual(1);
|
||||
var script = scripts[0];
|
||||
var url = script.src.split('?cb=');
|
||||
expect(url[0]).toEqual('http://example.org/path');
|
||||
expect(typeof fakeWindow[url[1]]).toEqual('function');
|
||||
fakeWindow[url[1]]('data');
|
||||
script.onload();
|
||||
|
||||
expect(notify).toHaveBeenCalled();
|
||||
expect(log).toEqual('200:data;');
|
||||
expect(scripts).toEqual(removedScripts);
|
||||
expect(fakeWindow[url[1]]).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
it('should call callback with status -2 when script fails to load', function() {
|
||||
browser.xhr('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, callback);
|
||||
var script = scripts[0];
|
||||
expect(typeof script.onload).toBe('function');
|
||||
expect(typeof script.onerror).toBe('function');
|
||||
script.onerror();
|
||||
|
||||
expect(log).toEqual('-2:undefined;');
|
||||
});
|
||||
|
||||
|
||||
it('should update the outstandingRequests counter for successful requests', function() {
|
||||
var notify = jasmine.createSpy('notify');
|
||||
browser.xhr('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, callback);
|
||||
browser.notifyWhenNoOutstandingRequests(notify);
|
||||
expect(notify).not.toHaveBeenCalled();
|
||||
|
||||
var script = scripts[0];
|
||||
var url = script.src.split('?cb=');
|
||||
fakeWindow[url[1]]('data');
|
||||
script.onload();
|
||||
|
||||
expect(notify).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it('should update the outstandingRequests counter for failed requests', function() {
|
||||
var notify = jasmine.createSpy('notify');
|
||||
browser.xhr('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, callback);
|
||||
browser.notifyWhenNoOutstandingRequests(notify);
|
||||
expect(notify).not.toHaveBeenCalled();
|
||||
|
||||
scripts[0].onerror();
|
||||
|
||||
expect(notify).toHaveBeenCalled();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it('should normalize IE\'s 1223 status code into 204', function() {
|
||||
var callback = jasmine.createSpy('XHR');
|
||||
|
||||
browser.xhr('GET', 'URL', 'POST', callback);
|
||||
|
||||
xhr.status = 1223;
|
||||
xhr.readyState = 4;
|
||||
xhr.onreadystatechange();
|
||||
|
||||
expect(callback).toHaveBeenCalled();
|
||||
expect(callback.argsForCall[0][0]).toEqual(204);
|
||||
});
|
||||
|
||||
it('should set only the requested headers', function() {
|
||||
var code, response, headers = {};
|
||||
browser.xhr('POST', 'URL', null, function(c,r){
|
||||
code = c;
|
||||
response = r;
|
||||
}, {'X-header1': 'value1', 'X-header2': 'value2'});
|
||||
|
||||
expect(xhr.method).toEqual('POST');
|
||||
expect(xhr.url).toEqual('URL');
|
||||
expect(xhr.post).toEqual('');
|
||||
expect(xhr.headers).toEqual({
|
||||
"X-header1":"value1",
|
||||
"X-header2":"value2"
|
||||
});
|
||||
|
||||
xhr.status = 202;
|
||||
xhr.responseText = 'RESPONSE';
|
||||
xhr.readyState = 4;
|
||||
xhr.onreadystatechange();
|
||||
|
||||
expect(code).toEqual(202);
|
||||
expect(response).toEqual('RESPONSE');
|
||||
});
|
||||
|
||||
it('should return raw xhr object', function() {
|
||||
expect(browser.xhr('GET', '/url', null, noop)).toBe(xhr);
|
||||
});
|
||||
|
||||
it('should abort request on timeout', function() {
|
||||
var callback = jasmine.createSpy('done').andCallFake(function(status, response) {
|
||||
expect(status).toBe(-1);
|
||||
});
|
||||
|
||||
browser.xhr('GET', '/url', null, callback, {}, 2000);
|
||||
xhr.abort = jasmine.createSpy('xhr.abort');
|
||||
|
||||
fakeWindow.setTimeout.flush();
|
||||
expect(xhr.abort).toHaveBeenCalledOnce();
|
||||
|
||||
xhr.status = 0;
|
||||
xhr.readyState = 4;
|
||||
xhr.onreadystatechange();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should be async even if xhr.send() is sync', function() {
|
||||
// IE6, IE7 is sync when serving from cache
|
||||
var xhr;
|
||||
function FakeXhr() {
|
||||
xhr = this;
|
||||
this.open = this.setRequestHeader = noop;
|
||||
this.send = function() {
|
||||
this.status = 200;
|
||||
this.responseText = 'response';
|
||||
this.readyState = 4;
|
||||
};
|
||||
}
|
||||
|
||||
var callback = jasmine.createSpy('done').andCallFake(function(status, response) {
|
||||
expect(status).toBe(200);
|
||||
expect(response).toBe('response');
|
||||
});
|
||||
|
||||
browser = new Browser(fakeWindow, jqLite(window.document), null, FakeXhr, null);
|
||||
browser.xhr('GET', '/url', null, callback);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
fakeWindow.setTimeout.flush();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
|
||||
(xhr.onreadystatechange || noop)();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
describe('defer', function() {
|
||||
it('should execute fn asynchroniously via setTimeout', function() {
|
||||
|
|
|
|||
179
test/service/httpBackendSpec.js
Normal file
179
test/service/httpBackendSpec.js
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
describe('$httpBackend', function() {
|
||||
|
||||
var $backend, $browser, $window,
|
||||
xhr, fakeBody, callback;
|
||||
|
||||
// TODO(vojta): should be replaced by $defer mock
|
||||
function fakeTimeout(fn, delay) {
|
||||
fakeTimeout.fns.push(fn);
|
||||
fakeTimeout.delays.push(delay);
|
||||
}
|
||||
|
||||
fakeTimeout.fns = [];
|
||||
fakeTimeout.delays = [];
|
||||
fakeTimeout.flush = function() {
|
||||
var len = fakeTimeout.fns.length;
|
||||
fakeTimeout.delays = [];
|
||||
while (len--) fakeTimeout.fns.shift()();
|
||||
};
|
||||
|
||||
|
||||
beforeEach(inject(function($injector) {
|
||||
$window = {};
|
||||
$browser = $injector.get('$browser');
|
||||
fakeBody = {removeChild: jasmine.createSpy('body.removeChild')};
|
||||
$backend = createHttpBackend($browser, MockXhr, fakeTimeout, $window, fakeBody);
|
||||
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 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.readyState = 4;
|
||||
xhr.onreadystatechange();
|
||||
|
||||
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.$$headers).toEqual({
|
||||
'X-header1': 'value1',
|
||||
'X-header2': 'value2'
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should return raw xhr object', function() {
|
||||
expect($backend('GET', '/url', null, noop)).toBe(MockXhr.$$lastInstance);
|
||||
});
|
||||
|
||||
|
||||
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(fakeTimeout.delays[0]).toBe(2000);
|
||||
|
||||
fakeTimeout.flush();
|
||||
expect(xhr.abort).toHaveBeenCalledOnce();
|
||||
|
||||
xhr.status = 0;
|
||||
xhr.readyState = 4;
|
||||
xhr.onreadystatechange();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
it('should be async even if xhr.send() is sync', function() {
|
||||
// IE6, IE7 is sync when serving from cache
|
||||
function SyncXhr() {
|
||||
xhr = this;
|
||||
this.open = this.setRequestHeader = noop;
|
||||
this.send = function() {
|
||||
this.status = 200;
|
||||
this.responseText = 'response';
|
||||
this.readyState = 4;
|
||||
};
|
||||
}
|
||||
|
||||
callback.andCallFake(function(status, response) {
|
||||
expect(status).toBe(200);
|
||||
expect(response).toBe('response');
|
||||
});
|
||||
|
||||
$backend = createHttpBackend($browser, SyncXhr, fakeTimeout);
|
||||
$backend('GET', '/url', null, callback);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
fakeTimeout.flush();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
|
||||
(xhr.onreadystatechange || noop)();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
describe('JSONP', function() {
|
||||
|
||||
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($browser.$$scripts.length).toBe(1);
|
||||
|
||||
var script = $browser.$$scripts.shift(),
|
||||
url = script.url.split('?cb=');
|
||||
|
||||
expect(url[0]).toBe('http://example.org/path');
|
||||
$window[url[1]]('some-data');
|
||||
script.done();
|
||||
|
||||
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($browser.$$scripts.length).toBe(1);
|
||||
|
||||
var script = $browser.$$scripts.shift(),
|
||||
callbackId = script.url.split('?cb=')[1];
|
||||
|
||||
$window[callbackId]('some-data');
|
||||
script.done();
|
||||
|
||||
expect($window[callbackId]).toBeUndefined();
|
||||
expect(fakeBody.removeChild).toHaveBeenCalledOnce();
|
||||
expect(fakeBody.removeChild).toHaveBeenCalledWith(script);
|
||||
});
|
||||
|
||||
|
||||
it('should call callback with status -2 when script fails to load', function() {
|
||||
callback.andCallFake(function(status, response) {
|
||||
expect(status).toBe(-2);
|
||||
expect(response).toBeUndefined();
|
||||
});
|
||||
|
||||
$backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, callback);
|
||||
expect($browser.$$scripts.length).toBe(1);
|
||||
|
||||
$browser.$$scripts.shift().done();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
// TODO(vojta): test whether it fires "async-start"
|
||||
// TODO(vojta): test whether it fires "async-end" on both success and error
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in a new issue