-
Notifications
You must be signed in to change notification settings - Fork 6.7k
fix(typeahead): dangling event listeners #4636
fix(typeahead): dangling event listeners #4636
Conversation
Appreciated, but I would like to see tests. |
@Foxandxss, before adding that comment, I looked and we don't even have tests to check to see that any event listeners are added which is why I didn't ask @oliversalzburg to add them. The onus shouldn't be on him to create tests for checking the adding and removing of all the event listeners. If you're ok with him just adding checks to see that they are removed, I'm ok with that as well. But note that we don't even have a framework in place yet for checking them at all. How do you want to proceed? |
The responsibility to add tests is for the person who creates a PR. In this case, creating a typeahead with the append to body, check that there are some events registered ( I guess that is possible) and then kill it and see if those events are gone. |
@oliversalzburg, please add a test to check that those two event handlers are removed once the typeahead is destroyed. Thanks. It should be self-explanatory, but let us know if you need help figuring out how to do that. |
Sure thing. I'll work on those tests. It might take me a while, since the weekend is approaching. I just wanted to put the PR out there to be able to discuss it :) Thanks for the feedback. |
I'd appreciate some help with these tests. There are confusing things going on here :P Right now, both tests fail for me:
When I remove the Secondly, the test for Also, sorry for the whitespace changes, I'll correct those once I have working tests. WebStorm will just mess this up again while I'm working on the files. |
@@ -1049,15 +1071,15 @@ describe('typeahead deprecation', function() { | |||
expect($log.warn.calls.argsFor(1)).toEqual(['typeahead is now deprecated. Use uib-typeahead instead.']); | |||
expect($log.warn.calls.argsFor(2)).toEqual(['typeahead-popup is now deprecated. Use uib-typeahead-popup instead.']); | |||
})); | |||
|
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.
please watch your formatting and only touch lines relevant to your code changes.
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.
Also, sorry for the whitespace changes, I'll correct those once I have working tests. WebStorm will just mess this up again while I'm working on the files.
This is not quite what I had in mind when I was asking for help with the tests ;)
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 know. I admit my karma/jasmine is a bit weak and would require the same investigation as you do to ensure the event listeners are being added/removed appropriately.
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.
Well, then I'm going to update the PR with the whitespace fixes and hope that someone can shed some light on this issue later.
@oliversalzburg, why not test to see if a flag in your event handler has been toggled (or something) when the event has been fired and not toggled when it's been removed and the event has been fired? Will that work? |
@@ -359,6 +359,10 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position']) | |||
if (appendToBody || appendToElementId) { | |||
$popup.remove(); | |||
} | |||
if (appendToBody) { |
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.
Add empty line above this line
@icfantv As I said in my previous post, the spies work right when the other "append to body" tests aren't run. So I'm pretty confident in the approach I just don't understand how the other test is affecting it. And the second test fails because the handler for |
Not sure why the tests would fail, probably would need to look into it |
@oliversalzburg, did you mean line 872 for the |
At first glance, I don't see how/why that test would cause yours to fail - each |
The code style and whitespace issues should be fixed now. @icfantv Yes, the tests starting on line 872 are what I was referring to (there was a typo in my other post). |
I guess the answer is actually pretty simple.
So it all boils down to, why is there no |
angularjs: why isn't $destroy triggered when I call element.remove? sounds pretty relevant… |
Perhaps instead of element.remove, you should take the $scope and execute |
@wesleycho But the other tests aren't causing the scope to be destroyed (or the When I change |
I fixed and merged it. |
@Foxandxss Thanks a lot for taking care of this! However, clearing the listeners manually in the tests doesn't seem like the right approach IMHO. In general, it seems like As mentioned in angularjs: why isn't $destroy triggered when I call element.remove?, the The docs even note:
I would propose that the event should be respected and the listeners should be cleaned up. |
I am sorry, but I am bit confused. When you test something, those objects will be created and disappear after each test, but if you bound some of the to the body, they will remain in there for all the eternity. In the describe for append to body, it is always better to make a cleanup after each test (Perhaps I would even need to delete the element entirely). For you concrete tests, first one works after you clean the previous ones (as you said, jquery was not adding new listeners because they were there yet). Second one works as soon as your start clean and you trigger $destroy. I don't see any issue with this approach, but, if you think there is a better way, I am open for it. |
Well, the tests aren't accurately testing the directive. The directive is supposed to clean up after itself. But it doesn't. You're cleaning up the event listeners yourself in the test. That being said, there is still a real issue in the code. If the DOM element (or any of its parents) is removed via jQuery (as it is done in the tests), the event listeners will not be cleaned up, because the |
About the first point, yes, the directive is cleaning itself correctly. describe('event listeners', function() {
it('should register event listeners when attached to body', function() {
spyOn(window, 'addEventListener');
spyOn(document.body, 'addEventListener');
var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-append-to-body="true"></div>');
expect(window.addEventListener).toHaveBeenCalledWith('resize', jasmine.any(Function), false);
expect(document.body.addEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function), false);
$scope.$destroy();
});
it('should remove event listeners when attached to body', function() {
spyOn(window, 'removeEventListener');
spyOn(document.body, 'removeEventListener');
var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-append-to-body="true"></div>');
$scope.$destroy();
expect(window.removeEventListener).toHaveBeenCalledWith('resize', jasmine.any(Function), false);
expect(document.body.removeEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function), false);
});
}); If you change your tests like that. No manual cleanup it works. If you comment out the $destroy on the first test, the second one won't work (because we have "stale" code in the dom). About the second point, not sure if I follow. If a $destroy is triggered in the parent, all the children will die too. |
I'm really not sure how to make this clearer than I already have with the provided explanation, links to the Stack Overflow question, the AngularJS documentation and the Plunker. So, in the interest of saving everyone's time, I'll just move on. |
Fixes #4632