Skip to content

Commit f9bc0b2

Browse files
Esemesekthymikee
authored andcommittedMay 28, 2019
feat: add a11y queries (#178)
* First implementation of matcher generator * A11y queries added * extract prepareErrorMessage * Add tests * Make aliases * Move create queryByError * Add missing aliases * simplify valid node check * add docs * fix types
1 parent efc4415 commit f9bc0b2

File tree

12 files changed

+576
-30
lines changed

12 files changed

+576
-30
lines changed
 

‎docs/Queries.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,63 @@ const { getByTestId } = render(<MyComponent />);
8383
const element = getByTestId('unique-id');
8484
```
8585

86+
### `ByA11yLabel`, `ByAccessibilityLabel`
87+
88+
> getByA11yLabel, getAllByA11yLabel, queryByA11yLabel, queryAllByA11yLabel
89+
> getByAccessibilityLabel, getAllByAccessibilityLabel, queryByAccessibilityLabel, queryAllByAccessibilityLabel
90+
91+
Returns a `ReactTestInstance` with matching `accessibilityLabel` prop.
92+
93+
```jsx
94+
import { render } from 'react-native-testing-library';
95+
96+
const { getByA11yLabel } = render(<MyComponent />);
97+
const element = getByA11yLabel('my-label');
98+
```
99+
100+
### `ByA11yHint`, `ByAccessibilityHint`
101+
102+
> getByA11yHint, getAllByA11yHint, queryByA11yHint, queryAllByA11yHint
103+
> getByAccessibilityHint, getAllByAccessibilityHint, queryByAccessibilityHint, queryAllByAccessibilityHint
104+
105+
Returns a `ReactTestInstance` with matching `accessibilityHint` prop.
106+
107+
```jsx
108+
import { render } from 'react-native-testing-library';
109+
110+
const { getByA11yHint } = render(<MyComponent />);
111+
const element = getByA11yHint('my-hint');
112+
```
113+
114+
### `ByA11yStates`, `ByAccessibilityStates`
115+
116+
> getByA11yStates, getAllByA11yStates, queryByA11yStates, queryAllByA11yStates
117+
> getByAccessibilityStates, getAllByAccessibilityStates, queryByAccessibilityStates, queryAllByAccessibilityStates
118+
119+
Returns a `ReactTestInstance` with matching `accessibilityStates` prop.
120+
121+
```jsx
122+
import { render } from 'react-native-testing-library';
123+
124+
const { getByA11yStates } = render(<MyComponent />);
125+
const element = getByA11yStates(['checked']);
126+
const element2 = getByA11yStates('checked');
127+
```
128+
129+
### `ByA11yRole`, `ByAccessibilityRole`
130+
131+
> getByA11yRole, getAllByA11yRole, queryByA11yRole, queryAllByA11yRole
132+
> getByAccessibilityRole, getAllByAccessibilityRole, queryByAccessibilityRole, queryAllByAccessibilityRole
133+
134+
Returns a `ReactTestInstance` with matching `accessibilityRole` prop.
135+
136+
```jsx
137+
import { render } from 'react-native-testing-library';
138+
139+
const { getByA11yRole } = render(<MyComponent />);
140+
const element = getByA11yRole('button');
141+
```
142+
86143
## Unit testing helpers
87144

88145
> Use sparingly and responsibly, escape hatches here

‎package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@callstack/eslint-config": "^6.0.0",
2121
"@types/react": "^16.7.11",
2222
"@types/react-test-renderer": "^16.0.3",
23+
"@typescript-eslint/eslint-plugin": "^1.9.0",
2324
"babel-jest": "^24.7.1",
2425
"chalk": "^2.4.1",
2526
"conventional-changelog-cli": "^2.0.11",

‎src/__tests__/a11yAPI.test.js

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// @flow
2+
import React from 'react';
3+
import { TouchableOpacity, Text } from 'react-native';
4+
import { render } from '..';
5+
6+
const BUTTON_LABEL = 'cool button';
7+
const BUTTON_HINT = 'click this button';
8+
const BUTTON_ROLE = 'button';
9+
const TEXT_LABEL = 'cool text';
10+
const TEXT_HINT = 'static text';
11+
const TEXT_ROLE = 'link';
12+
const NO_MATCHES_TEXT = 'not-existent-element';
13+
14+
const NO_INSTANCES_FOUND = 'No instances found';
15+
const FOUND_TWO_INSTANCES = 'Expected 1 but found 2 instances';
16+
17+
const Typography = ({ children, ...rest }) => {
18+
return <Text {...rest}>{children}</Text>;
19+
};
20+
21+
class Button extends React.Component<*> {
22+
render() {
23+
return (
24+
<TouchableOpacity
25+
accessibilityHint={BUTTON_HINT}
26+
accessibilityLabel={BUTTON_LABEL}
27+
accessibilityRole={BUTTON_ROLE}
28+
accessibilityStates={['selected']}
29+
>
30+
<Typography
31+
accessibilityHint={TEXT_HINT}
32+
accessibilityLabel={TEXT_LABEL}
33+
accessibilityRole={TEXT_ROLE}
34+
accessibilityStates={['selected']}
35+
>
36+
{this.props.children}
37+
</Typography>
38+
</TouchableOpacity>
39+
);
40+
}
41+
}
42+
43+
function Section() {
44+
return (
45+
<>
46+
<Typography
47+
accessibilityHint={TEXT_HINT}
48+
accessibilityLabel={TEXT_LABEL}
49+
accessibilityRole={TEXT_ROLE}
50+
accessibilityStates={['selected', 'disabled']}
51+
>
52+
Title
53+
</Typography>
54+
<Button>{TEXT_LABEL}</Button>
55+
</>
56+
);
57+
}
58+
59+
test('getByA11yLabel, queryByA11yLabel', () => {
60+
const { getByA11yLabel, queryByA11yLabel } = render(<Section />);
61+
62+
expect(getByA11yLabel(BUTTON_LABEL).props.accessibilityLabel).toEqual(
63+
BUTTON_LABEL
64+
);
65+
const button = queryByA11yLabel(/button/g);
66+
expect(button && button.props.accessibilityLabel).toEqual(BUTTON_LABEL);
67+
expect(() => getByA11yLabel(NO_MATCHES_TEXT)).toThrow(NO_INSTANCES_FOUND);
68+
expect(queryByA11yLabel(NO_MATCHES_TEXT)).toBeNull();
69+
70+
expect(() => getByA11yLabel(TEXT_LABEL)).toThrow(FOUND_TWO_INSTANCES);
71+
expect(() => queryByA11yLabel(TEXT_LABEL)).toThrow(FOUND_TWO_INSTANCES);
72+
});
73+
74+
test('getAllByA11yLabel, queryAllByA11yLabel', () => {
75+
const { getAllByA11yLabel, queryAllByA11yLabel } = render(<Section />);
76+
77+
expect(getAllByA11yLabel(TEXT_LABEL)).toHaveLength(2);
78+
expect(queryAllByA11yLabel(/cool/g)).toHaveLength(3);
79+
expect(() => getAllByA11yLabel(NO_MATCHES_TEXT)).toThrow(NO_INSTANCES_FOUND);
80+
expect(queryAllByA11yLabel(NO_MATCHES_TEXT)).toEqual([]);
81+
});
82+
83+
test('getByA11yHint, queryByA11yHint', () => {
84+
const { getByA11yHint, queryByA11yHint } = render(<Section />);
85+
86+
expect(getByA11yHint(BUTTON_HINT).props.accessibilityHint).toEqual(
87+
BUTTON_HINT
88+
);
89+
const button = queryByA11yHint(/button/g);
90+
expect(button && button.props.accessibilityHint).toEqual(BUTTON_HINT);
91+
expect(() => getByA11yHint(NO_MATCHES_TEXT)).toThrow(NO_INSTANCES_FOUND);
92+
expect(queryByA11yHint(NO_MATCHES_TEXT)).toBeNull();
93+
94+
expect(() => getByA11yHint(TEXT_HINT)).toThrow(FOUND_TWO_INSTANCES);
95+
expect(() => queryByA11yHint(TEXT_HINT)).toThrow(FOUND_TWO_INSTANCES);
96+
});
97+
98+
test('getAllByA11yHint, queryAllByA11yHint', () => {
99+
const { getAllByA11yHint, queryAllByA11yHint } = render(<Section />);
100+
101+
expect(getAllByA11yHint(TEXT_HINT)).toHaveLength(2);
102+
expect(queryAllByA11yHint(/static/g)).toHaveLength(2);
103+
expect(() => getAllByA11yHint(NO_MATCHES_TEXT)).toThrow(NO_INSTANCES_FOUND);
104+
expect(queryAllByA11yHint(NO_MATCHES_TEXT)).toEqual([]);
105+
});
106+
107+
test('getByA11yRole, queryByA11yRole', () => {
108+
const { getByA11yRole, queryByA11yRole } = render(<Section />);
109+
110+
expect(getByA11yRole('button').props.accessibilityRole).toEqual('button');
111+
const button = queryByA11yRole(/button/g);
112+
expect(button && button.props.accessibilityRole).toEqual('button');
113+
expect(() => getByA11yRole(NO_MATCHES_TEXT)).toThrow(NO_INSTANCES_FOUND);
114+
expect(queryByA11yRole(NO_MATCHES_TEXT)).toBeNull();
115+
116+
expect(() => getByA11yRole('link')).toThrow(FOUND_TWO_INSTANCES);
117+
expect(() => queryByA11yRole('link')).toThrow(FOUND_TWO_INSTANCES);
118+
});
119+
120+
test('getAllByA11yRole, queryAllByA11yRole', () => {
121+
const { getAllByA11yRole, queryAllByA11yRole } = render(<Section />);
122+
123+
expect(getAllByA11yRole('link')).toHaveLength(2);
124+
expect(queryAllByA11yRole(/ink/g)).toHaveLength(2);
125+
expect(() => getAllByA11yRole(NO_MATCHES_TEXT)).toThrow(NO_INSTANCES_FOUND);
126+
expect(queryAllByA11yRole(NO_MATCHES_TEXT)).toEqual([]);
127+
});
128+
129+
test('getByA11yStates, queryByA11yStates', () => {
130+
const { getByA11yStates, queryByA11yStates } = render(<Section />);
131+
132+
expect(getByA11yStates('disabled').props.accessibilityStates).toEqual([
133+
'selected',
134+
'disabled',
135+
]);
136+
const disabled = queryByA11yStates(['disabled']);
137+
expect(disabled && disabled.props.accessibilityStates).toMatchObject([
138+
'selected',
139+
'disabled',
140+
]);
141+
const disabledSelected = queryByA11yStates(['selected', 'disabled']);
142+
expect(
143+
disabledSelected && disabledSelected.props.accessibilityStates
144+
).toEqual(['selected', 'disabled']);
145+
146+
expect(() => getByA11yStates(NO_MATCHES_TEXT)).toThrow(NO_INSTANCES_FOUND);
147+
expect(queryByA11yStates(NO_MATCHES_TEXT)).toBeNull();
148+
expect(queryByA11yStates([])).toBeNull();
149+
150+
expect(() => getByA11yStates('selected')).toThrow(FOUND_TWO_INSTANCES);
151+
expect(() => queryByA11yStates('selected')).toThrow(FOUND_TWO_INSTANCES);
152+
});
153+
154+
test('getAllByA11yStates, queryAllByA11yStates', () => {
155+
const { getAllByA11yStates, queryAllByA11yStates } = render(<Section />);
156+
157+
expect(getAllByA11yStates('selected')).toHaveLength(3);
158+
expect(queryAllByA11yStates(['selected'])).toHaveLength(3);
159+
160+
expect(() => getAllByA11yStates([])).toThrow(NO_INSTANCES_FOUND);
161+
expect(queryAllByA11yStates(NO_MATCHES_TEXT)).toEqual([]);
162+
});

‎src/helpers/a11yAPI.js

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// @flow
2+
import makeQuery from './makeQuery';
3+
4+
type QueryFn = (string | RegExp) => ReactTestInstance | null;
5+
type GetFn = (string | RegExp) => ReactTestInstance;
6+
type GetAllFn = (string | RegExp) => Array<ReactTestInstance> | [];
7+
type ArrayQueryFn = (string | Array<string>) => ReactTestInstance | null;
8+
type ArrayGetFn = (string | Array<string>) => ReactTestInstance;
9+
type ArrayGetAllFn = (string | Array<string>) => Array<ReactTestInstance> | [];
10+
11+
type A11yAPI = {
12+
getByA11yLabel: GetFn,
13+
getAllByA11yLabel: GetAllFn,
14+
queryByA11yLabel: QueryFn,
15+
queryAllByA11yLabel: GetAllFn,
16+
getByA11yHint: GetFn,
17+
getAllByA11yHint: GetAllFn,
18+
queryByA11yHint: QueryFn,
19+
queryAllByA11yHint: GetAllFn,
20+
getByA11yRole: GetFn,
21+
getAllByA11yRole: GetAllFn,
22+
queryByA11yRole: QueryFn,
23+
queryAllByA11yRole: GetAllFn,
24+
getByA11yStates: ArrayGetFn,
25+
getAllByA11yStates: ArrayGetAllFn,
26+
queryByA11yStates: ArrayQueryFn,
27+
queryAllByA11yStates: ArrayGetAllFn,
28+
};
29+
30+
export function matchStringValue(prop?: string, matcher: string | RegExp) {
31+
if (!prop) {
32+
return false;
33+
}
34+
35+
if (typeof matcher === 'string') {
36+
return prop === matcher;
37+
}
38+
39+
return Boolean(prop.match(matcher));
40+
}
41+
42+
export function matchArrayValue(
43+
prop?: Array<string>,
44+
matcher: string | Array<string>
45+
) {
46+
if (!prop || matcher.length === 0) {
47+
return false;
48+
}
49+
50+
if (typeof matcher === 'string') {
51+
return prop.includes(matcher);
52+
}
53+
54+
// $FlowFixMe - callback is sync hence prop exists
55+
return !matcher.some(e => !prop.includes(e));
56+
}
57+
58+
const a11yAPI = (instance: ReactTestInstance): A11yAPI =>
59+
({
60+
...makeQuery(
61+
'accessibilityLabel',
62+
{
63+
getBy: ['getByA11yLabel', 'getByAccessibilityLabel'],
64+
getAllBy: ['getAllByA11yLabel', 'getAllByAccessibilityLabel'],
65+
queryBy: ['queryByA11yLabel', 'queryByAccessibilityLabel'],
66+
queryAllBy: ['queryAllByA11yLabel', 'queryAllByAccessibilityLabel'],
67+
},
68+
matchStringValue
69+
)(instance),
70+
...makeQuery(
71+
'accessibilityHint',
72+
{
73+
getBy: ['getByA11yHint', 'getByAccessibilityHint'],
74+
getAllBy: ['getAllByA11yHint', 'getAllByAccessibilityHint'],
75+
queryBy: ['queryByA11yHint', 'queryByAccessibilityHint'],
76+
queryAllBy: ['queryAllByA11yHint', 'queryAllByAccessibilityHint'],
77+
},
78+
matchStringValue
79+
)(instance),
80+
...makeQuery(
81+
'accessibilityRole',
82+
{
83+
getBy: ['getByA11yRole', 'getByAccessibilityRole'],
84+
getAllBy: ['getAllByA11yRole', 'getAllByAccessibilityRole'],
85+
queryBy: ['queryByA11yRole', 'queryByAccessibilityRole'],
86+
queryAllBy: ['queryAllByA11yRole', 'queryAllByAccessibilityRole'],
87+
},
88+
matchStringValue
89+
)(instance),
90+
...makeQuery(
91+
'accessibilityStates',
92+
{
93+
getBy: ['getByA11yStates', 'getByAccessibilityStates'],
94+
getAllBy: ['getAllByA11yStates', 'getAllByAccessibilityStates'],
95+
queryBy: ['queryByA11yStates', 'queryByAccessibilityStates'],
96+
queryAllBy: ['queryAllByA11yStates', 'queryAllByAccessibilityStates'],
97+
},
98+
matchArrayValue
99+
)(instance),
100+
}: any);
101+
102+
export default a11yAPI;

‎src/helpers/errors.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,14 @@ export const logDeprecationWarning = (
3838

3939
warned[deprecatedFnName] = true;
4040
};
41+
42+
export const prepareErrorMessage = (error: Error) =>
43+
// Strip info about custom predicate
44+
error.message.replace(/ matching custom predicate[^]*/gm, '');
45+
46+
export const createQueryByError = (error: Error, callsite: Function) => {
47+
if (error.message.includes('No instances found')) {
48+
return null;
49+
}
50+
throw new ErrorWithStack(error.message, callsite);
51+
};

‎src/helpers/getByAPI.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
ErrorWithStack,
66
createLibraryNotSupportedError,
77
logDeprecationWarning,
8+
prepareErrorMessage,
89
} from './errors';
910

1011
const filterNodeByType = (node, type) => node.type === type;
@@ -53,10 +54,6 @@ const getTextInputNodeByPlaceholder = (node, placeholder) => {
5354
}
5455
};
5556

56-
const prepareErrorMessage = error =>
57-
// Strip info about custom predicate
58-
error.message.replace(/ matching custom predicate[^]*/gm, '');
59-
6057
export const getByName = (instance: ReactTestInstance) =>
6158
function getByNameFn(name: string | React.ComponentType<*>) {
6259
logDeprecationWarning('getByName', 'getByType');

‎src/helpers/makeQuery.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// @flow
2+
import {
3+
ErrorWithStack,
4+
prepareErrorMessage,
5+
createQueryByError,
6+
} from './errors';
7+
8+
function isNodeValid(node: ReactTestInstance) {
9+
return typeof node.type === 'string';
10+
}
11+
12+
function makeAliases(aliases: Array<string>, query: Function) {
13+
return aliases
14+
.map(alias => ({ [alias]: query }))
15+
.reduce((acc, query) => ({ ...acc, ...query }), {});
16+
}
17+
18+
type QueryNames = {
19+
getBy: Array<string>,
20+
getAllBy: Array<string>,
21+
queryBy: Array<string>,
22+
queryAllBy: Array<string>,
23+
};
24+
25+
const makeQuery = <P: mixed, M: mixed>(
26+
name: string,
27+
queryNames: QueryNames,
28+
matcherFn: (prop: P, value: M) => boolean
29+
) => (instance: ReactTestInstance) => {
30+
const getBy = (matcher: M) => {
31+
try {
32+
return instance.find(
33+
node => isNodeValid(node) && matcherFn(node.props[name], matcher)
34+
);
35+
} catch (error) {
36+
throw new ErrorWithStack(prepareErrorMessage(error), getBy);
37+
}
38+
};
39+
40+
const getAllBy = (matcher: M) => {
41+
const results = instance.findAll(
42+
node => isNodeValid(node) && matcherFn(node.props[name], matcher)
43+
);
44+
45+
if (results.length === 0) {
46+
throw new ErrorWithStack('No instances found', getAllBy);
47+
}
48+
49+
return results;
50+
};
51+
52+
const queryBy = (matcher: M) => {
53+
try {
54+
return getBy(matcher);
55+
} catch (error) {
56+
return createQueryByError(error, queryBy);
57+
}
58+
};
59+
60+
const queryAllBy = (matcher: M) => {
61+
try {
62+
return getAllBy(matcher);
63+
} catch (error) {
64+
return [];
65+
}
66+
};
67+
68+
return {
69+
...makeAliases(queryNames.getBy, getBy),
70+
...makeAliases(queryNames.getAllBy, getAllBy),
71+
...makeAliases(queryNames.queryBy, queryBy),
72+
...makeAliases(queryNames.queryAllBy, queryAllBy),
73+
};
74+
};
75+
76+
export default makeQuery;

‎src/helpers/queryByAPI.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,7 @@ import {
1313
getAllByPlaceholder,
1414
getAllByProps,
1515
} from './getByAPI';
16-
import { ErrorWithStack, logDeprecationWarning } from './errors';
17-
18-
const createQueryByError = (error: Error, callsite: Function) => {
19-
if (error.message.includes('No instances found')) {
20-
return null;
21-
}
22-
throw new ErrorWithStack(error.message, callsite);
23-
};
16+
import { logDeprecationWarning, createQueryByError } from './errors';
2417

2518
export const queryByName = (instance: ReactTestInstance) =>
2619
function queryByNameFn(name: string | React.ComponentType<*>) {

‎src/render.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import TestRenderer, { type ReactTestRenderer } from 'react-test-renderer'; // e
44
import act from './act';
55
import { getByAPI } from './helpers/getByAPI';
66
import { queryByAPI } from './helpers/queryByAPI';
7+
import a11yAPI from './helpers/a11yAPI';
78
import debugShallow from './helpers/debugShallow';
89
import debugDeep from './helpers/debugDeep';
910

@@ -26,6 +27,7 @@ export default function render(
2627
return {
2728
...getByAPI(instance),
2829
...queryByAPI(instance),
30+
...a11yAPI(instance),
2931
update: updateWithAct(renderer),
3032
rerender: updateWithAct(renderer), // alias for `update`
3133
unmount: renderer.unmount,

‎typings/__tests__/index.test.tsx

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
12
import * as React from 'react';
23
import { ReactTestInstance } from 'react-test-renderer';
34
import {
@@ -39,8 +40,12 @@ const getByTypeWithRequiredProps: ReactTestInstance = tree.getByType(
3940
);
4041
const getByTextString: ReactTestInstance = tree.getByText('<View />');
4142
const getByTextRegExp: ReactTestInstance = tree.getByText(/View/g);
42-
const getByPlaceholderString: ReactTestInstance = tree.getByPlaceholder('my placeholder');
43-
const getByPlaceholderRegExp: ReactTestInstance = tree.getByPlaceholder(/placeholder/g);
43+
const getByPlaceholderString: ReactTestInstance = tree.getByPlaceholder(
44+
'my placeholder'
45+
);
46+
const getByPlaceholderRegExp: ReactTestInstance = tree.getByPlaceholder(
47+
/placeholder/g
48+
);
4449
const getByProps: ReactTestInstance = tree.getByProps({ value: 2 });
4550
const getByTestId: ReactTestInstance = tree.getByTestId('test-id');
4651
const getAllByNameString: Array<ReactTestInstance> = tree.getAllByName('View');
@@ -73,28 +78,72 @@ const queryByTextRegExp: ReactTestInstance | null = tree.queryByText(/View/g);
7378
const queryByPlaceholderString: ReactTestInstance | null = tree.queryByText(
7479
'my placeholder'
7580
);
76-
const queryByPlaceholderRegExp: ReactTestInstance | null = tree.queryByText(/placeholder/g);
81+
const queryByPlaceholderRegExp: ReactTestInstance | null = tree.queryByText(
82+
/placeholder/g
83+
);
7784
const queryByProps: ReactTestInstance | null = tree.queryByProps({ value: 2 });
7885
const queryByTestId: ReactTestInstance | null = tree.queryByTestId('test-id');
79-
const queryAllByNameString: Array<ReactTestInstance> = tree.getAllByName(
86+
const queryAllByNameString: Array<ReactTestInstance> = tree.queryAllByName(
8087
'View'
8188
);
82-
const queryAllByNameConstructor: Array<ReactTestInstance> = tree.getAllByName(
89+
const queryAllByNameConstructor: Array<ReactTestInstance> = tree.queryAllByName(
8390
View
8491
);
85-
const queryAllByType: Array<ReactTestInstance> = tree.getAllByType(View);
92+
const queryAllByType: Array<ReactTestInstance> = tree.queryAllByType(View);
8693
const queryAllByTypeWithRequiredProps: Array<
8794
ReactTestInstance
88-
> = tree.getAllByType(ElementWithRequiredProps);
95+
> = tree.queryAllByType(ElementWithRequiredProps);
8996
const queryAllByTextString: Array<ReactTestInstance> = tree.queryAllByText(
9097
'View'
9198
);
9299
const queryAllByTextRegExp: Array<ReactTestInstance> = tree.queryAllByText(
93100
/View/g
94101
);
95-
const queryAllByProps: Array<ReactTestInstance> = tree.getAllByProps({
96-
value: 2,
97-
});
102+
103+
// Accessibility queries
104+
const getByA11yLabel: ReactTestInstance = tree.getByA11yLabel('label');
105+
const getAllByA11yLabel: Array<ReactTestInstance> = tree.getAllByA11yLabel(
106+
'label'
107+
);
108+
const queryByA11yLabel: ReactTestInstance = tree.queryByA11yLabel('label');
109+
const queryAllByA11yLabel: Array<ReactTestInstance> = tree.queryAllByA11yLabel(
110+
'label'
111+
);
112+
const getByA11yHint: ReactTestInstance = tree.getByA11yHint('label');
113+
const getAllByA11yHint: Array<ReactTestInstance> = tree.getAllByA11yHint(
114+
'label'
115+
);
116+
const queryByA11yHint: ReactTestInstance = tree.queryByA11yHint('label');
117+
const queryAllByA11yHint: Array<ReactTestInstance> = tree.queryAllByA11yHint(
118+
'label'
119+
);
120+
const getByA11yRole: ReactTestInstance = tree.getByA11yRole('label');
121+
const getAllByA11yRole: Array<ReactTestInstance> = tree.getAllByA11yRole(
122+
'label'
123+
);
124+
const queryByA11yRole: ReactTestInstance = tree.queryByA11yRole('label');
125+
const queryAllByA11yRole: Array<ReactTestInstance> = tree.queryAllByA11yRole(
126+
'label'
127+
);
128+
const getByA11yStates: ReactTestInstance = tree.getByA11yStates('label');
129+
const getByA11yStatesArray: ReactTestInstance = tree.getByA11yStates(['label']);
130+
const getAllByA11yStates: Array<ReactTestInstance> = tree.getAllByA11yStates(
131+
'label'
132+
);
133+
const getAllByA11yStatesArray: Array<
134+
ReactTestInstance
135+
> = tree.getAllByA11yStates(['label']);
136+
const queryByA11yStates: ReactTestInstance = tree.queryByA11yStates('label');
137+
const queryByA11yStatesArray: ReactTestInstance = tree.queryByA11yStates([
138+
'label',
139+
]);
140+
const queryAllByA11yStates: Array<
141+
ReactTestInstance
142+
> = tree.queryAllByA11yStates('label');
143+
const queryAllByA11yStatesArray: Array<
144+
ReactTestInstance
145+
> = tree.queryAllByA11yStates(['label']);
146+
98147
const debugFn = tree.debug();
99148
const debugFnWithMessage = tree.debug('my message');
100149

‎typings/index.d.ts

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,73 @@ export interface GetByAPI {
1111
getAllByName: (name: React.ReactType | string) => Array<ReactTestInstance>;
1212
getAllByType: <P>(type: React.ComponentType<P>) => Array<ReactTestInstance>;
1313
getAllByText: (text: string | RegExp) => Array<ReactTestInstance>;
14-
getAllByPlaceholder: (placeholder: string | RegExp) => Array<ReactTestInstance>;
14+
getAllByPlaceholder: (
15+
placeholder: string | RegExp
16+
) => Array<ReactTestInstance>;
1517
getAllByProps: (props: Record<string, any>) => Array<ReactTestInstance>;
1618
}
1719

1820
export interface QueryByAPI {
1921
queryByName: (name: React.ReactType | string) => ReactTestInstance | null;
2022
queryByType: <P>(type: React.ComponentType<P>) => ReactTestInstance | null;
2123
queryByText: (name: string | RegExp) => ReactTestInstance | null;
22-
queryByPlaceholder: (placeholder: string | RegExp) => ReactTestInstance | null;
24+
queryByPlaceholder: (
25+
placeholder: string | RegExp
26+
) => ReactTestInstance | null;
2327
queryByProps: (props: Record<string, any>) => ReactTestInstance | null;
2428
queryByTestId: (testID: string) => ReactTestInstance | null;
25-
queryAllByName: (name: React.ReactType | string) => Array<ReactTestInstance> | [];
29+
queryAllByName: (
30+
name: React.ReactType | string
31+
) => Array<ReactTestInstance> | [];
2632
queryAllByType: <P>(
2733
type: React.ComponentType<P>
2834
) => Array<ReactTestInstance> | [];
2935
queryAllByText: (text: string | RegExp) => Array<ReactTestInstance> | [];
30-
queryAllByPlaceholder: (placeholder: string | RegExp) => Array<ReactTestInstance> | [];
36+
queryAllByPlaceholder: (
37+
placeholder: string | RegExp
38+
) => Array<ReactTestInstance> | [];
3139
queryAllByProps: (
3240
props: Record<string, any>
3341
) => Array<ReactTestInstance> | [];
3442
}
3543

44+
type QueryFn = (text: string | RegExp) => ReactTestInstance | null;
45+
type GetFn = (text: string | RegExp) => ReactTestInstance;
46+
type GetAllFn = (text: string | RegExp) => Array<ReactTestInstance> | [];
47+
type ArrayQueryFn = (text: string | Array<string>) => ReactTestInstance | null;
48+
type ArrayGetFn = (text: string | Array<string>) => ReactTestInstance;
49+
type ArrayGetAllFn = (
50+
text: string | Array<string>
51+
) => Array<ReactTestInstance> | [];
52+
53+
export interface A11yAPI {
54+
getByA11yLabel: GetFn;
55+
getAllByA11yLabel: GetAllFn;
56+
queryByA11yLabel: QueryFn;
57+
queryAllByA11yLabel: GetAllFn;
58+
getByA11yHint: GetFn;
59+
getAllByA11yHint: GetAllFn;
60+
queryByA11yHint: QueryFn;
61+
queryAllByA11yHint: GetAllFn;
62+
getByA11yRole: GetFn;
63+
getAllByA11yRole: GetAllFn;
64+
queryByA11yRole: QueryFn;
65+
queryAllByA11yRole: GetAllFn;
66+
getByA11yStates: ArrayGetFn;
67+
getAllByA11yStates: ArrayGetAllFn;
68+
queryByA11yStates: ArrayQueryFn;
69+
queryAllByA11yStates: ArrayGetAllFn;
70+
}
71+
3672
export interface Thenable {
37-
then: (resolve: () => any, reject?: () => any) => any,
73+
then: (resolve: () => any, reject?: () => any) => any;
3874
}
3975

4076
export interface RenderOptions {
4177
createNodeMock: (element: React.ReactElement<any>) => any;
4278
}
4379

44-
export interface RenderAPI extends GetByAPI, QueryByAPI {
80+
export interface RenderAPI extends GetByAPI, QueryByAPI, A11yAPI {
4581
update(nextElement: React.ReactElement<any>): void;
4682
rerender(nextElement: React.ReactElement<any>): void;
4783
unmount(nextElement?: React.ReactElement<any>): void;

‎yarn.lock

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,44 @@
10801080
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916"
10811081
integrity sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==
10821082

1083+
"@typescript-eslint/eslint-plugin@^1.9.0":
1084+
version "1.9.0"
1085+
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.9.0.tgz#29d73006811bf2563b88891ceeff1c5ea9c8d9c6"
1086+
integrity sha512-FOgfBorxjlBGpDIw+0LaZIXRX6GEEUfzj8LXwaQIUCp+gDOvkI+1WgugJ7SmWiISqK9Vj5r8S7NDKO/LB+6X9A==
1087+
dependencies:
1088+
"@typescript-eslint/experimental-utils" "1.9.0"
1089+
"@typescript-eslint/parser" "1.9.0"
1090+
eslint-utils "^1.3.1"
1091+
functional-red-black-tree "^1.0.1"
1092+
regexpp "^2.0.1"
1093+
requireindex "^1.2.0"
1094+
tsutils "^3.7.0"
1095+
1096+
"@typescript-eslint/experimental-utils@1.9.0":
1097+
version "1.9.0"
1098+
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-1.9.0.tgz#a92777d0c92d7bc8627abd7cdb06cdbcaf2b39e8"
1099+
integrity sha512-1s2dY9XxBwtS9IlSnRIlzqILPyeMly5tz1bfAmQ84Ul687xBBve5YsH5A5EKeIcGurYYqY2w6RkHETXIwnwV0A==
1100+
dependencies:
1101+
"@typescript-eslint/typescript-estree" "1.9.0"
1102+
1103+
"@typescript-eslint/parser@1.9.0":
1104+
version "1.9.0"
1105+
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-1.9.0.tgz#5796cbfcb9a3a5757aeb671c1ac88d7a94a95962"
1106+
integrity sha512-CWgC1XrQ34H/+LwAU7vY5xteZDkNqeAkeidEpJnJgkKu0yqQ3ZhQ7S+dI6MX4vmmM1TKRbOrKuXc6W0fIHhdbA==
1107+
dependencies:
1108+
"@typescript-eslint/experimental-utils" "1.9.0"
1109+
"@typescript-eslint/typescript-estree" "1.9.0"
1110+
eslint-scope "^4.0.0"
1111+
eslint-visitor-keys "^1.0.0"
1112+
1113+
"@typescript-eslint/typescript-estree@1.9.0":
1114+
version "1.9.0"
1115+
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.9.0.tgz#5d6d49be936e96fb0f859673480f89b070a5dd9b"
1116+
integrity sha512-7Eg0TEQpCkTsEwsl1lIzd6i7L3pJLQFWesV08dS87bNz0NeSjbL78gNAP1xCKaCejkds4PhpLnZkaAjx9SU8OA==
1117+
dependencies:
1118+
lodash.unescape "4.0.1"
1119+
semver "5.5.0"
1120+
10831121
JSONStream@^1.0.4:
10841122
version "1.3.5"
10851123
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
@@ -2705,7 +2743,7 @@ eslint-scope@3.7.1:
27052743
esrecurse "^4.1.0"
27062744
estraverse "^4.1.1"
27072745

2708-
eslint-scope@^4.0.3:
2746+
eslint-scope@^4.0.0, eslint-scope@^4.0.3:
27092747
version "4.0.3"
27102748
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
27112749
integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==
@@ -4890,6 +4928,11 @@ lodash.throttle@^4.1.1:
48904928
version "4.1.1"
48914929
resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
48924930

4931+
lodash.unescape@4.0.1:
4932+
version "4.0.1"
4933+
resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c"
4934+
integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=
4935+
48934936
lodash.uniq@^4.5.0:
48944937
version "4.5.0"
48954938
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
@@ -6687,6 +6730,11 @@ require-main-filename@^1.0.1:
66876730
version "1.0.1"
66886731
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
66896732

6733+
requireindex@^1.2.0:
6734+
version "1.2.0"
6735+
resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef"
6736+
integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==
6737+
66906738
resolve-cwd@^2.0.0:
66916739
version "2.0.0"
66926740
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
@@ -6873,6 +6921,11 @@ semver-diff@^2.0.0:
68736921
version "5.5.1"
68746922
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477"
68756923

6924+
semver@5.5.0:
6925+
version "5.5.0"
6926+
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
6927+
integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==
6928+
68766929
semver@6.0.0:
68776930
version "6.0.0"
68786931
resolved "https://registry.yarnpkg.com/semver/-/semver-6.0.0.tgz#05e359ee571e5ad7ed641a6eec1e547ba52dea65"
@@ -7429,10 +7482,17 @@ trim-right@^1.0.1:
74297482
version "1.0.1"
74307483
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
74317484

7432-
tslib@^1.9.0:
7485+
tslib@^1.8.1, tslib@^1.9.0:
74337486
version "1.9.3"
74347487
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
74357488

7489+
tsutils@^3.7.0:
7490+
version "3.10.0"
7491+
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.10.0.tgz#6f1c95c94606e098592b0dff06590cf9659227d6"
7492+
integrity sha512-q20XSMq7jutbGB8luhKKsQldRKWvyBO2BGqni3p4yq8Ys9bEP/xQw3KepKmMRt9gJ4lvQSScrihJrcKdKoSU7Q==
7493+
dependencies:
7494+
tslib "^1.8.1"
7495+
74367496
tunnel-agent@^0.6.0:
74377497
version "0.6.0"
74387498
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"

0 commit comments

Comments
 (0)
Please sign in to comment.