Skip to content

Commit bd5ddd7

Browse files
fix: *ByA11yState default value false value for busy, disabled & selected state (#1166)
1 parent 96bf63a commit bd5ddd7

File tree

6 files changed

+252
-73
lines changed

6 files changed

+252
-73
lines changed

src/helpers/accessiblity.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1-
import { StyleSheet } from 'react-native';
1+
import { AccessibilityState, StyleSheet } from 'react-native';
22
import { ReactTestInstance } from 'react-test-renderer';
33
import { getHostSiblings } from './component-tree';
44

5+
export type AccessibilityStateKey = keyof AccessibilityState;
6+
7+
export const accessibilityStateKeys: AccessibilityStateKey[] = [
8+
'disabled',
9+
'selected',
10+
'checked',
11+
'busy',
12+
'expanded',
13+
];
14+
515
export function isInaccessible(element: ReactTestInstance | null): boolean {
616
if (element == null) {
717
return true;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { AccessibilityState } from 'react-native';
2+
import { ReactTestInstance } from 'react-test-renderer';
3+
import { accessibilityStateKeys } from '../accessiblity';
4+
5+
/**
6+
* Default accessibility state values based on experiments using accessibility
7+
* inspector/screen reader on iOS and Android.
8+
*
9+
* @see https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State
10+
*/
11+
const defaultState: AccessibilityState = {
12+
disabled: false,
13+
selected: false,
14+
checked: undefined,
15+
busy: false,
16+
expanded: undefined,
17+
};
18+
19+
export function matchAccessibilityState(
20+
node: ReactTestInstance,
21+
matcher: AccessibilityState
22+
) {
23+
const state = node.props.accessibilityState;
24+
return accessibilityStateKeys.every((key) => matchState(state, matcher, key));
25+
}
26+
27+
function matchState(
28+
state: AccessibilityState,
29+
matcher: AccessibilityState,
30+
key: keyof AccessibilityState
31+
) {
32+
return (
33+
matcher[key] === undefined ||
34+
matcher[key] === (state?.[key] ?? defaultState[key])
35+
);
36+
}

src/queries/__tests__/a11yState.test.tsx

Lines changed: 146 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
11
import * as React from 'react';
2-
import { TouchableOpacity, Text } from 'react-native';
2+
import { View, Text, Pressable, TouchableOpacity } from 'react-native';
33
import { render } from '../..';
44

55
const TEXT_LABEL = 'cool text';
66

7-
const getMultipleInstancesFoundMessage = (value: string) => {
8-
return `Found multiple elements with accessibilityState: ${value}`;
9-
};
10-
11-
const getNoInstancesFoundMessage = (value: string) => {
12-
return `Unable to find an element with accessibilityState: ${value}`;
13-
};
14-
157
const Typography = ({ children, ...rest }: any) => {
168
return <Text {...rest}>{children}</Text>;
179
};
@@ -48,15 +40,15 @@ test('getByA11yState, queryByA11yState, findByA11yState', async () => {
4840
});
4941

5042
expect(() => getByA11yState({ disabled: true })).toThrow(
51-
getNoInstancesFoundMessage('{"disabled":true}')
43+
'Unable to find an element with disabled state: true'
5244
);
5345
expect(queryByA11yState({ disabled: true })).toEqual(null);
5446

5547
expect(() => getByA11yState({ expanded: false })).toThrow(
56-
getMultipleInstancesFoundMessage('{"expanded":false}')
48+
'Found multiple elements with expanded state: false'
5749
);
5850
expect(() => queryByA11yState({ expanded: false })).toThrow(
59-
getMultipleInstancesFoundMessage('{"expanded":false}')
51+
'Found multiple elements with expanded state: false'
6052
);
6153

6254
const asyncButton = await findByA11yState({ selected: true });
@@ -65,10 +57,10 @@ test('getByA11yState, queryByA11yState, findByA11yState', async () => {
6557
expanded: false,
6658
});
6759
await expect(findByA11yState({ disabled: true })).rejects.toThrow(
68-
getNoInstancesFoundMessage('{"disabled":true}')
60+
'Unable to find an element with disabled state: true'
6961
);
7062
await expect(findByA11yState({ expanded: false })).rejects.toThrow(
71-
getMultipleInstancesFoundMessage('{"expanded":false}')
63+
'Found multiple elements with expanded state: false'
7264
);
7365
});
7466

@@ -81,7 +73,7 @@ test('getAllByA11yState, queryAllByA11yState, findAllByA11yState', async () => {
8173
expect(queryAllByA11yState({ selected: true })).toHaveLength(1);
8274

8375
expect(() => getAllByA11yState({ disabled: true })).toThrow(
84-
getNoInstancesFoundMessage('{"disabled":true}')
76+
'Unable to find an element with disabled state: true'
8577
);
8678
expect(queryAllByA11yState({ disabled: true })).toEqual([]);
8779

@@ -90,9 +82,147 @@ test('getAllByA11yState, queryAllByA11yState, findAllByA11yState', async () => {
9082

9183
await expect(findAllByA11yState({ selected: true })).resolves.toHaveLength(1);
9284
await expect(findAllByA11yState({ disabled: true })).rejects.toThrow(
93-
getNoInstancesFoundMessage('{"disabled":true}')
85+
'Unable to find an element with disabled state: true'
9486
);
9587
await expect(findAllByA11yState({ expanded: false })).resolves.toHaveLength(
9688
2
9789
);
9890
});
91+
92+
describe('checked state matching', () => {
93+
it('handles true', () => {
94+
const view = render(<View accessibilityState={{ checked: true }} />);
95+
96+
expect(view.getByA11yState({ checked: true })).toBeTruthy();
97+
expect(view.queryByA11yState({ checked: 'mixed' })).toBeFalsy();
98+
expect(view.queryByA11yState({ checked: false })).toBeFalsy();
99+
});
100+
101+
it('handles mixed', () => {
102+
const view = render(<View accessibilityState={{ checked: 'mixed' }} />);
103+
104+
expect(view.getByA11yState({ checked: 'mixed' })).toBeTruthy();
105+
expect(view.queryByA11yState({ checked: true })).toBeFalsy();
106+
expect(view.queryByA11yState({ checked: false })).toBeFalsy();
107+
});
108+
109+
it('handles false', () => {
110+
const view = render(<View accessibilityState={{ checked: false }} />);
111+
112+
expect(view.getByA11yState({ checked: false })).toBeTruthy();
113+
expect(view.queryByA11yState({ checked: true })).toBeFalsy();
114+
expect(view.queryByA11yState({ checked: 'mixed' })).toBeFalsy();
115+
});
116+
117+
it('handles default', () => {
118+
const view = render(<View accessibilityState={{}} />);
119+
120+
expect(view.queryByA11yState({ checked: false })).toBeFalsy();
121+
expect(view.queryByA11yState({ checked: true })).toBeFalsy();
122+
expect(view.queryByA11yState({ checked: 'mixed' })).toBeFalsy();
123+
});
124+
});
125+
126+
describe('expanded state matching', () => {
127+
it('handles true', () => {
128+
const view = render(<View accessibilityState={{ expanded: true }} />);
129+
130+
expect(view.getByA11yState({ expanded: true })).toBeTruthy();
131+
expect(view.queryByA11yState({ expanded: false })).toBeFalsy();
132+
});
133+
134+
it('handles false', () => {
135+
const view = render(<View accessibilityState={{ expanded: false }} />);
136+
137+
expect(view.getByA11yState({ expanded: false })).toBeTruthy();
138+
expect(view.queryByA11yState({ expanded: true })).toBeFalsy();
139+
});
140+
141+
it('handles default', () => {
142+
const view = render(<View accessibilityState={{}} />);
143+
144+
expect(view.queryByA11yState({ expanded: false })).toBeFalsy();
145+
expect(view.queryByA11yState({ expanded: true })).toBeFalsy();
146+
});
147+
});
148+
149+
describe('disabled state matching', () => {
150+
it('handles true', () => {
151+
const view = render(<View accessibilityState={{ disabled: true }} />);
152+
153+
expect(view.getByA11yState({ disabled: true })).toBeTruthy();
154+
expect(view.queryByA11yState({ disabled: false })).toBeFalsy();
155+
});
156+
157+
it('handles false', () => {
158+
const view = render(<View accessibilityState={{ disabled: false }} />);
159+
160+
expect(view.getByA11yState({ disabled: false })).toBeTruthy();
161+
expect(view.queryByA11yState({ disabled: true })).toBeFalsy();
162+
});
163+
164+
it('handles default', () => {
165+
const view = render(<View accessibilityState={{}} />);
166+
167+
expect(view.getByA11yState({ disabled: false })).toBeTruthy();
168+
expect(view.queryByA11yState({ disabled: true })).toBeFalsy();
169+
});
170+
});
171+
172+
describe('busy state matching', () => {
173+
it('handles true', () => {
174+
const view = render(<View accessibilityState={{ busy: true }} />);
175+
176+
expect(view.getByA11yState({ busy: true })).toBeTruthy();
177+
expect(view.queryByA11yState({ busy: false })).toBeFalsy();
178+
});
179+
180+
it('handles false', () => {
181+
const view = render(<View accessibilityState={{ busy: false }} />);
182+
183+
expect(view.getByA11yState({ busy: false })).toBeTruthy();
184+
expect(view.queryByA11yState({ busy: true })).toBeFalsy();
185+
});
186+
187+
it('handles default', () => {
188+
const view = render(<View accessibilityState={{}} />);
189+
190+
expect(view.getByA11yState({ busy: false })).toBeTruthy();
191+
expect(view.queryByA11yState({ busy: true })).toBeFalsy();
192+
});
193+
});
194+
195+
describe('selected state matching', () => {
196+
it('handles true', () => {
197+
const view = render(<View accessibilityState={{ selected: true }} />);
198+
199+
expect(view.getByA11yState({ selected: true })).toBeTruthy();
200+
expect(view.queryByA11yState({ selected: false })).toBeFalsy();
201+
});
202+
203+
it('handles false', () => {
204+
const view = render(<View accessibilityState={{ selected: false }} />);
205+
206+
expect(view.getByA11yState({ selected: false })).toBeTruthy();
207+
expect(view.queryByA11yState({ selected: true })).toBeFalsy();
208+
});
209+
210+
it('handles default', () => {
211+
const view = render(<View accessibilityState={{}} />);
212+
213+
expect(view.getByA11yState({ selected: false })).toBeTruthy();
214+
expect(view.queryByA11yState({ selected: true })).toBeFalsy();
215+
});
216+
});
217+
218+
test('*ByA11yState on Pressable with "disabled" prop', () => {
219+
const view = render(<Pressable disabled />);
220+
expect(view.getByA11yState({ disabled: true })).toBeTruthy();
221+
expect(view.queryByA11yState({ disabled: false })).toBeFalsy();
222+
});
223+
224+
test('*ByA11yState on TouchableOpacity with "disabled" prop', () => {
225+
const view = render(<TouchableOpacity disabled />);
226+
expect(view.getByA11yState({ disabled: true })).toBeTruthy();
227+
expect(view.queryByA11yState({ disabled: false })).toBeFalsy();
228+
});

src/queries/a11yState.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ReactTestInstance } from 'react-test-renderer';
22
import type { AccessibilityState } from 'react-native';
3-
import { matchObjectProp } from '../helpers/matchers/matchObjectProp';
3+
import { accessibilityStateKeys } from '../helpers/accessiblity';
4+
import { matchAccessibilityState } from '../helpers/matchers/accessibilityState';
45
import { makeQueries } from './makeQueries';
56
import type {
67
FindAllByQuery,
@@ -13,19 +14,31 @@ import type {
1314

1415
const queryAllByA11yState = (
1516
instance: ReactTestInstance
16-
): ((state: AccessibilityState) => Array<ReactTestInstance>) =>
17-
function queryAllByA11yStateFn(state) {
17+
): ((matcher: AccessibilityState) => Array<ReactTestInstance>) =>
18+
function queryAllByA11yStateFn(matcher) {
1819
return instance.findAll(
1920
(node) =>
20-
typeof node.type === 'string' &&
21-
matchObjectProp(node.props.accessibilityState, state)
21+
typeof node.type === 'string' && matchAccessibilityState(node, matcher)
2222
);
2323
};
2424

25+
const buildErrorMessage = (state: AccessibilityState = {}) => {
26+
const errors: string[] = [];
27+
28+
accessibilityStateKeys.forEach((stateKey) => {
29+
if (state[stateKey] !== undefined) {
30+
errors.push(`${stateKey} state: ${state[stateKey]}`);
31+
}
32+
});
33+
34+
return errors.join(', ');
35+
};
36+
2537
const getMultipleError = (state: AccessibilityState) =>
26-
`Found multiple elements with accessibilityState: ${JSON.stringify(state)}`;
38+
`Found multiple elements with ${buildErrorMessage(state)}`;
39+
2740
const getMissingError = (state: AccessibilityState) =>
28-
`Unable to find an element with accessibilityState: ${JSON.stringify(state)}`;
41+
`Unable to find an element with ${buildErrorMessage(state)}`;
2942

3043
const { getBy, getAllBy, queryBy, queryAllBy, findBy, findAllBy } = makeQueries(
3144
queryAllByA11yState,

0 commit comments

Comments
 (0)