Study notes · 9% of the exam

Patterns, Accessibility and Testing

Pick component patterns that keep APIs flexible (context-based compound components, headless hooks with prop getters and state reducers), build on native semantics for accessibility, and test the way users interact: role-based queries, awaited user-event calls, and assertions on what people actually perceive.

Key points

  1. 1

    Query priority: getByRole (with name) > getByLabelText > placeholder/text/display value > alt text/title > getByTestId. If a role query can't find something, assistive tech may not either.

  2. 2

    getBy* throws on zero or many matches, queryBy* returns null (use it only for absence), and findBy* is async and retries (1000 ms default). *AllBy* variants return arrays.

  3. 3

    user-event 14 calls are async: create const user = userEvent.setup() and await every interaction. It enforces real constraints (pointer-events, maxLength, focus) that fireEvent skips.

  4. 4

    Keep waitFor callbacks free of side effects, prove an element exists before asserting it disappears, and fix act() warnings by awaiting the final UI rather than silencing them.

  5. 5

    Compound components should share state through context, not cloneElement, so consumers can wrap and reorder children. Apply HOCs once at module level and copy statics they hide.

  6. 6

    Accessibility basics in React: native elements (<button>, <label htmlFor>), useId for associations, live regions that are mounted before their text changes, focus management for dialogs and route changes, and native <dialog>.showModal() or inert for modals.

  7. 7

    Test with fresh per-test state (new QueryClient, providers via the wrapper option), mock the network at the boundary (MSW), and remember jsdom does not load your CSS or implement showModal; use a real browser for those.

Common traps

  • A negative assertion with a typo in the query (queryByText('Invalid emial')) passes forever; waitForElementToBeRemoved throws if the element is missing at the start.

  • aria-label replaces the visible text as the accessible name, so getByRole('button', { name: 'Delete' }) fails for <button aria-label="Remove item">Delete</button> and voice-control users can't say what they see.

  • Under fake timers, await user.type(...) hangs unless you pass advanceTimers (or delay: null) to userEvent.setup().

Test yourself on Patterns, Accessibility and Testing

Ten questions, with the answer and explanation after each one.