Skip to content

Add contains option to getByRole to allow filtering. #354

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
jsphstls opened this issue Sep 19, 2019 · 13 comments
Closed

Add contains option to getByRole to allow filtering. #354

jsphstls opened this issue Sep 19, 2019 · 13 comments

Comments

@jsphstls
Copy link

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.

@kentcdodds
Copy link
Member

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?

@alexkrolick
Copy link
Collaborator

Another idea

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

@jsphstls
Copy link
Author

jsphstls commented Sep 19, 2019

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

@alexkrolick
Copy link
Collaborator

alexkrolick commented Sep 19, 2019

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

@jsphstls
Copy link
Author

jsphstls commented Nov 5, 2019

Why not have an API like within() ?

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

@alexkrolick
Copy link
Collaborator

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

@gnapse
Copy link
Member

gnapse commented Nov 5, 2019

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.

@alexkrolick
Copy link
Collaborator

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. #266

@eps1lon
Copy link
Member

eps1lon commented Nov 5, 2019

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" })

@jsphstls
Copy link
Author

jsphstls commented Nov 5, 2019

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

That could be misinterpreted as querying within multiple parents.

@kentcdodds
Copy link
Member

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

@kentcdodds
Copy link
Member

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

@eric-burel
Copy link

eric-burel commented Jul 7, 2021

Hi, accessible name answers this ticket but what about having multiple select with the same options? I struggle to find the right selector for this. To give an example: you have 2 selectors "headerItems", "footerItems", both with options "A" and "B" => screen.getByRole("option", { name: "A"}) is ambiguous. It doesn't seem possible to chain queries.

Its probably documented somewhere but I struggle to find out sorry.

Edit: aaand I immediately found relevant answer 1 second after posting: https://testing-library.com/docs/dom-testing-library/api-within.

So the definitive solution as of 2021 is:

    const select = screen.getByRole('combobox', { name: 'Header items' });
    const option = within(select).getByRole('option', { name: 'Foobar' }); 

and if you have only one select: const option = screen.getByRole('option', { name: 'Foobar' }).

Hope it may help lost googlers.

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

No branches or pull requests

6 participants