Today I learned: How to assert the new active element after Tab keydown event in Jest

Context

Today I was writing a test that would assert that by using the Tab key onto the button of a component, the focus will move to the next focusable element.

The test was failing, although the expected behaviour was to be seen in the browser.

Failing test

Below createKeydownEvent is a custom utility function.

it('once focused on the button, it can be tabbed away', () => {
	// ...
	const keydownTabEvent = createKeydownEvent('Tab');
	const button = component.querySelector('button');
	const nextInteractiveElement = document.querySelector('a');
	button.focus();

	expect(document.activeElement).toBe(button);

	button.dispatchEvent(keydownTabEvent);

	expect(document.activeElement).toBe(nextInteractiveElement);
});

The expect fails because the expected document.activeElement happens to be the button, as if the Tab keydown event has no effect what so ever.

Solution

After some intense debugging, googling and reaching out to colleagues I came down to this conclusion:

When an element looses focus, the next element to acquire it actually gets it on the next tick of the event loop.

Calling setTimeout(callback, 0), where callback does the assertion, would execute its callback at the end of the next tick i.e. when the next element acquires the focus.

Equiped with this knowledge I could fix the failing test by wrapping the assertion into a setTimeout(callback, 0) call as show below:

setTimeout(() => {
	expect(document.activeElement).toBe(nextInteractiveElement);
}, 0);

Reference

Understanding process.nextTick()