-
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
Conversation
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, I think it would be better to re-use the method used in ngRoute
(see #14173 (comment)).
It would be similar to how we re-use shallowCopy() (configured in angularFiles.js).
src/ngMock/angular-mocks.js
Outdated
@@ -1680,14 +1681,14 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { | |||
+ (optional ? '' : slash) | |||
+ '(?:' | |||
+ (optional ? slash : '') | |||
+ (star && '(.+?)' || '([^/]+)') | |||
+ (star ? '(.+?)' : '([^/?]+)') |
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.
Maybe also add #
(just in case).
@gkalpak Where is it better to place the file with this function? |
I would put it in |
f984b29
to
04024ed
Compare
@gkalpak PTAL |
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.
Looks great 👍
Just a couple of minor things.
@@ -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: |
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 uses complete
as the XHR status), but it does accept one when a function is passed to createResponse()
.
I think this is a bug (or at least unintentional). createResponse()
should look something like:
function createResponse(status, data, headers, statusText) {
- if (angular.isFunction(status)) return status;
+ if (angular.isFunction(status)) {
+ return function(method, url, data, headers, params) {
+ var response = status(method, url, data, headers, params);
+ response[4] = 'complete';
+ return response;
+ };
+ }
return function() {
return angular.isNumber(status)
? [status, data, headers, statusText, 'complete']
: [200, status, data, headers, 'complete'];
};
}
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 😁).
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.
You can't have error with a "normal" response (it is something that will not come up in the actual app
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 of 0
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 (because aborted
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) 😛
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 😃
* – 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Again, I don't think it gets the XHR status.
src/routeToRegExp.js
Outdated
.replace(/(\/)?:(\w+)(\*\?|[?*])?/g, function(_, slash, key, option) { | ||
var optional = option === '?' || option === '*?'; | ||
var star = option === '*' || option === '*?'; | ||
keys.push({ name: key, optional: !!optional }); |
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.
!!optional
--> optional
(now that optional
is boolean)
src/routeToRegExp.js
Outdated
* Inspired by pathRexp in visionmedia/express/lib/utils.js. | ||
*/ | ||
function routeToRegExp(path, opts) { | ||
if (!angular.isString(path)) path = ''; |
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.
AFAICT, this function would never be called with something that is not a string (if people followed the docs 😁).
I suggest we remove this line (and fail hard if path
is not a string, so that we can detect issues quicker), instead of covering it up with a route that does not correspond to the passed in path
.
test/ngMock/angular-mocksSpec.js
Outdated
hb[routeShortcut](this, '/route/:id').respond(function(method, url, data, headers, params) { | ||
paramsSpy(params); | ||
// status, response, headers, statusText, xhrStatus | ||
return [200, 'path', { 'x-header': 'foo' }, 'OK', 'complete']; |
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 don't think complete
is used (here and below).
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'm verifying that params
are correct. Without the fix, paramSpy
is called with {id: '123?q=str&foo=bar', q: 'str', foo: 'bar'}
. So I'm testing exactly what is said in the name of the test: "should ignore query params when matching".
test/ngMock/angular-mocksSpec.js
Outdated
@@ -1943,10 +1943,30 @@ describe('ngMock', function() { | |||
); | |||
they('should ignore query param when matching in ' + routeShortcut + ' $prop method', methods, | |||
function() { | |||
hb[routeShortcut](this, '/route/:id').respond('path'); |
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 the changes in this test? What are you trying to verify?
test/ngMock/angular-mocksSpec.js
Outdated
they('should ignore query param when matching eager parameters in ' + routeShortcut + ' $prop method', methods, | ||
function() { | ||
var paramsSpy = jasmine.createSpy('paramsSpy'); | ||
hb[routeShortcut](this, '/route/:id*').respond(function(method, url, data, headers, params) { |
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.
How about adding a test without the *
(for good measure)?
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 misread this question before. There already is a test without *
. I added a test with *
. I thought you were asking about a test without any routing parameters at all.
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.
🙈
@gkalpak Should the URL |
I don't think it matters much (for tests). I don't have a strong opinion (unless changing the current behavior is a breaking change). |
The current behavior is a bit unexpected: ret.regexp = new RegExp('^' + url, 'i'); Which means even |
No, I meant the behavior wrt trailing Matching the So, I woud vote for matching with and without trailing |
Okay. Matching the |
ad838e1
to
82785e6
Compare
@gkalpak PTAL |
Thx, @thorn0 ❤️ |
…en}Route Previously, a route definition such as `$httpBackend.whenRoute('GET', '/route/:id')` matched against a URL with query params, for example `/route/1?q=foo`, would incorrectly include the query params in `id`: `{id: '1?q=foo', q: 'foo'}`. This commit fixes it, so that the extracted `params` will now be: `{id: '1', q: 'foo'}`. Fixes #14173 Closes #16589
What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)
fix
What is the current behavior? (You can also link to an open issue here)
#14173
Does this PR introduce a breaking change?
no
Please check if the PR fulfills these requirements