Skip to content

getByRole should accept a second argument to refine query as in react-testing-library #827

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
leoparis89 opened this issue Sep 27, 2021 · 10 comments · Fixed by #1127
Closed

Comments

@leoparis89
Copy link

leoparis89 commented Sep 27, 2021

Describe the Feature

Should be able to query a Button element as follows:

getByRole('button', {name: /submit/i})

At the present time, this is impossible so I have to add redundant accessiblityLabel props, for example:

<Button accessibilityLabel="submit-button">Submit</Button>

And then, use queryByA11yLabel('submit-button')

@leoparis89 leoparis89 changed the title getByRole should accept a second argument tu refine query as in react-testing-library getByRole should accept a second argument to refine query as in react-testing-library Sep 27, 2021
@thymikee
Copy link
Member

Definitely, would welcome such a feature :)

@guvenkaranfil
Copy link
Contributor

Hello @thymikee, @leoparis89 I want to contribute to this package. So, I am walking through the issues. Do you mind me asking, What would we benefit from having this feature? Because we can already simply access the elements by their value with getByText. Thanks :)

@thymikee
Copy link
Member

One thing would be mimicking React Testing Library API, which is a good thing. Another thing is that you could test 2 things with one query. To stay with the example, a single query would ensure that there's a button with a certain text. Right now you'd need to perform 2 queries and you wouldn't be sure if you actually hit the same element, unless you compare them by reference or check the props, which is not ideal.

@kiranjd
Copy link
Contributor

kiranjd commented Oct 26, 2021

I would like to work on this. I will mimic React Testing Library API. I will post a plan once I have one

@kiranjd
Copy link
Contributor

kiranjd commented Nov 8, 2021

const results = instance.findAll(

This seems to be the place where we will need to add the matcher for second arguments(like {name: 'press-me'}). This might needs to be added in all possible queries(getBy, findBy, etc.,)

@thymikee
Copy link
Member

@kiranjd still interested? :)

@kiranjd
Copy link
Contributor

kiranjd commented Nov 24, 2021

Yes, @thymikee. I was able to get it to work using getNodeByText function in byText.js file. Currently trying write tests to see if it works for all cases.

@kiranjd
Copy link
Contributor

kiranjd commented Nov 24, 2021

@thymikee Here's what I'am trying to do:

  1. Get matching nodes from getByRole
  2. For each matching role, if name is passed, then use getQueriesForElement to get matchers for it's children
  3. return the result of getQueriesForElement(node).queryByText(options.name); as we are trying to match for text

Here's a working code for couple tests that I made in makeA11yQuery.js: 46:

  const getBy = (matcher: M, options?: QueryOptions) => {
    try {
      if (options?.name) {
        return instance.find((node) => {
          const matchesRole =
            isNodeValid(node) && matcherFn(node.props[name], matcher);

          if (!matchesRole) return false;

          return !!getQueriesForElement(node).queryByText(options.name);
        });
      }

      return instance.find(
        (node) => isNodeValid(node) && matcherFn(node.props[name], matcher)
      );
    } catch (error) {
      throw new ErrorWithStack(
        prepareErrorMessage(error, name, matcher),
        getBy
      );
    }
  };

I still have to cover all other queries as well. But, this is the core of it. Would love to hear your feedback and proceed further :)

@AugustinLF
Copy link
Collaborator

A thing you'll need to consider is that if you match by name, you should also match by label text, since you should definitely match <Button accessibilityLabel="exit" onPress={...}><Icon type="door" /></Button> with getByRole('button', {name: 'exit'}).

Not necessarily relevant but which might be interesting to you, we've reimplemented that in userland in our codebase. As you can see the reasoning is the same (first find the element with the role, then try to match the name in its children). Since it does work for us (and does seem to work as well as web), I guess your implementation should do the job.

const queryByAccessibleName = (renderApi: Queries, name: string) =>
  renderApi.queryByLabelText(name) || renderApi.queryByText(name)

const getByRole = (role: AccessibilityRole, options: { name?: string } = {}) => {
      const elements = queryAllByAccessibleName(renderResult, name)

      const e = new Error(
        `Unable to find an accessible element with the role "${role}" and name "${name}"`
      )
      e.name = 'TestingLibraryElementError'
      Error.captureStackTrace(e, getByRole)

      if (elements.length === 0) {
        throw e
      }

      const elementWithRole = elements.find((element) => element.props.accessibilityRole === role)
      if (elementWithRole) {
        return elementWithRole
      }

      // a text can be nested within a button, so the accessibilityRole wouldn't be on the same
      // element
      try {
        const roledElements = renderResult.getAllByRole(role)
        for (const roledElement of roledElements) {
          const withinRoledElement = nativeWithin(roledElement)
          const labelledTextWithinRoledElement = queryByAccessibleName(withinRoledElement, name)
          if (labelledTextWithinRoledElement) {
            return labelledTextWithinRoledElement
          }
        }
        return
      } catch {
        throw e
      }

      throw e
}

@kiranjd
Copy link
Contributor

kiranjd commented Dec 2, 2021

@thymikee Issued a draft PR. Appreciate your feedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants