question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Document body should be a focusable element

See original GitHub issue
  • @testing-library/user-event version: @testing-library/user-event@10.4.0
  • Testing Framework and version: jest@25.1.0
  • DOM Environment: jsdom@16.2.0
  • node: node/10.20.1

Relevant code or config (I’m also using React)

it('manages the page tab sequence', () => {
  function TestComponent() {
    return (<ul role="listbox">
      <li role="option" tabindex="0">First option</li>
      <li role="option" tabindex="-1">Second option</li>
    </ul>);
  }

  render(<TestComponent />);

  expect(document.body).toHaveFocus();

  userEvent.tab();
  expect(screen.getByText('First option')).toHaveFocus();

  userEvent.tab();
  expect(document.body).toHaveFocus();

  userEvent.tab({ shift: true });
  expect(screen.getByText('First option')).toHaveFocus();
});

What you did:

Ran the test.

What happened:

The test failed.

    expect(element).toHaveFocus()

    Expected
      <body><div><ul role="listbox"><li role="option" tabindex="0">First option</li><li role="option" tabindex="-1">Second option</li></ul></div></body>
    Received:
      <li role="option" tabindex="0">First option</li>

      76 | 
      77 |       userEvent.tab();
    > 78 |       expect(document.body).toHaveFocus();
         |                             ^
      79 | 
      80 |       userEvent.tab({ shift: true });
      81 |       expect(screen.getByText('First option')).toHaveFocus();

Reproduction repository:

Problem description:

It looks like the tab method does not consider the body as a tabable element. That is actually not true – at least from doing a quick experiment in Chrome.

The following screenshot is the experiement.

  1. Render a button
  2. Look at active element (body)
  3. Press tab
  4. Look at active element (button)
  5. Press tab
  6. Look at active element (body)
Screen Shot 2020-06-17 at 12 21 03 PM

Suggested solution:

Assuming the focus trap is on the document, always set the body as the first tabable element.

Issue Analytics

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

github_iconTop GitHub Comments

1reaction
juancacommented, Jun 18, 2020

@nickmccurdy I appreciate the analysis and clarification. I think you’re 100% right.

I’m also not sure how we should handle continuing to tab past the end of the document when there’s no focus trap. Should we add an arbitrary number of fake buttons, loop back around immediately, or error because the functionality is undefined outside of a browser?

My latest train of thought: I think it is sensible to simulate a single active element outside the page tab sequence.

  1. When a developer is testing a UI element, it seems reasonable to test the case of tabbing past the UI element. If the UI element was a single page application, it seems reasonable to tab out of the application.

  2. As it now stands, the jsdom implementation sets the active element to be document.body on first render (given a React test). This allows a developer to test a UI element from lack of focus to focus without any additional testing overhead. It seems reasonable to provide the reverse: focus to lack of focus without any additional testing overhead.

Suggested theoretical solution:

  1. When there is no focused element (document.activeElement === document.body), pressing tab will focus the first thing in the page tab sequence.
  2. When there is no focused element (document.activeElement === document.body), pressing shift+tab will focus the last thing in the page tab sequence.
  3. When the focused element is the last element in the page tab sequence, pressing tab will set the active element to the body
    • I think this is simulated with document.activeElement.blur()
  4. When the focused element is the first element in the page tab sequence, pressing shift+tab will set the active element to the body
    • I think this is simulated with document.activeElement.blur()

Outside of scope:

  1. Renaming or splitting toHaveFocus seems to be a problem but I think it might be an additional issue instead of the same issue.

Whaddya’ll think?

0reactions
kentcdoddscommented, Jun 21, 2020

🎉 This issue has been resolved in version 12.0.3 🎉

The release is available on:

Your semantic-release bot 📦🚀

Read more comments on GitHub >

github_iconTop Results From Across the Web

ARIA: document role - Accessibility - MDN Web Docs - Mozilla
The document role is for focusable content within complex composite widgets or applications for which assistive technologies can switch ...
Read more >
Focusable Elements - Browser Compatibility Table - ally.js
Element Expected Chrome Chrome Microsoft Edge Microsoft Edge Microsoft Edge Element Expected 55.0 57.0 12.10240 13.10586 14.14393 tabbable. 0 tabbable. 0 tabbable. 0 tabbable. 0...
Read more >
Getting keyboard-focusable elements | Zell Liew
If you create JavaScript widgets, one of the key parts to accessibility is managing focus. To manage focus, you need to find keyboard-focusable...
Read more >
How to Determine Which Element Has Focus in JavaScript
Form controls like input, select, textarea, and button are all focusable if they are not disabled, as are objects. Other elements, such as ......
Read more >
First focusable element is link to main content - Rules
the element is keyboard actionable; and · the element is included in the accessibility tree; and · the element has a semantic role...
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