Skip to content

Commit 62cda22

Browse files
Esemesekthymikee
andauthored
feat: byA11yState matcher (#260)
* feat: byA11yState matcher * Kick CI * Update src/helpers/a11yAPI.js Co-Authored-By: Michał Pierzchała <thymikee@gmail.com> * Correct types Co-authored-by: Michał Pierzchała <thymikee@gmail.com>
1 parent db087f7 commit 62cda22

File tree

5 files changed

+204
-71
lines changed

5 files changed

+204
-71
lines changed

src/__tests__/a11yAPI.test.js

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ const BUTTON_ROLE = 'button';
99
const TEXT_LABEL = 'cool text';
1010
const TEXT_HINT = 'static text';
1111
const TEXT_ROLE = 'link';
12-
const NO_MATCHES_TEXT = 'not-existent-element';
12+
// Little hack to make all the methods happy with type
13+
const NO_MATCHES_TEXT: any = 'not-existent-element';
1314
const NO_INSTANCES_FOUND = 'No instances found';
1415
const FOUND_TWO_INSTANCES = 'Expected 1 but found 2 instances';
1516

16-
const Typography = ({ children, ...rest }) => {
17+
const Typography = ({ children, ...rest }: any) => {
1718
return <Text {...rest}>{children}</Text>;
1819
};
1920

@@ -31,6 +32,7 @@ class Button extends React.Component<any> {
3132
accessibilityLabel={TEXT_LABEL}
3233
accessibilityRole={TEXT_ROLE}
3334
accessibilityStates={['selected']}
35+
accessibilityState={{ expanded: false, selected: true }}
3436
>
3537
{this.props.children}
3638
</Typography>
@@ -47,6 +49,7 @@ function Section() {
4749
accessibilityLabel={TEXT_LABEL}
4850
accessibilityRole={TEXT_ROLE}
4951
accessibilityStates={['selected', 'disabled']}
52+
accessibilityState={{ expanded: false }}
5053
>
5154
Title
5255
</Typography>
@@ -159,3 +162,43 @@ test('getAllByA11yStates, queryAllByA11yStates', () => {
159162
expect(() => getAllByA11yStates([])).toThrow(NO_INSTANCES_FOUND);
160163
expect(queryAllByA11yStates(NO_MATCHES_TEXT)).toEqual([]);
161164
});
165+
166+
test('getByA11yState, queryByA11yState', () => {
167+
const { getByA11yState, queryByA11yState } = render(<Section />);
168+
169+
expect(getByA11yState({ selected: true }).props.accessibilityState).toEqual({
170+
selected: true,
171+
expanded: false,
172+
});
173+
expect(
174+
queryByA11yState({ selected: true })?.props.accessibilityState
175+
).toEqual({
176+
selected: true,
177+
expanded: false,
178+
});
179+
180+
expect(() => getByA11yState({ disabled: true })).toThrow(NO_INSTANCES_FOUND);
181+
expect(queryByA11yState({ disabled: true })).toEqual(null);
182+
183+
expect(() => getByA11yState({ expanded: false })).toThrow(
184+
FOUND_TWO_INSTANCES
185+
);
186+
expect(() => queryByA11yState({ expanded: false })).toThrow(
187+
FOUND_TWO_INSTANCES
188+
);
189+
});
190+
191+
test('getAllByA11yState, queryAllByA11yState', () => {
192+
const { getAllByA11yState, queryAllByA11yState } = render(<Section />);
193+
194+
expect(getAllByA11yState({ selected: true }).length).toEqual(1);
195+
expect(queryAllByA11yState({ selected: true }).length).toEqual(1);
196+
197+
expect(() => getAllByA11yState({ disabled: true })).toThrow(
198+
NO_INSTANCES_FOUND
199+
);
200+
expect(queryAllByA11yState({ disabled: true })).toEqual([]);
201+
202+
expect(getAllByA11yState({ expanded: false }).length).toEqual(2);
203+
expect(queryAllByA11yState({ expanded: false }).length).toEqual(2);
204+
});

src/helpers/a11yAPI.js

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,48 @@
11
// @flow
22
import makeQuery from './makeQuery';
3+
import type { A11yRole, A11yStates, A11yState } from '../types.flow';
34

4-
type QueryFn = (string | RegExp) => ReactTestInstance | null;
5-
type QueryAllFn = (string | RegExp) => Array<ReactTestInstance> | [];
6-
type GetFn = (string | RegExp) => ReactTestInstance;
7-
type GetAllFn = (string | RegExp) => Array<ReactTestInstance>;
8-
type ArrayQueryFn = (string | Array<string>) => ReactTestInstance | null;
9-
type ArrayQueryAllFn = (
10-
string | Array<string>
11-
) => Array<ReactTestInstance> | [];
12-
type ArrayGetFn = (string | Array<string>) => ReactTestInstance;
13-
type ArrayGetAllFn = (string | Array<string>) => Array<ReactTestInstance>;
5+
type GetReturn = ReactTestInstance;
6+
type GetAllReturn = Array<ReactTestInstance>;
7+
type QueryReturn = ReactTestInstance | null;
8+
type QueryAllReturn = Array<ReactTestInstance> | [];
149

1510
type A11yAPI = {|
16-
getByA11yLabel: GetFn,
17-
getAllByA11yLabel: GetAllFn,
18-
queryByA11yLabel: QueryFn,
19-
queryAllByA11yLabel: QueryAllFn,
20-
getByA11yHint: GetFn,
21-
getAllByA11yHint: GetAllFn,
22-
queryByA11yHint: QueryFn,
23-
queryAllByA11yHint: QueryAllFn,
24-
getByA11yRole: GetFn,
25-
getAllByA11yRole: GetAllFn,
26-
queryByA11yRole: QueryFn,
27-
queryAllByA11yRole: QueryAllFn,
28-
getByA11yStates: ArrayGetFn,
29-
getAllByA11yStates: ArrayGetAllFn,
30-
queryByA11yStates: ArrayQueryFn,
31-
queryAllByA11yStates: ArrayQueryAllFn,
11+
// Label
12+
getByA11yLabel: (string | RegExp) => GetReturn,
13+
getAllByA11yLabel: (string | RegExp) => GetAllReturn,
14+
queryByA11yLabel: (string | RegExp) => QueryReturn,
15+
queryAllByA11yLabel: (string | RegExp) => QueryAllReturn,
16+
17+
// Hint
18+
getByA11yHint: (string | RegExp) => GetReturn,
19+
getAllByA11yHint: (string | RegExp) => GetAllReturn,
20+
queryByA11yHint: (string | RegExp) => QueryReturn,
21+
queryAllByA11yHint: (string | RegExp) => QueryAllReturn,
22+
23+
// Role
24+
getByA11yRole: (A11yRole | RegExp) => GetReturn,
25+
getAllByA11yRole: (A11yRole | RegExp) => GetAllReturn,
26+
queryByA11yRole: (A11yRole | RegExp) => QueryReturn,
27+
queryAllByA11yRole: (A11yRole | RegExp) => QueryAllReturn,
28+
29+
// States
30+
getByA11yStates: (A11yStates | Array<A11yStates>) => GetReturn,
31+
getAllByA11yStates: (A11yStates | Array<A11yStates>) => GetAllReturn,
32+
queryByA11yStates: (A11yStates | Array<A11yStates>) => QueryReturn,
33+
queryAllByA11yStates: (A11yStates | Array<A11yStates>) => QueryAllReturn,
34+
35+
// State
36+
getByA11yState: A11yState => GetReturn,
37+
getAllByA11yState: A11yState => GetAllReturn,
38+
queryByA11yState: A11yState => QueryReturn,
39+
queryAllByA11yState: A11yState => QueryAllReturn,
3240
|};
3341

34-
export function matchStringValue(prop?: string, matcher: string | RegExp) {
42+
export function matchStringValue(
43+
prop?: string,
44+
matcher: string | RegExp
45+
): boolean {
3546
if (!prop) {
3647
return false;
3748
}
@@ -46,7 +57,7 @@ export function matchStringValue(prop?: string, matcher: string | RegExp) {
4657
export function matchArrayValue(
4758
prop?: Array<string>,
4859
matcher: string | Array<string>
49-
) {
60+
): boolean {
5061
if (!prop || matcher.length === 0) {
5162
return false;
5263
}
@@ -58,6 +69,14 @@ export function matchArrayValue(
5869
return !matcher.some(e => !prop.includes(e));
5970
}
6071

72+
export function matchObject<T: {}>(prop?: T, matcher: T): boolean {
73+
return prop
74+
? Object.keys(matcher).length !== 0 &&
75+
Object.keys(prop).length !== 0 &&
76+
!Object.keys(matcher).some(key => prop[key] !== matcher[key])
77+
: false;
78+
}
79+
6180
const a11yAPI = (instance: ReactTestInstance): A11yAPI =>
6281
({
6382
...makeQuery(
@@ -100,6 +119,16 @@ const a11yAPI = (instance: ReactTestInstance): A11yAPI =>
100119
},
101120
matchArrayValue
102121
)(instance),
122+
...makeQuery(
123+
'accessibilityState',
124+
{
125+
getBy: ['getByA11yState', 'getByAccessibilityState'],
126+
getAllBy: ['getAllByA11yState', 'getAllByAccessibilityState'],
127+
queryBy: ['queryByA11yState', 'queryByAccessibilityState'],
128+
queryAllBy: ['queryAllByA11yState', 'queryAllByAccessibilityState'],
129+
},
130+
matchObject
131+
)(instance),
103132
}: any);
104133

105134
export default a11yAPI;

src/types.flow.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// @flow
2+
3+
export type A11yRole =
4+
| 'none'
5+
| 'button'
6+
| 'link'
7+
| 'search'
8+
| 'image'
9+
| 'keyboardkey'
10+
| 'text'
11+
| 'adjustable'
12+
| 'imagebutton'
13+
| 'header'
14+
| 'summary'
15+
| 'alert'
16+
| 'checkbox'
17+
| 'combobox'
18+
| 'menu'
19+
| 'menubar'
20+
| 'menuitem'
21+
| 'progressbar'
22+
| 'radio'
23+
| 'radiogroup'
24+
| 'scrollbar'
25+
| 'spinbutton'
26+
| 'switch'
27+
| 'tab'
28+
| 'tablist'
29+
| 'timer'
30+
| 'toolbar';
31+
32+
export type A11yState = {|
33+
disabled?: boolean,
34+
selected?: boolean,
35+
checked?: boolean | 'mixed',
36+
busy?: boolean,
37+
expanded?: boolean,
38+
|};
39+
40+
export type A11yStates =
41+
| 'disabled'
42+
| 'selected'
43+
| 'checked'
44+
| 'unchecked'
45+
| 'busy'
46+
| 'expanded'
47+
| 'collapsed'
48+
| 'hasPopup';

typings/__tests__/index.test.tsx

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -137,32 +137,37 @@ const queryByA11yHint: ReactTestInstance = tree.queryByA11yHint('label');
137137
const queryAllByA11yHint: Array<ReactTestInstance> = tree.queryAllByA11yHint(
138138
'label'
139139
);
140-
const getByA11yRole: ReactTestInstance = tree.getByA11yRole('label');
140+
const getByA11yRole: ReactTestInstance = tree.getByA11yRole('button');
141141
const getAllByA11yRole: Array<ReactTestInstance> = tree.getAllByA11yRole(
142-
'label'
142+
'button'
143143
);
144-
const queryByA11yRole: ReactTestInstance = tree.queryByA11yRole('label');
144+
const queryByA11yRole: ReactTestInstance = tree.queryByA11yRole('button');
145145
const queryAllByA11yRole: Array<ReactTestInstance> = tree.queryAllByA11yRole(
146-
'label'
146+
'button'
147147
);
148-
const getByA11yStates: ReactTestInstance = tree.getByA11yStates('label');
149-
const getByA11yStatesArray: ReactTestInstance = tree.getByA11yStates(['label']);
148+
const getByA11yStates: ReactTestInstance = tree.getByA11yStates('selected');
149+
const getByA11yStatesArray: ReactTestInstance = tree.getByA11yStates(['selected']);
150150
const getAllByA11yStates: Array<ReactTestInstance> = tree.getAllByA11yStates(
151-
'label'
151+
'selected'
152152
);
153153
const getAllByA11yStatesArray: Array<
154154
ReactTestInstance
155-
> = tree.getAllByA11yStates(['label']);
156-
const queryByA11yStates: ReactTestInstance = tree.queryByA11yStates('label');
155+
> = tree.getAllByA11yStates(['selected']);
156+
const queryByA11yStates: ReactTestInstance = tree.queryByA11yStates('selected');
157157
const queryByA11yStatesArray: ReactTestInstance = tree.queryByA11yStates([
158-
'label',
158+
'selected',
159159
]);
160160
const queryAllByA11yStates: Array<
161161
ReactTestInstance
162-
> = tree.queryAllByA11yStates('label');
162+
> = tree.queryAllByA11yStates('selected');
163163
const queryAllByA11yStatesArray: Array<
164164
ReactTestInstance
165-
> = tree.queryAllByA11yStates(['label']);
165+
> = tree.queryAllByA11yStates(['selected']);
166+
167+
const getByA11yState: ReactTestInstance = tree.getByA11yState({ busy: true });
168+
const getAllByA11yState: Array<ReactTestInstance> = tree.getAllByA11yState({ busy: true });
169+
const queryByA11yState: ReactTestInstance = tree.queryByA11yState({ busy: true });
170+
const queryAllByA11yState: Array<ReactTestInstance> = tree.queryAllByA11yState({ busy: true });
166171

167172
const debugFn = tree.debug();
168173
const debugFnWithMessage = tree.debug('my message');

typings/index.d.ts

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as React from 'react';
2+
import { AccessibilityState, AccessibilityStates, AccessibilityRole } from 'react-native';
23
import { ReactTestInstance, ReactTestRendererJSON } from 'react-test-renderer';
34

45
export interface GetByAPI {
@@ -49,35 +50,42 @@ export interface QueryByAPI {
4950
) => Array<ReactTestInstance> | [];
5051
}
5152

52-
type QueryFn = (text: string | RegExp) => ReactTestInstance | null;
53-
type QueryAllFn = (text: string | RegExp) => Array<ReactTestInstance> | [];
54-
type GetFn = (text: string | RegExp) => ReactTestInstance;
55-
type GetAllFn = (text: string | RegExp) => Array<ReactTestInstance>;
56-
type ArrayQueryFn = (text: string | Array<string>) => ReactTestInstance | null;
57-
type ArrayQueryAllFn = (
58-
text: string | Array<string>
59-
) => Array<ReactTestInstance> | [];
60-
type ArrayGetFn = (text: string | Array<string>) => ReactTestInstance;
61-
type ArrayGetAllFn = (text: string | Array<string>) => Array<ReactTestInstance>;
62-
63-
export interface A11yAPI {
64-
getByA11yLabel: GetFn;
65-
getAllByA11yLabel: GetAllFn;
66-
queryByA11yLabel: QueryFn;
67-
queryAllByA11yLabel: QueryAllFn;
68-
getByA11yHint: GetFn;
69-
getAllByA11yHint: GetAllFn;
70-
queryByA11yHint: QueryFn;
71-
queryAllByA11yHint: QueryAllFn;
72-
getByA11yRole: GetFn;
73-
getAllByA11yRole: GetAllFn;
74-
queryByA11yRole: QueryFn;
75-
queryAllByA11yRole: QueryAllFn;
76-
getByA11yStates: ArrayGetFn;
77-
getAllByA11yStates: ArrayGetAllFn;
78-
queryByA11yStates: ArrayQueryFn;
79-
queryAllByA11yStates: ArrayQueryAllFn;
80-
}
53+
type GetReturn = ReactTestInstance;
54+
type GetAllReturn = Array<ReactTestInstance>;
55+
type QueryReturn = ReactTestInstance | null;
56+
type QueryAllReturn = Array<ReactTestInstance> | [];
57+
58+
type A11yAPI = {
59+
// Label
60+
getByA11yLabel: (matcher: string | RegExp) => GetReturn,
61+
getAllByA11yLabel: (matcher: string | RegExp) => GetAllReturn,
62+
queryByA11yLabel: (matcher: string | RegExp) => QueryReturn,
63+
queryAllByA11yLabel: (matcher: string | RegExp) => QueryAllReturn,
64+
65+
// Hint
66+
getByA11yHint: (matcher: string | RegExp) => GetReturn,
67+
getAllByA11yHint: (matcher: string | RegExp) => GetAllReturn,
68+
queryByA11yHint: (matcher: string | RegExp) => QueryReturn,
69+
queryAllByA11yHint: (matcher: string | RegExp) => QueryAllReturn,
70+
71+
// Role
72+
getByA11yRole: (matcher: AccessibilityRole | RegExp) => GetReturn,
73+
getAllByA11yRole: (matcher: AccessibilityRole | RegExp) => GetAllReturn,
74+
queryByA11yRole: (matcher: AccessibilityRole | RegExp) => QueryReturn,
75+
queryAllByA11yRole: (matcher: AccessibilityRole | RegExp) => QueryAllReturn,
76+
77+
// States
78+
getByA11yStates: (matcher: AccessibilityStates | Array<AccessibilityStates>) => GetReturn,
79+
getAllByA11yStates: (matcher: AccessibilityStates | Array<AccessibilityStates>) => GetAllReturn,
80+
queryByA11yStates: (matcher: AccessibilityStates | Array<AccessibilityStates>) => QueryReturn,
81+
queryAllByA11yStates: (matcher: AccessibilityStates | Array<AccessibilityStates>) => QueryAllReturn,
82+
83+
// State
84+
getByA11yState: (matcher: AccessibilityState) => GetReturn,
85+
getAllByA11yState: (matcher: AccessibilityState) => GetAllReturn,
86+
queryByA11yState: (matcher: AccessibilityState) => QueryReturn,
87+
queryAllByA11yState: (matcher: AccessibilityState) => QueryAllReturn,
88+
};
8189

8290
export interface Thenable {
8391
then: (resolve: () => any, reject?: () => any) => any;

0 commit comments

Comments
 (0)