Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test mousemove drag and drop with react-testing-library and framer-motion

I am trying to test the drag and drop functionality using react-testing-libary. The drag and drop functionality comes from framer-motion and the code is in reacy. From what I understand it uses the mousedown, mousemove and mouseup events to do this. I want to test drag and drop functionality of the following basic component:

export const Draggable: FC<DraggableInterface> = ({
  isDragging,
  setIsDragging,
  width,
  height,
  x,
  y,
  radius,
  children,
}) => {
  return (
      <motion.div
        {...{ isDragging }}
        {...{ setIsDragging }}
        drag
        dragConstraints={{
          left: Number(`${0 - x}`),
          right: Number(
            `${width - x}`,
          ),
          top: Number(`${0 - y}`),
          bottom: Number(
            `${height - y}`,
          ),
        }}
        dragElastic={0}
        dragMomentum={false}
        data-test-id='dragabble-element'
      >
        {children}
      </motion.div>
  );
};

And I have a snippet of the test as follows:

it('should drag the node to the new position', async () => {

    const DraggableItem = () => {
      const [isDragging, setIsDragging] = useState<boolean>(true);
      return (
          <Draggable
            isDragging={isDragging}
            setIsDragging={() => setIsDragging}
            x={0}
            y={0}
            onUpdateNodePosition={() => undefined}
            width={500}
            height={200}
          >
            <div
            style={{
                height: '32px',
                width: '32px'
            }}
            />
          </Draggable>
      );
    };

    const { rerender, getByTestId } = render(<DraggableItem />);
    rerender(<DraggableItem />);
    const draggableElement = getByTestId('dragabble-element');

    const { getByTestId, container } = render(
      <DraggableItem />
    );
    fireEvent.mouseDown(draggableElement);
    fireEvent.mouseMove(container, {
      clientX: 16,
      clientY: 16,
    })

    fireEvent.mouseUp(draggableElement)

    await waitFor(() =>
      expect(draggableElement).toHaveStyle(
        'transform: translateX(16px) translateY(16px) translateZ(0)',
      ),
    );

However, I cannot get the test to pass successfully as the transform value I test for is set to none. It does not update it the value with the updated CSS. I think there is some sort of async issue or animation delay so the mousemove is not detected and the value of the transform does not change. Would anyone know how to get the test to work or a way to test the mousemove changes?

Any advice or guidance on how I can solve this would be greatly appreciated!

like image 343
mineshmshah Avatar asked Nov 30 '25 03:11

mineshmshah


1 Answers

It looks like you are invoking mouseMove() on the container instead of your draggable item. The container here refers to a root div containing your DraggableItem but is not the item itself (API). Therefore, events are being fired on the root div and not the item.


Here is a simple working example for testing draggable elements (for passers-by looking to test mouse down, move, and up events on draggable elements):

//
// file: draggable-widget.tsx
//
import $ from 'jquery'
import * as React from 'react'

type WidgetProps = { children?: React.ReactNode }

export default class DraggableWidget extends React.Component<WidgetProps> {
    private element: React.RefObject<HTMLDivElement>

    constructor(props: WidgetProps) {
        super(props)
        this.element = React.createRef()
    }

    show() { if (this.element.current) $(this.element.current).show() }
    hide() { if (this.element.current) $(this.element.current).hide() }

    getLocation() {
        if (!this.element.current) return { x: 0, y: 0 }
        return {
            x: parseInt(this.element.current.style.left),
            y: parseInt(this.element.current.style.top)
        }
    }

    private onDraggingMouse(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
        let location = this.getLocation()
        let offsetX  = e.clientX - location.x
        let offsetY  = e.clientY - location.y
        let mouseMoveHandler = (e: MouseEvent) => {
            if (!this.element.current) return
            this.element.current.style.left = `${e.clientX - offsetX}px`
            this.element.current.style.top  = `${e.clientY - offsetY}px`
        }
        let reset = () => {
            window.removeEventListener('mousemove', mouseMoveHandler)
            window.removeEventListener('mouseup', reset)
        }
        window.addEventListener('mousemove', mouseMoveHandler)
        window.addEventListener('mouseup', reset)
    }

    render() {
        return (
            <div ref={this.element} className="draggable-widget">
                <div className="widget-header"
                    onMouseDown={e => this.onDraggingMouse(e)}>
                    <button className="widget-close" onClick={() => this.hide()}
                        onMouseDown={e => e.stopPropagation()}></button>
                </div>
            </div>
        )
    }
}

Then for the test logic:

//
// file: draggable-widget.spec.tsx
//
import 'mocha'
import $ from 'jquery'
import * as React from 'react'
import { assert, expect } from 'chai'
import { render, fireEvent } from '@testing-library/react'
import Widget from './draggable-widget'

describe('draggable widget', () => {
    it('should move the widget by mouse delta-xy', () => {
        const mouse = [
            { clientX: 10, clientY: 20 },
            { clientX: 15, clientY: 30 }
        ]

        let ref = React.createRef<Widget>()
        let { container } = render(<Widget ref={ref} />)
        let element = $(container).find('.widget-header')
        assert(ref.current)
        expect(ref.current.getLocation()).to.deep.equal({ x: 0, y: 0 })

        fireEvent.mouseDown(element[0], mouse[0])
        fireEvent.mouseMove(element[0], mouse[1])
        fireEvent.mouseUp(element[0])

        expect(ref.current.getLocation()).to.deep.equal({
            x: mouse[1].clientX - mouse[0].clientX,
            y: mouse[1].clientY - mouse[0].clientY
        })
    })
})
like image 178
TekuConcept Avatar answered Dec 03 '25 01:12

TekuConcept



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!