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 https://github.com/jsdom/jsdom/issues/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?
I created a test case in https://github.com/julienw/testcase-react-testing-library, branch testcase-2:
To test in a browser:
git clone https://github.com/julienw/testcase-react-testing-library
cd testcase-react-testing-library
git checkout testcase-2
yarn install
yarn start
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
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 :-(
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.
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!
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.
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
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?
(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.
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 馃帀
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)
Most helpful comment
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
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)