We have some a custom drag-n-drop multi select form control in our project and we've added a small helper to simulate drag and drop events (we found the code posted somewhere and we tweaked it a bit). Our project is using React DnD but I believe that the helper would apply to any generic html5 DnD implementation.
I wonder whether this is something that would be useful to add and maintain here.
Relevant code or config
const fireMouseEvent = function (
type: string,
elem: EventTarget,
centerX: number,
centerY: number
) {
const evt = document.createEvent('MouseEvents');
evt.initMouseEvent(
type,
true,
true,
window,
1,
1,
1,
centerX,
centerY,
false,
false,
false,
false,
0,
elem
);
return elem.dispatchEvent(evt);
};
export const dragAndDrop = (elemDrag: HTMLElement, elemDrop: HTMLElement) => {
act(() => {
// calculate positions
let pos = elemDrag.getBoundingClientRect();
const center1X = Math.floor((pos.left + pos.right) / 2);
const center1Y = Math.floor((pos.top + pos.bottom) / 2);
pos = elemDrop.getBoundingClientRect();
const center2X = Math.floor((pos.left + pos.right) / 2);
const center2Y = Math.floor((pos.top + pos.bottom) / 2);
// mouse over dragged element and mousedown
fireMouseEvent('mousemove', elemDrag, center1X, center1Y);
fireMouseEvent('mouseenter', elemDrag, center1X, center1Y);
fireMouseEvent('mouseover', elemDrag, center1X, center1Y);
fireMouseEvent('mousedown', elemDrag, center1X, center1Y);
// start dragging process over to drop target
const dragStarted = fireMouseEvent(
'dragstart',
elemDrag,
center1X,
center1Y
);
if (!dragStarted) {
return;
}
fireMouseEvent('drag', elemDrag, center1X, center1Y);
fireMouseEvent('mousemove', elemDrag, center1X, center1Y);
fireMouseEvent('drag', elemDrag, center2X, center2Y);
fireMouseEvent('mousemove', elemDrop, center2X, center2Y);
// trigger dragging process on top of drop target
fireMouseEvent('mouseenter', elemDrop, center2X, center2Y);
fireMouseEvent('dragenter', elemDrop, center2X, center2Y);
fireMouseEvent('mouseover', elemDrop, center2X, center2Y);
fireMouseEvent('dragover', elemDrop, center2X, center2Y);
// release dragged element on top of drop target
fireMouseEvent('drop', elemDrop, center2X, center2Y);
fireMouseEvent('dragend', elemDrag, center2X, center2Y);
fireMouseEvent('mouseup', elemDrag, center2X, center2Y);
});
};
// example usage
dragAndDrop(screen.getByText('text of item to drag'), screen.getByLabelText('label of area to drop'));
I'm all for it, though you should wait for a member position on this before to spend time on the implementation.
Are those all the events triggered by a drag event? Cypress can help us with that, I make do with it for some drag test I did in the past.
Yeah, for this kind of thing I think that Cypress is the place to go. Simulating drag-and-drop in an environment which does not support layout like jsdom (which is where most people use user-event) would be very tricky to get legit confidence from.
Thank you for your answers! I didn't know about this jsdom limitation until now. We're not using cypress at the moment but perhaps it is time we do.
Do you still feel strongly about this @kentcdodds ? I have written quite a few tests with jest and jsdom to make sure logic related to dnd actions works as expected and I'm happy with the confidence they give me.
e.g.
it('sorts the list once I drop an item');
it('doesn't sort the list if I drop an invalid item');
If you're happy with it then fell free to continue doing it that way. I personally wouldn't want components that really heavily on layout to have that important aspect of their behavior mocked.
So I assume that's a No, I don't want an userEvent.drag/drop event` :sweat_smile: ?
That's a: "I don't think I would use it, but some people like yourself and others who use user-event in a real browser might. This means I'll accept a pull request for it, but I won't maintain it." 馃槄
haha that sounds good to me! Thanks
That is a great addition. One can use this to test that drag&drop implementations work in principle.
E.g. providing a handler in a library that is later added on some concrete component.
We should just alert everyone in the documentation that the coordinates received for the PointerEvents are not reliable and that the test does not verify if a drag operation can be actually performed by the user because the source or target element might be hidden.
It would be great if the first argument of userEvent.drag could be either a draggable element, a Selection or an instance of DataTransfer.
Most helpful comment
Yeah, for this kind of thing I think that Cypress is the place to go. Simulating drag-and-drop in an environment which does not support layout like jsdom (which is where most people use user-event) would be very tricky to get legit confidence from.