Skip to content

Commit 300541b

Browse files
committed
Add support for ByRole with name
1 parent 10e69c5 commit 300541b

File tree

4 files changed

+113
-17
lines changed

4 files changed

+113
-17
lines changed

src/queries/__tests__/role.test.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ test('getByRole, queryByRole, findByRole', async () => {
4242
expect(() => getByRole(NO_MATCHES_TEXT)).toThrow(
4343
getNoInstancesFoundMessage(NO_MATCHES_TEXT)
4444
);
45+
4546
expect(queryByRole(NO_MATCHES_TEXT)).toBeNull();
4647

4748
expect(() => getByRole('link')).toThrow(
@@ -77,3 +78,61 @@ test('getAllByRole, queryAllByRole, findAllByRole', async () => {
7778
getNoInstancesFoundMessage(NO_MATCHES_TEXT)
7879
);
7980
});
81+
82+
describe('*ByRole with a name', () => {
83+
test('Find an element that has the corresponding role and a children with the name', () => {
84+
const { getByRole } = render(
85+
<TouchableOpacity accessibilityRole="button" testID="target-button">
86+
<Text>Save</Text>
87+
</TouchableOpacity>
88+
);
89+
90+
// assert on the testId to be sure that the returned element is the one with the accessibilityRole
91+
expect(getByRole('button', { name: 'Save' }).props.testID).toBe(
92+
'target-button'
93+
);
94+
});
95+
96+
test('Find an element that has the corresponding role and a children with a matching accessibilityLabel', () => {
97+
const { getByRole } = render(
98+
<TouchableOpacity accessibilityRole="button" testID="target-button">
99+
<Text accessibilityLabel="Save" />
100+
</TouchableOpacity>
101+
);
102+
103+
// assert on the testId to be sure that the returned element is the one with the accessibilityRole
104+
expect(getByRole('button', { name: 'Save' }).props.testID).toBe(
105+
'target-button'
106+
);
107+
});
108+
109+
test('Find an element that has the corresponding role and a matching accessibilityLabel', () => {
110+
const { getByRole } = render(
111+
<TouchableOpacity
112+
accessibilityRole="button"
113+
testID="target-button"
114+
accessibilityLabel="Save"
115+
></TouchableOpacity>
116+
);
117+
118+
// assert on the testId to be sure that the returned element is the one with the accessibilityRole
119+
expect(getByRole('button', { name: 'Save' }).props.testID).toBe(
120+
'target-button'
121+
);
122+
});
123+
124+
test('Can find by name using a regex', () => {
125+
const { getByRole } = render(
126+
<TouchableOpacity
127+
accessibilityRole="button"
128+
testID="target-button"
129+
accessibilityLabel="Save"
130+
></TouchableOpacity>
131+
);
132+
133+
// assert on the testId to be sure that the returned element is the one with the accessibilityRole
134+
expect(getByRole('button', { name: /Save/ }).props.testID).toBe(
135+
'target-button'
136+
);
137+
});
138+
});

src/queries/role.ts

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { ReactTestInstance } from 'react-test-renderer';
2-
import { TextMatch } from '../matches';
2+
import type { AccessibilityRole } from 'react-native';
33
import { matchStringProp } from '../helpers/matchers/matchStringProp';
4+
import { TextMatch } from '../matches';
5+
import { getQueriesForElement } from '../within';
46
import { makeQueries } from './makeQueries';
57
import type {
68
FindAllByQuery,
@@ -11,20 +13,37 @@ import type {
1113
QueryByQuery,
1214
} from './makeQueries';
1315

16+
type Role = AccessibilityRole | TextMatch;
17+
18+
type ByRoleOptions = {
19+
name?: TextMatch;
20+
};
21+
22+
const filterByAccessibleName = (
23+
node: ReactTestInstance,
24+
name: TextMatch | undefined
25+
) => {
26+
if (!name) return true;
27+
28+
const { queryByText, queryByLabelText } = getQueriesForElement(node);
29+
return Boolean(queryByText(name) || queryByLabelText(name));
30+
};
31+
1432
const queryAllByRole = (
1533
instance: ReactTestInstance
16-
): ((role: TextMatch) => Array<ReactTestInstance>) =>
17-
function queryAllByRoleFn(role) {
34+
): ((role: Role, options?: ByRoleOptions) => Array<ReactTestInstance>) =>
35+
function queryAllByRoleFn(role, options) {
1836
return instance.findAll(
1937
(node) =>
2038
typeof node.type === 'string' &&
21-
matchStringProp(node.props.accessibilityRole, role)
39+
matchStringProp(node.props.accessibilityRole, role) &&
40+
filterByAccessibleName(node, options?.name)
2241
);
2342
};
2443

25-
const getMultipleError = (role: TextMatch) =>
44+
const getMultipleError = (role: Role) =>
2645
`Found multiple elements with accessibilityRole: ${String(role)} `;
27-
const getMissingError = (role: TextMatch) =>
46+
const getMissingError = (role: Role) =>
2847
`Unable to find an element with accessibilityRole: ${String(role)}`;
2948

3049
const { getBy, getAllBy, queryBy, queryAllBy, findBy, findAllBy } = makeQueries(
@@ -34,12 +53,12 @@ const { getBy, getAllBy, queryBy, queryAllBy, findBy, findAllBy } = makeQueries(
3453
);
3554

3655
export type ByRoleQueries = {
37-
getByRole: GetByQuery<TextMatch>;
38-
getAllByRole: GetAllByQuery<TextMatch>;
39-
queryByRole: QueryByQuery<TextMatch>;
40-
queryAllByRole: QueryAllByQuery<TextMatch>;
41-
findByRole: FindByQuery<TextMatch>;
42-
findAllByRole: FindAllByQuery<TextMatch>;
56+
getByRole: GetByQuery<Role, ByRoleOptions>;
57+
getAllByRole: GetAllByQuery<Role, ByRoleOptions>;
58+
queryByRole: QueryByQuery<Role, ByRoleOptions>;
59+
queryAllByRole: QueryAllByQuery<Role, ByRoleOptions>;
60+
findByRole: FindByQuery<Role, ByRoleOptions>;
61+
findAllByRole: FindAllByQuery<Role, ByRoleOptions>;
4362
};
4463

4564
export const bindByRoleQueries = (

typings/index.flow.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,11 @@ interface UnsafeByPropsQueries {
202202
| Array<ReactTestInstance>
203203
| [];
204204
}
205+
206+
interface ByRoleOption {
207+
name: string;
208+
}
209+
205210
interface A11yAPI {
206211
// Label
207212
getByLabelText: (matcher: TextMatch) => GetReturn;
@@ -244,16 +249,24 @@ interface A11yAPI {
244249
) => FindAllReturn;
245250

246251
// Role
247-
getByRole: (matcher: A11yRole | RegExp) => GetReturn;
248-
getAllByRole: (matcher: A11yRole | RegExp) => GetAllReturn;
249-
queryByRole: (matcher: A11yRole | RegExp) => QueryReturn;
250-
queryAllByRole: (matcher: A11yRole | RegExp) => QueryAllReturn;
252+
getByRole: (matcher: A11yRole | RegExp, role?: ByRoleOption) => GetReturn;
253+
getAllByRole: (
254+
matcher: A11yRole | RegExp,
255+
role?: ByRoleOption
256+
) => GetAllReturn;
257+
queryByRole: (matcher: A11yRole | RegExp, role?: ByRoleOption) => QueryReturn;
258+
queryAllByRole: (
259+
matcher: A11yRole | RegExp,
260+
role?: ByRoleOption
261+
) => QueryAllReturn;
251262
findByRole: (
252263
matcher: A11yRole | RegExp,
264+
role?: ByRoleOption,
253265
waitForOptions?: WaitForOptions
254266
) => FindReturn;
255267
findAllByRole: (
256268
matcher: A11yRole | RegExp,
269+
role?: ByRoleOption,
257270
waitForOptions?: WaitForOptions
258271
) => FindAllReturn;
259272

website/docs/Queries.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ render(<MyComponent />);
189189
const element = screen.getByRole('button');
190190
```
191191

192+
#### Options
193+
194+
`name`: Finds an element with given `accessibilityRole` and an accessible name (equivalent to `ByText` or `queryByLabelText`).
195+
192196
### `ByA11yState`, `ByAccessibilityState`
193197

194198
> getByA11yState, getAllByA11yState, queryByA11yState, queryAllByA11yState, findByA11yState, findAllByA11yState
@@ -305,7 +309,8 @@ To override normalization to remove some Unicode characters whilst keeping some
305309

306310
```typescript
307311
screen.getByText(node, 'text', {
308-
normalizer: (str) => getDefaultNormalizer({ trim: false })(str).replace(/[\u200E-\u200F]*/g, ''),
312+
normalizer: (str) =>
313+
getDefaultNormalizer({ trim: false })(str).replace(/[\u200E-\u200F]*/g, ''),
309314
});
310315
```
311316

0 commit comments

Comments
 (0)