Skip to content

Commit

Permalink
feat($http): add xhr statusText to completeRequest callback
Browse files Browse the repository at this point in the history
Makes xhr status text accessible is $http success/error callback.
See www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-statustext

Closes angular#2335
  • Loading branch information
jimlyndon authored and caitp committed Mar 17, 2014
1 parent d09056d commit c9dbf56
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 43 deletions.
16 changes: 9 additions & 7 deletions src/ng/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ function $HttpProvider() {
* - **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.
* - **statusText** – `{string}` – HTTP status text of the response.
*
* @property {Array.<Object>} pendingRequests Array of config objects for currently pending
* requests. This is primarily meant to be used for debugging purposes.
Expand Down Expand Up @@ -946,9 +947,9 @@ function $HttpProvider() {
} else {
// serving from cache
if (isArray(cachedResp)) {
resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2]));
resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2]), cachedResp[3]);
} else {
resolvePromise(cachedResp, 200, {});
resolvePromise(cachedResp, 200, {}, 'OK');
}
}
} else {
Expand All @@ -972,33 +973,34 @@ function $HttpProvider() {
* - resolves the raw $http promise
* - calls $apply
*/
function done(status, response, headersString) {
function done(status, response, headersString, statusText) {
if (cache) {
if (isSuccess(status)) {
cache.put(url, [status, response, parseHeaders(headersString)]);
cache.put(url, [status, response, parseHeaders(headersString), statusText]);
} else {
// remove promise from the cache
cache.remove(url);
}
}

resolvePromise(response, status, headersString);
resolvePromise(response, status, headersString, statusText);
if (!$rootScope.$$phase) $rootScope.$apply();
}


/**
* Resolves the raw $http promise.
*/
function resolvePromise(response, status, headers) {
function resolvePromise(response, status, headers, statusText) {
// normalize internal statuses to 0
status = Math.max(status, 0);

(isSuccess(status) ? deferred.resolve : deferred.reject)({
data: response,
status: status,
headers: headersGetter(headers),
config: config
config: config,
statusText : statusText
});
}

Expand Down
16 changes: 12 additions & 4 deletions src/ng/httpBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
completeRequest(callback,
status || xhr.status,
response,
responseHeaders);
responseHeaders,
xhr.statusText || '');
}
};

Expand Down Expand Up @@ -138,7 +139,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
xhr && xhr.abort();
}

function completeRequest(callback, status, response, headersString) {
function completeRequest(callback, status, response, headersString, statusText) {
// cancel timeout and subsequent timeout promise resolution
timeoutId && $browserDefer.cancel(timeoutId);
jsonpDone = xhr = null;
Expand All @@ -148,12 +149,19 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
// while retrieving files from application cache.
if (status === 0) {
status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;

if (!statusText) {
statusText = status === 200 ? 'OK' : status === 404 ? 'Not Found' : 'Error';
}
}

// normalize IE bug (http://bugs.jquery.com/ticket/1450)
status = status == 1223 ? 204 : status;
if (status == 1223) {
status = 204;
statusText = 'No Content';
}

callback(status, response, headersString);
callback(status, response, headersString, statusText);
$browser.$$completeOutstandingRequest(noop);
}
};
Expand Down
46 changes: 25 additions & 21 deletions src/ngMock/angular-mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -1090,12 +1090,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
responsesPush = angular.bind(responses, responses.push),
copy = angular.copy;

function createResponse(status, data, headers) {
function createResponse(status, data, headers, statusText) {
if (angular.isFunction(status)) return status;

return function() {
return angular.isNumber(status)
? [status, data, headers]
? [status, data, headers, statusText]
: [200, status, data];
};
}
Expand All @@ -1120,7 +1120,8 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
function handleResponse() {
var response = wrapped.response(method, url, data, headers);
xhr.$$respHeaders = response[2];
callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders());
callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(),
copy(response[3] || ''));
}

