How do I use testing-library to fire React's animationEnd event with a value for animationName?
14:53 13 Jun 2024

I have a React + TypeScript project where I want to use Jest and testing-library to confirm that a div element is removed from the DOM after a CSS animation completes. However, the removal must only happen after an animation with the name "fadeOut" is completed.

The code below is a simplified and contrived example. I only want the div to be removed when an animation with the name "fadeOut" is completed.

The problem is that jest's JSDOM headless browser doesn't fire css animation events. I need to fire them manually. And it seems that I can't pass the animationName property along with the event to my component. A console.log of the animationName always indicates that the animationName is undefined in jest, but correct in the browser.

Here is the SASS file Example.module.scss

@keyframes fadeIn {
  0% {
    opacity: 0;
  }

  100% {
    opacity: 1;
  }
}

@keyframes fadeOut {
  0% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

Here is the Example.tsx component React code:

import React, { useCallback, useState } from 'react';
import s from './Example.module.scss';

const Example = () => {
  const [isMounted, setIsMounted] = useState(true);
  const [isFading, setIsFading] = useState(false);

  const handleOnAnimationEnd = useCallback(
    (ev: React.AnimationEvent) => {
      console.log(`The animationName is : "${ev.animationName}"`);
      if (ev.animationName === s.fadeOut) {
        console.log('finished fading out.');
        setIsMounted(false);
      }
      if (ev.animationName === s.fadeIn) {
        console.log('finished fading in.');
      }
    },
    []
  );

  const handleOnClick = useCallback(() => {
    if (isMounted) {
      setIsFading(true);
    }
  }, [isMounted]);

  return (
    <>
      

Contrived example for Stack Overflow:

{(isMounted || isFading) && (
Hello!
)} ); }; export default Example;

Here is my Example.test.tsx test file:

import React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Example from './Example';

describe('Example', () => {
  it('should fade out', async () => {
    expect.hasAssertions();
    const user = userEvent.setup();
    render();
    const divEl = screen.getByTestId('div');
    const buttonEl = screen.getByRole('button');
    user.click(buttonEl);

    // jsdom doesn't fire animation events
    fireEvent.animationStart(divEl);

    // manually fire animationEnd event because jsdom doesn't.
    fireEvent.animationEnd(divEl, { animationName: 'fadeOut' });

    await waitFor(() => {
      expect(screen.queryByTestId('div')).not.toBeInTheDocument();
    });
  });
});

And this is the CLI output from running my test:

console.log
    The animationName is : "undefined"

      at lib/Example/Example.tsx:10:15

 FAIL  lib/Example/Example.test.tsx
  Example
    ✕ should fade out (1064 ms)

  ● Example › should fade out

    expect(element).not.toBeInTheDocument()

    expected document not to contain element, found 
Hello!
instead Ignored nodes: comments, script, style

Contrived example for Stack Overflow:

Hello!
20 | 21 | await waitFor(() => { > 22 | expect(screen.queryByTestId('div')).not.toBeInTheDocument(); | ^ 23 | }); 24 | }); 25 | }); at lib/Example/Example.test.tsx:22:47 at runWithExpensiveErrorDiagnosticsDisabled (../../node_modules/@testing-library/dom/dist/config.js:47:12) at checkCallback (../../node_modules/@testing-library/dom/dist/wait-for.js:124:77) at checkRealTimersCallback (../../node_modules/@testing-library/dom/dist/wait-for.js:118:16) at Timeout.task [as _onTimeout] (../../node_modules/jsdom/lib/jsdom/browser/Window.js:520:19)

As you can see in the output, the onAnimationEnd callback function is called, but the animationName property is undefined. You can also see that the animation did switch from fadeIn to fadeOut after the button was clicked, so the correct animation is getting applied to the div.

If I take out the check for the animationName, it works. But in this contrived example, I really need to be able to reference the animationName. It is a requirement for my actual code in my real project.

How does one pass the animationName property along with the event using testing-library's fireEvent.animationEnd() method?

reactjs typescript jestjs react-testing-library jsdom