ember-test-helpers
ember-test-helpers copied to clipboard
When using native DOM helpers with jQuery, some events are not handled correctly
I believe this effects 'mouseenter', 'mouseleave', 'pointerenter' , and 'pointerleave'.
The issue is that if jQuery is present, Ember's event dispatcher will use jQuery to manage events. This means if you have, for instance a mouseEnter
handler, on('mouseenter')
will be used. Due to historical reasons, jQuery will not actually watch for the real 'mouseenter' event but will watch for 'mouseover'.
If you are using $(el).mouseenter()
jQuery will handle this correctly. However, if you transition to native DOM helpers and attempt to do triggerEvent(el, 'mouseenter')
we will send only the 'mouseenter' event which the jQuery-backed event dispatcher will not recognize.
A temporary workaround in this case is triggerEvent(el, 'mouseover')
.
Given that many people will probably be switching to native DOM helpers soon, we should consider whether anything should be done to account for this scenario.
I've been tripped up by this! I wondering if we can do a few things.
- If
has-jquery
, then mapmouseleave
tomouseout
for example. This may be problematic due tomouseout
/mouseover
's default bubbling behavior or that it also fires events on child elements. - Create a special, documented, event type as the second argument to
triggerEvent
calledmouseLeave
andmouseEnter
that maps to their respective counterparts. This may be confusing given the similarities but avoids the problems with blindly mapping to the event that bubbles and fires events that the consumer oftriggerEvent
may have not expected. - Simply document this workaround.
Any thoughts?
@wycats thoughts?
This tripped me up too!
simple component that applies a class on mouse enter/leave
import Component from '@ember/component';
import { set } from '@ember/object';
export default Component.extend({
classNames: ['hovertip'],
hovered: false,
classNameBindings: ['hovered'],
mouseEnter() {
set(this, 'hovered', true);
},
mouseLeave() {
set(this, 'hovered', false);
}
});
Test (passing 3.1.x, failing on 3.3.x)
assert.notOk(this.$('.hovertip').hasClass('hovered'), "no hovered class without mouseenter");
assert.ok(this.$('.hovertip').trigger('mouseenter').hasClass('hovered'), "hovered class on mouseenter");
assert.notOk(this.$('.hovertip').trigger('mouseleave').hasClass('hovered'), "no hovered class after mouseleave");
Updated test for 3.3.x (middle one fails)
assert.notOk(this.$('.hovertip').hasClass('hovered'), "no hovered class without mouseenter");
await triggerEvent('.hovertip', 'mouseenter');
assert.ok(this.$('.hovertip').hasClass('hovered'), "hovered class on mouseenter");
await triggerEvent('.hovertip', 'mouseleave');
assert.notOk(this.$('.hovertip').hasClass('hovered'), "no hovered class after mouseleave");
Final updated test for 3.3.x using mouseover/mouseout (all passing)
assert.notOk(this.$('.hovertip').hasClass('hovered'), "no hovered class without mouseenter");
await triggerEvent('.hovertip', 'mouseover');
assert.ok(this.$('.hovertip').hasClass('hovered'), "hovered class on mouseenter");
await triggerEvent('.hovertip', 'mouseout');
assert.notOk(this.$('.hovertip').hasClass('hovered'), "no hovered class after mouseleave");
Maybe this is worth adding a separate helper for? Then we could just trigger all of the events that the browser would trigger, and not care about whether or not jQuery is in use. That's the whole point of this library, right? 😄