aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/angular-mocks.js57
-rw-r--r--src/service/http.js680
-rw-r--r--src/service/httpBackend.js40
-rw-r--r--test/angular-mocksSpec.js288
-rw-r--r--test/service/httpBackendSpec.js23
-rw-r--r--test/service/httpSpec.js53
-rw-r--r--test/widgetsSpec.js9
7 files changed, 533 insertions, 617 deletions
diff --git a/src/angular-mocks.js b/src/angular-mocks.js
index 16be7038..b4a1cbbc 100644
--- a/src/angular-mocks.js
+++ b/src/angular-mocks.js
@@ -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');
diff --git a/src/service/http.js b/src/service/http.js
index e6a42b65..bd8e6e65 100644
--- a/src/service/http.js
+++ b/src/service/http.js
@@ -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);
- }
- });
+ forEach(providerResponseInterceptors, function(interceptor) {
+ responseInterceptors.push(isString(interceptor) ? $injector.get(interceptor) : 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);
- });
+ /**
+ * @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 $http(config) {
+ config.method = uppercase(config.method);
- promise.success = function(fn) {
- promise.then(function(response) {
- fn(response.data, response.status, response.headers, config);
- });
- return promise;
- };
+ 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;
- 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});
- });
+ // send request
+ promise = sendReq(config, reqData, reqHeaders);
+
- 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
- }));
+ // 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;
};
- });
- }
-
- function createShortMethodsWithData(name) {
- forEach(arguments, function(name) {
- $http[name] = function(url, data, config) {
- return $http(extend(config || {}, {
- method: name,
- url: url,
- data: data
- }));
+
+ promise.error = function(fn) {
+ promise.then(null, function(response) {
+ fn(response.data, response.status, response.headers, config);
+ });
+ return promise;
};
- });
- }
-
- /**
- * 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;
+
+ 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 = [];
/**
- * Callback registered to $httpBackend():
- * - caches the response if desired
- * - calls fireCallbacks()
- * - clears the reference to raw request object
+ * @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
*/
- function done(status, response) {
- // aborted request or jsonp
- if (!rawRequest) parsedHeaders = {};
-
- 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);
- }
- }
- 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;
- }
+ /**
+ * @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
+ */
/**
- * Fire all registered callbacks for given status code
+ * @ngdoc method
+ * @name angular.module.ng.$http#head
+ * @methodOf angular.module.ng.$http
*
- * This method when:
- * - serving response from real request
- * - serving response from cache
+ * @description
+ * Shortcut method to perform `HEAD` request
*
- * It does:
- * - transform the response
- * - call proper callbacks
- * - log errors
- * - apply the $scope
- * - clear parsed headers
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {Object=} config Optional configuration object
+ * @returns {XhrFuture} Future object
*/
- 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;
- }
+ /**
+ * @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
+ */
/**
- * This is the third argument in any user callback
- * @see parseHeaders
+ * @ngdoc method
+ * @name angular.module.ng.$http#jsonp
+ * @methodOf angular.module.ng.$http
*
- * Return single header value or all headers parsed as object.
- * Headers all lazy parsed when first requested.
+ * @description
+ * Shortcut method to perform `JSONP` request
*
- * @param {string=} name Name of header
- * @returns {string|Object}
+ * @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
*/
- function headers(name) {
- if (name) {
- return parsedHeaders ?
- parsedHeaders[lowercase(name)] || null :
- rawRequest.getResponseHeader(name);
- }
+ createShortMethods('get', 'delete', 'head', 'patch', 'jsonp');
- parsedHeaders = parsedHeaders || parseHeaders(rawRequest.getAllResponseHeaders());
+ /**
+ * @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
+ */
- return parsedHeaders;
+ /**
+ * @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
+ }));
+ };
+ });
+ }
+
+
/**
- * Retry the request
+ * Makes the request
*
- * @param {Object=} config Optional config object to extend the original configuration
- * @returns {HttpPromise}
+ * !!! ACCESSES CLOSURE VARS:
+ * $httpBackend, $config, $log, $rootScope, defaultCache, $http.pendingRequests
*/
- this.retry = function(config) {
- if (rawRequest) throw 'Can not retry request. Abort pending request first.';
+ function sendReq(config, reqData, reqHeaders) {
+ var deferred = $q.defer(),
+ promise = deferred.promise,
+ cache,
+ cachedResp;
- extend(cfg, config);
- cfg.method = uppercase(cfg.method);
+ $http.pendingRequests.push(config);
+ promise.then(removePendingReq, removePendingReq);
- 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 (config.cache && config.method == 'GET') {
+ cache = isObject(config.cache) ? config.cache : defaultCache;
+ }
- 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);
+ }
+ }
+ }];
+}
diff --git a/src/service/httpBackend.js b/src/service/httpBackend.js
index c64519d9..c3114814 100644
--- a/src/service/httpBackend.js
+++ b/src/service/httpBackend.js
@@ -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);
}
};
}
-
diff --git a/test/angular-mocksSpec.js b/test/angular-mocksSpec.js
index a8328f41..88e6a590 100644
--- a/test/angular-mocksSpec.js
+++ b/test/angular-mocksSpec.js
@@ -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'}];
- });
-
- var xhr = hb('GET', '/some', 'data', callback, {a: 'b'});
- hb.flush();
+ 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).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');
- });
+ expect(callback).toHaveBeenCalledOnceWith(200, 'first', 'header: val');
+ });
+ 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'}];
+ });
- it('expect() should require specified order', function() {
- hb.expect('GET', '/url1').respond(200, '');
- hb.expect('GET', '/url2').respond(200, '');
+ hb('GET', '/some', 'data', callback, {a: 'b'});
+ hb.flush();
- expect(function() {
- hb('GET', '/url2', null, noop, {});
- }).toThrow('Unexpected request: GET /url2\nExpected GET /url1');
- });
+ expect(callback).toHaveBeenCalledOnceWith(301, 'GET/some;data;a=b', 'Connection: keep-alive');
+ });
+ it('should default status code to 200', function() {
+ callback.andCallFake(function(status, response) {
+ expect(status).toBe(200);
+ expect(response).toBe('some-data');
+ });
- it('expect() should have precendence over when()', function() {
- callback.andCallFake(function(status, response) {
- expect(status).toBe(300);
- expect(response).toBe('expect');
+ 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', '/url').respond(200, 'when');
- hb.expect('GET', '/url').respond(300, 'expect');
- hb('GET', '/url', null, callback, {});
- hb.flush();
- expect(callback).toHaveBeenCalledOnce();
- });
+ 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);
- 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'});
+ hb.flush();
- expect(function() {
- hb('GET', '/match', null, noop, {});
- }).toThrow('Expected GET /match with different headers\n' +
- 'EXPECTED: {"Content-Type":"application/json"}\nGOT: {}');
+ expect(callback.callCount).toBe(2);
+ expect(callback.argsForCall[0]).toEqual([200, 'first', '']);
+ expect(callback.argsForCall[1]).toEqual([200, 'second', '']);
+ });
});
- 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');
- });
-
+ describe('expect()', function() {
+ it('should require specified order', function() {
+ hb.expect('GET', '/url1').respond(200, '');
+ hb.expect('GET', '/url2').respond(200, '');
- it('expect() should without respond() and use respond()', function() {
- callback.andCallFake(function(status, response) {
- expect(status).toBe(201);
- expect(response).toBe('data');
+ expect(function() {
+ hb('GET', '/url2', null, noop, {});
+ }).toThrow('Unexpected request: GET /url2\nExpected GET /url1');
});
- 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 have precedence over when()', function() {
+ callback.andCallFake(function(status, response) {
+ expect(status).toBe(300);
+ expect(response).toBe('expect');
+ });
+ hb.when('GET', '/url').respond(200, 'when');
+ hb.expect('GET', '/url').respond(300, 'expect');
- 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('GET', '/url', null, callback, {});
+ hb.flush();
+ expect(callback).toHaveBeenCalledOnce();
});
- hb.flush();
- expect(callback).toHaveBeenCalled();
- });
-
- 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);
+ 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'});
- hb.flush(2);
- expect(callback).toHaveBeenCalled();
- expect(callback.callCount).toBe(2);
- });
+ expect(function() {
+ hb('GET', '/match', null, noop, {});
+ }).toThrow('Expected GET /match with different headers\n' +
+ 'EXPECTED: {"Content-Type":"application/json"}\nGOT: {}');
+ });
- it('flush() should throw exception when flushing more requests than pending', function() {
- hb.when('GET').respond(200, '');
- hb('GET', '/url', null, callback);
+ 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.flush(2);}).toThrow('No more pending request to flush !');
- expect(callback).toHaveBeenCalledOnce();
- });
+ expect(function() {
+ hb('GET', '/match', 'different', noop, {});
+ }).toThrow('Expected GET /match with different data\n' +
+ 'EXPECTED: some-data\nGOT: different');
+ });
- it('(flush) should throw exception when no request to flush', function() {
- expect(function() {hb.flush();}).toThrow('No pending request to flush !');
+ 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').respond(200, '');
- hb('GET', '/some', null, callback);
- hb.flush();
+ hb.when('GET', '/some').respond(201, 'data');
+ hb.expect('GET', '/some');
+ hb('GET', '/some', null, callback);
+ hb.flush();
- expect(function() {hb.flush();}).toThrow('No pending request to flush !');
+ expect(callback).toHaveBeenCalled();
+ expect(function() { hb.verifyNoOutstandingExpectation(); }).not.toThrow();
+ });
});
- it('(flush) should throw exception if not all expectations satasfied', function() {
- hb.expect('GET', '/url1').respond();
- hb.expect('GET', '/url2').respond();
+ 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('GET', '/url1', null, angular.noop);
- expect(function() {hb.flush();}).toThrow('Unsatisfied requests: GET /url2');
- });
+ hb.flush();
+ expect(callback).toHaveBeenCalled();
+ });
- 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');
+ 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);
+
+ hb.flush(2);
+ expect(callback).toHaveBeenCalled();
+ expect(callback.callCount).toBe(2);
});
- 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 flushing more requests than pending', function() {
+ hb.when('GET').respond(200, '');
+ hb('GET', '/url', null, callback);
- 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');
+ expect(function() {hb.flush(2);}).toThrow('No more pending request to flush !');
+ expect(callback).toHaveBeenCalledOnce();
});
- 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 throw exception when no request to flush', function() {
+ expect(function() {hb.flush();}).toThrow('No pending request to flush !');
- 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.when('GET').respond(200, '');
+ hb('GET', '/some', null, callback);
+ hb.flush();
+
+ expect(function() {hb.flush();}).toThrow('No pending request to flush !');
});
- 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 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, '');
diff --git a/test/service/httpBackendSpec.js b/test/service/httpBackendSpec.js
index b9bf2b18..1c6d3a51 100644
--- a/test/service/httpBackendSpec.js
+++ b/test/service/httpBackendSpec.js
@@ -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();
});
diff --git a/test/service/httpSpec.js b/test/service/httpSpec.js
index 5b8f43f5..c1f8645e 100644
--- a/test/service/httpSpec.js
+++ b/test/service/httpSpec.js
@@ -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);
}));
diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js
index e8ff4b27..09d807b5 100644
--- a/test/widgetsSpec.js
+++ b/test/widgetsSpec.js
@@ -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');
}));
});