pmndrs / use-gesture

👇Bread n butter utility for component-tied mouse/touch gestures in React and Vanilla Javascript.
https://use-gesture.netlify.app
MIT License
8.95k stars 307 forks source link

filterTaps disables all click events in Jest #418

Open jantimon opened 2 years ago

jantimon commented 2 years ago

Describe the bug

the filterTaps option works great in all browser however in JEST it prevents child listeners from working.

I am only guessing but maybe the following line doesn't work well as JEST is not able to get real size measurements: https://github.com/pmndrs/use-gesture/blob/9217f29802b8e4d87dab42e2cbd7313d2179ffea/packages/core/src/engines/DragEngine.ts#L210

Sandbox or Video

I prepared this CodeSandbox with unit tests.

It contains Demo1 and Demo2:

import { useDrag } from '@use-gesture/react'

export const Demo1 = ({onClick}: {onClick: () => void}) => {
  const bind = useDrag(({ down, movement: [mx, my] }) => {
  }, {

  })
  return <div {...bind()}>
      <button onClick={onClick}>
      click me
      </button>
    </div>
import { useDrag } from '@use-gesture/react'

export const Demo2 = ({onClick}: {onClick: () => void}) => {
  const bind = useDrag(({ down, movement: [mx, my] }) => {
  }, {
    filterTaps: true
  })
  return <div {...bind()}>
      <button onClick={onClick}>
      click me
      </button>
    </div>
}

As you can see the only difference is filterTaps: true.

The result is that the unit test which tries to click the button fails for Demo2:

testResult

Checklist:

dbismut commented 2 years ago

Hey, thanks for reporting this.

The filterTaps option checks if the drag is an actual drag or a tap (it does this by checking whether the displacement is greater than 3px). If the drag is a drag, then it prevents the click event to propagate (the click listener is added using capture: true so that events bubble down).

https://github.com/pmndrs/use-gesture/blob/9217f29802b8e4d87dab42e2cbd7313d2179ffea/packages/core/src/engines/DragEngine.ts#L231-L236

So I'm not sure how I would be able to solve this. Shouldn't this be a bug in Jest instead?

jantimon commented 2 years ago

After some further investigation I found out that fireEvent.click does not trigger mouseDown and mouseUp therefore state.tap is always false

There is a helper library which triggers a full click which works:

import userEvent from '@testing-library/user-event';

userEvent.click()

Could we set state.taps = true by default?

dbismut commented 2 years ago

Oh right.

You mean setting state.tap = false on pointerDown then?

I'd have to make sure there's no collaterals.

jantimon commented 2 years ago

Rather here:

https://github.com/pmndrs/use-gesture/blob/9217f29802b8e4d87dab42e2cbd7313d2179ffea/packages/core/src/engines/DragEngine.ts#L29

For normal browsers this shouldn't have any effect as it will be set to a correct value in pointerUp. However for JEST where there is no pointerUp it would allow to catch click events.

dbismut commented 2 years ago

Yes, that's what I meant, but I would have to restore state.tap to a falsy value as soon as the gesture starts, as people might rely on it before pointerUp. In any case it wouldn't make sense to have state.tap = true when the user drags things around.

jantimon commented 2 years ago

Oh I see - good point

marcusstenbeck commented 2 years ago

For anyone coming here wanting to trigger the tap, try this:

fireEvent.mouseDown(element, { buttons: 1 });
fireEvent.mouseUp(element, { buttons: 1 });

See: MDN - MouseEvent.buttons

The mouseUp() call probably doesn't need the { buttons: 1 }, but let's keep it since it means the mouseDown is followed by a mouseUp with the same button.


This is a more complete example of what's working for me using v10.2.6.

import { fireEvent } from '@testing-library/react';

const click: typeof fireEvent.click = (element, options) => {
  const _options: Record<string, any> = options ?? {};

  if (!_options.buttons) {
    // default to left mouse button
    // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
    _options.buttons = 1;
  }

  return fireEvent.mouseDown(element, _options) && fireEvent.mouseUp(element, _options);
};

Here's how use-gesture tests the tap event: https://github.com/pmndrs/use-gesture/blob/main/test/drag.test.tsx#L232