ember-test-helpers icon indicating copy to clipboard operation
ember-test-helpers copied to clipboard

When using native DOM helpers with jQuery, some events are not handled correctly

Open wagenet opened this issue 6 years ago • 4 comments

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.

wagenet avatar Mar 14 '18 04:03 wagenet

I've been tripped up by this! I wondering if we can do a few things.

  1. If has-jquery, then map mouseleave to mouseout for example. This may be problematic due to mouseout/mouseover's default bubbling behavior or that it also fires events on child elements.
  2. Create a special, documented, event type as the second argument to triggerEvent called mouseLeave and mouseEnter 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 of triggerEvent may have not expected.
  3. Simply document this workaround.

Any thoughts?

snewcomer avatar Mar 17 '18 13:03 snewcomer

@wycats thoughts?

wagenet avatar Mar 20 '18 00:03 wagenet

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");

Frozenfire92 avatar Jul 19 '18 13:07 Frozenfire92

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? 😄

ginomiglio avatar Dec 22 '20 21:12 ginomiglio