diff --git a/src/queries/__tests__/role.test.tsx b/src/queries/__tests__/role.test.tsx
index de5ebfd7f..8fb28fc0f 100644
--- a/src/queries/__tests__/role.test.tsx
+++ b/src/queries/__tests__/role.test.tsx
@@ -42,6 +42,7 @@ test('getByRole, queryByRole, findByRole', async () => {
expect(() => getByRole(NO_MATCHES_TEXT)).toThrow(
getNoInstancesFoundMessage(NO_MATCHES_TEXT)
);
+
expect(queryByRole(NO_MATCHES_TEXT)).toBeNull();
expect(() => getByRole('link')).toThrow(
@@ -77,3 +78,60 @@ test('getAllByRole, queryAllByRole, findAllByRole', async () => {
getNoInstancesFoundMessage(NO_MATCHES_TEXT)
);
});
+
+describe('supports name option', () => {
+ test('returns an element that has the corresponding role and a children with the name', () => {
+ const { getByRole } = render(
+
+ Save
+
+ );
+
+ // assert on the testId to be sure that the returned element is the one with the accessibilityRole
+ expect(getByRole('button', { name: 'Save' }).props.testID).toBe(
+ 'target-button'
+ );
+ });
+
+ test('returns an element that has the corresponding role when several children include the name', () => {
+ const { getByRole } = render(
+
+ Save
+ Save
+
+ );
+
+ // assert on the testId to be sure that the returned element is the one with the accessibilityRole
+ expect(getByRole('button', { name: 'Save' }).props.testID).toBe(
+ 'target-button'
+ );
+ });
+
+ test('returns an element that has the corresponding role and a children with a matching accessibilityLabel', () => {
+ const { getByRole } = render(
+
+
+
+ );
+
+ // assert on the testId to be sure that the returned element is the one with the accessibilityRole
+ expect(getByRole('button', { name: 'Save' }).props.testID).toBe(
+ 'target-button'
+ );
+ });
+
+ test('returns an element that has the corresponding role and a matching accessibilityLabel', () => {
+ const { getByRole } = render(
+
+ );
+
+ // assert on the testId to be sure that the returned element is the one with the accessibilityRole
+ expect(getByRole('button', { name: 'Save' }).props.testID).toBe(
+ 'target-button'
+ );
+ });
+});
diff --git a/src/queries/role.ts b/src/queries/role.ts
index 552998b22..02d3aa223 100644
--- a/src/queries/role.ts
+++ b/src/queries/role.ts
@@ -1,6 +1,7 @@
import type { ReactTestInstance } from 'react-test-renderer';
-import { TextMatch } from '../matches';
import { matchStringProp } from '../helpers/matchers/matchStringProp';
+import { TextMatch } from '../matches';
+import { getQueriesForElement } from '../within';
import { makeQueries } from './makeQueries';
import type {
FindAllByQuery,
@@ -11,14 +12,31 @@ import type {
QueryByQuery,
} from './makeQueries';
+type ByRoleOptions = {
+ name?: TextMatch;
+};
+
+const matchAccessibleNameIfNeeded = (
+ node: ReactTestInstance,
+ name?: TextMatch
+) => {
+ if (name == null) return true;
+
+ const { queryAllByText, queryAllByLabelText } = getQueriesForElement(node);
+ return (
+ queryAllByText(name).length > 0 || queryAllByLabelText(name).length > 0
+ );
+};
+
const queryAllByRole = (
instance: ReactTestInstance
-): ((role: TextMatch) => Array) =>
- function queryAllByRoleFn(role) {
+): ((role: TextMatch, options?: ByRoleOptions) => Array) =>
+ function queryAllByRoleFn(role, options) {
return instance.findAll(
(node) =>
typeof node.type === 'string' &&
- matchStringProp(node.props.accessibilityRole, role)
+ matchStringProp(node.props.accessibilityRole, role) &&
+ matchAccessibleNameIfNeeded(node, options?.name)
);
};
@@ -34,12 +52,12 @@ const { getBy, getAllBy, queryBy, queryAllBy, findBy, findAllBy } = makeQueries(
);
export type ByRoleQueries = {
- getByRole: GetByQuery;
- getAllByRole: GetAllByQuery;
- queryByRole: QueryByQuery;
- queryAllByRole: QueryAllByQuery;
- findByRole: FindByQuery;
- findAllByRole: FindAllByQuery;
+ getByRole: GetByQuery;
+ getAllByRole: GetAllByQuery;
+ queryByRole: QueryByQuery;
+ queryAllByRole: QueryAllByQuery;
+ findByRole: FindByQuery;
+ findAllByRole: FindAllByQuery;
};
export const bindByRoleQueries = (
diff --git a/typings/index.flow.js b/typings/index.flow.js
index b8f2ec0f1..a0f9acbc8 100644
--- a/typings/index.flow.js
+++ b/typings/index.flow.js
@@ -202,6 +202,11 @@ interface UnsafeByPropsQueries {
| Array
| [];
}
+
+interface ByRoleOptions {
+ name?: string;
+}
+
interface A11yAPI {
// Label
getByLabelText: (matcher: TextMatch) => GetReturn;
@@ -244,16 +249,27 @@ interface A11yAPI {
) => FindAllReturn;
// Role
- getByRole: (matcher: A11yRole | RegExp) => GetReturn;
- getAllByRole: (matcher: A11yRole | RegExp) => GetAllReturn;
- queryByRole: (matcher: A11yRole | RegExp) => QueryReturn;
- queryAllByRole: (matcher: A11yRole | RegExp) => QueryAllReturn;
+ getByRole: (matcher: A11yRole | RegExp, role?: ByRoleOptions) => GetReturn;
+ getAllByRole: (
+ matcher: A11yRole | RegExp,
+ role?: ByRoleOptions
+ ) => GetAllReturn;
+ queryByRole: (
+ matcher: A11yRole | RegExp,
+ role?: ByRoleOptions
+ ) => QueryReturn;
+ queryAllByRole: (
+ matcher: A11yRole | RegExp,
+ role?: ByRoleOptions
+ ) => QueryAllReturn;
findByRole: (
matcher: A11yRole | RegExp,
+ role?: ByRoleOptions,
waitForOptions?: WaitForOptions
) => FindReturn;
findAllByRole: (
matcher: A11yRole | RegExp,
+ role?: ByRoleOptions,
waitForOptions?: WaitForOptions
) => FindAllReturn;
diff --git a/website/docs/Queries.md b/website/docs/Queries.md
index 65196a1ea..fa446397c 100644
--- a/website/docs/Queries.md
+++ b/website/docs/Queries.md
@@ -189,6 +189,10 @@ render();
const element = screen.getByRole('button');
```
+#### Options
+
+`name`: Finds an element with given `accessibilityRole` and an accessible name (equivalent to `byText` or `byLabelText` query).
+
### `ByA11yState`, `ByAccessibilityState`
> getByA11yState, getAllByA11yState, queryByA11yState, queryAllByA11yState, findByA11yState, findAllByA11yState
@@ -305,7 +309,8 @@ To override normalization to remove some Unicode characters whilst keeping some
```typescript
screen.getByText(node, 'text', {
- normalizer: (str) => getDefaultNormalizer({ trim: false })(str).replace(/[\u200E-\u200F]*/g, ''),
+ normalizer: (str) =>
+ getDefaultNormalizer({ trim: false })(str).replace(/[\u200E-\u200F]*/g, ''),
});
```