testing-library / dom-testing-library

🐙 Simple and complete DOM testing utilities that encourage good testing practices.
https://testing-library.com/dom
MIT License
3.26k stars 466 forks source link

`getByRole('textbox', { name: 'field' })` & `getByLabelText('Field', selector: 'input'})` don't work as documented. #1203

Closed zettadam closed 1 year ago

zettadam commented 1 year ago

According to documentation, I can search for an element using query, like getByRole('textbox', { name: 'field' }). That query, however, fails with an error "Unable to find an accessible element with the role "textbox" and name: "field", even though the error report shows rendered component as expected DOM tree.

Also, query getByLabelText('Field', { selector: 'input' }) also fails with error Found a label with the text of: Field, however no form control was found associated to that label. Make sure you're using the "for" attribute or "aria-labelledby" attribute correctly. even though it is a valid HTML input element wrapped in a label. The error message is incorrect: I don't need to provide for or aria-labelledby attributes if an <input /> element is wrapped by a <label /> element.

Here's my TextInput component:

export const TextInput = ({ label = 'Label', field = {} }) => {
  const inputProps = {
    ...field,
    role: 'textbox',
    type: 'text',
  };

  return (
    <label>
      {label && <span>{label}</span>}
      <div>
        <input {...inputProps} />
      </div>
    </label>
  );
};

and corresponding test:

import * as React from 'react';
import { render } from '@testing-library/react';

import { TextInput } from './TextInput';

it('renders', () => {
  const { getByLabelText, getByRole } = render(
    <TextInput
      label="Field"
      field={{
        name: 'field',
        onChange: (e) => {
          console.log(e.target.value);
        },
        value: '',
      }}
    />
  );

  expect(getByRole('textbox', { name: 'field' })).toBeInTheDocument();
  expect(getByLabelText('Field', { selector: 'input' })).toBeInTheDocument();
});

Here are relevant dependencies:

"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "13.4.0",
"@vitejs/plugin-react": "^3.0.0",
"happy-dom": "^8.1.3",
"vite": "^4.0.4",
"vitest": "^0.27.0"

Node version? I'm using the latest version locally, and whatever version Stackblitz uses produce the same error. So it might be irrelevant, I think.

Here is a replica of this error: https://stackblitz.com/edit/vitejs-vite-cib1lv?file=src%2FTextInput.test.jsx

Problem description:

getByRole & getByLabelText queries don't work as documented when name & selector options are added.

Suggested solution:

First, if it is a problem with the queries and not my implementation and test, fix the problem with those queries. I'd rather not revert to data-testid or querying the DOM myself :)

Second, perhaps it would be good to improve a misleading error message suggesting use of "for" and "aria-labelledby". They are not necessary for accessible <input /> elements wrapped in a <label /> element.

Or am I just misreading documentation?

timdeschryver commented 1 year ago

I think that this on happy-dom's side. Have you tried this with JSDom?

zettadam commented 1 year ago

I get these errors when I use jsdom also.

error-getByRole-jsdom

MatanBobi commented 1 year ago

IIUC, the reason you're seeing this is because the name attribute isn't treated as an accessible name. For an input element, the accessible name is it's label (either a label element, an aria-label attribute or aria-labelledby attribute) so changing:

expect(getByRole('textbox', { name: 'field' })).toBeInTheDocument();

to:

expect(getByRole('textbox', { name: 'Field' })).toBeInTheDocument();

With a capital letter will work because that's the label's text.

zettadam commented 1 year ago

And what about { selector: 'input' }? That expectation produces an error. Does selector here have another library-specific meaning or is it what I think it is or should be?

MatanBobi commented 1 year ago

Two things:

  1. Accessible name is not a testing library meaning, it's from the ARIA spec: https://www.w3.org/TR/wai-aria-1.2/#namecalculation
  2. In the stackblitz example you attached, if I comment out the first assertion, the test passes: image
zettadam commented 1 year ago

Thanks @MatanBobi ! It makes sense. I missed that part about accessible name.