function handleTimeout() {
Expand Down Expand Up @@ -1187,16 +1188,17 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* request is handled.
*
* - respond –
* `{function([status,] data[, headers])|function(function(method, url, data, headers)}`
* – The respond method takes a set of static data to be returned or a function that can return
* an array containing response status (number), response data (string) and response headers
* (Object).
* `{function([status,] data[, headers, statusText])
* | function(function(method, url, data, headers)}`
* – The respond method takes a set of static data to be returned or a function that can
* return an array containing response status (number), response data (string), response
* headers (Object), and the text for the status (string).
*/
$httpBackend.when = function(method, url, data, headers) {
var definition = new MockHttpExpectation(method, url, data, headers),
chain = {
respond: function(status, data, headers) {
definition.response = createResponse(status, data, headers);
respond: function(status, data, headers, statusText) {
definition.response = createResponse(status, data, headers, statusText);
}
};

Expand Down Expand Up @@ -1304,17 +1306,18 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
* request is handled.
*
* - respond –
* `{function([status,] data[, headers])|function(function(method, url, data, headers)}`
* – The respond method takes a set of static data to be returned or a function that can return
* an array containing response status (number), response data (string) and response headers
* (Object).
* `{function([status,] data[, headers, statusText])
* | function(function(method, url, data, headers)}`
* – The respond method takes a set of static data to be returned or a function that can
* return an array containing response status (number), response data (string), response
* headers (Object), and the text for the status (string).
*/
$httpBackend.expect = function(method, url, data, headers) {
var expectation = new MockHttpExpectation(method, url, data, headers);
expectations.push(expectation);
return {
respond: function(status, data, headers) {
expectation.response = createResponse(status, data, headers);
respond: function (status, data, headers, statusText) {
expectation.response = createResponse(status, data, headers, statusText);
}
};
};
Expand Down Expand Up @@ -1816,13 +1819,14 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* control how a matched request is handled.
*
* - respond –
* `{function([status,] data[, headers])|function(function(method, url, data, headers)}`
* `{function([status,] data[, headers, statusText])
* | function(function(method, url, data, headers)}`
* – The respond method takes a set of static data to be returned or a function that can return
* an array containing response status (number), response data (string) and response headers
* (Object).
* - passThrough – `{function()}` – Any request matching a backend definition with `passThrough`
* handler will be passed through to the real backend (an XHR request will be made to the
* server.)
* an array containing response status (number), response data (string), response headers
* (Object), and the text for the status (string).
* - passThrough – `{function()}` – Any request matching a backend definition with
* `passThrough` handler will be passed through to the real backend (an XHR request will be made
* to the server.)
*/

/**
Expand Down
19 changes: 18 additions & 1 deletion test/ng/httpBackendSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,16 @@ describe('$httpBackend', function() {
});

it('should normalize IE\'s 1223 status code into 204', function() {
callback.andCallFake(function(status) {
callback.andCallFake(function(status, response, headers, statusText) {
expect(status).toBe(204);
expect(statusText).toBe('No Content');
});

$backend('GET', 'URL', null, callback);
xhr = MockXhr.$$lastInstance;

xhr.status = 1223;
xhr.statusText = 'Unknown';
xhr.readyState = 4;
xhr.onreadystatechange();

Expand Down Expand Up @@ -464,6 +466,7 @@ describe('$httpBackend', function() {

expect(callback).toHaveBeenCalled();
expect(callback.mostRecentCall.args[0]).toBe(200);
expect(callback.mostRecentCall.args[3]).toBe('OK');
});

it('should convert 0 to 200 if content for protocols other than file', function() {
Expand All @@ -474,6 +477,7 @@ describe('$httpBackend', function() {

expect(callback).toHaveBeenCalled();
expect(callback.mostRecentCall.args[0]).toBe(200);
expect(callback.mostRecentCall.args[3]).toBe('OK');
});

it('should convert 0 to 404 if no content and file protocol', function() {
Expand All @@ -484,6 +488,7 @@ describe('$httpBackend', function() {

expect(callback).toHaveBeenCalled();
expect(callback.mostRecentCall.args[0]).toBe(404);
expect(callback.mostRecentCall.args[3]).toBe('Not Found');
});

it('should not convert 0 to 404 if no content for protocols other than file', function() {
Expand Down Expand Up @@ -553,6 +558,18 @@ describe('$httpBackend', function() {
expect(callback).toHaveBeenCalled();
expect(callback.mostRecentCall.args[0]).toBe(503);
});


it('should convert 0 to 404 if no content - relative url', function() {
$backend = createHttpBackend($browser, createMockXhr);

$backend('GET', 'file://whatever/index.html', null, callback);
respond(0, '');

expect(callback).toHaveBeenCalled();
expect(callback.mostRecentCall.args[0]).toBe(404);
expect(callback.mostRecentCall.args[3]).toBe('Not Found');
});
});
});

20 changes: 10 additions & 10 deletions test/ngMock/angular-mocksSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1065,29 +1065,29 @@ describe('ngMock', 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', '', '']);
});


describe('respond()', function() {
it('should take values', function() {
hb.expect('GET', '/url1').respond(200, 'first', {'header': 'val'});
hb.expect('GET', '/url1').respond(200, 'first', {'header': 'val'}, 'OK');
hb('GET', '/url1', undefined, callback);
hb.flush();

expect(callback).toHaveBeenCalledOnceWith(200, 'first', 'header: val');
expect(callback).toHaveBeenCalledOnceWith(200, 'first', 'header: val', 'OK');
});

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'}];
hb.expect('GET', '/some').respond(function (m, u, d, h) {
return [301, m + u + ';' + d + ';a=' + h.a, {'Connection': 'keep-alive'}, 'Moved Permanently'];
});

hb('GET', '/some', 'data', callback, {a: 'b'});
hb.flush();

expect(callback).toHaveBeenCalledOnceWith(301, 'GET/some;data;a=b', 'Connection: keep-alive');
expect(callback).toHaveBeenCalledOnceWith(301, 'GET/some;data;a=b', 'Connection: keep-alive', 'Moved Permanently');
});

it('should default status code to 200', function() {
Expand Down Expand Up @@ -1116,8 +1116,8 @@ describe('ngMock', function() {
hb.flush();

expect(callback.callCount).toBe(2);
expect(callback.argsForCall[0]).toEqual([200, 'first', '']);
expect(callback.argsForCall[1]).toEqual([200, 'second', '']);
expect(callback.argsForCall[0]).toEqual([200, 'first', '', '']);
expect(callback.argsForCall[1]).toEqual([200, 'second', '', '']);
});
});

Expand Down Expand Up @@ -1412,7 +1412,7 @@ describe('ngMock', function() {
hb[shortcut]('/foo').respond('bar');
hb(method, '/foo', undefined, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'bar', '');
expect(callback).toHaveBeenCalledOnceWith(200, 'bar', '', '');
});
});
});
Expand Down

0 comments on commit c9dbf56

Please sign in to comment.