Hone logo
Hone
Problems

Jest Keyboard Navigation Testing for Accessible UI Components

This challenge focuses on writing robust Jest tests for keyboard navigation within a hypothetical UI component. Ensuring that interactive elements can be accessed and operated solely through the keyboard is a critical aspect of web accessibility. You will simulate keyboard events to verify that focus management and component behavior are as expected.

Problem Description

You are tasked with creating a set of Jest tests to ensure that a given interactive UI component, let's call it NavigableComponent, supports proper keyboard navigation. This component will have several focusable elements, and users should be able to move between them using the Tab key and potentially other navigation keys (like Arrow keys for list items or menus).

Key Requirements:

  • Tab Navigation: Test that pressing the Tab key moves focus sequentially through all focusable elements within the NavigableComponent in the correct order.
  • Shift+Tab Navigation: Test that pressing Shift+Tab moves focus in reverse order.
  • Arrow Key Navigation (Optional but Recommended): If the component contains elements that logically group together (e.g., a list of menu items), test that ArrowUp and ArrowDown (or ArrowLeft/ArrowRight) keys correctly move focus between these related items.
  • Enter/Spacebar Activation: For interactive elements that should be activated by keyboard (e.g., buttons, links, menu items), test that pressing Enter or Spacebar triggers the expected action.
  • Focus State: Verify that the currently focused element has the appropriate visual indicator (though the test will primarily focus on focus management, not styling).

Expected Behavior:

  1. When the NavigableComponent is rendered, the first focusable element should receive initial focus.
  2. Pressing Tab should move focus to the next focusable element in the DOM order.
  3. Pressing Shift+Tab should move focus to the previous focusable element in the DOM order.
  4. For specific element types (like lists), arrow keys should navigate within that group of elements.
  5. Pressing Enter or Spacebar on an activatable element should trigger its associated event handler.
  6. Focus should be correctly managed when the component's state changes or elements are added/removed.

Edge Cases to Consider:

  • Disabled Elements: Ensure focus does not land on or pass through disabled elements.
  • Hidden Elements: Ensure focus does not land on or pass through elements that are visually hidden or not interactive.
  • Circular Navigation: If the component is designed to loop focus (e.g., from the last element back to the first), test this behavior.
  • Component Mount/Unmount: Test focus behavior after the component is mounted and potentially after it's unmounted and re-mounted.
  • Focus Restoration: If the component manages focus that is temporarily moved elsewhere, test that focus is restored correctly.

Examples

Example 1: Basic Tab Navigation

Assume a NavigableComponent with three button elements:

<div data-testid="navigable-component">
  <button>Button 1</button>
  <button>Button 2</button>
  <button>Button 3</button>
</div>

Test Scenario:

  1. Render the NavigableComponent.
  2. Verify that "Button 1" has initial focus.
  3. Simulate Tab key press.
  4. Verify that "Button 2" has focus.
  5. Simulate Tab key press again.
  6. Verify that "Button 3" has focus.

Expected Output (Conceptual Test Logic):

  • expect(screen.getByText('Button 1')).toHaveFocus();
  • fireEvent.keyDown(document.activeElement, { key: 'Tab' });
  • expect(screen.getByText('Button 2')).toHaveFocus();
  • fireEvent.keyDown(document.activeElement, { key: 'Tab' });
  • expect(screen.getByText('Button 3')).toHaveFocus();

Example 2: Shift+Tab Navigation and Activation

Assume a NavigableComponent with an input field and a submit button:

<div data-testid="navigable-component">
  <label for="username">Username:</label>
  <input id="username" type="text" />
  <button>Submit</button>
</div>

Test Scenario:

  1. Render the NavigableComponent.
  2. Verify that the input field has initial focus.
  3. Simulate Tab key press.
  4. Verify that the "Submit" button has focus.
  5. Simulate Shift+Tab key press.
  6. Verify that the input field has focus again.
  7. Simulate Enter key press while the "Submit" button has focus.
  8. Verify that a mock submission function was called.

Expected Output (Conceptual Test Logic):

  • expect(screen.getByLabelText('Username:')).toHaveFocus();
  • fireEvent.keyDown(document.activeElement, { key: 'Tab' });
  • expect(screen.getByRole('button', { name: 'Submit' })).toHaveFocus();
  • fireEvent.keyDown(document.activeElement, { key: 'Shift' });
  • fireEvent.keyDown(document.activeElement, { key: 'Tab' });
  • fireEvent.keyUp(document.activeElement, { key: 'Shift' }); // Release Shift
  • expect(screen.getByLabelText('Username:')).toHaveFocus();
  • fireEvent.keyDown(screen.getByRole('button', { name: 'Submit' }), { key: 'Enter' });
  • expect(mockSubmitFunction).toHaveBeenCalledTimes(1);

Example 3: Arrow Key Navigation in a Menu

Assume a NavigableComponent representing a dropdown menu:

<div data-testid="navigable-component">
  <button aria-haspopup="true">Menu</button>
  <ul role="menu">
    <li role="menuitem" tabindex="-1">Option 1</li>
    <li role="menuitem" tabindex="-1">Option 2</li>
    <li role="menuitem" tabindex="-1">Option 3</li>
  </ul>
</div>

Test Scenario:

  1. Render the NavigableComponent.
  2. Click the "Menu" button to open the menu and focus the first menu item.
  3. Verify that "Option 1" has focus.
  4. Simulate ArrowDown key press.
  5. Verify that "Option 2" has focus.
  6. Simulate ArrowDown key press again.
  7. Verify that "Option 3" has focus.
  8. Simulate ArrowUp key press.
  9. Verify that "Option 2" has focus.
  10. Simulate Enter key press.
  11. Verify that the handler for "Option 2" was called.

Expected Output (Conceptual Test Logic):

  • userEvent.click(screen.getByRole('button', { name: 'Menu' }));
  • expect(screen.getByRole('menuitem', { name: 'Option 1' })).toHaveFocus();
  • fireEvent.keyDown(document.activeElement, { key: 'ArrowDown', code: 'ArrowDown' });
  • expect(screen.getByRole('menuitem', { name: 'Option 2' })).toHaveFocus();
  • // ... and so on for other arrow key tests and activation

Constraints

  • Your tests must be written in TypeScript.
  • You must use Jest and the @testing-library/react (or equivalent for your framework) for rendering and interacting with components.
  • Focus management should be tested using document.activeElement and simulated keyboard events (fireEvent.keyDown, fireEvent.keyUp, userEvent.type, etc.).
  • Assume the NavigableComponent is implemented in React (or can be easily adapted for other frameworks). You will need to mock any external dependencies or context if necessary for rendering.
  • Tests should be efficient and not rely on excessive setTimeout delays unless absolutely necessary for asynchronous operations.
  • Aim for a comprehensive test suite covering the requirements and at least two of the edge cases mentioned.

Notes

  • The @testing-library/user-event library is highly recommended for simulating user interactions as it provides a more realistic and accessible API than fireEvent alone.
  • When simulating keyboard events, be mindful of the key and code properties for accuracy, especially with modifier keys like Shift.
  • Consider using tabbable or similar utilities if you need to programmatically determine which elements are focusable in a complex DOM.
  • Remember that for elements like custom components that might not inherently be focusable, you might need to add tabindex="0" or ensure they are inherently focusable (like buttons or inputs).
  • Success is defined by a test suite that reliably passes when the NavigableComponent is correctly implemented and fails when keyboard navigation is broken.
Loading editor...
typescript