fireEvent - mouseEnter/mouseLeave not working with addEventListener

See original GitHub issue
  • react-testing-library version: 9.4.0
  • react version: 16.12.0
  • node version: 10.16.0
  • npm (or yarn) version: 1.21.1

Relevant code or config:

import React, { useEffect, useState, useRef } from "react";
import { render, fireEvent } from "@testing-library/react";

const HoverMe = () => {
  const ref = useRef();
  const [isMouseEntered, setIsMouseEntered] = useState(false);

  useEffect(() => {
    const setYes = () => setIsMouseEntered(true);
    const button = ref.current;
    // If you change the event to "mouseover"
    // the test passes, even if you don't update the test
    button.addEventListener("mouseenter", setYes);

    return () => {
      button.removeEventListener("mouseenter", setYes);
    };
  });

  return <button ref={ref}>{isMouseEntered ? "yes" : "no"}</button>;
};

describe("mouseenter/mouseleave bug", () => {
  test("mouseenter should update text to 'yes'", () => {
    const { getByText } = render(<HoverMe />);
    fireEvent.mouseEnter(getByText("no"));
    expect(getByText("yes")).toBeTruthy();
  });
});

What you did:

I’m using a third party library that attaches mouseenter and mouseleave events to a DOM element accessed via a React ref via HTMLElement.addEventListener. Was testing this behavior.

What happened:

Works fine in the browser, but when writing my tests, I noticed that fireEvent.mouseEnter and fireEvent.mouseLeave have no effect. What’s weird is if I leave the test alone, but change the event in the component to mouseover/mouseout, it works. If I attach the events the react way, via onMouseEnter and onMouseLeave, it also works.

Reproduction:

https://codesandbox.io/s/react-testing-library-demo-37yqv?fontsize=14&hidenavigation=1&theme=dark

Problem description:

fireEvent.mouseLeave and fireEvent.mouseOver do not work when the events are added via addEventListener.

Suggested solution:

It may have something to do with this: https://reactjs.org/docs/events.html#mouse-events

If this is a React limitation, or if there’s a workaround, it would be great to add it to the FAQ.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:4
  • Comments:9 (3 by maintainers)

github_iconTop GitHub Comments

13reactions
greypantscommented, Jan 27, 2020

Upon further testing, I noticed by creating the event myself and passing it to fireEvent, DOES pass ✅ :

const mouseenter = new MouseEvent("mouseenter", {
      bubbles: false,
      cancelable: false
    });

fireEvent(getByText("no"), mouseenter);

// Passes!
8reactions
jessethomsoncommented, Feb 12, 2020

So there are the four cases we want to work with the fireEvent utility.

  1. mouseEnter/mouseLeave with native events
  2. mouseOver/mouseOut with native events
  3. mouseEnter/mouseLeave with react events
  4. mouseOver/mouseOut with react events

Currently, number 1 does not work.

We want to make a change to fix 1, without breaking the others. Here’s a test people we can use to ensure we have done that. So as long as the following tests pass, I’m assuming there shouldn’t be any issues getting this change in.

(Like others, I unfortunately don’t have time to look into this right now, but maybe in a couple months)

import React, { useState, useEffect, useRef } from 'react';
import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';

const NativeEnterLeave = () => {
    const btnRef = useRef();
    const [label, setLabel] = useState('outside');
    useEffect(() => {
        const btn = btnRef.current;
        function handleMouseEnter() {
            setLabel('inside');
        }
        function handleMouseLeave() {
            setLabel('outside');
        }
        btn.addEventListener('mouseenter', handleMouseEnter);
        btn.addEventListener('mouseleave', handleMouseLeave);
        return () => {
            btn.removeEventListener('mouseenter', handleMouseEnter);
            btn.removeEventListener('mouseleave', handleMouseLeave);
        };
    });
    return <button ref={btnRef}>{label}</button>;
};

const NativeOverOut = () => {
    const btnRef = useRef();
    const [label, setLabel] = useState('outside');
    useEffect(() => {
        const btn = btnRef.current;
        function handleMouseEnter() {
            setLabel('inside');
        }
        function handleMouseLeave() {
            setLabel('outside');
        }
        btn.addEventListener('mouseover', handleMouseEnter);
        btn.addEventListener('mouseout', handleMouseLeave);
        return () => {
            btn.removeEventListener('mouseover', handleMouseEnter);
            btn.removeEventListener('mouseout', handleMouseLeave);
        };
    });
    return <button ref={btnRef}>{label}</button>;
};

const ReactEnterLeave = () => {
    const [label, setLabel] = useState('outside');
    return (
        <button
            onMouseEnter={() => setLabel('inside')}
            onMouseLeave={() => setLabel('outside')}
        >
            {label}
        </button>
    );
};

const ReactOverOut = () => {
    const [label, setLabel] = useState('outside');
    return (
        <button
            onMouseOver={() => setLabel('inside')}
            onMouseOut={() => setLabel('outside')}
        >
            {label}
        </button>
    );
};

test('fires native mouseEnter/mouseLeave events', () => {
    const { getByRole } = render(<NativeEnterLeave />);
    const btn = getByRole('button');
    expect(btn).toHaveTextContent('outside');
    fireEvent.mouseEnter(btn);
    expect(btn).toHaveTextContent('inside');
    fireEvent.mouseLeave(btn);
    expect(btn).toHaveTextContent('outside');
});

test('fires native mouseOver/mouseOut events', () => {
    const { getByRole } = render(<NativeOverOut />);
    const btn = getByRole('button');
    expect(btn).toHaveTextContent('outside');
    fireEvent.mouseOver(btn);
    expect(btn).toHaveTextContent('inside');
    fireEvent.mouseOut(btn);
    expect(btn).toHaveTextContent('outside');
});

test('fires react mouseEnter/mouseLeave events', () => {
    const { getByRole } = render(<ReactEnterLeave />);
    const btn = getByRole('button');
    expect(btn).toHaveTextContent('outside');
    fireEvent.mouseEnter(btn);
    expect(btn).toHaveTextContent('inside');
    fireEvent.mouseLeave(btn);
    expect(btn).toHaveTextContent('outside');
});

test('fires react mouseOver/mouseOut events', () => {
    const { getByRole } = render(<ReactOverOut />);
    const btn = getByRole('button');
    expect(btn).toHaveTextContent('outside');
    fireEvent.mouseOver(btn);
    expect(btn).toHaveTextContent('inside');
    fireEvent.mouseOut(btn);
    expect(btn).toHaveTextContent('outside');
});
Read more comments on GitHub >

github_iconTop Results From Across the Web

Element: mouseleave event - Web APIs | MDN
The mouseleave event is fired at an Element when the cursor of a pointing device (usually a mouse) is moved out of it....
Read more >
react mouseleave not firing - You.com | The search engine you control.
I'm using a third party library that attaches mouseenter and mouseleave events to a DOM element accessed via a React ref via HTMLElement.addEventListener...
Read more >
MouseEnter/MouseOver doesn't work with react-testing-library ...
I have a ant-design dropdown which shows a list on hovering the element. I want to test the list inside the dropdown menu....
Read more >
How to use the react-testing-library.fireEvent.mouseEnter ...
Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues...
Read more >
Moving the mouse: mouseover/out, mouseenter/leave
Events mouseenter/mouseleave do not bubble. These events are extremely simple. When the pointer enters an element – mouseenter triggers. The ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found