Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't use MouseEvent properties that aren't supported by jsdom #268

Closed
julienw opened this issue Jan 16, 2019 · 12 comments
Closed

Can't use MouseEvent properties that aren't supported by jsdom #268

julienw opened this issue Jan 16, 2019 · 12 comments

Comments

@julienw
Copy link

julienw commented Jan 16, 2019

I'm migrating our tests from enzyme to react-testing-library and encountered a roadblock. Indeed some of our code relies on the properties offsetX offsetY pageX pageY of a MouseEvent instance. In Enzyme we could simply pass these as part of a simulate call.

But because fireEvent relies on real DOM events, and we rely on jsdom, and jsdom's MouseEvent doesn't support these properties, we can't test this code anymore.

There's an issue for pageX and pageY at jsdom/jsdom#1911, I mentioned the other missing properties there too. But maybe the issue is broader than just for these properties.

What's the best path forward in your opinion? Is it the best path to add the support to jsdom, or can we somehow pass the direct values to React's event subsystem?

@julienw
Copy link
Author

julienw commented Jan 16, 2019

I created a test case in https://github.com/julienw/testcase-react-testing-library, branch testcase-2:

To test in a browser:

  1. run the following commands
git clone https://github.com/julienw/testcase-react-testing-library
cd testcase-react-testing-library
git checkout testcase-2
yarn install
yarn start
  1. open http://localhost:9000
  2. hover the green square

Compare what you get in your browser:

clientX: 137
clientY: 146
pageX: 137
pageY: 146
x: 137
y: 146
offsetX: 129
offsetY: 138

with the result in tests:

clientX: 5
clientY: 10
pageX: undefined
pageY: undefined
x: undefined
y: undefined
offsetX: undefined
offsetY: undefined

@kentcdodds
Copy link
Member

Is it the best path to add the support to jsdom

Unfortunately yes. It does seem odd that even if you provide the values explicitly it doesn't support adding those 🤔

You could also consider testing those things in cypress (with cypress-testing-library) or karma.

I don't think there's anything we can do in this project though :-(

@julienw
Copy link
Author

julienw commented Jan 16, 2019

It does seem odd that even if you provide the values explicitly it doesn't support adding those.

That's because that's not how the MouseEvent constructor is supposed to work. According to the spec it only accepts clientX clientY screenX and screenY, and computes the other properties. JSDom obeys the spec :)
I guess that's the price of using real events instead of using mocks.

@kentcdodds
Copy link
Member

Yeah, that's why I say I'd typically use a real browser for situations where I need those things.

I wonder if there'd be a way to mock this sufficiently to do this in jsdom though. If you figure it out please let's add an example!

@julienw
Copy link
Author

julienw commented Jan 16, 2019

This seems to work for me (with some Flow typing on top of it):

type FakeMouseEventInit = $Shape<{
  bubbles: boolean,
  cancelable: boolean,
  composed: boolean,
  altKey: boolean,
  button: 0 | 1 | 2 | 3 | 4,
  buttons: number,
  clientX: number,
  clientY: number,
  ctrlKey: boolean,
  metaKey: boolean,
  movementX: number,
  movementY: number,
  offsetX: number,
  offsetY: number,
  pageX: number,
  pageY: number,
  screenX: number,
  screenY: number,
  shiftKey: boolean,
  x: number,
  y: number,
}>;

class FakeMouseEvent extends MouseEvent {
  offsetX: number;
  offsetY: number;
  pageX: number;
  pageY: number;
  x: number;
  y: number;

  constructor(type: string, values: FakeMouseEventInit) {
    const { pageX, pageY, offsetX, offsetY, x, y, ...mouseValues } = values;
    super(type, (mouseValues: Object));

    Object.assign(this, {
      offsetX: offsetX || 0,
      offsetY: offsetY || 0,
      pageX: pageX || 0,
      pageY: pageY || 0,
      x: x || 0,
      y: y || 0,
    });
  }
}

