diff options
| author | Sylvester Keil | 2013-02-26 10:22:12 +0100 | 
|---|---|---|
| committer | James deBoer | 2013-03-27 13:13:59 -0700 | 
| commit | 4ae46814ff4e7c0bbcdbbefc0a97277283a84065 (patch) | |
| tree | 2c5bcbe2d5bcc143043cb989d8b40899eb5ef8f9 | |
| parent | 5c735eb4ab07144a62949472ed388cb185099201 (diff) | |
| download | angular.js-4ae46814ff4e7c0bbcdbbefc0a97277283a84065.tar.bz2 | |
feat(http): support request/response promise chaining
myApp.factory('myAroundInterceptor', function($rootScope, $timeout) {
    return function(configPromise, responsePromise) {
        return {
            request: configPromise.then(function(config) {
                return config
            });
            response: responsePromise.then(function(response) {
                return 'ha!';
            }
        });
}
myApp.config(function($httpProvider){
    $httpProvider.aroundInterceptors.push('myAroundInterceptor');
});
| -rw-r--r-- | src/ng/compile.js | 8 | ||||
| -rw-r--r-- | src/ng/http.js | 214 | ||||
| -rw-r--r-- | src/ngMock/angular-mocks.js | 8 | ||||
| -rw-r--r-- | test/ng/directive/ngIncludeSpec.js | 12 | ||||
| -rw-r--r-- | test/ng/httpSpec.js | 288 | ||||
| -rw-r--r-- | test/ngResource/resourceSpec.js | 32 | 
6 files changed, 480 insertions, 82 deletions
| diff --git a/src/ng/compile.js b/src/ng/compile.js index 6606dc6c..68a3dca1 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -1018,10 +1018,10 @@ function $CompileProvider($provide) {            while(linkQueue.length) { -            var controller = linkQueue.pop(), -                linkRootElement = linkQueue.pop(), -                beforeTemplateLinkNode = linkQueue.pop(), -                scope = linkQueue.pop(), +            var scope = linkQueue.shift(), +                beforeTemplateLinkNode = linkQueue.shift(), +                linkRootElement = linkQueue.shift(), +                controller = linkQueue.shift(),                  linkNode = compileNode;              if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { diff --git a/src/ng/http.js b/src/ng/http.js index e4d695c4..d54e8bd3 100644 --- a/src/ng/http.js +++ b/src/ng/http.js @@ -155,20 +155,52 @@ function $HttpProvider() {      xsrfHeaderName: 'X-XSRF-TOKEN'    }; -  var providerResponseInterceptors = this.responseInterceptors = []; +  /** +   * Are order by request. I.E. they are applied in the same order as +   * array on request, but revers order on response. +   */ +  var interceptorFactories = this.interceptors = []; +  /** +   * For historical reasons, response interceptors ordered by the order in which +   * they are applied to response. (This is in revers to interceptorFactories) +   */ +  var responseInterceptorFactories = this.responseInterceptors = [];    this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',        function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { -    var defaultCache = $cacheFactory('$http'), -        responseInterceptors = []; +    var defaultCache = $cacheFactory('$http'); -    forEach(providerResponseInterceptors, function(interceptor) { -      responseInterceptors.push( -          isString(interceptor) -              ? $injector.get(interceptor) -              : $injector.invoke(interceptor) -      ); +    /** +     * Interceptors stored in reverse order. Inner interceptors before outer interceptors. +     * The reversal is needed so that we can build up the interception chain around the +     * server request. +     */ +    var reversedInterceptors = []; + +    forEach(interceptorFactories, function(interceptorFactory) { +      reversedInterceptors.unshift(isString(interceptorFactory) +          ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); +    }); + +    forEach(responseInterceptorFactories, function(interceptorFactory, index) { +      var responseFn = isString(interceptorFactory) +          ? $injector.get(interceptorFactory) +          : $injector.invoke(interceptorFactory); + +      /** +       * Response interceptors go before "around" interceptors (no real reason, just +       * had to pick one.) But they are already revesed, so we can't use unshift, hence +       * the splice. +       */ +      reversedInterceptors.splice(index, 0, { +        response: function(response) { +          return responseFn($q.when(response)); +        }, +        responseError: function(response) { +          return responseFn($q.reject(response)); +        } +      });      }); @@ -310,7 +342,90 @@ function $HttpProvider() {       * To skip it, set configuration property `cache` to `false`.       *        * -     * # Response interceptors +     * # Interceptors +     * +     * Before you start creating interceptors, be sure to understand the +     * {@link ng.$q $q and deferred/promise APIs}. +     * +     * For purposes of global error handling, authentication or any kind of synchronous or +     * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be +     * able to intercept requests before they are handed to the server and +     * responses  before they are handed over to the application code that +     * initiated these requests. The interceptors leverage the {@link ng.$q +     * promise APIs} to fulfil this need for both synchronous and asynchronous pre-processing. +     * +     * The interceptors are service factories that are registered with the $httpProvider by +     * adding them to the `$httpProvider.interceptors` array. The factory is called and +     * injected with dependencies (if specified) and returns the interceptor. +     * +     * There are two kinds of interceptors (and two kinds of rejection interceptors): +     * +     *   * `request`: interceptors get called with http `config` object. The function is free to modify +     *     the `config` or create a new one. The function needs to return the `config` directly or as a +     *     promise. +     *   * `requestError`: interceptor gets called when a previous interceptor threw an error or resolved +     *      with a rejection. +     *   * `response`: interceptors get called with http `response` object. The function is free to modify +     *     the `response` or create a new one. The function needs to return the `response` directly or as a +     *     promise. +     *   * `responseError`: interceptor gets called when a previous interceptor threw an error or resolved +     *      with a rejection. +     * +     * +     * <pre> +     *   // register the interceptor as a service +     *   $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { +     *     return { +     *       // optional method +     *       'request': function(config) { +     *         // do something on success +     *         return config || $q.when(config); +     *       }, +     * +     *       // optional method +     *      'requestError': function(rejection) { +     *         // do something on error +     *         if (canRecover(rejection)) { +     *           return responseOrNewPromise +     *         } +     *         return $q.reject(rejection); +     *       }, +     * +     * +     * +     *       // optional method +     *       'response': function(response) { +     *         // do something on success +     *         return response || $q.when(response); +     *       }, +     * +     *       // optional method +     *      'responseError': function(rejection) { +     *         // do something on error +     *         if (canRecover(rejection)) { +     *           return responseOrNewPromise +     *         } +     *         return $q.reject(rejection); +     *       }; +     *     } +     *   }); +     * +     *   $httpProvider.interceptors.push('myHttpInterceptor'); +     * +     * +     *   // register the interceptor via an anonymous factory +     *   $httpProvider.interceptors.push(function($q, dependency1, dependency2) { +     *     return { +     *      'request': function(config) { +     *          // same as above +     *       }, +     *       'response': function(response) { +     *          // same as above +     *       } +     *   }); +     * </pre> +     * +     * # Response interceptors (DEPRECATED)       *       * Before you start creating interceptors, be sure to understand the       * {@link ng.$q $q and deferred/promise APIs}. @@ -526,45 +641,66 @@ function $HttpProvider() {          </file>        </example>       */ -    function $http(config) { +    function $http(requestConfig) { +      var config = { +        transformRequest: defaults.transformRequest, +        transformResponse: defaults.transformResponse +      }; +      var headers = {}; + +      extend(config, requestConfig); +      config.headers = headers;        config.method = uppercase(config.method); -      var xsrfHeader = {}, -          xsrfCookieName = config.xsrfCookieName || defaults.xsrfCookieName, -          xsrfHeaderName = config.xsrfHeaderName || defaults.xsrfHeaderName, -          xsrfToken = isSameDomain(config.url, $browser.url()) ? -                          $browser.cookies()[xsrfCookieName] : undefined; -      xsrfHeader[xsrfHeaderName] = xsrfToken; - -      var reqTransformFn = config.transformRequest || defaults.transformRequest, -          respTransformFn = config.transformResponse || defaults.transformResponse, -          defHeaders = defaults.headers, -          reqHeaders = extend(xsrfHeader, -              defHeaders.common, defHeaders[lowercase(config.method)], config.headers), -          reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn), -          promise; - -      // strip content-type if data is undefined -      if (isUndefined(config.data)) { -        delete reqHeaders['Content-Type']; -      } +      extend(headers, +          defaults.headers.common, +          defaults.headers[lowercase(config.method)], +          requestConfig.headers); -      if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { -        config.withCredentials = defaults.withCredentials; +      var xsrfValue = isSameDomain(config.url, $browser.url()) +          ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] +          : undefined; +      if (xsrfValue) { +        headers[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;        } -      // send request -      promise = sendReq(config, reqData, reqHeaders); +      var serverRequest = function(config) { +        var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); -      // transform future response -      promise = promise.then(transformResponse, transformResponse); +        // strip content-type if data is undefined +        if (isUndefined(config.data)) { +          delete headers['Content-Type']; +        } + +        if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { +          config.withCredentials = defaults.withCredentials; +        } + +        // send request +        return sendReq(config, reqData, headers).then(transformResponse, transformResponse); +      }; + +      var chain = [serverRequest, undefined]; +      var promise = $q.when(config);        // apply interceptors -      forEach(responseInterceptors, function(interceptor) { -        promise = interceptor(promise); +      forEach(reversedInterceptors, function(interceptor) { +        if (interceptor.request || interceptor.requestError) { +          chain.unshift(interceptor.request, interceptor.requestError); +        } +        if (interceptor.response || interceptor.responseError) { +          chain.push(interceptor.response, interceptor.responseError); +        }        }); +      while(chain.length) { +        var thenFn = chain.shift(); +        var rejectFn = chain.shift(); + +        promise = promise.then(thenFn, rejectFn); +      }; +        promise.success = function(fn) {          promise.then(function(response) {            fn(response.data, response.status, response.headers, config); @@ -584,7 +720,7 @@ function $HttpProvider() {        function transformResponse(response) {          // make a copy since the response must be cacheable          var resp = extend({}, response, { -          data: transformData(response.data, response.headers, respTransformFn) +          data: transformData(response.data, response.headers, config.transformResponse)          });          return (isSuccess(response.status))            ? resp diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index 8c91e628..3980a391 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -826,7 +826,7 @@ angular.mock.dump = function(object) {     </pre>   */  angular.mock.$HttpBackendProvider = function() { -  this.$get = [createHttpBackendMock]; +  this.$get = ['$rootScope', createHttpBackendMock];  };  /** @@ -843,7 +843,7 @@ angular.mock.$HttpBackendProvider = function() {   * @param {Object=} $browser Auto-flushing enabled if specified   * @return {Object} Instance of $httpBackend mock   */ -function createHttpBackendMock($delegate, $browser) { +function createHttpBackendMock($rootScope, $delegate, $browser) {    var definitions = [],        expectations = [],        responses = [], @@ -1173,6 +1173,7 @@ function createHttpBackendMock($delegate, $browser) {     *   is called an exception is thrown (as this typically a sign of programming error).     */    $httpBackend.flush = function(count) { +    $rootScope.$digest();      if (!responses.length) throw Error('No pending request to flush !');      if (angular.isDefined(count)) { @@ -1205,6 +1206,7 @@ function createHttpBackendMock($delegate, $browser) {     * </pre>     */    $httpBackend.verifyNoOutstandingExpectation = function() { +    $rootScope.$digest();      if (expectations.length) {        throw Error('Unsatisfied requests: ' + expectations.join(', '));      } @@ -1606,7 +1608,7 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) {   *   control how a matched request is handled.   */  angular.mock.e2e = {}; -angular.mock.e2e.$httpBackendDecorator = ['$delegate', '$browser', createHttpBackendMock]; +angular.mock.e2e.$httpBackendDecorator = ['$rootScope', '$delegate', '$browser', createHttpBackendMock];  angular.mock.clearDataCache = function() { diff --git a/test/ng/directive/ngIncludeSpec.js b/test/ng/directive/ngIncludeSpec.js index 7c94a70e..dce803b5 100644 --- a/test/ng/directive/ngIncludeSpec.js +++ b/test/ng/directive/ngIncludeSpec.js @@ -178,25 +178,23 @@ describe('ngInclude', function() {    it('should discard pending xhr callbacks if a new template is requested before the current ' +        'finished loading', inject(function($rootScope, $compile, $httpBackend) {      element = jqLite("<ng:include src='templateUrl'></ng:include>"); -    var log = []; +    var log = {};      $rootScope.templateUrl = 'myUrl1';      $rootScope.logger = function(msg) { -      log.push(msg); +      log[msg] = true;      }      $compile(element)($rootScope); -    expect(log.join('; ')).toEqual(''); +    expect(log).toEqual({});      $httpBackend.expect('GET', 'myUrl1').respond('<div>{{logger("url1")}}</div>');      $rootScope.$digest(); -    expect(log.join('; ')).toEqual(''); +    expect(log).toEqual({});      $rootScope.templateUrl = 'myUrl2';      $httpBackend.expect('GET', 'myUrl2').respond('<div>{{logger("url2")}}</div>'); -    $rootScope.$digest();      $httpBackend.flush(); // now that we have two requests pending, flush! -    expect(log.join('; ')).toEqual('url2; url2'); // it's here twice because we go through at -                                                  // least two digest cycles +    expect(log).toEqual({ url2 : true });    })); diff --git a/test/ng/httpSpec.js b/test/ng/httpSpec.js index 2dd14192..e6d1cf4f 100644 --- a/test/ng/httpSpec.js +++ b/test/ng/httpSpec.js @@ -12,7 +12,7 @@ describe('$http', function() {        $exceptionHandlerProvider.mode('log');    })); -  afterEach(inject(function($exceptionHandler, $httpBackend) { +  afterEach(inject(function($exceptionHandler, $httpBackend, $rootScope) {      forEach($exceptionHandler.errors, function(e) {        dump('Unhandled exception: ', e)      }); @@ -21,13 +21,150 @@ describe('$http', function() {        throw 'Unhandled exceptions trapped in $exceptionHandler!';      } +    $rootScope.$digest();      $httpBackend.verifyNoOutstandingExpectation();    }));    describe('$httpProvider', function() { -      describe('interceptors', function() { +     it('should accept injected rejected response interceptor', function() { +        var wasCalled = false; +        module(function($httpProvider, $provide) { +          $httpProvider.responseInterceptors.push('injectedInterceptor'); +          $provide.factory('injectedInterceptor', ['$q', function($q) { +            return function(promise) { +              return promise.then(null, function authInterceptor(response) { +                wasCalled = true; +                expect(response.status).toEqual(401); +                return $q.reject(response); +              }); +            }; +          }]); +        }); +        inject(function($http, $httpBackend) { +          $httpBackend.expect('GET', '/url').respond(401); +          $http({method: 'GET', url: '/url'}); +          $httpBackend.flush(); +          expect(wasCalled).toEqual(true); +        }); +      }); + + +      it('should chain request, requestReject, response and responseReject interceptors', function() { +        module(function($httpProvider) { +          var savedConfig, savedResponse; +          $httpProvider.interceptors.push(function($q) { +            return { +              request: function(config) { +                config.url += '/1'; +                savedConfig = config; +                return $q.reject('/2'); +              } +            }; +          }); +          $httpProvider.interceptors.push(function($q) { +            return { +              requestError: function(error) { +                savedConfig.url += error; +                return $q.when(savedConfig); +              } +            }; +          }); +          $httpProvider.interceptors.push(function() { +            return { +              responseError: function(rejection) { +                savedResponse.data += rejection; +                return savedResponse; +              } +            }; +          }); +          $httpProvider.interceptors.push(function($q) { +            return { +              response: function(response) { +                response.data += ':1'; +                savedResponse = response +                return $q.reject(':2'); +              } +            }; +          }); +        }); +        inject(function($http, $httpBackend, $rootScope) { +          var response; +          $httpBackend.expect('GET', '/url/1/2').respond('response'); +          $http({method: 'GET', url: '/url'}).then(function(r) { +            response = r; +          }); +          $rootScope.$apply(); +          $httpBackend.flush(); +          expect(response.data).toEqual('response:1:2'); +        }); +      }); + + +      it('should verify order of execution', function() { +        module(function($httpProvider) { +          $httpProvider.interceptors.push(function($q) { +            return { +              request: function(config) { +                config.url += '/outer'; +                return config; +              }, +              response: function(response) { +                response.data = '{' + response.data + '} outer'; +                return response; +              } +            }; +          }); +          $httpProvider.interceptors.push(function($q) { +            return { +              request: function(config) { +                config.url += '/inner'; +                return config; +              }, +              response: function(response) { +                response.data = '{' + response.data + '} inner'; +                return response; +              } +            }; +          }); +          $httpProvider.responseInterceptors.push(function($q) { +            return function(promise) { +              var defer = $q.defer(); + +              promise.then(function(response) { +                response.data = '[' + response.data + '] legacy-1'; +                defer.resolve(response); +              }); +              return defer.promise; +            }; +          }); +          $httpProvider.responseInterceptors.push(function($q) { +            return function(promise) { +              var defer = $q.defer(); + +              promise.then(function(response) { +                response.data = '[' + response.data + '] legacy-2'; +                defer.resolve(response); +              }); +              return defer.promise; +            }; +          }); +        }); +        inject(function($http, $httpBackend) { +          var response; +          $httpBackend.expect('GET', '/url/outer/inner').respond('response'); +          $http({method: 'GET', url: '/url'}).then(function(r) { +            response = r; +          }); +          $httpBackend.flush(); +          expect(response.data).toEqual('{{[[response] legacy-1] legacy-2} inner} outer'); +        }); +      }); +    }); + + +    describe('response interceptors', function() {        it('should default to an empty array', module(function($httpProvider) {          expect($httpProvider.responseInterceptors).toEqual([]); @@ -44,7 +181,7 @@ describe('$http', function() {                    data: response.data + '?',                    status: 209,                    headers: response.headers, -                  config: response.config +                  request: response.config                  });                  return deferred.promise;                }); @@ -100,6 +237,136 @@ describe('$http', function() {          });        });      }); + + +    describe('request interceptors', function() { +      it('should pass request config as a promise', function() { +        var run = false; +        module(function($httpProvider) { +          $httpProvider.interceptors.push(function() { +            return { +              request: function(config) { +                expect(config.url).toEqual('/url'); +                expect(config.data).toEqual({one: "two"}); +                expect(config.headers.foo).toEqual('bar'); +                run = true; +                return config; +              } +            }; +          }); +        }); +        inject(function($http, $httpBackend, $rootScope) { +          $httpBackend.expect('POST', '/url').respond(''); +          $http({method: 'POST', url: '/url', data: {one: 'two'}, headers: {foo: 'bar'}}); +          $rootScope.$apply(); +          expect(run).toEqual(true); +        }); +      }); + +      it('should allow manipulation of request', function() { +        module(function($httpProvider) { +          $httpProvider.interceptors.push(function() { +            return { +              request: function(config) { +                config.url = '/intercepted'; +                config.headers.foo = 'intercepted'; +                return config; +              } +            }; +          }); +        }); +        inject(function($http, $httpBackend, $rootScope) { +          $httpBackend.expect('GET', '/intercepted', null, function (headers) { +            return headers.foo === 'intercepted'; +          }).respond(''); +          $http.get('/url'); +          $rootScope.$apply(); +        }); +      }); + +      it('should reject the http promise if an interceptor fails', function() { +        var reason = new Error('interceptor failed'); +        module(function($httpProvider) { +          $httpProvider.interceptors.push(function($q) { +            return { +              request: function(promise) { +                return $q.reject(reason); +              } +            }; +          }); +        }); +        inject(function($http, $httpBackend, $rootScope) { +          var success = jasmine.createSpy(), error = jasmine.createSpy(); +          $http.get('/url').then(success, error); +          $rootScope.$apply(); +          expect(success).not.toHaveBeenCalled(); +          expect(error).toHaveBeenCalledWith(reason); +        }); +      }); + +      it('should not manipulate the passed-in config', function() { +        module(function($httpProvider) { +          $httpProvider.interceptors.push(function() { +            return { +              request: function(config) { +                config.url = '/intercepted'; +                config.headers.foo = 'intercepted'; +                return config; +              } +            }; +          }); +        }); +        inject(function($http, $httpBackend, $rootScope) { +          var config = { method: 'get', url: '/url', headers: { foo: 'bar'} }; +          $httpBackend.expect('GET', '/intercepted').respond(''); +          $http.get('/url'); +          $rootScope.$apply(); +          expect(config.method).toEqual('get'); +          expect(config.url).toEqual('/url'); +          expect(config.headers.foo).toEqual('bar') +        }); +      }); + +      it('should support interceptors defined as services', function() { +        module(function($provide, $httpProvider) { +          $provide.factory('myInterceptor', function() { +            return { +              request: function(config) { +                config.url = '/intercepted'; +                return config; +              } +            }; +          }); +          $httpProvider.interceptors.push('myInterceptor'); +        }); +        inject(function($http, $httpBackend, $rootScope) { +          $httpBackend.expect('POST', '/intercepted').respond(''); +          $http.post('/url'); +          $rootScope.$apply(); +        }); +      }); + +      it('should support complex interceptors based on promises', function() { +        module(function($provide, $httpProvider) { +          $provide.factory('myInterceptor', function($q, $rootScope) { +            return { +              request: function(config) { +                return $q.when('/intercepted').then(function(intercepted) { +                  config.url = intercepted; +                  return config; +                }); +              } +            }; +          }); +          $httpProvider.interceptors.push('myInterceptor'); +        }); +        inject(function($http, $httpBackend, $rootScope) { +          $httpBackend.expect('POST', '/intercepted').respond(''); +          $http.post('/two'); +          $rootScope.$apply(); +        }); +      }); +    });    }); @@ -938,7 +1205,7 @@ describe('$http', function() {            $http({method: 'GET', url: '/url'}); // Notice no cache given in config.            $httpBackend.flush(); -          // Second should be served from cache, without sending request to server.  +          // Second should be served from cache, without sending request to server.            $http({method: 'get', url: '/url'}).success(callback);            $rootScope.$digest(); @@ -1004,6 +1271,7 @@ describe('$http', function() {          expect($http.pendingRequests.length).toBe(0);          $http({method: 'get', url: '/some'}); +        $rootScope.$digest();          expect($http.pendingRequests.length).toBe(1);          $httpBackend.flush(); @@ -1016,13 +1284,16 @@ describe('$http', function() {          $http({method: 'get', url: '/cached', cache: true});          $http({method: 'get', url: '/cached', cache: true}); +        $rootScope.$digest();          expect($http.pendingRequests.length).toBe(2);          $httpBackend.flush();          expect($http.pendingRequests.length).toBe(0);          $http({method: 'get', url: '/cached', cache: true}); -        expect($http.pendingRequests.length).toBe(1); +        spyOn($http.pendingRequests, 'push').andCallThrough(); +        $rootScope.$digest(); +        expect($http.pendingRequests.push).toHaveBeenCalledOnce();          $rootScope.$apply();          expect($http.pendingRequests.length).toBe(0); @@ -1035,6 +1306,7 @@ describe('$http', function() {            expect($http.pendingRequests.length).toBe(0);          }); +        $rootScope.$digest();          expect($http.pendingRequests.length).toBe(1);          $httpBackend.flush();        }); @@ -1071,10 +1343,11 @@ describe('$http', function() {        $provide.value('$httpBackend', $httpBackend);      }); -    inject(function($http) { +    inject(function($http, $rootScope) {        $http({          method: 'GET', url: 'some.html', timeout: 12345, withCredentials: true, responseType: 'json'        }); +      $rootScope.$digest();        expect($httpBackend).toHaveBeenCalledOnce();      }); @@ -1093,11 +1366,12 @@ describe('$http', function() {        $provide.value('$httpBackend', $httpBackend);      }); -    inject(function($http) { +    inject(function($http, $rootScope) {        $http.defaults.withCredentials = true;        $http({          method: 'GET', url: 'some.html', timeout: 12345, responseType: 'json'        }); +      $rootScope.$digest();        expect($httpBackend).toHaveBeenCalledOnce();      }); diff --git a/test/ngResource/resourceSpec.js b/test/ngResource/resourceSpec.js index 11124739..225f96a1 100644 --- a/test/ngResource/resourceSpec.js +++ b/test/ngResource/resourceSpec.js @@ -468,12 +468,9 @@ describe("resource", function() {          var response = callback.mostRecentCall.args[0]; -        expect(response).toEqualData({ -          data: {id: 123, number: '9876'}, -          status: 200, -          config: {method: 'GET', data: undefined, url: '/CreditCard/123'}, -          resource: {id: 123, number: '9876', $resolved: true} -        }); +        expect(response.data).toEqual({id: 123, number: '9876'}); +        expect(response.status).toEqual(200); +        expect(response.resource).toEqualData({id: 123, number: '9876', $resolved: true});          expect(typeof response.resource.$save).toBe('function');        }); @@ -516,11 +513,8 @@ describe("resource", function() {          var response = callback.mostRecentCall.args[0]; -        expect(response).toEqualData({ -          data : 'resource not found', -          status : 404, -          config : { method : 'GET', data : undefined, url : '/CreditCard/123' } -        }); +        expect(response.data).toEqual('resource not found'); +        expect(response.status).toEqual(404);        }); @@ -564,12 +558,9 @@ describe("resource", function() {          var response = callback.mostRecentCall.args[0]; -        expect(response).toEqualData({ -          data: [{id: 1}, {id :2}], -          status: 200, -          config: {method: 'GET', data: undefined, url: '/CreditCard', params: {key: 'value'}}, -          resource: [ { id : 1 }, { id : 2 } ] -        }); +        expect(response.data).toEqual([{id: 1}, {id :2}]); +        expect(response.status).toEqual(200); +        expect(response.resource).toEqualData([ { id : 1 }, { id : 2 } ]);          expect(typeof response.resource[0].$save).toBe('function');          expect(typeof response.resource[1].$save).toBe('function');        }); @@ -613,11 +604,8 @@ describe("resource", function() {          var response = callback.mostRecentCall.args[0]; -        expect(response).toEqualData({ -          data : 'resource not found', -          status : 404, -          config : { method : 'GET', data : undefined, url : '/CreditCard', params: {key: 'value'}} -        }); +        expect(response.data).toEqual('resource not found'); +        expect(response.status).toEqual(404);        }); | 
