diff options
| author | Karl Seamon | 2011-07-22 15:56:45 -0400 | 
|---|---|---|
| committer | Igor Minar | 2011-07-27 15:21:31 -0700 | 
| commit | b5594a773a6f07dcba914aa385f92d3305285b24 (patch) | |
| tree | 40823e64e9f74b356a8065edae9bbdf351082164 | |
| parent | f39420e7d7aca2a97eaa01853991facf65dcbd6e (diff) | |
| download | angular.js-b5594a773a6f07dcba914aa385f92d3305285b24.tar.bz2 | |
feat($xhr): add custom error callback to $xhr, $xhr.cache, $xhr.bulk, $resource
Closes #408
| -rw-r--r-- | src/Resource.js | 69 | ||||
| -rw-r--r-- | src/service/resource.js | 10 | ||||
| -rw-r--r-- | src/service/xhr.bulk.js | 60 | ||||
| -rw-r--r-- | src/service/xhr.cache.js | 90 | ||||
| -rw-r--r-- | src/service/xhr.js | 29 | ||||
| -rw-r--r-- | test/ResourceSpec.js | 42 | ||||
| -rw-r--r-- | test/service/xhr.bulkSpec.js | 29 | ||||
| -rw-r--r-- | test/service/xhr.cacheSpec.js | 38 | ||||
| -rw-r--r-- | test/service/xhr.errorSpec.js | 6 | ||||
| -rw-r--r-- | test/service/xhrSpec.js | 42 | 
10 files changed, 291 insertions, 124 deletions
| diff --git a/src/Resource.js b/src/Resource.js index 5462826d..3c149d8b 100644 --- a/src/Resource.js +++ b/src/Resource.js @@ -67,29 +67,36 @@ ResourceFactory.prototype = {      forEach(actions, function(action, name){        var isPostOrPut = action.method == 'POST' || action.method == 'PUT'; -      Resource[name] = function (a1, a2, a3) { +      Resource[name] = function (a1, a2, a3, a4) {          var params = {};          var data; -        var callback = noop; +        var success = noop; +        var error = null;          switch(arguments.length) { -        case 3: callback = a3; +        case 4: +          error = a4; +          success = a3; +        case 3:          case 2:            if (isFunction(a2)) { -            callback = a2; +            success = a2; +            error = a3;              //fallthrough            } else {              params = a1;              data = a2; +            success = a3;              break;            }          case 1: -          if (isFunction(a1)) callback = a1; +          if (isFunction(a1)) success = a1;            else if (isPostOrPut) data = a1;            else params = a1;            break;          case 0: break;          default: -          throw "Expected between 0-3 arguments [params, data, callback], got " + arguments.length + " arguments."; +          throw "Expected between 0-4 arguments [params, data, success, error], got " + +            arguments.length + " arguments.";          }          var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data)); @@ -97,23 +104,20 @@ ResourceFactory.prototype = {            action.method,            route.url(extend({}, action.params || {}, extractParams(data), params)),            data, -          function(status, response, clear) { -            if (200 <= status && status < 300) { -              if (response) { -                if (action.isArray) { -                  value.length = 0; -                  forEach(response, function(item){ -                    value.push(new Resource(item)); -                  }); -                } else { -                  copy(response, value); -                } +          function(status, response) { +            if (response) { +              if (action.isArray) { +                value.length = 0; +                forEach(response, function(item) { +                  value.push(new Resource(item)); +                }); +              } else { +                copy(response, value);                } -              (callback||noop)(value); -            } else { -              throw {status: status, response:response, message: status + ": " + response};              } +            (success||noop)(value);            }, +          error || action.verifyCache,            action.verifyCache);          return value;        }; @@ -122,18 +126,29 @@ ResourceFactory.prototype = {          return self.route(url, extend({}, paramDefaults, additionalParamDefaults), actions);        }; -      Resource.prototype['$' + name] = function(a1, a2){ -        var params = extractParams(this); -        var callback = noop; +      Resource.prototype['$' + name] = function(a1, a2, a3) { +        var params = extractParams(this), +            success = noop, +            error; +          switch(arguments.length) { -        case 2: params = a1; callback = a2; -        case 1: if (typeof a1 == $function) callback = a1; else params = a1; +        case 3: params = a1; success = a2; error = a3; break; +        case 2: +        case 1: +          if (isFunction(a1)) { +            success = a1; +            error = a2; +          } else { +            params = a1; +            success = a2 || noop; +          }          case 0: break;          default: -          throw "Expected between 1-2 arguments [params, callback], got " + arguments.length + " arguments."; +          throw "Expected between 1-3 arguments [params, success, error], got " + +            arguments.length + " arguments.";          }          var data = isPostOrPut ? this : undefined; -        Resource[name].call(this, params, data, callback); +        Resource[name].call(this, params, data, success, error);        };      });      return Resource; diff --git a/src/service/resource.js b/src/service/resource.js index 31d7ceeb..c11067b1 100644 --- a/src/service/resource.js +++ b/src/service/resource.js @@ -82,9 +82,9 @@   *   The action methods on the class object or instance object can be invoked with the following   *   parameters:   * - *   - HTTP GET "class" actions: `Resource.action([parameters], [callback])` - *   - non-GET "class" actions: `Resource.action(postData, [parameters], [callback])` - *   - non-GET instance actions:  `instance.$action([parameters], [callback])` + *   - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` + *   - non-GET "class" actions: `Resource.action(postData, [parameters], [success], [error])` + *   - non-GET instance actions:  `instance.$action([parameters], [success], [error])`   *   *   * @example @@ -142,8 +142,8 @@       });     </pre>   * - *     It's worth noting that the callback for `get`, `query` and other method gets passed in the - *     response that came from the server, so one could rewrite the above example as: + *     It's worth noting that the success callback for `get`, `query` and other method gets passed + *     in the response that came from the server, so one could rewrite the above example as:   *     <pre>       var User = $resource('/user/:userId', {userId:'@id'}); diff --git a/src/service/xhr.bulk.js b/src/service/xhr.bulk.js index c0940d9d..d7fc7990 100644 --- a/src/service/xhr.bulk.js +++ b/src/service/xhr.bulk.js @@ -15,9 +15,10 @@  angularServiceInject('$xhr.bulk', function($xhr, $error, $log){    var requests = [],        scope = this; -  function bulkXHR(method, url, post, callback) { +  function bulkXHR(method, url, post, success, error) {      if (isFunction(post)) { -      callback = post; +      error = success; +      success = post;        post = null;      }      var currentQueue; @@ -28,32 +29,55 @@ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){      });      if (currentQueue) {        if (!currentQueue.requests) currentQueue.requests = []; -      currentQueue.requests.push({method: method, url: url, data:post, callback:callback}); +      var request = { +          method: method, +          url: url, +          data: post, +          success: success}; +      if (error) request.error = error; +      currentQueue.requests.push(request);      } else { -      $xhr(method, url, post, callback); +      $xhr(method, url, post, success, error);      }    }    bulkXHR.urls = {}; -  bulkXHR.flush = function(callback){ -    forEach(bulkXHR.urls, function(queue, url){ +  bulkXHR.flush = function(success, error) { +    forEach(bulkXHR.urls, function(queue, url) {        var currentRequests = queue.requests;        if (currentRequests && currentRequests.length) {          queue.requests = [];          queue.callbacks = []; -        $xhr('POST', url, {requests:currentRequests}, function(code, response){ -          forEach(response, function(response, i){ -            try { -              if (response.status == 200) { -                (currentRequests[i].callback || noop)(response.status, response.response); -              } else { -                $error(currentRequests[i], response); +        $xhr('POST', url, {requests: currentRequests}, +          function(code, response) { +            forEach(response, function(response, i) { +              try { +                if (response.status == 200) { +                  (currentRequests[i].success || noop)(response.status, response.response); +                } else if (isFunction(currentRequests[i].error)) { +                    currentRequests[i].error(response.status, response.response); +                } else { +                  $error(currentRequests[i], response); +                } +              } catch(e) { +                $log.error(e);                } -            } catch(e) { -              $log.error(e); -            } +            }); +            (success || noop)(); +          }, +          function(code, response) { +            forEach(currentRequests, function(request, i) { +              try { +                if (isFunction(request.error)) { +                  request.error(code, response); +                } else { +                  $error(request, response); +                } +              } catch(e) { +                $log.error(e); +              } +            }); +            (error || noop)();            }); -          (callback || noop)(); -        });          scope.$eval();        }      }); diff --git a/src/service/xhr.cache.js b/src/service/xhr.cache.js index 42b666e1..256b936e 100644 --- a/src/service/xhr.cache.js +++ b/src/service/xhr.cache.js @@ -5,7 +5,11 @@   * @ngdoc service   * @name angular.service.$xhr.cache   * @function - * @requires $xhr + * + * @requires $xhr.bulk + * @requires $defer + * @requires $xhr.error + * @requires $log   *   * @description   * Acts just like the {@link angular.service.$xhr $xhr} service but caches responses for `GET` @@ -18,27 +22,42 @@   * @param {string} method HTTP method.   * @param {string} url Destination URL.   * @param {(string|Object)=} post Request body. - * @param {function(number, (string|Object))} callback Response callback. + * @param {function(number, (string|Object))} success Response success callback. + * @param {function(number, (string|Object))=} error Response error callback.   * @param {boolean=} [verifyCache=false] If `true` then a result is immediately returned from cache   *   (if present) while a request is sent to the server for a fresh response that will update the - *   cached entry. The `callback` function will be called when the response is received. - * @param {boolean=} [sync=false] in case of cache hit execute `callback` synchronously. + *   cached entry. The `success` function will be called when the response is received. + * @param {boolean=} [sync=false] in case of cache hit execute `success` synchronously.   */ -angularServiceInject('$xhr.cache', function($xhr, $defer, $log){ +angularServiceInject('$xhr.cache', function($xhr, $defer, $error, $log) {    var inflight = {}, self = this; -  function cache(method, url, post, callback, verifyCache, sync){ +  function cache(method, url, post, success, error, verifyCache, sync) {      if (isFunction(post)) { -      callback = post; +      if (!isFunction(success)) { +        verifyCache = success; +        sync = error; +        error = null; +      } else { +        sync = verifyCache; +        verifyCache = error; +        error = success; +      } +      success = post;        post = null; +    } else if (!isFunction(error)) { +      sync = verifyCache; +      verifyCache = error; +      error = null;      } +      if (method == 'GET') {        var data, dataCached;        if (dataCached = cache.data[url]) {          if (sync) { -          callback(200, copy(dataCached.value)); +          success(200, copy(dataCached.value));          } else { -          $defer(function() { callback(200, copy(dataCached.value)); }); +          $defer(function() { success(200, copy(dataCached.value)); });          }          if (!verifyCache) @@ -46,30 +65,51 @@ angularServiceInject('$xhr.cache', function($xhr, $defer, $log){        }        if (data = inflight[url]) { -        data.callbacks.push(callback); +        data.successes.push(success); +        data.errors.push(error);        } else { -        inflight[url] = {callbacks: [callback]}; -        cache.delegate(method, url, post, function(status, response){ -          if (status == 200) -            cache.data[url] = { value: response }; -          var callbacks = inflight[url].callbacks; -          delete inflight[url]; -          forEach(callbacks, function(callback){ -            try { -              (callback||noop)(status, copy(response)); -            } catch(e) { -              $log.error(e); -            } +        inflight[url] = {successes: [success], errors: [error]}; +        cache.delegate(method, url, post, +          function(status, response) { +            if (status == 200) +              cache.data[url] = {value: response}; +            var successes = inflight[url].successes; +            delete inflight[url]; +            forEach(successes, function(success) { +              try { +                (success||noop)(status, copy(response)); +              } catch(e) { +                $log.error(e); +              } +            }); +          }, +          function(status, response) { +            var errors = inflight[url].errors, +                successes = inflight[url].successes; +            delete inflight[url]; + +            forEach(errors, function(error, i) { +              try { +                if (isFunction(error)) { +                  error(status, copy(response)); +                } else { +                  $error( +                    {method: method, url: url, data: post, success: successes[i]}, +                    {status: status, body: response}); +                } +              } catch(e) { +                $log.error(e); +              } +            });            }); -        });        }      } else {        cache.data = {}; -      cache.delegate(method, url, post, callback); +      cache.delegate(method, url, post, success, error);      }    }    cache.data = {};    cache.delegate = $xhr;    return cache; -}, ['$xhr.bulk', '$defer', '$log']); +}, ['$xhr.bulk', '$defer', '$xhr.error', '$log']); diff --git a/src/service/xhr.js b/src/service/xhr.js index 5fc5223e..dc18419d 100644 --- a/src/service/xhr.js +++ b/src/service/xhr.js @@ -21,10 +21,10 @@   * {@link angular.service.$resource $resource} service.   *   * # Error handling - * All XHR responses with response codes other then `2xx` are delegated to - * {@link angular.service.$xhr.error $xhr.error}. The `$xhr.error` can intercept the request - * and process it in application specific way, or resume normal execution by calling the - * request callback method. + * If no `error callback` is specified, XHR response with response code other then `2xx` will be + * delegated to {@link angular.service.$xhr.error $xhr.error}. The `$xhr.error` can intercept the + * request and process it in application specific way, or resume normal execution by calling the + * request `success` method.   *   * # HTTP Headers   * The $xhr service will automatically add certain http headers to all requests. These defaults can @@ -96,14 +96,16 @@   *   angular generated callback function.   * @param {(string|Object)=} post Request content as either a string or an object to be stringified   *   as JSON before sent to the server. - * @param {function(number, (string|Object))} callback A function to be called when the response is - *   received. The callback will be called with: + * @param {function(number, (string|Object))} success A function to be called when the response is + *   received. The success function will be called with:   *   *   - {number} code [HTTP status code](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes) of   *     the response. This will currently always be 200, since all non-200 responses are routed to - *     {@link angular.service.$xhr.error} service. + *     {@link angular.service.$xhr.error} service (or custom error callback).   *   - {string|Object} response Response object as string or an Object if the response was in JSON   *     format. + * @param {function(number, (string|Object))} error A function to be called if the response code is + *   not 2xx.. Accepts the same arguments as success, above.   *   * @example     <doc:example> @@ -158,9 +160,10 @@ angularServiceInject('$xhr', function($browser, $error, $log, $updateView){      patch: {}    }; -  function xhr(method, url, post, callback){ +  function xhr(method, url, post, success, error) {      if (isFunction(post)) { -      callback = post; +      error = success; +      success = post;        post = null;      }      if (post && isObject(post)) { @@ -176,11 +179,13 @@ angularServiceInject('$xhr', function($browser, $error, $log, $updateView){            }          }          if (200 <= code && code < 300) { -          callback(code, response); +          success(code, response); +        } else if (isFunction(error)) { +          error(code, response);          } else {            $error( -            {method: method, url:url, data:post, callback:callback}, -            {status: code, body:response}); +            {method: method, url: url, data: post, success: success}, +            {status: code, body: response});          }        } catch (e) {          $log.error(e); diff --git a/test/ResourceSpec.js b/test/ResourceSpec.js index 0b8d2187..81519f0f 100644 --- a/test/ResourceSpec.js +++ b/test/ResourceSpec.js @@ -1,12 +1,12 @@  'use strict';  describe("resource", function() { -  var xhr, resource, CreditCard, callback; +  var xhr, resource, CreditCard, callback, $xhrErr; -  beforeEach(function(){ -    var browser = new MockBrowser(); -    xhr = browser.xhr; -    resource = new ResourceFactory(xhr); +  beforeEach(function() { +    var scope = angular.scope({}, null, {'$xhr.error': $xhrErr = jasmine.createSpy('xhr.error')}); +    xhr = scope.$service('$browser').xhr; +    resource = new ResourceFactory(scope.$service('$xhr'));      CreditCard = resource.route('/CreditCard/:id:verb', {id:'@id.key'}, {        charge:{          method:'POST', @@ -242,19 +242,25 @@ describe("resource", function() {      dealoc(scope);    }); -  describe('failure mode', function(){ -    it('should report error when non 200', function(){ -      xhr.expectGET('/CreditCard/123').respond(500, "Server Error"); -      var cc = CreditCard.get({id:123}); -      try { -        xhr.flush(); -        fail('expected exception, non thrown'); -      } catch (e) { -        expect(e.status).toEqual(500); -        expect(e.response).toEqual('Server Error'); -        expect(e.message).toEqual('500: Server Error'); -      } +  describe('failure mode', function() { +    var ERROR_CODE = 500, +        ERROR_RESPONSE = 'Server Error'; + +    beforeEach(function() { +      xhr.expectGET('/CreditCard/123').respond(ERROR_CODE, ERROR_RESPONSE);      }); -  }); +    it('should report error when non 2xx if error callback is not provided', function() { +      CreditCard.get({id:123}); +      xhr.flush(); +      expect($xhrErr).toHaveBeenCalled(); +    }); + +    it('should call the error callback if provided on non 2xx response', function() { +      CreditCard.get({id:123}, noop, callback); +      xhr.flush(); +      expect(callback).toHaveBeenCalledWith(500, ERROR_RESPONSE); +      expect($xhrErr).not.toHaveBeenCalled(); +    }); +  });  }); diff --git a/test/service/xhr.bulkSpec.js b/test/service/xhr.bulkSpec.js index bc8d03f8..adcb61fa 100644 --- a/test/service/xhr.bulkSpec.js +++ b/test/service/xhr.bulkSpec.js @@ -4,13 +4,11 @@ describe('$xhr.bulk', function() {    var scope, $browser, $browserXhr, $log, $xhrBulk, $xhrError, log;    beforeEach(function(){ -    scope = angular.scope({}, angular.service, { -      '$xhr.error': $xhrError = jasmine.createSpy('$xhr.error'), -      '$log': $log = {} -    }); +    scope = angular.scope({}, null, {'$xhr.error': $xhrError = jasmine.createSpy('$xhr.error')});      $browser = scope.$service('$browser');      $browserXhr = $browser.xhr;      $xhrBulk = scope.$service('$xhr.bulk'); +    $log = scope.$service('$log');      log = '';    }); @@ -60,12 +58,29 @@ describe('$xhr.bulk', function() {      $browserXhr.flush();      expect($xhrError).toHaveBeenCalled(); -    var cb = $xhrError.mostRecentCall.args[0].callback; +    var cb = $xhrError.mostRecentCall.args[0].success;      expect(typeof cb).toEqual($function);      expect($xhrError).toHaveBeenCalledWith( -        {url:'/req1', method:'GET', data:null, callback:cb}, -        {status:404, response:'NotFound'}); +        {url: '/req1', method: 'GET', data: null, success: cb}, +        {status: 404, response: 'NotFound'});      expect(log).toEqual('"second";DONE');    }); + +  it('should handle non 200 status code by calling error callback if provided', function() { +    var callback = jasmine.createSpy('error'); + +    $xhrBulk.urls['/'] = {match: /.*/}; +    $xhrBulk('GET', '/req1', null, noop, callback); + +    $browserXhr.expectPOST('/', { +      requests:[{method: 'GET',  url: '/req1', data: null}] +    }).respond([{status: 404, response: 'NotFound'}]); + +    $xhrBulk.flush(); +    $browserXhr.flush(); + +    expect($xhrError).not.toHaveBeenCalled(); +    expect(callback).toHaveBeenCalledWith(404, 'NotFound'); +  });  }); diff --git a/test/service/xhr.cacheSpec.js b/test/service/xhr.cacheSpec.js index 905a9dae..f4654cd4 100644 --- a/test/service/xhr.cacheSpec.js +++ b/test/service/xhr.cacheSpec.js @@ -1,10 +1,10 @@  'use strict';  describe('$xhr.cache', function() { -  var scope, $browser, $browserXhr, cache, log; +  var scope, $browser, $browserXhr, $xhrErr, cache, log; -  beforeEach(function(){ -    scope = angular.scope(); +  beforeEach(function() { +    scope = angular.scope({}, null, {'$xhr.error': $xhrErr = jasmine.createSpy('$xhr.error')});      $browser = scope.$service('$browser');      $browserXhr = $browser.xhr;      cache = scope.$service('$xhr.cache'); @@ -143,4 +143,36 @@ describe('$xhr.cache', function() {      $browser.defer.flush();      expect(evalSpy).toHaveBeenCalled();    }); + +  it('should call the error callback on error if provided', function() { +    var errorSpy = jasmine.createSpy('error'), +        successSpy = jasmine.createSpy('success'); + +    $browserXhr.expectGET('/url').respond(500, 'error'); + +    cache('GET', '/url', null, successSpy, errorSpy, false, true); +    $browserXhr.flush(); +    expect(errorSpy).toHaveBeenCalledWith(500, 'error'); +    expect(successSpy).not.toHaveBeenCalled(); + +    errorSpy.reset(); +    cache('GET', '/url', successSpy, errorSpy, false, true); +    $browserXhr.flush(); +    expect(errorSpy).toHaveBeenCalledWith(500, 'error'); +    expect(successSpy).not.toHaveBeenCalled(); +  }); + +  it('should call the $xhr.error on error if error callback not provided', function() { +    var errorSpy = jasmine.createSpy('error'), +        successSpy = jasmine.createSpy('success'); + +    $browserXhr.expectGET('/url').respond(500, 'error'); +    cache('GET', '/url', null, successSpy, false, true); +    $browserXhr.flush(); + +    expect(successSpy).not.toHaveBeenCalled(); +    expect($xhrErr).toHaveBeenCalledWith( +      {method: 'GET', url: '/url', data: null, success: successSpy}, +      {status: 500, body: 'error'}); +  });  }); diff --git a/test/service/xhr.errorSpec.js b/test/service/xhr.errorSpec.js index fdca93ec..d3af4565 100644 --- a/test/service/xhr.errorSpec.js +++ b/test/service/xhr.errorSpec.js @@ -29,10 +29,10 @@ describe('$xhr.error', function() {      $browserXhr.expectPOST('/req', 'MyData').respond(500, 'MyError');      $xhr('POST', '/req', 'MyData', callback);      $browserXhr.flush(); -    var cb = $xhrError.mostRecentCall.args[0].callback; +    var cb = $xhrError.mostRecentCall.args[0].success;      expect(typeof cb).toEqual($function);      expect($xhrError).toHaveBeenCalledWith( -        {url:'/req', method:'POST', data:'MyData', callback:cb}, -        {status:500, body:'MyError'}); +        {url: '/req', method: 'POST', data: 'MyData', success: cb}, +        {status: 500, body: 'MyError'});    });  }); diff --git a/test/service/xhrSpec.js b/test/service/xhrSpec.js index ed7cfc93..9f496535 100644 --- a/test/service/xhrSpec.js +++ b/test/service/xhrSpec.js @@ -1,12 +1,11 @@  'use strict';  describe('$xhr', function() { -  var scope, $browser, $browserXhr, $log, $xhr, log; +  var scope, $browser, $browserXhr, $log, $xhr, $xhrErr, log;    beforeEach(function(){ -    scope = angular.scope({}, angular.service, { '$log': $log = { -        error: dump -    } }); +    var scope = angular.scope({}, null, {'$xhr.error': $xhrErr = jasmine.createSpy('xhr.error')}); +    $log = scope.$service('$log');      $browser = scope.$service('$browser');      $browserXhr = $browser.xhr;      $xhr = scope.$service('$xhr'); @@ -57,12 +56,11 @@ describe('$xhr', function() {    it('should handle exceptions in callback', function(){ -    $log.error = jasmine.createSpy('$log.error');      $browserXhr.expectGET('/reqGET').respond('first');      $xhr('GET', '/reqGET', null, function(){ throw "MyException"; });      $browserXhr.flush(); -    expect($log.error).toHaveBeenCalledWith("MyException"); +    expect($log.error.logs.shift()).toContain('MyException');    }); @@ -104,6 +102,38 @@ describe('$xhr', function() {      expect(response).toEqual([1, 'abc', {foo:'bar'}]);    }); +  it('should call $xhr.error on error if no error callback provided', function() { +    var successSpy = jasmine.createSpy('success'); + +    $browserXhr.expectGET('/url').respond(500, 'error'); +    $xhr('GET', '/url', null, successSpy); +    $browserXhr.flush(); + +    expect(successSpy).not.toHaveBeenCalled(); +    expect($xhrErr).toHaveBeenCalledWith( +      {method: 'GET', url: '/url', data: null, success: successSpy}, +      {status: 500, body: 'error'} +    ); +  }); + +  it('should call the error callback on error if provided', function() { +    var errorSpy = jasmine.createSpy('error'), +        successSpy = jasmine.createSpy('success'); + +    $browserXhr.expectGET('/url').respond(500, 'error'); +    $xhr('GET', '/url', null, successSpy, errorSpy); +    $browserXhr.flush(); + +    expect(errorSpy).toHaveBeenCalledWith(500, 'error'); +    expect(successSpy).not.toHaveBeenCalled(); + +    errorSpy.reset(); +    $xhr('GET', '/url', successSpy, errorSpy); +    $browserXhr.flush(); + +    expect(errorSpy).toHaveBeenCalledWith(500, 'error'); +    expect(successSpy).not.toHaveBeenCalled(); +  });    describe('http headers', function() { | 