export function getMouseEvent(
  type: string,
  values: FakeMouseEventInit = {}
): FakeMouseEvent {
  values = {
    bubbles: true,
    cancelable: true,
    ...values,
  };
  return new FakeMouseEvent(type, values);
}

Then I'd use this with fireEvent directly, not fireEvent.mouseMove. Also I don't know how this behaves in implementations that actually implement these properties (I believe they would be non-writable, and so the Object.assign would fail). In that case we may want to extends Event instead of MouseEvent and redefine all properties.

@alexkrolick
Copy link
Collaborator

JSDom would be the main place to look for this support, but maybe our friendly neighborhood user-event helper might be willing to add in these extra properties...
https://github.com/Gpx/user-event

@kentcdodds
Copy link
Member

That seems like a pretty good workaround (especially considering this is not a typical necessity). Would you be willing to add an example of this workaround until we can get it built-into something like user-event as @alexkrolick mentioned?

@julienw
Copy link
Author

julienw commented Jan 16, 2019

(note: slightly updated the workaround, I made a mistake in the Object.assign call).

I can work on adding an example, but more likely on Friday or Monday.

@victorhqc
Copy link

Your code works for me @julienw 😄 I copied what you posted and implemented it like so

// I got an alias for root, which is `@app` but the file could be anywhere.
import getMouseEvent from '@app/__tests__/helpers/getMouseEvent';

...

const mouseMove = getMouseEvent('mousemove', {
  pageX: 250,
  pageY: 250,
})

fireEvent(document.body, mouseMove)

And now I get the correct pageX and pageY value. Thanks a lot 🎉

@cmdcolin
Copy link
Contributor

just wanted to add some comments to this thread in case anyone stumbled on it. I personally wanted to simulate a mouse drag in my code. I thought I was basically limited to just fireEvent.click(container) and wasn't even aware that I could perform other events, so I thought this thread was basically my key to success but I ended up with alternate route where I just used

  fireEvent.mouseDown(container, { clientX: 250, clientY: 20 })
  fireEvent.mouseMove(container, { clientX: 100, clientY: 20 })
  fireEvent.mouseUp(container, { clientX: 100, clientY: 20 })

In jsdom clientX/Y is supported by jsdom. While possibly things like pageX/Y are not, clientX/Y is modern and very intuitive and works fine with these fireEvent react-testing-library calls :) it was also helpful to stumble on the full "events" list here https://github.com/testing-library/dom-testing-library/blob/master/src/events.js which is linked from the docs (could be useful to add that stuff directly to the docs even though the code is basically helpful in showing all the events you can use)

@solidevolution
Copy link

Also had this issue with jest, vue-test-utils and Drag & Drop.

TIL: use client instead of page!

    window.dispatchEvent(
      new MouseEvent('mousedown', { clientX: 50, clientY: 50 })
    )
    window.dispatchEvent(
      new MouseEvent('mouseup', { clientX: 60, clientY: 60 })
    )

@orrin-naylor-instacart
Copy link

orrin-naylor-instacart commented Apr 1, 2022

If you're looking for a TS version of FakeMouseEvent

interface MouseEventWithOffsets extends MouseEventInit {
  pageX?: number
  pageY?: number
  offsetX?: number
  offsetY?: number
  x?: number
  y?: number
}

class FakeMouseEvent extends MouseEvent {
  constructor(type: string, values: MouseEventWithOffsets) {
    const { pageX, pageY, offsetX, offsetY, x, y, ...mouseValues } = values
    super(type, mouseValues)

    Object.assign(this, {
      offsetX: offsetX || 0,
      offsetY: offsetY || 0,
      pageX: pageX || 0,
      pageY: pageY || 0,
      x: x || 0,
      y: y || 0,
    })
  }
}

Usage:

fireEvent(
  element,
  new FakeMouseEvent('mouseover', {
    bubbles: true,
    pageX: 350,
    pageY: 125,
  })
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants