react-testing-library: .focus is not focusing on the input

Hi, thanks for the library. I think I have found a bug and I want to get some help here.

  • react-testing-library version: 5.4.4
  • react version: 16.5.2

Relevant code or config:

import React from 'react'
import { render, fireEvent } from 'react-testing-library'

it('focus on the input', () => {
  const { container } = render(<input type="text" />)

  fireEvent.focus(container.querySelector('input'))

  expect(document.activeElement).toBe(container.querySelector('input'))
})

What you did:

I want to focus on an input and assert about that.

What happened:

The test fails to check the activeElement. The received element is the body.

Reproduction:

Edit issue-focus-rtl

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 19 (7 by maintainers)

Commits related to this issue

Most upvoted comments

I’m not sure if it’s the same thing, but fireEvent.focus() never works for me. I have always to use act(() => element.focus()).

fireEvent.focus(element);
expect(element).toHaveFocus(); // throws

act(() => element.focus());
expect(element).toHaveFocus(); // works

Confirmed that fireEvent.focus() doesn’t work correctly.

This returns document.body in console (the default behaviour when document.activeElement is undefined):

fireEvent.focus(input);
console.log(document.activeElement);

This works correctly:

input.focus();
console.log(document.activeElement);

I believe the same issue may be occurring with all other fireEvent’s as well, which would explain why a lot of my tests fail that use fireEvent.click().

fireEvent internally uses the dispatchEvent method of the element which only triggers the bound handlers to that event. It does not trigger the UI (browser) behaviour of the element that received the event.

So if you bind events for the focus event they will be triggered, but the element will not receive focus. You could run container.querySelector('input').focus() for that which will trigger the UI behaviour, but there might be problems with that in a headless browser environment.

Updated reproduction:

Edit issue-focus-rtl

Had the same problem as @phuwin95.

Writing a pretty simple test. I have a component which renders a label with htmlFor and a corresponding input. Test should verify that whenever I click on label, input is focused.

I made it work after replacing fireEvent.click(label) with userEvent.click(label) from @testing-library/user-event:

import userEvent from '@testing-library/user-event'

In my experience, focus is not assigned instantaneously to an element. It happens asynchronously, so you may want to wrap that expect inside a wait call:

await wait(() => {
  expect(document.activeElement).toBe(container.querySelector('input'))
});

Also, if this still does not work, it is most likely a jsdom issue rather than an issue with this library. This library is not responsible for setting document.activeElement. Still not likely if you’re reproducing it on CodeSandbox, which does not use jsdom, but the actual browser instead. It could be a problem with how the focus event is simulated to be fired by this library. But I’d try the above first to make sure.

Finally, take a look at jest-dom’s toHaveFocus custom matcher, the way it works and the way it is being tested.

With my example, it doesn’t matter what the current activeElement which imo should be part of e2e testing. What do you think?

It absolutely should matter what your activeElement is. It’s a cursor for screen reader users.

Why would you want to test the activeElement while calling the focus method in the test file itself?

I don’t understand to what you’re referring. I just wanted to say that if you want to test if an element got focused then you should query that element and check if it’s equal to the activeElement i.e. use toHaveFocus from jest-dom. Testing if the focus event was fired allows cheating by calling fireEvent.focus which is dispatched regardless of whether the element in question can even receive focus.

You could write

const div = document.createElement('div');
div.addEventListener('focus', () => pass());
fireEvent.focus(div)

which would pass. But the element will never be the active element nor will it receive any focus event in your actual app which leads to the question: What behavior are you testing here?

So if we run this in the browser, does it work? If that’s the case then maybe we can fix it in JSDOM? That’s probably best.

My problem was that document.activeElement wasn’t always giving me the expected result.

I would bet that you’re focusing elements that can’t receive focus (e.g. they have no tabIndex). Or you’re dispatching change events without adding a value. For debug purposes I would make sure that handleChange is actually called and if it even reaches the .focus() line.

I thought I was having this problem as well yesterday but it turns out the component I was testing was rendering other components and one of them was debouncing the input of the text input I was changing the value on. so if I just waited that the focus did, in fact, work as expected.