[{"data":1,"prerenderedAt":110},["ShallowReactive",2],{"qa-\u002Freact\u002Ftesting\u002Fcomponent-interaction-testing":3},{"page":4,"siblings":93,"blog":107},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":12,"path":20,"questions":21,"questionsCount":84,"related":85,"seo":86,"seoDescription":87,"stem":88,"subtopic":6,"topic":89,"topicSlug":90,"updated":91,"__hash__":92},"qa\u002Freact\u002Ftesting\u002Fcomponent-interaction-testing.md","Component Interaction Testing",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","React","react",{},true,"\u002Freact\u002Ftesting\u002Fcomponent-interaction-testing",[22,27,31,35,39,43,47,51,55,59,64,68,72,76,80],{"id":23,"difficulty":24,"q":25,"a":26},"user-click-test","easy","How do you test a button click with React Testing Library?","Use `userEvent.click()` from `@testing-library\u002Fuser-event`. It fires the\nfull sequence of pointer and mouse events a real click produces.\n\n```js\nimport { render, screen } from '@testing-library\u002Freact'\nimport userEvent from '@testing-library\u002Fuser-event'\n\nfunction Counter() {\n  const [count, setCount] = React.useState(0)\n  return (\n    \u003C>\n      \u003Cp>Count: {count}\u003C\u002Fp>\n      \u003Cbutton onClick={() => setCount(c => c + 1)}>Increment\u003C\u002Fbutton>\n    \u003C\u002F>\n  )\n}\n\ntest('increments count on click', async () => {\n  const user = userEvent.setup()    \u002F\u002F setup once per test\n  render(\u003CCounter \u002F>)\n\n  expect(screen.getByText('Count: 0')).toBeInTheDocument()\n\n  await user.click(screen.getByRole('button', { name: \u002Fincrement\u002Fi }))\n\n  expect(screen.getByText('Count: 1')).toBeInTheDocument()\n})\n```\n\nKey points:\n- `userEvent.setup()` returns a user instance; create it once per test\n  outside any loops to keep fake timers consistent.\n- `await` every `user.*` call — they're async since user-event v14.\n- Query by role + name so the test doubles as an accessibility check.\n\n**Rule of thumb:** `await user.click()` → assert. One interaction, one\nassertion block. Don't chain multiple un-awaited clicks.\n",{"id":28,"difficulty":14,"q":29,"a":30},"form-submission-test","How do you test form submission?","The most reliable approach is to fill in all fields via `userEvent.type`,\nthen submit via `userEvent.click` on the submit button (or\n`userEvent.keyboard('{Enter}')` if that's how users submit).\n\n```js\nimport { render, screen, waitFor } from '@testing-library\u002Freact'\nimport userEvent from '@testing-library\u002Fuser-event'\n\ntest('submits registration form', async () => {\n  const handleSubmit = vi.fn()\n  const user = userEvent.setup()\n\n  render(\u003CRegistrationForm onSubmit={handleSubmit} \u002F>)\n\n  await user.type(screen.getByLabelText(\u002Femail\u002Fi), 'user@example.com')\n  await user.type(screen.getByLabelText(\u002Fpassword\u002Fi), 'secret123')\n  await user.click(screen.getByRole('button', { name: \u002Fregister\u002Fi }))\n\n  await waitFor(() => {\n    expect(handleSubmit).toHaveBeenCalledWith({\n      email: 'user@example.com',\n      password: 'secret123',\n    })\n  })\n})\n```\n\nFor native form submission (no JS handler), you can also listen for the\n`submit` event on the form element:\n```js\nconst handleSubmit = vi.fn(e => e.preventDefault())\nrender(\u003Cform onSubmit={handleSubmit}>\u003Cbutton type=\"submit\">Go\u003C\u002Fbutton>\u003C\u002Fform>)\nawait user.click(screen.getByRole('button', { name: \u002Fgo\u002Fi }))\nexpect(handleSubmit).toHaveBeenCalled()\n```\n\n**Rule of thumb:** Test the *outcome* of submission (callback called,\nsuccess message shown, navigation triggered) rather than testing the\nform's internal state.\n",{"id":32,"difficulty":24,"q":33,"a":34},"controlled-input-test","How do you test a controlled input that updates as the user types?","`userEvent.type()` fires a keydown\u002Fkeypress\u002Finput\u002Fkeyup sequence for\neach character, causing controlled inputs to update through the standard\nReact synthetic event flow.\n\n```js\nimport { render, screen } from '@testing-library\u002Freact'\nimport userEvent from '@testing-library\u002Fuser-event'\n\nfunction SearchBox() {\n  const [query, setQuery] = React.useState('')\n  return (\n    \u003Cinput\n      aria-label=\"Search\"\n      value={query}\n      onChange={e => setQuery(e.target.value)}\n    \u002F>\n  )\n}\n\ntest('updates value as user types', async () => {\n  const user = userEvent.setup()\n  render(\u003CSearchBox \u002F>)\n\n  const input = screen.getByRole('textbox', { name: \u002Fsearch\u002Fi })\n  await user.type(input, 'react hooks')\n\n  expect(input).toHaveValue('react hooks')\n})\n```\n\nTo clear an existing value before typing, use `user.clear()`:\n```js\nawait user.clear(input)\nawait user.type(input, 'new value')\n```\n\nTo type into a `\u003Cselect>`:\n```js\nawait user.selectOptions(screen.getByRole('combobox'), 'Option B')\nexpect(screen.getByRole('combobox')).toHaveDisplayValue('Option B')\n```\n\n**Rule of thumb:** Prefer `user.type()` over manually setting\n`.value` + dispatching a change event — it tests the full keyboard\ninteraction path.\n",{"id":36,"difficulty":24,"q":37,"a":38},"conditional-rendering-test","How do you test conditional rendering?","Assert presence with `getByRole`\u002F`getByText` and absence with\n`queryBy*` returning `null` or `not.toBeInTheDocument()`.\n\n```js\nimport { render, screen } from '@testing-library\u002Freact'\nimport userEvent from '@testing-library\u002Fuser-event'\n\nfunction Alert({ type, message }) {\n  if (!message) return null\n  return \u003Cdiv role=\"alert\" className={`alert-${type}`}>{message}\u003C\u002Fdiv>\n}\n\ntest('renders nothing when message is empty', () => {\n  render(\u003CAlert type=\"error\" message=\"\" \u002F>)\n  expect(screen.queryByRole('alert')).not.toBeInTheDocument()\n})\n\ntest('renders error message', () => {\n  render(\u003CAlert type=\"error\" message=\"Something failed\" \u002F>)\n  expect(screen.getByRole('alert')).toHaveTextContent('Something failed')\n})\n```\n\nTesting a toggle:\n```js\ntest('shows\u002Fhides details on button click', async () => {\n  const user = userEvent.setup()\n  render(\u003CExpandableSection title=\"Details\" content=\"Hidden text\" \u002F>)\n\n  expect(screen.queryByText('Hidden text')).not.toBeInTheDocument()\n\n  await user.click(screen.getByRole('button', { name: \u002Fdetails\u002Fi }))\n  expect(screen.getByText('Hidden text')).toBeInTheDocument()\n\n  await user.click(screen.getByRole('button', { name: \u002Fdetails\u002Fi }))\n  expect(screen.queryByText('Hidden text')).not.toBeInTheDocument()\n})\n```\n\n**Rule of thumb:** Always use `queryBy*` (not `getBy*`) when asserting\nan element is absent — `getBy*` throws before your assertion runs.\n",{"id":40,"difficulty":14,"q":41,"a":42},"context-provider-test","How do you test a component that consumes React context?","Wrap the component with the real provider (not a mock) in the `wrapper`\noption or a custom render helper.\n\n```js\nimport { render, screen } from '@testing-library\u002Freact'\nimport userEvent from '@testing-library\u002Fuser-event'\nimport { ThemeProvider } from '.\u002FThemeContext'\nimport ThemeToggle from '.\u002FThemeToggle'\n\nfunction renderWithTheme(ui, { theme = 'light' } = {}) {\n  return render(ui, {\n    wrapper: ({ children }) => (\n      \u003CThemeProvider initialTheme={theme}>{children}\u003C\u002FThemeProvider>\n    ),\n  })\n}\n\ntest('switches to dark mode', async () => {\n  const user = userEvent.setup()\n  renderWithTheme(\u003CThemeToggle \u002F>)\n\n  expect(document.body).toHaveClass('light')\n  await user.click(screen.getByRole('button', { name: \u002Fdark mode\u002Fi }))\n  expect(document.body).toHaveClass('dark')\n})\n\ntest('reads initial theme from context', () => {\n  renderWithTheme(\u003CThemeToggle \u002F>, { theme: 'dark' })\n  expect(screen.getByRole('button', { name: \u002Flight mode\u002Fi })).toBeInTheDocument()\n})\n```\n\nAvoid mocking the context value directly (`vi.mock`) unless the context\ncomes from a third-party library you can't control. Testing through the\nreal provider catches integration bugs between the provider and consumer.\n\n**Rule of thumb:** Use the real provider with controlled initial values;\nmock only external context sources (auth libraries, feature-flag SDKs).\n",{"id":44,"difficulty":14,"q":45,"a":46},"router-testing","How do you test components that use React Router hooks like `useNavigate` or `useParams`?","Wrap with `MemoryRouter` (or `createMemoryRouter` + `RouterProvider` for\nv6 data routers) so the component has a router context without a real\nbrowser history.\n\n```js\nimport { render, screen } from '@testing-library\u002Freact'\nimport userEvent from '@testing-library\u002Fuser-event'\nimport { MemoryRouter, Route, Routes } from 'react-router-dom'\n\n\u002F\u002F Testing useParams\ntest('renders product with correct id', () => {\n  render(\n    \u003CMemoryRouter initialEntries={['\u002Fproducts\u002F42']}>\n      \u003CRoutes>\n        \u003CRoute path=\"\u002Fproducts\u002F:id\" element={\u003CProductPage \u002F>} \u002F>\n      \u003C\u002FRoutes>\n    \u003C\u002FMemoryRouter>\n  )\n  expect(screen.getByText('Product 42')).toBeInTheDocument()\n})\n\n\u002F\u002F Testing useNavigate\ntest('navigates to home after logout', async () => {\n  const user = userEvent.setup()\n  render(\n    \u003CMemoryRouter initialEntries={['\u002Fdashboard']}>\n      \u003CRoutes>\n        \u003CRoute path=\"\u002Fdashboard\" element={\u003CDashboard \u002F>} \u002F>\n        \u003CRoute path=\"\u002F\" element={\u003Cp>Home\u003C\u002Fp>} \u002F>\n      \u003C\u002FRoutes>\n    \u003C\u002FMemoryRouter>\n  )\n\n  await user.click(screen.getByRole('button', { name: \u002Flogout\u002Fi }))\n  expect(screen.getByText('Home')).toBeInTheDocument()\n})\n```\n\nFor components using the newer data router APIs, use\n`createMemoryRouter` + `RouterProvider`:\n```js\nimport { createMemoryRouter, RouterProvider } from 'react-router-dom'\n\nconst router = createMemoryRouter(routes, { initialEntries: ['\u002Fdashboard'] })\nrender(\u003CRouterProvider router={router} \u002F>)\n```\n\n**Rule of thumb:** `MemoryRouter` for simple path\u002Fparam tests;\n`createMemoryRouter` when you need loaders, actions, or data-router\nfeatures.\n",{"id":48,"difficulty":24,"q":49,"a":50},"list-rendering-test","How do you test that a list renders the correct number of items?","Query all list items with `getAllBy*` or `queryAllBy*` and check the\narray length, then assert individual items by their content.\n\n```js\nimport { render, screen } from '@testing-library\u002Freact'\n\nconst todos = [\n  { id: 1, text: 'Buy milk' },\n  { id: 2, text: 'Walk dog' },\n  { id: 3, text: 'Read book' },\n]\n\ntest('renders correct number of todo items', () => {\n  render(\u003CTodoList todos={todos} \u002F>)\n\n  const items = screen.getAllByRole('listitem')\n  expect(items).toHaveLength(3)\n})\n\ntest('renders each todo text', () => {\n  render(\u003CTodoList todos={todos} \u002F>)\n\n  expect(screen.getByText('Buy milk')).toBeInTheDocument()\n  expect(screen.getByText('Walk dog')).toBeInTheDocument()\n  expect(screen.getByText('Read book')).toBeInTheDocument()\n})\n\ntest('shows empty state when no todos', () => {\n  render(\u003CTodoList todos={[]} \u002F>)\n  expect(screen.getByText(\u002Fno todos yet\u002Fi)).toBeInTheDocument()\n  expect(screen.queryAllByRole('listitem')).toHaveLength(0)\n})\n```\n\n**Rule of thumb:** Test the count and a sample of items; don't assert\nevery item in a large list — that couples tests to data and makes them\nbrittle.\n",{"id":52,"difficulty":14,"q":53,"a":54},"modal-portal-test","How do you test a component that renders into a portal (`ReactDOM.createPortal`)?","RTL's `screen` queries target `document.body`, which includes portal\ncontent — no special setup needed.\n\n```js\nimport { render, screen } from '@testing-library\u002Freact'\nimport userEvent from '@testing-library\u002Fuser-event'\nimport Modal from '.\u002FModal'\n\n\u002F\u002F Modal renders into document.body via createPortal\ntest('opens and closes modal', async () => {\n  const user = userEvent.setup()\n  render(\u003CModal trigger={\u003Cbutton>Open\u003C\u002Fbutton>}>Dialog content\u003C\u002FModal>)\n\n  \u002F\u002F Before open — modal not in DOM\n  expect(screen.queryByRole('dialog')).not.toBeInTheDocument()\n\n  \u002F\u002F Open the modal\n  await user.click(screen.getByRole('button', { name: \u002Fopen\u002Fi }))\n  expect(screen.getByRole('dialog')).toBeInTheDocument()\n  expect(screen.getByText('Dialog content')).toBeInTheDocument()\n\n  \u002F\u002F Close with Escape\n  await user.keyboard('{Escape}')\n  expect(screen.queryByRole('dialog')).not.toBeInTheDocument()\n})\n```\n\nIf the portal target is a custom element (not `document.body`), you may\nneed to create it in setup:\n```js\nbeforeEach(() => {\n  const el = document.createElement('div')\n  el.setAttribute('id', 'modal-root')\n  document.body.appendChild(el)\n})\nafterEach(() => {\n  document.getElementById('modal-root')?.remove()\n})\n```\n\n**Rule of thumb:** Test portals through `screen` like any other\nelement — their implementation detail (portal vs inline) is irrelevant\nto user-facing behavior.\n",{"id":56,"difficulty":14,"q":57,"a":58},"keyboard-navigation-test","How do you test keyboard navigation and focus management?","Use `userEvent.keyboard()` or `userEvent.tab()` to simulate keyboard\ninput and check focus with `toHaveFocus()`.\n\n```js\nimport { render, screen } from '@testing-library\u002Freact'\nimport userEvent from '@testing-library\u002Fuser-event'\n\ntest('traps focus inside modal', async () => {\n  const user = userEvent.setup()\n  render(\u003CModal open>\u003Cinput aria-label=\"Name\" \u002F>\u003Cbutton>Close\u003C\u002Fbutton>\u003C\u002FModal>)\n\n  const nameInput = screen.getByRole('textbox', { name: \u002Fname\u002Fi })\n  const closeButton = screen.getByRole('button', { name: \u002Fclose\u002Fi })\n\n  \u002F\u002F Focus starts on first focusable element\n  expect(nameInput).toHaveFocus()\n\n  \u002F\u002F Tab moves to next element\n  await user.tab()\n  expect(closeButton).toHaveFocus()\n\n  \u002F\u002F Tab wraps back to first element (focus trap)\n  await user.tab()\n  expect(nameInput).toHaveFocus()\n})\n\ntest('closes dropdown on Escape', async () => {\n  const user = userEvent.setup()\n  render(\u003CDropdown label=\"Options\">\u003Cli>Item 1\u003C\u002Fli>\u003C\u002FDropdown>)\n\n  await user.click(screen.getByRole('button', { name: \u002Foptions\u002Fi }))\n  expect(screen.getByRole('listbox')).toBeInTheDocument()\n\n  await user.keyboard('{Escape}')\n  expect(screen.queryByRole('listbox')).not.toBeInTheDocument()\n})\n```\n\nUseful keyboard shortcuts for `user.keyboard()`:\n```\n'{Tab}'    '{Shift>}{Tab}{\u002FShift}'    '{Enter}'\n'{Space}'  '{Escape}'                  '{ArrowDown}'\n```\n\n**Rule of thumb:** Always test keyboard navigation for interactive\ncomponents (modals, dropdowns, menus) — it's both a UX and accessibility\nrequirement.\n",{"id":60,"difficulty":61,"q":62,"a":63},"error-boundary-test","hard","How do you test error boundaries?","Render a child component that throws inside an error boundary, and assert\nthe fallback UI. Since React logs errors to `console.error`, suppress\nthat to keep test output clean.\n\n```js\nimport { render, screen } from '@testing-library\u002Freact'\nimport ErrorBoundary from '.\u002FErrorBoundary'\n\nfunction ThrowOnMount() {\n  throw new Error('Test error')\n}\n\ntest('renders fallback when child throws', () => {\n  \u002F\u002F Suppress React's error output in test logs\n  const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})\n\n  render(\n    \u003CErrorBoundary fallback={\u003Cp>Something went wrong\u003C\u002Fp>}>\n      \u003CThrowOnMount \u002F>\n    \u003C\u002FErrorBoundary>\n  )\n\n  expect(screen.getByText('Something went wrong')).toBeInTheDocument()\n  consoleSpy.mockRestore()\n})\n\ntest('renders children when no error', () => {\n  render(\n    \u003CErrorBoundary fallback={\u003Cp>Something went wrong\u003C\u002Fp>}>\n      \u003Cp>All good\u003C\u002Fp>\n    \u003C\u002FErrorBoundary>\n  )\n  expect(screen.getByText('All good')).toBeInTheDocument()\n  expect(screen.queryByText('Something went wrong')).not.toBeInTheDocument()\n})\n```\n\nFor error boundaries with a \"retry\" button, update the child after\nerror to test recovery:\n```js\ntest('recovers after reset', async () => {\n  const user = userEvent.setup()\n  let shouldThrow = true\n  function MaybeThrow() {\n    if (shouldThrow) throw new Error('oops')\n    return \u003Cp>Recovered\u003C\u002Fp>\n  }\n  \u002F\u002F ... render, suppress, click retry, update shouldThrow, assert\n})\n```\n\n**Rule of thumb:** Always mock `console.error` in error boundary tests —\nRTL and React both log thrown errors, cluttering test output without it.\n",{"id":65,"difficulty":61,"q":66,"a":67},"integration-test-pattern","How do you write an integration test covering multiple components together?","Render the top-level component that composes the components you want to\ntest together, provide any required context or mocks at the boundary,\nand simulate the full user flow from start to finish.\n\n```js\nimport { render, screen } from '@testing-library\u002Freact'\nimport userEvent from '@testing-library\u002Fuser-event'\nimport { MemoryRouter } from 'react-router-dom'\nimport { rest } from 'msw'\nimport { setupServer } from 'msw\u002Fnode'\nimport App from '.\u002FApp'\n\nconst server = setupServer(\n  rest.post('\u002Fapi\u002Flogin', (req, res, ctx) =>\n    res(ctx.json({ token: 'abc123', user: { name: 'Alice' } }))\n  )\n)\n\nbeforeAll(() => server.listen())\nafterEach(() => server.resetHandlers())\nafterAll(() => server.close())\n\ntest('full login flow: form → API → dashboard', async () => {\n  const user = userEvent.setup()\n\n  render(\n    \u003CMemoryRouter initialEntries={['\u002Flogin']}>\n      \u003CApp \u002F>\n    \u003C\u002FMemoryRouter>\n  )\n\n  \u002F\u002F Fill login form\n  await user.type(screen.getByLabelText(\u002Femail\u002Fi), 'alice@example.com')\n  await user.type(screen.getByLabelText(\u002Fpassword\u002Fi), 'secret')\n  await user.click(screen.getByRole('button', { name: \u002Fsign in\u002Fi }))\n\n  \u002F\u002F After successful login, should show dashboard\n  expect(await screen.findByText('Welcome, Alice')).toBeInTheDocument()\n  expect(screen.queryByRole('form')).not.toBeInTheDocument()\n})\n```\n\nIntegration tests give the highest confidence but are slower and harder\nto debug. Strategy:\n- Unit tests for complex logic (reducers, utilities).\n- Component tests for individual component behavior.\n- Integration tests for critical user flows (login, checkout, onboarding).\n\n**Rule of thumb:** Write integration tests for flows that cross component\nboundaries and involve real user journeys, not for individual component\nrendering.\n",{"id":69,"difficulty":24,"q":70,"a":71},"spy-prop-function","How do you test that a callback prop is called with the right arguments?","Pass a `vi.fn()` (or `jest.fn()`) as the prop and assert on it after\nthe interaction.\n\n```js\nimport { render, screen } from '@testing-library\u002Freact'\nimport userEvent from '@testing-library\u002Fuser-event'\nimport { vi, expect } from 'vitest'\nimport ProductCard from '.\u002FProductCard'\n\ntest('calls onAddToCart with product id when button clicked', async () => {\n  const onAddToCart = vi.fn()\n  const user = userEvent.setup()\n\n  render(\u003CProductCard id={42} name=\"Widget\" onAddToCart={onAddToCart} \u002F>)\n\n  await user.click(screen.getByRole('button', { name: \u002Fadd to cart\u002Fi }))\n\n  expect(onAddToCart).toHaveBeenCalledTimes(1)\n  expect(onAddToCart).toHaveBeenCalledWith(42)\n})\n```\n\nFor multiple interactions:\n```js\nawait user.click(incrementBtn)\nawait user.click(incrementBtn)\nexpect(onChange).toHaveBeenCalledTimes(2)\nexpect(onChange).toHaveBeenNthCalledWith(1, 1)\nexpect(onChange).toHaveBeenNthCalledWith(2, 2)\n```\n\n**Rule of thumb:** Use `vi.fn()` for callback props; use `toHaveBeenCalledWith`\nrather than checking internal state — you're testing the contract between\nparent and child.\n",{"id":73,"difficulty":24,"q":74,"a":75},"select-checkbox-test","How do you test checkboxes, radio buttons, and select elements?","RTL has dedicated `userEvent` methods for each form element type.\n\n```js\nimport { render, screen } from '@testing-library\u002Freact'\nimport userEvent from '@testing-library\u002Fuser-event'\n\nconst user = userEvent.setup()\n\n\u002F\u002F Checkbox\ntest('toggles checkbox', async () => {\n  render(\u003Clabel>\u003Cinput type=\"checkbox\" \u002F>Accept terms\u003C\u002Flabel>)\n  const checkbox = screen.getByRole('checkbox', { name: \u002Faccept terms\u002Fi })\n\n  expect(checkbox).not.toBeChecked()\n  await user.click(checkbox)\n  expect(checkbox).toBeChecked()\n  await user.click(checkbox)\n  expect(checkbox).not.toBeChecked()\n})\n\n\u002F\u002F Radio buttons\ntest('selects radio option', async () => {\n  render(\n    \u003Cfieldset>\n      \u003Clegend>Size\u003C\u002Flegend>\n      \u003Clabel>\u003Cinput type=\"radio\" name=\"size\" value=\"S\" \u002F>Small\u003C\u002Flabel>\n      \u003Clabel>\u003Cinput type=\"radio\" name=\"size\" value=\"L\" \u002F>Large\u003C\u002Flabel>\n    \u003C\u002Ffieldset>\n  )\n  await user.click(screen.getByRole('radio', { name: \u002Fsmall\u002Fi }))\n  expect(screen.getByRole('radio', { name: \u002Fsmall\u002Fi })).toBeChecked()\n  expect(screen.getByRole('radio', { name: \u002Flarge\u002Fi })).not.toBeChecked()\n})\n\n\u002F\u002F Select\ntest('selects option from dropdown', async () => {\n  render(\n    \u003Cselect aria-label=\"Country\">\n      \u003Coption value=\"\">--\u003C\u002Foption>\n      \u003Coption value=\"us\">US\u003C\u002Foption>\n      \u003Coption value=\"ca\">Canada\u003C\u002Foption>\n    \u003C\u002Fselect>\n  )\n  await user.selectOptions(screen.getByRole('combobox', { name: \u002Fcountry\u002Fi }), 'us')\n  expect(screen.getByRole('combobox', { name: \u002Fcountry\u002Fi })).toHaveDisplayValue('US')\n})\n```\n\n**Rule of thumb:** Always query form elements by their accessible role +\nname (derived from `\u003Clabel>`) — this forces you to write accessible markup.\n",{"id":77,"difficulty":24,"q":78,"a":79},"disabled-element-test","How do you test that a button or input is disabled, and that it can't be interacted with?","Assert the `disabled` state with `toBeDisabled()` and verify that\nclicking a disabled button does not invoke callbacks.\n\n```js\nimport { render, screen } from '@testing-library\u002Freact'\nimport userEvent from '@testing-library\u002Fuser-event'\n\ntest('submit button is disabled while loading', () => {\n  render(\u003CSubmitButton loading={true} \u002F>)\n  expect(screen.getByRole('button', { name: \u002Fsubmit\u002Fi })).toBeDisabled()\n})\n\ntest('disabled button does not call onClick', async () => {\n  const onClick = vi.fn()\n  const user = userEvent.setup()\n\n  render(\u003Cbutton disabled onClick={onClick}>Click me\u003C\u002Fbutton>)\n\n  await user.click(screen.getByRole('button'))\n  \u002F\u002F userEvent respects the disabled attribute and does not fire the click\n  expect(onClick).not.toHaveBeenCalled()\n})\n\ntest('submit button becomes enabled after required fields filled', async () => {\n  const user = userEvent.setup()\n  render(\u003CForm \u002F>)\n\n  const button = screen.getByRole('button', { name: \u002Fsubmit\u002Fi })\n  expect(button).toBeDisabled()\n\n  await user.type(screen.getByLabelText(\u002Femail\u002Fi), 'user@test.com')\n  expect(button).toBeEnabled()\n})\n```\n\n**Rule of thumb:** `toBeDisabled()` and `toBeEnabled()` from\n`@testing-library\u002Fjest-dom` are clearer than `toHaveAttribute('disabled')`.\n",{"id":81,"difficulty":24,"q":82,"a":83},"snapshot-testing","What is snapshot testing and when should (and shouldn't) you use it with RTL?","Snapshot testing captures the rendered output as a string on first run and\ncompares future runs against that snapshot. Vitest\u002FJest provide this via\n`toMatchSnapshot()` or `toMatchInlineSnapshot()`.\n\n```js\nimport { render } from '@testing-library\u002Freact'\n\ntest('renders badge correctly', () => {\n  const { container } = render(\u003CBadge count={5} \u002F>)\n  expect(container.firstChild).toMatchSnapshot()\n})\n```\n\nRTL's stance: **use sparingly**. Snapshots are good for:\n- Preventing unintentional markup changes in leaf UI components.\n- Locking down third-party rendered output you don't control.\n\nSnapshots are bad for:\n- Testing behavior — they catch \"it changed\", not \"it's wrong\".\n- Large components — snapshots become huge and are mindlessly updated.\n- Fast-moving codebases — you spend more time updating snapshots than\n  fixing real bugs.\n\nPrefer inline snapshots when you do use them:\n```js\nexpect(container.firstChild).toMatchInlineSnapshot(`\n  \u003Cspan class=\"badge badge--blue\">5\u003C\u002Fspan>\n`)\n```\n\n**Rule of thumb:** Write behavioral assertions (text content, role,\nvisibility) instead of snapshots. Use snapshots only for small, stable,\npurely presentational components.\n",15,null,{"description":11},"React Testing Library interview questions on component interaction — user events, form testing, conditional rendering, context, portals, keyboard navigation, and integration testing patterns.","react\u002Ftesting\u002Fcomponent-interaction-testing","Testing","testing","2026-06-24","-AUbvLNtLgCYWx1KFc_HmPgK-UBn7Rgfrv2MfaNc8PY",[94,98,99,103],{"subtopic":95,"path":96,"order":97},"RTL Basics","\u002Freact\u002Ftesting\u002Frtl-basics",1,{"subtopic":6,"path":20,"order":12},{"subtopic":100,"path":101,"order":102},"Mocking Async","\u002Freact\u002Ftesting\u002Fmocking-async",3,{"subtopic":104,"path":105,"order":106},"Testing Custom Hooks","\u002Freact\u002Ftesting\u002Ftesting-custom-hooks",4,{"path":108,"title":109},"\u002Fblog\u002Freact-component-interaction-testing-guide","React Component Interaction Testing — Complete Guide",1782244101350]