mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-23 09:50:24 +00:00
fix($http): fix and cleanup $http and friends
$http: - use promises internally - get rid of XhrFuture that was previously used internally - get rid of $browser.defer calls for async stuff (serving from cache), promises will take care of asynchronicity - fix transformation bugs (when caching requested + multiple request pending + error is returned) - get rid of native header parsing and instead just lazily parse the header string $httpBackend: - don't return raw/mock XMLHttpRequest object (we don't use it for anything anymore) - call the callback with response headers string mock $httpBackend: - unify response api for expect and when - call the callback with response headers string - changed the expect/when failure error message so that EXPECTED and GOT values are aligned Conflicts: src/service/http.js test/service/compilerSpec.js test/service/httpSpec.js
This commit is contained in:
parent
63cca9afbc
commit
a13b5ed3bc
7 changed files with 578 additions and 662 deletions
57
src/angular-mocks.js
vendored
57
src/angular-mocks.js
vendored
|
|
@ -48,9 +48,7 @@ angular.module.ngMock.$BrowserProvider = function(){
|
|||
};
|
||||
};
|
||||
angular.module.ngMock.$Browser = function() {
|
||||
var self = this,
|
||||
expectations = {},
|
||||
requests = [];
|
||||
var self = this;
|
||||
|
||||
this.isMock = true;
|
||||
self.$$url = "http://server";
|
||||
|
|
@ -590,6 +588,10 @@ angular.module.ngMock.dump = function(object){
|
|||
/**
|
||||
* @ngdoc object
|
||||
* @name angular.module.ngMock.$httpBackend
|
||||
* @describe
|
||||
* Fake HTTP backend used by the $http service during testing. This implementation can be used to
|
||||
* respond with static or dynamic responses via the `expect` and `when` apis and their shortcuts
|
||||
* (`expectGET`, `whenPOST`, etc).
|
||||
*/
|
||||
angular.module.ngMock.$HttpBackendProvider = function() {
|
||||
this.$get = function() {
|
||||
|
|
@ -598,7 +600,13 @@ angular.module.ngMock.$HttpBackendProvider = function() {
|
|||
responses = [];
|
||||
|
||||
function createResponse(status, data, headers) {
|
||||
return angular.isNumber(status) ? [status, data, headers] : [200, status, data];
|
||||
if (isFunction(status)) return status;
|
||||
|
||||
return function() {
|
||||
return angular.isNumber(status)
|
||||
? [status, data, headers]
|
||||
: [200, status, data];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(vojta): change params to: method, url, data, headers, callback
|
||||
|
|
@ -608,28 +616,29 @@ angular.module.ngMock.$HttpBackendProvider = function() {
|
|||
wasExpected = false;
|
||||
|
||||
function prettyPrint(data) {
|
||||
if (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp)
|
||||
return data;
|
||||
return angular.toJson(data);
|
||||
return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp)
|
||||
? data
|
||||
: angular.toJson(data);
|
||||
}
|
||||
|
||||
if (expectation && expectation.match(method, url)) {
|
||||
if (!expectation.matchData(data))
|
||||
throw Error('Expected ' + expectation + ' with different data\n' +
|
||||
'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data);
|
||||
'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data);
|
||||
|
||||
if (!expectation.matchHeaders(headers))
|
||||
throw Error('Expected ' + expectation + ' with different headers\n' +
|
||||
'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + prettyPrint(headers));
|
||||
'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + prettyPrint(headers));
|
||||
|
||||
expectations.shift();
|
||||
|
||||
if (expectation.response) {
|
||||
responses.push(function() {
|
||||
xhr.$$headers = expectation.response[2];
|
||||
callback(expectation.response[0], expectation.response[1]);
|
||||
var response = expectation.response(method, url, data, headers);
|
||||
xhr.$$respHeaders = response[2];
|
||||
callback(response[0], response[1], xhr.getAllResponseHeaders());
|
||||
});
|
||||
return method == 'JSONP' ? undefined : xhr;
|
||||
return;
|
||||
}
|
||||
wasExpected = true;
|
||||
}
|
||||
|
|
@ -639,12 +648,11 @@ angular.module.ngMock.$HttpBackendProvider = function() {
|
|||
if (definition.match(method, url, data, headers || {})) {
|
||||
if (!definition.response) throw Error('No response defined !');
|
||||
responses.push(function() {
|
||||
var response = angular.isFunction(definition.response) ?
|
||||
definition.response(method, url, data, headers) : definition.response;
|
||||
xhr.$$headers = response[2];
|
||||
callback(response[0], response[1]);
|
||||
var response = definition.response(method, url, data, headers);
|
||||
xhr.$$respHeaders = response[2];
|
||||
callback(response[0], response[1], xhr.getAllResponseHeaders());
|
||||
});
|
||||
return method == 'JSONP' ? undefined : xhr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw wasExpected ?
|
||||
|
|
@ -658,7 +666,7 @@ angular.module.ngMock.$HttpBackendProvider = function() {
|
|||
definitions.push(definition);
|
||||
return {
|
||||
respond: function(status, data, headers) {
|
||||
definition.response = angular.isFunction(status) ? status : createResponse(status, data, headers);
|
||||
definition.response = createResponse(status, data, headers);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -756,7 +764,8 @@ function MockXhr() {
|
|||
this.$$method = method;
|
||||
this.$$url = url;
|
||||
this.$$async = async;
|
||||
this.$$headers = {};
|
||||
this.$$reqHeaders = {};
|
||||
this.$$respHeaders = {};
|
||||
};
|
||||
|
||||
this.send = function(data) {
|
||||
|
|
@ -764,20 +773,20 @@ function MockXhr() {
|
|||
};
|
||||
|
||||
this.setRequestHeader = function(key, value) {
|
||||
this.$$headers[key] = value;
|
||||
this.$$reqHeaders[key] = value;
|
||||
};
|
||||
|
||||
this.getResponseHeader = function(name) {
|
||||
// the lookup must be case insensitive, that's why we try two quick lookups and full scan at last
|
||||
var header = this.$$headers[name];
|
||||
var header = this.$$respHeaders[name];
|
||||
if (header) return header;
|
||||
|
||||
name = angular.lowercase(name);
|
||||
header = this.$$headers[name];
|
||||
header = this.$$respHeaders[name];
|
||||
if (header) return header;
|
||||
|
||||
header = undefined;
|
||||
angular.forEach(this.$$headers, function(headerVal, headerName) {
|
||||
angular.forEach(this.$$respHeaders, function(headerVal, headerName) {
|
||||
if (!header && angular.lowercase(headerName) == name) header = headerVal;
|
||||
});
|
||||
return header;
|
||||
|
|
@ -786,7 +795,7 @@ function MockXhr() {
|
|||
this.getAllResponseHeaders = function() {
|
||||
var lines = [];
|
||||
|
||||
angular.forEach(this.$$headers, function(value, key) {
|
||||
angular.forEach(this.$$respHeaders, function(value, key) {
|
||||
lines.push(key + ': ' + value);
|
||||
});
|
||||
return lines.join('\n');
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@
|
|||
* Parse headers into key value object
|
||||
*
|
||||
* @param {string} headers Raw headers as a string
|
||||
* @returns {Object} Parsed headers as key valu object
|
||||
* @returns {Object} Parsed headers as key value object
|
||||
*/
|
||||
function parseHeaders(headers) {
|
||||
var parsed = {}, key, val, i;
|
||||
|
||||
if (!headers) return parsed;
|
||||
|
||||
forEach(headers.split('\n'), function(line) {
|
||||
i = line.indexOf(':');
|
||||
key = lowercase(trim(line.substr(0, i)));
|
||||
|
|
@ -26,6 +28,34 @@ function parseHeaders(headers) {
|
|||
return parsed;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a function that provides access to parsed headers.
|
||||
*
|
||||
* Headers are lazy parsed when first requested.
|
||||
* @see parseHeaders
|
||||
*
|
||||
* @param {(string|Object)} headers Headers to provide access to.
|
||||
* @returns {function(string=)} Returns a getter function which if called with:
|
||||
*
|
||||
* - if called with single an argument returns a single header value or null
|
||||
* - if called with no arguments returns an object containing all headers.
|
||||
*/
|
||||
function headersGetter(headersString) {
|
||||
var headers = isObject(headersString) ? headersString : undefined;
|
||||
|
||||
return function(name) {
|
||||
if (!headers) headers = parseHeaders(headersString);
|
||||
|
||||
if (name) {
|
||||
return headers[lowercase(name)] || null;
|
||||
}
|
||||
|
||||
return headers;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Chain all given functions
|
||||
*
|
||||
|
|
@ -36,7 +66,7 @@ function parseHeaders(headers) {
|
|||
* @param {*=} param Optional parameter to be passed to all transform functions.
|
||||
* @returns {*} Transformed data.
|
||||
*/
|
||||
function transform(data, fns, param) {
|
||||
function transformData(data, fns, param) {
|
||||
if (isFunction(fns))
|
||||
return fns(data);
|
||||
|
||||
|
|
@ -48,13 +78,18 @@ function transform(data, fns, param) {
|
|||
}
|
||||
|
||||
|
||||
function isSuccess(status) {
|
||||
return 200 <= status && status < 300;
|
||||
}
|
||||
|
||||
|
||||
function $HttpProvider() {
|
||||
var JSON_START = /^\s*(\[|\{[^\{])/,
|
||||
JSON_END = /[\}\]]\s*$/,
|
||||
PROTECTION_PREFIX = /^\)\]\}',?\n/;
|
||||
|
||||
var $config = this.defaults = {
|
||||
// transform in-coming reponse data
|
||||
// transform incoming response data
|
||||
transformResponse: function(data) {
|
||||
if (isString(data)) {
|
||||
// strip json vulnerability protection prefix
|
||||
|
|
@ -65,7 +100,7 @@ function $HttpProvider() {
|
|||
return data;
|
||||
},
|
||||
|
||||
// transform out-going request data
|
||||
// transform outgoing request data
|
||||
transformRequest: function(d) {
|
||||
return isObject(d) ? toJson(d) : d;
|
||||
},
|
||||
|
|
@ -81,431 +116,328 @@ function $HttpProvider() {
|
|||
}
|
||||
};
|
||||
|
||||
var responseInterceptors = this.responseInterceptors = [];
|
||||
var providerResponseInterceptors = this.responseInterceptors = [];
|
||||
|
||||
this.$get = ['$httpBackend', '$browser', '$exceptionHandler', '$cacheFactory', '$rootScope', '$q', '$injector',
|
||||
function($httpBackend, $browser, $exceptionHandler, $cacheFactory, $rootScope, $q, $injector) {
|
||||
|
||||
var defaultCache = $cacheFactory('$http');
|
||||
var defaultCache = $cacheFactory('$http'),
|
||||
responseInterceptors = [];
|
||||
|
||||
forEach(responseInterceptors, function(interceptor, index) {
|
||||
if (isString(interceptor)) {
|
||||
responseInterceptors[index] = $injector.get(interceptor);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.module.ng.$http
|
||||
* @requires $httpBacked
|
||||
* @requires $browser
|
||||
* @requires $exceptionHandler
|
||||
* @requires $cacheFactory
|
||||
*
|
||||
* @param {object} config Object describing the request to be made and how it should be processed.
|
||||
* The object has following properties:
|
||||
*
|
||||
* - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
|
||||
* - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
|
||||
* - **data** – `{string|Object}` – Data to be sent as the request message data.
|
||||
* - **headers** – `{Object}` – Map of strings representing HTTP headers to send to the server.
|
||||
* - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
|
||||
* GET request, otherwise if a cache instance built with $cacheFactory, this cache will be
|
||||
* used for caching.
|
||||
*
|
||||
* @returns {HttpPromise} Returns a promise object with the standard `then` method and two http
|
||||
* specific methods: `success` and `error`. The `then` method takes two arguments a success and
|
||||
* an error callback which will be called with a response object. The `success` and `error`
|
||||
* methods take a single argument - a function that will be called when the request succeeds or
|
||||
* fails respectively. The arguments passed into these functions are destructured representation
|
||||
* of the response object passed into the `then` method. The response object has these
|
||||
* properties:
|
||||
*
|
||||
* - **data** – `{string|Object}` – The response body transformed with the transform functions.
|
||||
* - **status** – `{number}` – HTTP status code of the response.
|
||||
* - **headers** – `{function([headerName])}` – Header getter function.
|
||||
* - **config** – `{Object}` – The configuration object that was used to generate the request.
|
||||
*
|
||||
* @property {Array.<Object>} pendingRequests Array of config objects for pending requests.
|
||||
* This is primarily meant to be used for debugging purposes.
|
||||
*
|
||||
* @description
|
||||
* $http is a service through which XHR and JSONP requests can be made.
|
||||
*/
|
||||
function $http(config) {
|
||||
var req = new XhrFuture().send(config),
|
||||
deferredResp = $q.defer(),
|
||||
promise = deferredResp.promise;
|
||||
|
||||
forEach(responseInterceptors, function(interceptor) {
|
||||
promise = interceptor(promise);
|
||||
forEach(providerResponseInterceptors, function(interceptor) {
|
||||
responseInterceptors.push(isString(interceptor) ? $injector.get(interceptor) : interceptor);
|
||||
});
|
||||
|
||||
promise.success = function(fn) {
|
||||
promise.then(function(response) {
|
||||
fn(response.data, response.status, response.headers, config);
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
promise.error = function(fn) {
|
||||
promise.then(null, function(response) {
|
||||
fn(response.data, response.status, response.headers, config);
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
req.on('success', function(data, status, headers) {
|
||||
deferredResp.resolve({data: data, status: status, headers: headers, config: config});
|
||||
}).on('error', function(data, status, headers) {
|
||||
deferredResp.reject({data: data, status: status, headers: headers, config: config});
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
$http.pendingRequests = [];
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ng.$http#get
|
||||
* @methodOf angular.module.ng.$http
|
||||
*
|
||||
* @description
|
||||
* Shortcut method to perform `GET` request
|
||||
*
|
||||
* @param {string} url Relative or absolute URL specifying the destination of the request
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {HttpPromise} Future object
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ng.$http#delete
|
||||
* @methodOf angular.module.ng.$http
|
||||
*
|
||||
* @description
|
||||
* Shortcut method to perform `DELETE` request
|
||||
*
|
||||
* @param {string} url Relative or absolute URL specifying the destination of the request
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {HttpPromise} Future object
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ng.$http#head
|
||||
* @methodOf angular.module.ng.$http
|
||||
*
|
||||
* @description
|
||||
* Shortcut method to perform `HEAD` request
|
||||
*
|
||||
* @param {string} url Relative or absolute URL specifying the destination of the request
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {XhrFuture} Future object
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ng.$http#patch
|
||||
* @methodOf angular.module.ng.$http
|
||||
*
|
||||
* @description
|
||||
* Shortcut method to perform `PATCH` request
|
||||
*
|
||||
* @param {string} url Relative or absolute URL specifying the destination of the request
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {HttpPromise} Future object
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ng.$http#jsonp
|
||||
* @methodOf angular.module.ng.$http
|
||||
*
|
||||
* @description
|
||||
* Shortcut method to perform `JSONP` request
|
||||
*
|
||||
* @param {string} url Relative or absolute URL specifying the destination of the request.
|
||||
* Should contain `JSON_CALLBACK` string.
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {XhrFuture} Future object
|
||||
*/
|
||||
createShortMethods('get', 'delete', 'head', 'patch', 'jsonp');
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ng.$http#post
|
||||
* @methodOf angular.module.ng.$http
|
||||
*
|
||||
* @description
|
||||
* Shortcut method to perform `POST` request
|
||||
*
|
||||
* @param {string} url Relative or absolute URL specifying the destination of the request
|
||||
* @param {*} data Request content
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {HttpPromise} Future object
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ng.$http#put
|
||||
* @methodOf angular.module.ng.$http
|
||||
*
|
||||
* @description
|
||||
* Shortcut method to perform `PUT` request
|
||||
*
|
||||
* @param {string} url Relative or absolute URL specifying the destination of the request
|
||||
* @param {*} data Request content
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {XhrFuture} Future object
|
||||
*/
|
||||
createShortMethodsWithData('post', 'put');
|
||||
|
||||
return $http;
|
||||
|
||||
function createShortMethods(names) {
|
||||
forEach(arguments, function(name) {
|
||||
$http[name] = function(url, config) {
|
||||
return $http(extend(config || {}, {
|
||||
method: name,
|
||||
url: url
|
||||
}));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function createShortMethodsWithData(name) {
|
||||
forEach(arguments, function(name) {
|
||||
$http[name] = function(url, data, config) {
|
||||
return $http(extend(config || {}, {
|
||||
method: name,
|
||||
url: url,
|
||||
data: data
|
||||
}));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents Request object, returned by $http()
|
||||
*
|
||||
* !!! ACCESSES CLOSURE VARS:
|
||||
* $httpBackend, $browser, $config, $log, $rootScope, defaultCache, $http.pendingRequests
|
||||
*/
|
||||
function XhrFuture() {
|
||||
var rawRequest, parsedHeaders,
|
||||
cfg = {}, callbacks = [],
|
||||
defHeaders = $config.headers,
|
||||
self = this;
|
||||
|
||||
/**
|
||||
* Callback registered to $httpBackend():
|
||||
* - caches the response if desired
|
||||
* - calls fireCallbacks()
|
||||
* - clears the reference to raw request object
|
||||
* @ngdoc function
|
||||
* @name angular.module.ng.$http
|
||||
* @requires $httpBacked
|
||||
* @requires $browser
|
||||
* @requires $exceptionHandler //TODO(i): still needed?
|
||||
* @requires $cacheFactory
|
||||
*
|
||||
* @param {object} config Object describing the request to be made and how it should be processed.
|
||||
* The object has following properties:
|
||||
*
|
||||
* - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
|
||||
* - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
|
||||
* - **data** – `{string|Object}` – Data to be sent as the request message data.
|
||||
* - **headers** – `{Object}` – Map of strings representing HTTP headers to send to the server.
|
||||
* - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
|
||||
* GET request, otherwise if a cache instance built with $cacheFactory, this cache will be
|
||||
* used for caching.
|
||||
*
|
||||
* @returns {HttpPromise} Returns a promise object with the standard `then` method and two http
|
||||
* specific methods: `success` and `error`. The `then` method takes two arguments a success and
|
||||
* an error callback which will be called with a response object. The `success` and `error`
|
||||
* methods take a single argument - a function that will be called when the request succeeds or
|
||||
* fails respectively. The arguments passed into these functions are destructured representation
|
||||
* of the response object passed into the `then` method. The response object has these
|
||||
* properties:
|
||||
*
|
||||
* - **data** – `{string|Object}` – The response body transformed with the transform functions.
|
||||
* - **status** – `{number}` – HTTP status code of the response.
|
||||
* - **headers** – `{function([headerName])}` – Header getter function.
|
||||
* - **config** – `{Object}` – The configuration object that was used to generate the request.
|
||||
*
|
||||
* @property {Array.<Object>} pendingRequests Array of config objects for pending requests.
|
||||
* This is primarily meant to be used for debugging purposes.
|
||||
*
|
||||
* @description
|
||||
* $http is a service through which XHR and JSONP requests can be made.
|
||||
*/
|
||||
function done(status, response) {
|
||||
// aborted request or jsonp
|
||||
if (!rawRequest) parsedHeaders = {};
|
||||
function $http(config) {
|
||||
config.method = uppercase(config.method);
|
||||
|
||||
if (cfg.cache && cfg.method == 'GET') {
|
||||
var cache = isObject(cfg.cache) && cfg.cache || defaultCache;
|
||||
if (200 <= status && status < 300) {
|
||||
parsedHeaders = parsedHeaders || parseHeaders(rawRequest.getAllResponseHeaders());
|
||||
cache.put(cfg.url, [status, response, parsedHeaders]);
|
||||
} else {
|
||||
// remove future object from cache
|
||||
cache.remove(cfg.url);
|
||||
}
|
||||
var reqTransformFn = config.transformRequest || $config.transformRequest,
|
||||
respTransformFn = config.transformResponse || $config.transformResponse,
|
||||
reqData = transformData(config.data, reqTransformFn),
|
||||
defHeaders = $config.headers,
|
||||
reqHeaders = extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']},
|
||||
defHeaders.common, defHeaders[lowercase(config.method)], config.headers),
|
||||
promise;
|
||||
|
||||
|
||||
// send request
|
||||
promise = sendReq(config, reqData, reqHeaders);
|
||||
|
||||
|
||||
// transform future response
|
||||
promise = promise.then(transformResponse, transformResponse);
|
||||
|
||||
// apply interceptors
|
||||
forEach(responseInterceptors, function(interceptor) {
|
||||
promise = interceptor(promise);
|
||||
});
|
||||
|
||||
promise.success = function(fn) {
|
||||
promise.then(function(response) {
|
||||
fn(response.data, response.status, response.headers, config);
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
promise.error = function(fn) {
|
||||
promise.then(null, function(response) {
|
||||
fn(response.data, response.status, response.headers, config);
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
return promise;
|
||||
|
||||
function transformResponse(response) {
|
||||
// make a copy since the response must be cacheable
|
||||
var resp = extend({}, response, {
|
||||
data: transformData(response.data, respTransformFn, response.headers)
|
||||
});
|
||||
return (isSuccess(response.status))
|
||||
? resp
|
||||
: $q.reject(resp);
|
||||
}
|
||||
}
|
||||
|
||||
$http.pendingRequests = [];
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ng.$http#get
|
||||
* @methodOf angular.module.ng.$http
|
||||
*
|
||||
* @description
|
||||
* Shortcut method to perform `GET` request
|
||||
*
|
||||
* @param {string} url Relative or absolute URL specifying the destination of the request
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {HttpPromise} Future object
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ng.$http#delete
|
||||
* @methodOf angular.module.ng.$http
|
||||
*
|
||||
* @description
|
||||
* Shortcut method to perform `DELETE` request
|
||||
*
|
||||
* @param {string} url Relative or absolute URL specifying the destination of the request
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {HttpPromise} Future object
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ng.$http#head
|
||||
* @methodOf angular.module.ng.$http
|
||||
*
|
||||
* @description
|
||||
* Shortcut method to perform `HEAD` request
|
||||
*
|
||||
* @param {string} url Relative or absolute URL specifying the destination of the request
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {XhrFuture} Future object
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ng.$http#patch
|
||||
* @methodOf angular.module.ng.$http
|
||||
*
|
||||
* @description
|
||||
* Shortcut method to perform `PATCH` request
|
||||
*
|
||||
* @param {string} url Relative or absolute URL specifying the destination of the request
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {HttpPromise} Future object
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ng.$http#jsonp
|
||||
* @methodOf angular.module.ng.$http
|
||||
*
|
||||
* @description
|
||||
* Shortcut method to perform `JSONP` request
|
||||
*
|
||||
* @param {string} url Relative or absolute URL specifying the destination of the request.
|
||||
* Should contain `JSON_CALLBACK` string.
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {XhrFuture} Future object
|
||||
*/
|
||||
createShortMethods('get', 'delete', 'head', 'patch', 'jsonp');
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ng.$http#post
|
||||
* @methodOf angular.module.ng.$http
|
||||
*
|
||||
* @description
|
||||
* Shortcut method to perform `POST` request
|
||||
*
|
||||
* @param {string} url Relative or absolute URL specifying the destination of the request
|
||||
* @param {*} data Request content
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {HttpPromise} Future object
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.module.ng.$http#put
|
||||
* @methodOf angular.module.ng.$http
|
||||
*
|
||||
* @description
|
||||
* Shortcut method to perform `PUT` request
|
||||
*
|
||||
* @param {string} url Relative or absolute URL specifying the destination of the request
|
||||
* @param {*} data Request content
|
||||
* @param {Object=} config Optional configuration object
|
||||
* @returns {XhrFuture} Future object
|
||||
*/
|
||||
createShortMethodsWithData('post', 'put');
|
||||
|
||||
|
||||
return $http;
|
||||
|
||||
|
||||
function createShortMethods(names) {
|
||||
forEach(arguments, function(name) {
|
||||
$http[name] = function(url, config) {
|
||||
return $http(extend(config || {}, {
|
||||
method: name,
|
||||
url: url
|
||||
}));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function createShortMethodsWithData(name) {
|
||||
forEach(arguments, function(name) {
|
||||
$http[name] = function(url, data, config) {
|
||||
return $http(extend(config || {}, {
|
||||
method: name,
|
||||
url: url,
|
||||
data: data
|
||||
}));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Makes the request
|
||||
*
|
||||
* !!! ACCESSES CLOSURE VARS:
|
||||
* $httpBackend, $config, $log, $rootScope, defaultCache, $http.pendingRequests
|
||||
*/
|
||||
function sendReq(config, reqData, reqHeaders) {
|
||||
var deferred = $q.defer(),
|
||||
promise = deferred.promise,
|
||||
cache,
|
||||
cachedResp;
|
||||
|
||||
$http.pendingRequests.push(config);
|
||||
promise.then(removePendingReq, removePendingReq);
|
||||
|
||||
|
||||
if (config.cache && config.method == 'GET') {
|
||||
cache = isObject(config.cache) ? config.cache : defaultCache;
|
||||
}
|
||||
|
||||
fireCallbacks(response, status);
|
||||
// TODO(i): we can't null the rawRequest because we might need to be able to call
|
||||
// rawRequest.getAllResponseHeaders from a promise
|
||||
// rawRequest = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire all registered callbacks for given status code
|
||||
*
|
||||
* This method when:
|
||||
* - serving response from real request
|
||||
* - serving response from cache
|
||||
*
|
||||
* It does:
|
||||
* - transform the response
|
||||
* - call proper callbacks
|
||||
* - log errors
|
||||
* - apply the $scope
|
||||
* - clear parsed headers
|
||||
*/
|
||||
function fireCallbacks(response, status) {
|
||||
var strStatus = status + '';
|
||||
|
||||
// transform the response
|
||||
response = transform(response, cfg.transformResponse || $config.transformResponse, rawRequest);
|
||||
|
||||
var idx; // remove from pending requests
|
||||
if ((idx = indexOf($http.pendingRequests, cfg)) !== -1)
|
||||
$http.pendingRequests.splice(idx, 1);
|
||||
|
||||
// normalize internal statuses to 0
|
||||
status = Math.max(status, 0);
|
||||
forEach(callbacks, function(callback) {
|
||||
if (callback.regexp.test(strStatus)) {
|
||||
try {
|
||||
// use local var to call it without context
|
||||
var fn = callback.fn;
|
||||
fn(response, status, headers);
|
||||
} catch(e) {
|
||||
$exceptionHandler(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$rootScope.$apply();
|
||||
parsedHeaders = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the third argument in any user callback
|
||||
* @see parseHeaders
|
||||
*
|
||||
* Return single header value or all headers parsed as object.
|
||||
* Headers all lazy parsed when first requested.
|
||||
*
|
||||
* @param {string=} name Name of header
|
||||
* @returns {string|Object}
|
||||
*/
|
||||
function headers(name) {
|
||||
if (name) {
|
||||
return parsedHeaders ?
|
||||
parsedHeaders[lowercase(name)] || null :
|
||||
rawRequest.getResponseHeader(name);
|
||||
}
|
||||
|
||||
parsedHeaders = parsedHeaders || parseHeaders(rawRequest.getAllResponseHeaders());
|
||||
|
||||
return parsedHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry the request
|
||||
*
|
||||
* @param {Object=} config Optional config object to extend the original configuration
|
||||
* @returns {HttpPromise}
|
||||
*/
|
||||
this.retry = function(config) {
|
||||
if (rawRequest) throw 'Can not retry request. Abort pending request first.';
|
||||
|
||||
extend(cfg, config);
|
||||
cfg.method = uppercase(cfg.method);
|
||||
|
||||
var data = transform(cfg.data, cfg.transformRequest || $config.transformRequest),
|
||||
headers = extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']},
|
||||
defHeaders.common, defHeaders[lowercase(cfg.method)], cfg.headers);
|
||||
|
||||
var cache = isObject(cfg.cache) && cfg.cache || defaultCache,
|
||||
fromCache;
|
||||
|
||||
if (cfg.cache && cfg.method == 'GET') {
|
||||
fromCache = cache.get(cfg.url);
|
||||
if (fromCache) {
|
||||
if (fromCache instanceof XhrFuture) {
|
||||
// cached request has already been sent, but there is no reponse yet,
|
||||
if (cache) {
|
||||
cachedResp = cache.get(config.url);
|
||||
if (cachedResp) {
|
||||
if (cachedResp.then) {
|
||||
// cached request has already been sent, but there is no response yet,
|
||||
// we need to register callback and fire callbacks when the request is back
|
||||
// note, we have to get the values from cache and perform transformations on them,
|
||||
// as the configurations don't have to be same
|
||||
fromCache.on('always', function() {
|
||||
var requestFromCache = cache.get(cfg.url);
|
||||
parsedHeaders = requestFromCache[2];
|
||||
fireCallbacks(requestFromCache[1], requestFromCache[0]);
|
||||
});
|
||||
cachedResp.then(removePendingReq, removePendingReq);
|
||||
return cachedResp;
|
||||
} else {
|
||||
// serving from cache - still needs to be async
|
||||
$browser.defer(function() {
|
||||
parsedHeaders = fromCache[2];
|
||||
fireCallbacks(fromCache[1], fromCache[0]);
|
||||
});
|
||||
// serving from cache
|
||||
if (isArray(cachedResp)) {
|
||||
resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2]));
|
||||
} else {
|
||||
resolvePromise(cachedResp, 200, {});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// put future object into cache
|
||||
cache.put(cfg.url, self);
|
||||
// put the promise for the non-transformed response into cache as a placeholder
|
||||
cache.put(config.url, promise);
|
||||
}
|
||||
}
|
||||
|
||||
// really send the request
|
||||
if (!cfg.cache || cfg.method !== 'GET' || !fromCache) {
|
||||
rawRequest = $httpBackend(cfg.method, cfg.url, data, done, headers, cfg.timeout);
|
||||
// if we won't have the response in cache, send the request to the backend
|
||||
if (!cachedResp) {
|
||||
$httpBackend(config.method, config.url, reqData, done, reqHeaders, config.timeout);
|
||||
}
|
||||
|
||||
$http.pendingRequests.push(cfg);
|
||||
return self;
|
||||
};
|
||||
return promise;
|
||||
|
||||
// just alias so that in stack trace we can see send() instead of retry()
|
||||
this.send = this.retry;
|
||||
|
||||
/**
|
||||
* Abort the request
|
||||
*/
|
||||
this.abort = function() {
|
||||
if (rawRequest) {
|
||||
rawRequest.abort();
|
||||
/**
|
||||
* Callback registered to $httpBackend():
|
||||
* - caches the response if desired
|
||||
* - resolves the raw $http promise
|
||||
* - calls $apply
|
||||
*/
|
||||
function done(status, response, headersString) {
|
||||
if (cache) {
|
||||
if (isSuccess(status)) {
|
||||
cache.put(config.url, [status, response, parseHeaders(headersString)]);
|
||||
} else {
|
||||
// remove promise from the cache
|
||||
cache.remove(config.url);
|
||||
}
|
||||
}
|
||||
|
||||
resolvePromise(response, status, headersString);
|
||||
$rootScope.$apply();
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a callback function based on status code
|
||||
* Note: all matched callbacks will be called, preserving registered order !
|
||||
*
|
||||
* Internal statuses:
|
||||
* `-2` = jsonp error
|
||||
* `-1` = timeout
|
||||
* `0` = aborted
|
||||
*
|
||||
* @example
|
||||
* .on('2xx', function(){});
|
||||
* .on('2x1', function(){});
|
||||
* .on('404', function(){});
|
||||
* .on('20x,3xx', function(){});
|
||||
* .on('success', function(){});
|
||||
* .on('error', function(){});
|
||||
* .on('always', function(){});
|
||||
* .on('timeout', function(){});
|
||||
* .on('abort', function(){});
|
||||
*
|
||||
* @param {string} pattern Status code pattern with "x" for any number
|
||||
* @param {function(*, number, function)} callback Function to be called when response arrives
|
||||
* @returns {XhrFuture}
|
||||
*/
|
||||
this.on = function(pattern, callback) {
|
||||
var alias = {
|
||||
success: '2xx',
|
||||
error: '-2,-1,0,4xx,5xx',
|
||||
always: 'xxx,xx,x',
|
||||
timeout: '-1',
|
||||
abort: '0'
|
||||
};
|
||||
|
||||
callbacks.push({
|
||||
fn: callback,
|
||||
// create regexp from given pattern
|
||||
regexp: new RegExp('^(' + (alias[pattern] || pattern).replace(/,/g, '|').
|
||||
replace(/x/g, '.') + ')$')
|
||||
});
|
||||
/**
|
||||
* Resolves the raw $http promise.
|
||||
*/
|
||||
function resolvePromise(response, status, headers) {
|
||||
// normalize internal statuses to 0
|
||||
status = Math.max(status, 0);
|
||||
|
||||
return this;
|
||||
};
|
||||
(isSuccess(status) ? deferred.resolve : deferred.reject)({
|
||||
data: response,
|
||||
status: status,
|
||||
headers: headersGetter(headers),
|
||||
config: config
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration object of the request
|
||||
*/
|
||||
this.config = cfg;
|
||||
}
|
||||
}];
|
||||
|
||||
function removePendingReq() {
|
||||
var idx = indexOf($http.pendingRequests, config);
|
||||
if (idx !== -1) $http.pendingRequests.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,11 @@ var XHR = window.XMLHttpRequest || function() {
|
|||
* @requires $document
|
||||
*
|
||||
* @description
|
||||
* HTTP backend used by the {@link angular.module.ng.$http service} that delegates to
|
||||
* XMLHttpRequest object.
|
||||
*
|
||||
* During testing this implementation is swapped with {@link angular.module.ngMock.$httpBackend mock
|
||||
* $httpBackend} which can be trained with responses.
|
||||
*/
|
||||
function $HttpBackendProvider() {
|
||||
this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) {
|
||||
|
|
@ -46,24 +51,21 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, body, locati
|
|||
var xhr = new XHR();
|
||||
xhr.open(method, url, true);
|
||||
forEach(headers, function(value, key) {
|
||||
if (value) xhr.setRequestHeader(key, value);
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
// In IE6 and 7, this might be called synchronously when xhr.send below is called and the
|
||||
// response is in the cache
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4) {
|
||||
completeRequest(
|
||||
callback, status || xhr.status, xhr.responseText, xhr.getAllResponseHeaders());
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send(post || '');
|
||||
|
||||
if (timeout > 0) {
|
||||
$browserDefer(function() {
|
||||
|
|
@ -71,23 +73,21 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, body, locati
|
|||
xhr.abort();
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
return xhr;
|
||||
}
|
||||
|
||||
function completeRequest(callback, status, response) {
|
||||
|
||||
function completeRequest(callback, status, response, headersString) {
|
||||
// URL_MATCH is defined in src/service/location.js
|
||||
var protocol = (url.match(URL_MATCH) || ['', locationProtocol])[1];
|
||||
|
||||
// fix status code for file protocol (it's always 0)
|
||||
status = protocol == 'file' ? (response ? 200 : 404) : status;
|
||||
status = (protocol == 'file') ? (response ? 200 : 404) : status;
|
||||
|
||||
// normalize IE bug (http://bugs.jquery.com/ticket/1450)
|
||||
status = status == 1223 ? 204 : status;
|
||||
|
||||
callback(status, response);
|
||||
callback(status, response, headersString);
|
||||
$browser.$$completeOutstandingRequest(noop);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
326
test/angular-mocksSpec.js
vendored
326
test/angular-mocksSpec.js
vendored
|
|
@ -465,39 +465,6 @@ describe('mocks', function() {
|
|||
});
|
||||
|
||||
|
||||
it('should expose given headers', function() {
|
||||
hb.when('GET', '/u1').respond(200, null, {'X-Fake': 'Header', 'Content-Type': 'application/json'});
|
||||
var xhr = hb('GET', '/u1', null, noop, {});
|
||||
hb.flush();
|
||||
expect(xhr.getResponseHeader('X-Fake')).toBe('Header');
|
||||
expect(xhr.getAllResponseHeaders()).toBe('X-Fake: Header\nContent-Type: application/json');
|
||||
});
|
||||
|
||||
|
||||
it('should normalize when header name case when accessed via getResponseHeader', function() {
|
||||
hb.when('GET', '/u1').respond(200, null, {'X-Fake': 'Header',
|
||||
'Content-Type': 'application/json',
|
||||
'Location': '/foo'});
|
||||
var xhr = hb('GET', '/u1', null, noop, {});
|
||||
hb.flush();
|
||||
expect(xhr.getResponseHeader('x-fAKE')).toBe('Header');
|
||||
expect(xhr.getResponseHeader('content-type')).toBe('application/json');
|
||||
expect(xhr.getResponseHeader('Location')).toBe('/foo');
|
||||
});
|
||||
|
||||
|
||||
it('should normalize expect header name case when accessed via getResponseHeader', function() {
|
||||
hb.expect('GET', '/u1').respond(200, null, {'X-Fake': 'Header',
|
||||
'Content-Type': 'application/json',
|
||||
'Location': '/foo'});
|
||||
var xhr = hb('GET', '/u1', null, noop, {});
|
||||
hb.flush();
|
||||
expect(xhr.getResponseHeader('x-fAKE')).toBe('Header');
|
||||
expect(xhr.getResponseHeader('content-type')).toBe('application/json');
|
||||
expect(xhr.getResponseHeader('Location')).toBe('/foo');
|
||||
});
|
||||
|
||||
|
||||
it('should preserve the order of requests', function() {
|
||||
hb.when('GET', '/url1').respond(200, 'first');
|
||||
hb.when('GET', '/url2').respond(201, 'second');
|
||||
|
|
@ -508,186 +475,179 @@ describe('mocks', function() {
|
|||
hb.flush();
|
||||
|
||||
expect(callback.callCount).toBe(2);
|
||||
expect(callback.argsForCall[0]).toEqual([201, 'second']);
|
||||
expect(callback.argsForCall[1]).toEqual([200, 'first']);
|
||||
expect(callback.argsForCall[0]).toEqual([201, 'second', '']);
|
||||
expect(callback.argsForCall[1]).toEqual([200, 'first', '']);
|
||||
});
|
||||
|
||||
|
||||
it('respond() should take function', function() {
|
||||
hb.when('GET', '/some').respond(function(m, u, d, h) {
|
||||
return [301, m + u + ';' + d + ';a=' + h.a, {'Connection': 'keep-alive'}];
|
||||
describe('respond()', function() {
|
||||
it('should take values', function() {
|
||||
hb.expect('GET', '/url1').respond(200, 'first', {'header': 'val'});
|
||||
hb('GET', '/url1', undefined, callback);
|
||||
hb.flush();
|
||||
|
||||
expect(callback).toHaveBeenCalledOnceWith(200, 'first', 'header: val');
|
||||
});
|
||||
|
||||
var xhr = hb('GET', '/some', 'data', callback, {a: 'b'});
|
||||
hb.flush();
|
||||
it('should take function', function() {
|
||||
hb.expect('GET', '/some').respond(function(m, u, d, h) {
|
||||
return [301, m + u + ';' + d + ';a=' + h.a, {'Connection': 'keep-alive'}];
|
||||
});
|
||||
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
expect(callback.mostRecentCall.args[0]).toBe(301);
|
||||
expect(callback.mostRecentCall.args[1]).toBe('GET/some;data;a=b');
|
||||
expect(xhr.getResponseHeader('Connection')).toBe('keep-alive');
|
||||
});
|
||||
hb('GET', '/some', 'data', callback, {a: 'b'});
|
||||
hb.flush();
|
||||
|
||||
|
||||
it('expect() should require specified order', function() {
|
||||
hb.expect('GET', '/url1').respond(200, '');
|
||||
hb.expect('GET', '/url2').respond(200, '');
|
||||
|
||||
expect(function() {
|
||||
hb('GET', '/url2', null, noop, {});
|
||||
}).toThrow('Unexpected request: GET /url2\nExpected GET /url1');
|
||||
});
|
||||
|
||||
|
||||
it('expect() should have precendence over when()', function() {
|
||||
callback.andCallFake(function(status, response) {
|
||||
expect(status).toBe(300);
|
||||
expect(response).toBe('expect');
|
||||
expect(callback).toHaveBeenCalledOnceWith(301, 'GET/some;data;a=b', 'Connection: keep-alive');
|
||||
});
|
||||
|
||||
hb.when('GET', '/url').respond(200, 'when');
|
||||
hb.expect('GET', '/url').respond(300, 'expect');
|
||||
it('should default status code to 200', function() {
|
||||
callback.andCallFake(function(status, response) {
|
||||
expect(status).toBe(200);
|
||||
expect(response).toBe('some-data');
|
||||
});
|
||||
|
||||
hb('GET', '/url', null, callback, {});
|
||||
hb.flush();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
it ('should throw exception when only headers differes from expectation', function() {
|
||||
hb.when('GET').respond(200, '', {});
|
||||
hb.expect('GET', '/match', undefined, {'Content-Type': 'application/json'});
|
||||
|
||||
expect(function() {
|
||||
hb('GET', '/match', null, noop, {});
|
||||
}).toThrow('Expected GET /match with different headers\n' +
|
||||
'EXPECTED: {"Content-Type":"application/json"}\nGOT: {}');
|
||||
});
|
||||
|
||||
|
||||
it ('should throw exception when only data differes from expectation', function() {
|
||||
hb.when('GET').respond(200, '', {});
|
||||
hb.expect('GET', '/match', 'some-data');
|
||||
|
||||
expect(function() {
|
||||
hb('GET', '/match', 'different', noop, {});
|
||||
}).toThrow('Expected GET /match with different data\n' +
|
||||
'EXPECTED: some-data\nGOT: different');
|
||||
});
|
||||
|
||||
|
||||
it('expect() should without respond() and use respond()', function() {
|
||||
callback.andCallFake(function(status, response) {
|
||||
expect(status).toBe(201);
|
||||
expect(response).toBe('data');
|
||||
hb.expect('GET', '/url1').respond('some-data');
|
||||
hb.expect('GET', '/url2').respond('some-data', {'X-Header': 'true'});
|
||||
hb('GET', '/url1', null, callback);
|
||||
hb('GET', '/url2', null, callback);
|
||||
hb.flush();
|
||||
expect(callback).toHaveBeenCalled();
|
||||
expect(callback.callCount).toBe(2);
|
||||
});
|
||||
|
||||
hb.when('GET', '/some').respond(201, 'data');
|
||||
hb.expect('GET', '/some');
|
||||
hb('GET', '/some', null, callback);
|
||||
hb.flush();
|
||||
|
||||
expect(callback).toHaveBeenCalled();
|
||||
expect(function() { hb.verifyNoOutstandingExpectation(); }).not.toThrow();
|
||||
it('should default response headers to ""', function() {
|
||||
hb.expect('GET', '/url1').respond(200, 'first');
|
||||
hb.expect('GET', '/url2').respond('second');
|
||||
|
||||
hb('GET', '/url1', null, callback);
|
||||
hb('GET', '/url2', null, callback);
|
||||
|
||||
hb.flush();
|
||||
|
||||
expect(callback.callCount).toBe(2);
|
||||
expect(callback.argsForCall[0]).toEqual([200, 'first', '']);
|
||||
expect(callback.argsForCall[1]).toEqual([200, 'second', '']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('flush() should flush requests fired during callbacks', function() {
|
||||
hb.when('GET').respond(200, '');
|
||||
hb('GET', '/some', null, function() {
|
||||
hb('GET', '/other', null, callback);
|
||||
describe('expect()', function() {
|
||||
it('should require specified order', function() {
|
||||
hb.expect('GET', '/url1').respond(200, '');
|
||||
hb.expect('GET', '/url2').respond(200, '');
|
||||
|
||||
expect(function() {
|
||||
hb('GET', '/url2', null, noop, {});
|
||||
}).toThrow('Unexpected request: GET /url2\nExpected GET /url1');
|
||||
});
|
||||
|
||||
hb.flush();
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have precedence over when()', function() {
|
||||
callback.andCallFake(function(status, response) {
|
||||
expect(status).toBe(300);
|
||||
expect(response).toBe('expect');
|
||||
});
|
||||
|
||||
it('flush() should flush given number of pending requests', function() {
|
||||
hb.when('GET').respond(200, '');
|
||||
hb('GET', '/some', null, callback);
|
||||
hb('GET', '/some', null, callback);
|
||||
hb('GET', '/some', null, callback);
|
||||
hb.when('GET', '/url').respond(200, 'when');
|
||||
hb.expect('GET', '/url').respond(300, 'expect');
|
||||
|
||||
hb.flush(2);
|
||||
expect(callback).toHaveBeenCalled();
|
||||
expect(callback.callCount).toBe(2);
|
||||
});
|
||||
|
||||
|
||||
it('flush() should throw exception when flushing more requests than pending', function() {
|
||||
hb.when('GET').respond(200, '');
|
||||
hb('GET', '/url', null, callback);
|
||||
|
||||
expect(function() {hb.flush(2);}).toThrow('No more pending request to flush !');
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
it('(flush) should throw exception when no request to flush', function() {
|
||||
expect(function() {hb.flush();}).toThrow('No pending request to flush !');
|
||||
|
||||
hb.when('GET').respond(200, '');
|
||||
hb('GET', '/some', null, callback);
|
||||
hb.flush();
|
||||
|
||||
expect(function() {hb.flush();}).toThrow('No pending request to flush !');
|
||||
});
|
||||
|
||||
|
||||
it('(flush) should throw exception if not all expectations satasfied', function() {
|
||||
hb.expect('GET', '/url1').respond();
|
||||
hb.expect('GET', '/url2').respond();
|
||||
|
||||
hb('GET', '/url1', null, angular.noop);
|
||||
expect(function() {hb.flush();}).toThrow('Unsatisfied requests: GET /url2');
|
||||
});
|
||||
|
||||
|
||||
it('respond() should set default status 200 if not defined', function() {
|
||||
callback.andCallFake(function(status, response) {
|
||||
expect(status).toBe(200);
|
||||
expect(response).toBe('some-data');
|
||||
hb('GET', '/url', null, callback, {});
|
||||
hb.flush();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
hb.expect('GET', '/url1').respond('some-data');
|
||||
hb.expect('GET', '/url2').respond('some-data', {'X-Header': 'true'});
|
||||
hb('GET', '/url1', null, callback);
|
||||
hb('GET', '/url2', null, callback);
|
||||
hb.flush();
|
||||
expect(callback).toHaveBeenCalled();
|
||||
expect(callback.callCount).toBe(2);
|
||||
|
||||
it ('should throw exception when only headers differs from expectation', function() {
|
||||
hb.when('GET').respond(200, '', {});
|
||||
hb.expect('GET', '/match', undefined, {'Content-Type': 'application/json'});
|
||||
|
||||
expect(function() {
|
||||
hb('GET', '/match', null, noop, {});
|
||||
}).toThrow('Expected GET /match with different headers\n' +
|
||||
'EXPECTED: {"Content-Type":"application/json"}\nGOT: {}');
|
||||
});
|
||||
|
||||
|
||||
it ('should throw exception when only data differs from expectation', function() {
|
||||
hb.when('GET').respond(200, '', {});
|
||||
hb.expect('GET', '/match', 'some-data');
|
||||
|
||||
expect(function() {
|
||||
hb('GET', '/match', 'different', noop, {});
|
||||
}).toThrow('Expected GET /match with different data\n' +
|
||||
'EXPECTED: some-data\nGOT: different');
|
||||
});
|
||||
|
||||
|
||||
it("should use when's respond() when no expect() respond is defined", function() {
|
||||
callback.andCallFake(function(status, response) {
|
||||
expect(status).toBe(201);
|
||||
expect(response).toBe('data');
|
||||
});
|
||||
|
||||
hb.when('GET', '/some').respond(201, 'data');
|
||||
hb.expect('GET', '/some');
|
||||
hb('GET', '/some', null, callback);
|
||||
hb.flush();
|
||||
|
||||
expect(callback).toHaveBeenCalled();
|
||||
expect(function() { hb.verifyNoOutstandingExpectation(); }).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('respond() should set default status 200 if not defined', function() {
|
||||
callback.andCallFake(function(status, response) {
|
||||
expect(status).toBe(200);
|
||||
expect(response).toBe('some-data');
|
||||
describe('flush()', function() {
|
||||
it('flush() should flush requests fired during callbacks', function() {
|
||||
hb.when('GET').respond(200, '');
|
||||
hb('GET', '/some', null, function() {
|
||||
hb('GET', '/other', null, callback);
|
||||
});
|
||||
|
||||
hb.flush();
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
hb.when('GET', '/url1').respond('some-data');
|
||||
hb.when('GET', '/url2').respond('some-data', {'X-Header': 'true'});
|
||||
hb('GET', '/url1', null, callback);
|
||||
hb('GET', '/url2', null, callback);
|
||||
hb.flush();
|
||||
expect(callback).toHaveBeenCalled();
|
||||
expect(callback.callCount).toBe(2);
|
||||
});
|
||||
|
||||
it('should flush given number of pending requests', function() {
|
||||
hb.when('GET').respond(200, '');
|
||||
hb('GET', '/some', null, callback);
|
||||
hb('GET', '/some', null, callback);
|
||||
hb('GET', '/some', null, callback);
|
||||
|
||||
it('should respond with definition if no response for expectation', function() {
|
||||
callback.andCallFake(function(status, response) {
|
||||
expect(status).toBe(201);
|
||||
expect(response).toBe('def-response');
|
||||
hb.flush(2);
|
||||
expect(callback).toHaveBeenCalled();
|
||||
expect(callback.callCount).toBe(2);
|
||||
});
|
||||
|
||||
hb.when('GET').respond(201, 'def-response');
|
||||
hb.expect('GET', '/some-url');
|
||||
|
||||
hb('GET', '/some-url', null, callback);
|
||||
hb.flush();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
hb.verifyNoOutstandingExpectation();
|
||||
it('should throw exception when flushing more requests than pending', function() {
|
||||
hb.when('GET').respond(200, '');
|
||||
hb('GET', '/url', null, callback);
|
||||
|
||||
expect(function() {hb.flush(2);}).toThrow('No more pending request to flush !');
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
it('should throw exception when no request to flush', function() {
|
||||
expect(function() {hb.flush();}).toThrow('No pending request to flush !');
|
||||
|
||||
hb.when('GET').respond(200, '');
|
||||
hb('GET', '/some', null, callback);
|
||||
hb.flush();
|
||||
|
||||
expect(function() {hb.flush();}).toThrow('No pending request to flush !');
|
||||
});
|
||||
|
||||
|
||||
it('should throw exception if not all expectations satisfied', function() {
|
||||
hb.expect('GET', '/url1').respond();
|
||||
hb.expect('GET', '/url2').respond();
|
||||
|
||||
hb('GET', '/url1', null, angular.noop);
|
||||
expect(function() {hb.flush();}).toThrow('Unsatisfied requests: GET /url2');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -699,7 +659,7 @@ describe('mocks', function() {
|
|||
});
|
||||
|
||||
|
||||
it('should throw an exception if no response for expection and no definition', function() {
|
||||
it('should throw an exception if no response for exception and no definition', function() {
|
||||
hb.expect('GET', '/url');
|
||||
expect(function() {
|
||||
hb('GET', '/url', null, callback);
|
||||
|
|
@ -762,7 +722,7 @@ describe('mocks', function() {
|
|||
});
|
||||
|
||||
|
||||
describe('reset', function() {
|
||||
describe('resetExpectations', function() {
|
||||
|
||||
it('should remove all expectations', function() {
|
||||
hb.expect('GET', '/u2').respond(200, '', {});
|
||||
|
|
@ -773,7 +733,7 @@ describe('mocks', function() {
|
|||
});
|
||||
|
||||
|
||||
it('should remove all responses', function() {
|
||||
it('should remove all pending responses', function() {
|
||||
var cancelledClb = jasmine.createSpy('cancelled');
|
||||
|
||||
hb.expect('GET', '/url').respond(200, '');
|
||||
|
|
|
|||
|
|
@ -58,18 +58,13 @@ describe('$httpBackend', function() {
|
|||
$backend('POST', 'URL', null, noop, {'X-header1': 'value1', 'X-header2': 'value2'});
|
||||
xhr = MockXhr.$$lastInstance;
|
||||
|
||||
expect(xhr.$$headers).toEqual({
|
||||
expect(xhr.$$reqHeaders).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);
|
||||
|
|
@ -91,16 +86,20 @@ describe('$httpBackend', function() {
|
|||
});
|
||||
|
||||
|
||||
it('should be async even if xhr.send() is sync', function() {
|
||||
// IE6, IE7 is sync when serving from cache
|
||||
it('should register onreadystatechange callback before sending', function() {
|
||||
// send() in 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;
|
||||
this.onreadystatechange();
|
||||
};
|
||||
|
||||
this.getAllResponseHeaders = valueFn('');
|
||||
}
|
||||
|
||||
callback.andCallFake(function(status, response) {
|
||||
|
|
@ -108,14 +107,8 @@ describe('$httpBackend', function() {
|
|||
expect(response).toBe('response');
|
||||
});
|
||||
|
||||
$backend = createHttpBackend($browser, SyncXhr, fakeTimeout);
|
||||
$backend = createHttpBackend($browser, SyncXhr);
|
||||
$backend('GET', '/url', null, callback);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
fakeTimeout.flush();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
|
||||
(xhr.onreadystatechange || noop)();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ describe('$http', function() {
|
|||
expect(data).toBe('Hello!?');
|
||||
expect(status).toBe(209);
|
||||
callback();
|
||||
})
|
||||
});
|
||||
$httpBackend.flush();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
}));
|
||||
|
|
@ -550,7 +550,7 @@ describe('$http', function() {
|
|||
});
|
||||
|
||||
|
||||
describe('transform', function() {
|
||||
describe('transformData', function() {
|
||||
|
||||
describe('request', function() {
|
||||
|
||||
|
|
@ -648,17 +648,19 @@ describe('$http', function() {
|
|||
cache = $cacheFactory('testCache');
|
||||
}));
|
||||
|
||||
|
||||
function doFirstCacheRequest(method, respStatus, headers) {
|
||||
$httpBackend.expect(method || 'GET', '/url').respond(respStatus || 200, 'content', headers);
|
||||
$http({method: method || 'GET', url: '/url', cache: cache});
|
||||
$httpBackend.flush();
|
||||
}
|
||||
|
||||
it('should cache GET request when cache is provided', inject(function($browser) {
|
||||
|
||||
it('should cache GET request when cache is provided', inject(function($rootScope) {
|
||||
doFirstCacheRequest();
|
||||
|
||||
$http({method: 'get', url: '/url', cache: cache}).success(callback);
|
||||
$browser.defer.flush();
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
expect(callback.mostRecentCall.args[0]).toBe('content');
|
||||
|
|
@ -737,7 +739,7 @@ describe('$http', function() {
|
|||
});
|
||||
|
||||
|
||||
it('should cache the headers as well', inject(function($browser) {
|
||||
it('should cache the headers as well', inject(function($rootScope) {
|
||||
doFirstCacheRequest('GET', 200, {'content-encoding': 'gzip', 'server': 'Apache'});
|
||||
callback.andCallFake(function(r, s, headers) {
|
||||
expect(headers()).toEqual({'content-encoding': 'gzip', 'server': 'Apache'});
|
||||
|
|
@ -745,24 +747,37 @@ describe('$http', function() {
|
|||
});
|
||||
|
||||
$http({method: 'GET', url: '/url', cache: cache}).success(callback);
|
||||
$browser.defer.flush();
|
||||
$rootScope.$digest();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
}));
|
||||
|
||||
|
||||
it('should cache status code as well', inject(function($browser) {
|
||||
it('should not share the cached headers object instance', inject(function($rootScope) {
|
||||
doFirstCacheRequest('GET', 200, {'content-encoding': 'gzip', 'server': 'Apache'});
|
||||
callback.andCallFake(function(r, s, headers) {
|
||||
expect(headers()).toEqual(cache.get('/url')[2]);
|
||||
expect(headers()).not.toBe(cache.get('/url')[2]);
|
||||
});
|
||||
|
||||
$http({method: 'GET', url: '/url', cache: cache}).success(callback);
|
||||
$rootScope.$digest();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
}));
|
||||
|
||||
|
||||
it('should cache status code as well', inject(function($rootScope) {
|
||||
doFirstCacheRequest('GET', 201);
|
||||
callback.andCallFake(function(r, status, h) {
|
||||
expect(status).toBe(201);
|
||||
});
|
||||
|
||||
$http({method: 'get', url: '/url', cache: cache}).success(callback);
|
||||
$browser.defer.flush();
|
||||
$rootScope.$digest();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
}));
|
||||
|
||||
|
||||
it('should use cache even if request fired before first response is back', function() {
|
||||
it('should use cache even if second request was made before the first returned', function() {
|
||||
$httpBackend.expect('GET', '/url').respond(201, 'fake-response');
|
||||
|
||||
callback.andCallFake(function(response, status, headers) {
|
||||
|
|
@ -777,6 +792,22 @@ describe('$http', function() {
|
|||
expect(callback).toHaveBeenCalled();
|
||||
expect(callback.callCount).toBe(2);
|
||||
});
|
||||
|
||||
|
||||
it('should default to status code 200 and empty headers if cache contains a non-array element',
|
||||
inject(function($rootScope) {
|
||||
cache.put('/myurl', 'simple response');
|
||||
$http.get('/myurl', {cache: cache}).success(function(data, status, headers) {
|
||||
expect(data).toBe('simple response');
|
||||
expect(status).toBe(200);
|
||||
expect(headers()).toEqual({});
|
||||
callback();
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -794,7 +825,7 @@ describe('$http', function() {
|
|||
});
|
||||
|
||||
|
||||
it('should update pending requests even when served from cache', inject(function($browser) {
|
||||
it('should update pending requests even when served from cache', inject(function($rootScope) {
|
||||
$httpBackend.when('GET').respond(200);
|
||||
|
||||
$http({method: 'get', url: '/cached', cache: true});
|
||||
|
|
@ -807,7 +838,7 @@ describe('$http', function() {
|
|||
$http({method: 'get', url: '/cached', cache: true});
|
||||
expect($http.pendingRequests.length).toBe(1);
|
||||
|
||||
$browser.defer.flush();
|
||||
$rootScope.$apply();
|
||||
expect($http.pendingRequests.length).toBe(0);
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ describe('widget', function() {
|
|||
$rootScope.childScope.name = 'misko';
|
||||
$rootScope.url = 'myUrl';
|
||||
$rootScope.$digest();
|
||||
$browser.defer.flush();
|
||||
expect(element.text()).toEqual('misko');
|
||||
}));
|
||||
|
||||
|
|
@ -86,7 +85,6 @@ describe('widget', function() {
|
|||
$rootScope.childScope.name = 'igor';
|
||||
$rootScope.url = 'myUrl';
|
||||
$rootScope.$digest();
|
||||
$browser.defer.flush();
|
||||
|
||||
expect(element.text()).toEqual('igor');
|
||||
|
||||
|
|
@ -103,7 +101,6 @@ describe('widget', function() {
|
|||
element = $compile(element)($rootScope);
|
||||
$rootScope.url = 'myUrl';
|
||||
$rootScope.$digest();
|
||||
$browser.defer.flush();
|
||||
|
||||
// TODO(misko): because we are using scope==this, the eval gets registered
|
||||
// during the flush phase and hence does not get called.
|
||||
|
|
@ -125,7 +122,6 @@ describe('widget', function() {
|
|||
|
||||
$rootScope.url = 'myUrl';
|
||||
$rootScope.$digest();
|
||||
$browser.defer.flush();
|
||||
|
||||
expect(element.text()).toEqual('my partial');
|
||||
expect($rootScope.loaded).toBe(true);
|
||||
|
|
@ -141,7 +137,6 @@ describe('widget', function() {
|
|||
|
||||
$rootScope.url = 'myUrl';
|
||||
$rootScope.$digest();
|
||||
$browser.defer.flush();
|
||||
expect($rootScope.$$childHead).toBeTruthy();
|
||||
|
||||
$rootScope.url = null;
|
||||
|
|
@ -166,7 +161,6 @@ describe('widget', function() {
|
|||
|
||||
$rootScope.url = 'myUrl';
|
||||
$rootScope.$digest();
|
||||
$browser.defer.flush();
|
||||
expect(element.text()).toEqual('my partial');
|
||||
dealoc($rootScope);
|
||||
}));
|
||||
|
|
@ -199,7 +193,6 @@ describe('widget', function() {
|
|||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
$browser.defer.flush();
|
||||
expect(element.text()).toBe('my partial');
|
||||
}));
|
||||
|
||||
|
|
@ -746,7 +739,6 @@ describe('widget', function() {
|
|||
$rootScope.log = [];
|
||||
$location.path('/foo');
|
||||
$rootScope.$apply();
|
||||
$browser.defer.flush();
|
||||
|
||||
expect($rootScope.log).toEqual(['parent', 'init', 'child']);
|
||||
}));
|
||||
|
|
@ -801,7 +793,6 @@ describe('widget', function() {
|
|||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
$browser.defer.flush();
|
||||
expect(element.text()).toBe('my partial');
|
||||
}));
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue