This bug seems to be very similar to #12569: events are fired twice, with jQuery event being triggered first followed by a native event.
The difference is that 1) this bug occurs only during integration tests 2) event has to be triggered on one of the descendants of a DOM node listening to the event.
It is not fired twice only in integration tests, but also when application runs. It will occur every time you invoke event with jQuery that then is handled by Ember action which is not returning false explicitly.
Why you have to return false explicitly in Ember action?
In Ember events bubble in two cases:
Bubbling happens when an 'actionName' can
not be found in the 'ActionHandler's' 'actions' hash or if the
action target function returns 'true'. So if you are using Ember to trigger events then is fine if you skip return false statement in action handler.
But... If you are using jQuery to trigger events then you have to return false in Ember action handler to make it work, because jQuery invokes Ember action as a function and it will prevent default behaviour only if Ember action return false.
What will happen if you did not return false?
Event will bubble from your component to document object and if event will not be prevented, then jQuery will fire your Ember action once more. That is why you noticed in test that it is fired twice.
So there is a question to Ember Core @rwjblue, how do we want to solve this issue? Write info in Ember guides, that this happens and how to fix this? Fix this on Ember side, push return false to every action that it doesn't have it? Something else?
Based on @Dremora's Ember Twiddle, i've prepared other that shows how return false event bubbling. Demo
There is a bit of a disconnect here going on here between "actions" and "events".
When you do <div onclick={{action 'foo'}}></div> you are explicitly assigning an onclick handler function to that element. This is absolutely no different than if you had done:
var el = document.createElement('div');
el.onclick = function() { /* code from your action here */ };
In the above, you do not get action bubbling behaviors at all, however you _would_ get standard DOM event bubbling.
The issue you are seeing though is really due to using jQuery to simulate click, instead of using a native click. What is happening is:
<div onclick={{action 'clicked'}}></div> parent element, and invokes its native onclick handler (which is the "closure action" that you assigned to el.onclick in your template). hereevent.isDefaultPrevented() is true (or if the handler had return false; explicitly). (_Please note that return false; here has nothing to do with "action bubbling" (RE: the section of the guides/docs that you linked) since this is a standard event handler._)event.preventDefault(); nor return false; (jQueryism mentioned above to be shorthand for event.preventDefault();) jQuery assumes that the event still needs to be delivered and therefore invokes .click() method on the .clickme element directly. here..click() on the DOM element forces the same parent bubbling and event handler invocation (though done by the browser this time, not jQuery itself) as mentioned in steps 1 through 3 above. Thereby invoking your click handler a second time.The simplest solution is to use <div {{action 'clicked'}}></div> instead of <div onclick={{action 'clicked'}}></div>, as this prevents the action from being fired in step 2 (since it isn't directly attached to an ancestors onclick), but the better solution is to avoid jQuery simulated clicks completely and instead rely on triggering native events. This is what the click() helper method does in acceptance tests...
The above description explains what is happening and shows that the issue lies with the way jQuery works (and not Ember at all). I do not believe this is a bug in Ember, though it is possible that we want to tweak any documentation (API or guides) that may infer that $('.foo').click() is good. If we do need documentation modification, we should move this issue to the guides repo and track that separately.
Closing...
Also to address the original posters points:
this bug occurs only during integration tests
This is because in acceptance tests you would typically use click() helper that is provided by Ember which uses native event dispatching (not jQuery simulated events).
event has to be triggered on one of the descendants of a DOM node listening to the event
This is because jQuery specifically attempts to guard for the exact issue that you have reported by:
el.click(). hereSo as you can see, if the element that is clicked is the one that has the onclick={{action 'foo'}} on it, jQuery avoids the double invocation itself.
Thank you @rwjblue for explanation.
@Dremora as pointed out by @rwjblue using a native event will work best in your tests.
this.$('.clickme')[0].click() or this.$('.clickme').parent().click() make the test pass but the former is firing the inline event handler of the parent as the event bubbles.
Also, as mentioned when assigning inline click handlers e.g. online=(action 'foo') the action receives the event as the first argument when normal actions do not. It's a function that becomes a closure and set on the element as an event handler, no longer an action. So you can use return false; or event.preventDefault(); as needed to prevent additional bubbling.
Most helpful comment
There is a bit of a disconnect here going on here between "actions" and "events".
When you do
<div onclick={{action 'foo'}}></div>you are explicitly assigning anonclickhandler function to that element. This is absolutely no different than if you had done:In the above, you do not get action bubbling behaviors at all, however you _would_ get standard DOM event bubbling.
The issue you are seeing though is really due to using jQuery to simulate click, instead of using a native click. What is happening is:
<div onclick={{action 'clicked'}}></div>parent element, and invokes its nativeonclickhandler (which is the "closure action" that you assigned toel.onclickin your template). hereevent.isDefaultPrevented()is true (or if the handler hadreturn false;explicitly). (_Please note thatreturn false;here has nothing to do with "action bubbling" (RE: the section of the guides/docs that you linked) since this is a standard event handler._)event.preventDefault();norreturn false;(jQueryism mentioned above to be shorthand forevent.preventDefault();) jQuery assumes that the event still needs to be delivered and therefore invokes.click()method on the.clickmeelement directly. here..click()on the DOM element forces the same parent bubbling and event handler invocation (though done by the browser this time, not jQuery itself) as mentioned in steps 1 through 3 above. Thereby invoking your click handler a second time.The simplest solution is to use
<div {{action 'clicked'}}></div>instead of<div onclick={{action 'clicked'}}></div>, as this prevents the action from being fired in step 2 (since it isn't directly attached to an ancestorsonclick), but the better solution is to avoid jQuery simulated clicks completely and instead rely on triggering native events. This is what theclick()helper method does in acceptance tests...The above description explains what is happening and shows that the issue lies with the way jQuery works (and not Ember at all). I do not believe this is a bug in Ember, though it is possible that we want to tweak any documentation (API or guides) that may infer that
$('.foo').click()is good. If we do need documentation modification, we should move this issue to the guides repo and track that separately.Closing...