Skip to content

Commit c9ab3cf

Browse files
AugustinLFkiranjd
andauthored
Add support for ByRole with name (#1127)
Co-authored-by: Kiran Jd <kiranjd8@gmail.com>
1 parent 9892c21 commit c9ab3cf

File tree

4 files changed

+112
-15
lines changed

4 files changed

+112
-15
lines changed

src/queries/__tests__/role.test.tsx

+58
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,60 @@ test('getAllByRole, queryAllByRole, findAllByRole', async () => {
7778
getNoInstancesFoundMessage(NO_MATCHES_TEXT)
7879
);
7980
});
81+
82+
describe('supports name option', () => {
83+
test('returns 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('returns an element that has the corresponding role when several children include the name', () => {
97+
const { getByRole } = render(
98+
<TouchableOpacity accessibilityRole="button" testID="target-button">
99+
<Text>Save</Text>
100+
<Text>Save</Text>
101+
</TouchableOpacity>
102+
);
103+
104+
// assert on the testId to be sure that the returned element is the one with the accessibilityRole
105+
expect(getByRole('button', { name: 'Save' }).props.testID).toBe(
106+
'target-button'
107+
);
108+
});
109+
110+
test('returns an element that has the corresponding role and a children with a matching accessibilityLabel', () => {
111+
const { getByRole } = render(
112+
<TouchableOpacity accessibilityRole="button" testID="target-button">
113+
<Text accessibilityLabel="Save" />
114+
</TouchableOpacity>
115+
);
116+
117+
// assert on the testId to be sure that the returned element is the one with the accessibilityRole
118+
expect(getByRole('button', { name: 'Save' }).props.testID).toBe(
119+
'target-button'
120+
);
121+
});
122+
123+
test('returns an element that has the corresponding role and a matching accessibilityLabel', () => {
124+
const { getByRole } = render(
125+
<TouchableOpacity
126+
accessibilityRole="button"
127+
testID="target-button"
128+
accessibilityLabel="Save"
129+
></TouchableOpacity>
130+
);
131+
132+
// assert on the testId to be sure that the returned element is the one with the accessibilityRole
133+
expect(getByRole('button', { name: 'Save' }).props.testID).toBe(
134+
'target-button'
135+
);
136+
});
137+
});

src/queries/role.ts

+28-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ReactTestInstance } from 'react-test-renderer';
2-
import { TextMatch } from '../matches';
32
import { matchStringProp } from '../helpers/matchers/matchStringProp';
3+
import { TextMatch } from '../matches';
4+
import { getQueriesForElement } from '../within';
45
import { makeQueries } from './makeQueries';
56
import type {
67
FindAllByQuery,
@@ -11,14 +12,31 @@ import type {
1112
QueryByQuery,
1213
} from './makeQueries';
1314

15+
type ByRoleOptions = {
16+
name?: TextMatch;
17+
};
18+
19+
const matchAccessibleNameIfNeeded = (
20+
node: ReactTestInstance,
21+
name?: TextMatch
22+
) => {
23+
if (name == null) return true;
24+
25+
const { queryAllByText, queryAllByLabelText } = getQueriesForElement(node);
26+
return (
27+
queryAllByText(name).length > 0 || queryAllByLabelText(name).length > 0
28+
);
29+
};
30+
1431
const queryAllByRole = (
1532
instance: ReactTestInstance
16-
): ((role: TextMatch) => Array<ReactTestInstance>) =>
17-
function queryAllByRoleFn(role) {
33+
): ((role: TextMatch, options?: ByRoleOptions) => Array<ReactTestInstance>) =>
34+
function queryAllByRoleFn(role, options) {
1835
return instance.findAll(
1936
(node) =>
2037
typeof node.type === 'string' &&
21-
matchStringProp(node.props.accessibilityRole, role)
38+
matchStringProp(node.props.accessibilityRole, role) &&
39+
matchAccessibleNameIfNeeded(node, options?.name)
2240
);
2341
};
2442

@@ -34,12 +52,12 @@ const { getBy, getAllBy, queryBy, queryAllBy, findBy, findAllBy } = makeQueries(
3452
);
3553

3654
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>;
55+
getByRole: GetByQuery<TextMatch, ByRoleOptions>;
56+
getAllByRole: GetAllByQuery<TextMatch, ByRoleOptions>;
57+
queryByRole: QueryByQuery<TextMatch, ByRoleOptions>;
58+
queryAllByRole: QueryAllByQuery<TextMatch, ByRoleOptions>;
59+
findByRole: FindByQuery<TextMatch, ByRoleOptions>;
60+
findAllByRole: FindAllByQuery<TextMatch, ByRoleOptions>;
4361
};
4462

4563
export const bindByRoleQueries = (

typings/index.flow.js

+20-4
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,11 @@ interface UnsafeByPropsQueries {
202202
| Array<ReactTestInstance>
203203
| [];
204204
}
205+
206+
interface ByRoleOptions {
207+
name?: string;
208+
}
209+
205210
interface A11yAPI {
206211
// Label
207212
getByLabelText: (matcher: TextMatch) => GetReturn;
@@ -244,16 +249,27 @@ 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?: ByRoleOptions) => GetReturn;
253+
getAllByRole: (
254+
matcher: A11yRole | RegExp,
255+
role?: ByRoleOptions
256+
) => GetAllReturn;
257+
queryByRole: (
258+
matcher: A11yRole | RegExp,
259+
role?: ByRoleOptions
260+
) => QueryReturn;
261+
queryAllByRole: (
262+
matcher: A11yRole | RegExp,
263+
role?: ByRoleOptions
264+
) => QueryAllReturn;
251265
findByRole: (
252266
matcher: A11yRole | RegExp,
267+
role?: ByRoleOptions,
253268
waitForOptions?: WaitForOptions
254269
) => FindReturn;
255270
findAllByRole: (
256271
matcher: A11yRole | RegExp,
272+
role?: ByRoleOptions,
257273
waitForOptions?: WaitForOptions
258274
) => FindAllReturn;
259275

website/docs/Queries.md

+6-1
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 `byLabelText` query).
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)