This repository was archived by the owner on Apr 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 27.4k
fix(ngMock/$httpBackend): correctly ignore query params in {expect,when}Route #16589
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
'use strict'; | ||
|
||
/* global routeToRegExp: false */ | ||
|
||
/** | ||
* @ngdoc object | ||
* @name angular.mock | ||
|
@@ -1282,7 +1284,7 @@ angular.mock.dump = function(object) { | |
* ## Matching route requests | ||
* | ||
* For extra convenience, `whenRoute` and `expectRoute` shortcuts are available. These methods offer colon | ||
* delimited matching of the url path, ignoring the query string. This allows declarations | ||
* delimited matching of the url path, ignoring the query string and trailing slashes. This allows declarations | ||
* similar to how application routes are configured with `$routeProvider`. Because these methods convert | ||
* the definition url to regex, declaration order is important. Combined with query parameter parsing, | ||
* the following is possible: | ||
|
@@ -1481,8 +1483,9 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { | |
* ``` | ||
* – 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 (Array|Object|string), | ||
* response headers (Object), and the text for the status (string). The respond method returns | ||
* the `requestHandler` object for possible overrides. | ||
* response headers (Object), HTTP status text (string), and XMLHttpRequest status (string: | ||
* `complete`, `error`, `timeout` or `abort`). The respond method returns the `requestHandler` | ||
* object for possible overrides. | ||
*/ | ||
$httpBackend.when = function(method, url, data, headers, keys) { | ||
|
||
|
@@ -1663,40 +1666,10 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { | |
* See {@link ngMock.$httpBackend#when `when`} for more info. | ||
*/ | ||
$httpBackend.whenRoute = function(method, url) { | ||
var pathObj = parseRoute(url); | ||
var pathObj = routeToRegExp(url, {caseInsensitiveMatch: true, ignoreTrailingSlashes: true}); | ||
return $httpBackend.when(method, pathObj.regexp, undefined, undefined, pathObj.keys); | ||
}; | ||
|
||
function parseRoute(url) { | ||
var ret = { | ||
regexp: url | ||
}, | ||
keys = ret.keys = []; | ||
|
||
if (!url || !angular.isString(url)) return ret; | ||
|
||
url = url | ||
.replace(/([().])/g, '\\$1') | ||
.replace(/(\/)?:(\w+)([?*])?/g, function(_, slash, key, option) { | ||
var optional = option === '?' ? option : null; | ||
var star = option === '*' ? option : null; | ||
keys.push({ name: key, optional: !!optional }); | ||
slash = slash || ''; | ||
return '' | ||
+ (optional ? '' : slash) | ||
+ '(?:' | ||
+ (optional ? slash : '') | ||
+ (star && '(.+?)' || '([^/]+)') | ||
+ (optional || '') | ||
+ ')' | ||
+ (optional || ''); | ||
}) | ||
.replace(/([/$*])/g, '\\$1'); | ||
|
||
ret.regexp = new RegExp('^' + url, 'i'); | ||
return ret; | ||
} | ||
|
||
/** | ||
* @ngdoc method | ||
* @name $httpBackend#expect | ||
|
@@ -1717,14 +1690,15 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { | |
* order to change how a matched request is handled. | ||
* | ||
* - respond – | ||
* ``` | ||
* { function([status,] data[, headers, statusText]) | ||
* | function(function(method, url, data, headers, params)} | ||
* ``` | ||
* ```js | ||
* {function([status,] data[, headers, statusText]) | ||
* | function(function(method, url, data, headers, params)} | ||
* ``` | ||
* – 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 (Array|Object|string), | ||
* response headers (Object), and the text for the status (string). The respond method returns | ||
* the `requestHandler` object for possible overrides. | ||
* response headers (Object), HTTP status text (string), and XMLHttpRequest status (string: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, I don't think it gets the XHR status. |
||
* `complete`, `error`, `timeout` or `abort`). The respond method returns the `requestHandler` | ||
* object for possible overrides. | ||
*/ | ||
$httpBackend.expect = function(method, url, data, headers, keys) { | ||
|
||
|
@@ -1876,7 +1850,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { | |
* See {@link ngMock.$httpBackend#expect `expect`} for more info. | ||
*/ | ||
$httpBackend.expectRoute = function(method, url) { | ||
var pathObj = parseRoute(url); | ||
var pathObj = routeToRegExp(url, {caseInsensitiveMatch: true, ignoreTrailingSlashes: true}); | ||
return $httpBackend.expect(method, pathObj.regexp, undefined, undefined, pathObj.keys); | ||
}; | ||
|
||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
'use strict'; | ||
|
||
/* global routeToRegExp: true */ | ||
|
||
/** | ||
* @param path {string} path | ||
* @param opts {Object} options | ||
* @return {?Object} | ||
* | ||
* @description | ||
* Normalizes the given path, returning a regular expression | ||
* and the original path. | ||
* | ||
* Inspired by pathRexp in visionmedia/express/lib/utils.js. | ||
*/ | ||
function routeToRegExp(path, opts) { | ||
var keys = []; | ||
|
||
var pattern = path | ||
.replace(/([().])/g, '\\$1') | ||
.replace(/(\/)?:(\w+)(\*\?|[?*])?/g, function(_, slash, key, option) { | ||
var optional = option === '?' || option === '*?'; | ||
var star = option === '*' || option === '*?'; | ||
keys.push({ name: key, optional: optional }); | ||
slash = slash || ''; | ||
return ( | ||
(optional ? '(?:' + slash : slash + '(?:') + | ||
(star ? '([^?#]+?)' : '([^/?#]+)') + | ||
(optional ? '?)?' : ')') | ||
); | ||
}) | ||
.replace(/([/$*])/g, '\\$1'); | ||
|
||
if (opts.ignoreTrailingSlashes) { | ||
pattern = pattern.replace(/\/+$/, '') + '/*'; | ||
} | ||
|
||
return { | ||
originalPath: path, | ||
keys: keys, | ||
regexp: new RegExp( | ||
'^' + pattern + '(?:[?#]|$)', | ||
opts.caseInsensitiveMatch ? 'i' : '' | ||
) | ||
}; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it get
XHR status
? AFAICT it doesn't (because it is auto-generated).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't get it. It returns it. See http://jsbin.com/vumefiz/edit?html,js,output
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I misread that 😳
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, our createResponse() method isn't consistently treating
XHR status
. It does not accept one when passing the data directly to.respond()
(and it automatically usescomplete
as the XHR status), but it does accept one when a function is passed tocreateResponse()
.I think this is a bug (or at least unintentional).
createResponse()
should look something like:But this is unrelated to this PR and would probably be a breaking change now 😞
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you think so? What if we want to test the behavior of our app with XHR statuses other than
complete
(e.g.error
)?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But (like I said), this is not something to worry about in this PR. It is not related (and that ship has probably sailed 😁).
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it can. It happens in case of network error, doesn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's not a "normal" response. I.e. an XHR status other than
complete
can only happen if the XHR's.onabort
/.onerror
/.ontimeout
is called. In that case, you would have a status of0
and empty values for other fields (statusText
,headers
, etc).But the mock
$httpBackend
is not really designed to let you test these scenarios. Sure you can emulate it by returning the correct values from your response function, you need to know$httpBackend
internals. E.g. returning[200, 'OK', ..., 'aborted']
is a scenario that will never happen in the actual app and will lead to unexpected results (becauseaborted
will have been called with a "success" status).So, ideally, we should not let users specify the XHR status, but instead infer it from other params.
Even more ideally,
$httpBackend
could support testing scenarions when an XHR errored, or timed out or was aborted (and again automatically setting the correct XHR status; not letting the user specify it).But as it is now,
$httpBackend
is better used with "normal" responses (unless you really know what you are doing) 😛Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a separate PR, we can add simple checks and throw in cases like
[200, 'OK', ..., 'abort']
. What are your thoughts on that?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't be opposed to that 😃