react-native-testing-library
react-native-testing-library copied to clipboard
getByRole should accept a second argument to refine query as in react-testing-library
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')
Definitely, would welcome such a feature :)
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 :)
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.
I would like to work on this. I will mimic React Testing Library API. I will post a plan once I have one
https://github.com/callstack/react-native-testing-library/blob/34fc03a8c936aa38f91e3c2b042363f964c711c3/src/helpers/makeA11yQuery.js#L50
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.,)
@kiranjd still interested? :)
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.
@thymikee Here's what I'am trying to do:
- Get matching nodes from
getByRole - For each matching role, if
nameis passed, then usegetQueriesForElementto get matchers for it's children - 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 :)
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
}
@thymikee Issued a draft PR. Appreciate your feedback