Dom-testing-library: Add contains option to getByRole to allow filtering.

Created on 19 Sep 2019  路  12Comments  路  Source: testing-library/dom-testing-library

Describe the feature you'd like:

I want to be able to select an option by the text that it contains, or improve documentation to make an existing strategy easier to find.

I use react-testing-library and cypress-testing-library.

Suggested implementation:

getByRole('option', { contains: 'Some Option' })

Describe alternatives you've considered:

Best:
getByText('Some Option', { selector: '[role=option]' })

Not best:

getAllByRole('option').filter(option =>
   checkOptionForText('Some Option') // not sure where to look
)

Teachability, Documentation, Adoption, Migration Strategy:

If a new option would not break anything. I realize that getByRole supports every role available. Some roles like navigation would be a bad case for this enhancement. Other roles such as link would be improved.

Most helpful comment

Another idea

within(getByLabelText('select something')).getByText('the option I want')

All 12 comments

Hi @jsphstls,

Thanks for bringing this up. What do you think of this:

const someOption = getByRole((role, element) => role === 'option' && element.textContent === 'Some Option')

That is supported today and gives you the most amount of flexibility. I'm not sure that I want to promote this use case to a simpler API, so I'm thinking that the current API should be enough for you. What does everyone watching think?

Another idea

within(getByLabelText('select something')).getByText('the option I want')

Thanks folks, that's some speedy feedback 馃槃

I made a codesandbox: https://codesandbox.io/s/dom-testing-libraryissues354-zscq0

@kentcdodds I tried out your suggestion but the feedback for failed selections was cryptic:

Error: Unable to find an element with the role "function (role, element) {
    return role === "option" && element.textContent === "Hello";
  }"

Once I realized I needed a match for whitespace (my bad) it started working 馃憤

@alexkrolick I forgot about the magical power of within. Unfortunately I still need to select role=option specifically due to duplicate text. Even if I clean up the HTML, a real user is looking for an option regardless and I want to match their usage.

Overall, there are ways to currently achieve this but each comes with their own fragility. What I am after is:

  • Clean API
  • Clean errors
  • Mimic the user

IDK, maybe there should be a way of combining queries. Someone did propose that before. This would work for combining queryAll queries:

_.intersection(queryAllByRole('option'), queryAllByText('whatever'))

https://lodash.com/docs/4.17.15#intersection

If we had a builtin version of that it would need to have a way to do something like the get/find/(All) variants

Why not have an API like within() ?

among(getAllByRole('option')).getByText('Some option')

I guess within could take an array, that wouldn't be a breaking change

How about a more generic queryAllBy (and the corresponding getAllBy({ ... })):

queryAllBy({ text: /some option/, role: 'option' })

And why not, the corresponding versions without All in the name, that returns the first match.

Someone suggested something like that a while ago, but we weren't sure it was worth making a substantial API change to support this use case. https://github.com/testing-library/dom-testing-library/issues/266

I might be falling into the habit of teasing too often but this would be covered by accessible name computation as well. The API would be as simple as getByRole('option', { name: "Option 1" })

I guess within could take an array, that wouldn't be a breaking change

That could be misinterpreted as querying within multiple parents.

I'm really intrigued by this accessible name query @eps1lon :)

I'm going to go ahead and close this in favor of the accessible name computation @eps1lon is working on.

Was this page helpful?
0 / 5 - 0 ratings