Skip to content

Commit 36efd7e

Browse files
[v12] refactor: exclude hidden elements by defaults (#1317)
Co-authored-by: Michał Pierzchała <[email protected]>
1 parent 4f6d299 commit 36efd7e

19 files changed

+147
-57
lines changed

examples/basic/jest-setup.js

-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
/* eslint-disable no-undef, import/no-extraneous-dependencies */
2-
import { configure } from '@testing-library/react-native';
32

43
// Import Jest Native matchers
54
import '@testing-library/jest-native/extend-expect';
65

76
// Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing
87
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');
9-
10-
// Enable excluding hidden elements from the queries by default
11-
configure({
12-
defaultIncludeHiddenElements: false,
13-
});

examples/react-navigation/README.md

-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ There are two types of recommeded tests:
88
1. Tests operating on navigator level - these use `renderNavigator` helper to render a navigator component used in the app. It is useful when you want to test a scenario that includes multiple screens.
99
2. Tests operating on single screen level - these use regular `render` helper but require refactoring screen components into `Screen` and `ScreenContent` components. Where `Screen` receives React Navigation props and/or uses hooks like `useNavigation` while `ScreenContent` does not have a direct relation to React Navigation API but gets props from `Screen` and calls relevant callbacks to trigger navigation.
1010

11-
> Note that this example applies `includeHiddenElements: false` by default, so all queries will ignore elements on the hidden screens, e.g. inactive tabs or screens present in stack navigators. This option is enabled in `jest-setup.js` file, using `defaultIncludeHiddenElements: false` option to `configure` function.
12-
1311
## Non-recommended tests
1412

1513
There also exists another popular type of screen level tests, where users mock React Navigation objects like `navigation`, `route` and/or hooks like `useNavigation`, etc. We don't recommend this way of testing. **Mocking internal parts of the libraries is effectively testing implementation details, which goes against the Testing Library's [Guiding Principles](https://testing-library.com/docs/guiding-principles/)**.
-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
/* eslint-disable no-undef, import/no-extraneous-dependencies */
2-
import { configure } from '@testing-library/react-native';
32

43
// Import Jest Native matchers
54
import '@testing-library/jest-native/extend-expect';
@@ -10,8 +9,3 @@ jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');
109
// Setup Reanimated mocking for Drawer navigation
1110
global.ReanimatedDataMock = { now: () => Date.now() };
1211
require('react-native-reanimated/lib/reanimated2/jestUtils').setUpTests();
13-
14-
// Enable excluding hidden elements from the queries by default
15-
configure({
16-
defaultIncludeHiddenElements: false,
17-
});

src/__tests__/config.test.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77

88
test('getConfig() returns existing configuration', () => {
99
expect(getConfig().asyncUtilTimeout).toEqual(1000);
10-
expect(getConfig().defaultIncludeHiddenElements).toEqual(true);
10+
expect(getConfig().defaultIncludeHiddenElements).toEqual(false);
1111
});
1212

1313
test('configure() overrides existing config values', () => {
@@ -16,21 +16,21 @@ test('configure() overrides existing config values', () => {
1616
expect(getConfig()).toEqual({
1717
asyncUtilTimeout: 5000,
1818
defaultDebugOptions: { message: 'debug message' },
19-
defaultIncludeHiddenElements: true,
19+
defaultIncludeHiddenElements: false,
2020
});
2121
});
2222

2323
test('resetToDefaults() resets config to defaults', () => {
2424
configure({
2525
asyncUtilTimeout: 5000,
26-
defaultIncludeHiddenElements: false,
26+
defaultIncludeHiddenElements: true,
2727
});
2828
expect(getConfig().asyncUtilTimeout).toEqual(5000);
29-
expect(getConfig().defaultIncludeHiddenElements).toEqual(false);
29+
expect(getConfig().defaultIncludeHiddenElements).toEqual(true);
3030

3131
resetToDefaults();
3232
expect(getConfig().asyncUtilTimeout).toEqual(1000);
33-
expect(getConfig().defaultIncludeHiddenElements).toEqual(true);
33+
expect(getConfig().defaultIncludeHiddenElements).toEqual(false);
3434
});
3535

3636
test('resetToDefaults() resets internal config to defaults', () => {
@@ -44,8 +44,8 @@ test('resetToDefaults() resets internal config to defaults', () => {
4444
});
4545

4646
test('configure handles alias option defaultHidden', () => {
47-
expect(getConfig().defaultIncludeHiddenElements).toEqual(true);
48-
49-
configure({ defaultHidden: false });
5047
expect(getConfig().defaultIncludeHiddenElements).toEqual(false);
48+
49+
configure({ defaultHidden: true });
50+
expect(getConfig().defaultIncludeHiddenElements).toEqual(true);
5151
});

src/config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export type InternalConfig = Config & {
3232

3333
const defaultConfig: InternalConfig = {
3434
asyncUtilTimeout: 1000,
35-
defaultIncludeHiddenElements: true,
35+
defaultIncludeHiddenElements: false,
3636
};
3737

3838
let config = { ...defaultConfig };

src/helpers/__tests__/accessiblity.test.tsx

+114-18
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,25 @@ describe('isHiddenFromAccessibility', () => {
1414
test('returns false for accessible elements', () => {
1515
expect(
1616
isHiddenFromAccessibility(
17-
render(<View testID="subject" />).getByTestId('subject')
17+
render(<View testID="subject" />).getByTestId('subject', {
18+
includeHiddenElements: true,
19+
})
1820
)
1921
).toBe(false);
2022

2123
expect(
2224
isHiddenFromAccessibility(
23-
render(<Text testID="subject">Hello</Text>).getByTestId('subject')
25+
render(<Text testID="subject">Hello</Text>).getByTestId('subject', {
26+
includeHiddenElements: true,
27+
})
2428
)
2529
).toBe(false);
2630

2731
expect(
2832
isHiddenFromAccessibility(
29-
render(<TextInput testID="subject" />).getByTestId('subject')
33+
render(<TextInput testID="subject" />).getByTestId('subject', {
34+
includeHiddenElements: true,
35+
})
3036
)
3137
).toBe(false);
3238
});
@@ -37,7 +43,13 @@ describe('isHiddenFromAccessibility', () => {
3743

3844
test('detects elements with accessibilityElementsHidden prop', () => {
3945
const view = render(<View testID="subject" accessibilityElementsHidden />);
40-
expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true);
46+
expect(
47+
isHiddenFromAccessibility(
48+
view.getByTestId('subject', {
49+
includeHiddenElements: true,
50+
})
51+
)
52+
).toBe(true);
4153
});
4254

4355
test('detects nested elements with accessibilityElementsHidden prop', () => {
@@ -46,7 +58,13 @@ describe('isHiddenFromAccessibility', () => {
4658
<View testID="subject" />
4759
</View>
4860
);
49-
expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true);
61+
expect(
62+
isHiddenFromAccessibility(
63+
view.getByTestId('subject', {
64+
includeHiddenElements: true,
65+
})
66+
)
67+
).toBe(true);
5068
});
5169

5270
test('detects deeply nested elements with accessibilityElementsHidden prop', () => {
@@ -59,14 +77,26 @@ describe('isHiddenFromAccessibility', () => {
5977
</View>
6078
</View>
6179
);
62-
expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true);
80+
expect(
81+
isHiddenFromAccessibility(
82+
view.getByTestId('subject', {
83+
includeHiddenElements: true,
84+
})
85+
)
86+
).toBe(true);
6387
});
6488

6589
test('detects elements with importantForAccessibility="no-hide-descendants" prop', () => {
6690
const view = render(
6791
<View testID="subject" importantForAccessibility="no-hide-descendants" />
6892
);
69-
expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true);
93+
expect(
94+
isHiddenFromAccessibility(
95+
view.getByTestId('subject', {
96+
includeHiddenElements: true,
97+
})
98+
)
99+
).toBe(true);
70100
});
71101

72102
test('detects nested elements with importantForAccessibility="no-hide-descendants" prop', () => {
@@ -75,12 +105,24 @@ describe('isHiddenFromAccessibility', () => {
75105
<View testID="subject" />
76106
</View>
77107
);
78-
expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true);
108+
expect(
109+
isHiddenFromAccessibility(
110+
view.getByTestId('subject', {
111+
includeHiddenElements: true,
112+
})
113+
)
114+
).toBe(true);
79115
});
80116

81117
test('detects elements with display=none', () => {
82118
const view = render(<View testID="subject" style={{ display: 'none' }} />);
83-
expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true);
119+
expect(
120+
isHiddenFromAccessibility(
121+
view.getByTestId('subject', {
122+
includeHiddenElements: true,
123+
})
124+
)
125+
).toBe(true);
84126
});
85127

86128
test('detects nested elements with display=none', () => {
@@ -89,7 +131,13 @@ describe('isHiddenFromAccessibility', () => {
89131
<View testID="subject" />
90132
</View>
91133
);
92-
expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true);
134+
expect(
135+
isHiddenFromAccessibility(
136+
view.getByTestId('subject', {
137+
includeHiddenElements: true,
138+
})
139+
)
140+
).toBe(true);
93141
});
94142

95143
test('detects deeply nested elements with display=none', () => {
@@ -102,7 +150,13 @@ describe('isHiddenFromAccessibility', () => {
102150
</View>
103151
</View>
104152
);
105-
expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true);
153+
expect(
154+
isHiddenFromAccessibility(
155+
view.getByTestId('subject', {
156+
includeHiddenElements: true,
157+
})
158+
)
159+
).toBe(true);
106160
});
107161

108162
test('detects elements with display=none with complex style', () => {
@@ -116,12 +170,24 @@ describe('isHiddenFromAccessibility', () => {
116170
]}
117171
/>
118172
);
119-
expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true);
173+
expect(
174+
isHiddenFromAccessibility(
175+
view.getByTestId('subject', {
176+
includeHiddenElements: true,
177+
})
178+
)
179+
).toBe(true);
120180
});
121181

122182
test('is not trigged by opacity = 0', () => {
123183
const view = render(<View testID="subject" style={{ opacity: 0 }} />);
124-
expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(false);
184+
expect(
185+
isHiddenFromAccessibility(
186+
view.getByTestId('subject', {
187+
includeHiddenElements: true,
188+
})
189+
)
190+
).toBe(false);
125191
});
126192

127193
test('detects siblings of element with accessibilityViewIsModal prop', () => {
@@ -131,7 +197,13 @@ describe('isHiddenFromAccessibility', () => {
131197
<View testID="subject" />
132198
</View>
133199
);
134-
expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true);
200+
expect(
201+
isHiddenFromAccessibility(
202+
view.getByTestId('subject', {
203+
includeHiddenElements: true,
204+
})
205+
)
206+
).toBe(true);
135207
});
136208

137209
test('detects deeply nested siblings of element with accessibilityViewIsModal prop', () => {
@@ -145,7 +217,13 @@ describe('isHiddenFromAccessibility', () => {
145217
</View>
146218
</View>
147219
);
148-
expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(true);
220+
expect(
221+
isHiddenFromAccessibility(
222+
view.getByTestId('subject', {
223+
includeHiddenElements: true,
224+
})
225+
)
226+
).toBe(true);
149227
});
150228

151229
test('is not triggered for element with accessibilityViewIsModal prop', () => {
@@ -154,7 +232,13 @@ describe('isHiddenFromAccessibility', () => {
154232
<View accessibilityViewIsModal testID="subject" />
155233
</View>
156234
);
157-
expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(false);
235+
expect(
236+
isHiddenFromAccessibility(
237+
view.getByTestId('subject', {
238+
includeHiddenElements: true,
239+
})
240+
)
241+
).toBe(false);
158242
});
159243

160244
test('is not triggered for child of element with accessibilityViewIsModal prop', () => {
@@ -165,7 +249,13 @@ describe('isHiddenFromAccessibility', () => {
165249
</View>
166250
</View>
167251
);
168-
expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(false);
252+
expect(
253+
isHiddenFromAccessibility(
254+
view.getByTestId('subject', {
255+
includeHiddenElements: true,
256+
})
257+
)
258+
).toBe(false);
169259
});
170260

171261
test('is not triggered for descendent of element with accessibilityViewIsModal prop', () => {
@@ -180,7 +270,13 @@ describe('isHiddenFromAccessibility', () => {
180270
</View>
181271
</View>
182272
);
183-
expect(isHiddenFromAccessibility(view.getByTestId('subject'))).toBe(false);
273+
expect(
274+
isHiddenFromAccessibility(
275+
view.getByTestId('subject', {
276+
includeHiddenElements: true,
277+
})
278+
)
279+
).toBe(false);
184280
});
185281

186282
test('has isInaccessible alias', () => {

src/helpers/accessiblity.ts

+5
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ export function isHiddenFromAccessibility(
5959
export const isInaccessible = isHiddenFromAccessibility;
6060

6161
function isSubtreeInaccessible(element: ReactTestInstance): boolean {
62+
// Null props can happen for React.Fragments
63+
if (element.props == null) {
64+
return false;
65+
}
66+
6267
// iOS: accessibilityElementsHidden
6368
// See: https://reactnative.dev/docs/accessibility#accessibilityelementshidden-ios
6469
if (element.props.accessibilityElementsHidden) {

src/queries/__tests__/a11yState.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,11 @@ test('byA11yState queries support hidden option', () => {
244244
</Pressable>
245245
);
246246

247-
expect(getByA11yState({ expanded: false })).toBeTruthy();
248247
expect(
249248
getByA11yState({ expanded: false }, { includeHiddenElements: true })
250249
).toBeTruthy();
251250

251+
expect(queryByA11yState({ expanded: false })).toBeFalsy();
252252
expect(
253253
queryByA11yState({ expanded: false }, { includeHiddenElements: false })
254254
).toBeFalsy();

src/queries/__tests__/a11yValue.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,11 @@ test('byA11yValue queries support hidden option', () => {
9999
</Text>
100100
);
101101

102-
expect(getByA11yValue({ max: 10 })).toBeTruthy();
103102
expect(
104103
getByA11yValue({ max: 10 }, { includeHiddenElements: true })
105104
).toBeTruthy();
106105

106+
expect(queryByA11yValue({ max: 10 })).toBeFalsy();
107107
expect(
108108
queryByA11yValue({ max: 10 }, { includeHiddenElements: false })
109109
).toBeFalsy();

src/queries/__tests__/displayValue.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,11 @@ test('byDisplayValue queries support hidden option', () => {
111111
<TextInput value="hidden" style={{ display: 'none' }} />
112112
);
113113

114-
expect(getByDisplayValue('hidden')).toBeTruthy();
115114
expect(
116115
getByDisplayValue('hidden', { includeHiddenElements: true })
117116
).toBeTruthy();
118117

118+
expect(queryByDisplayValue('hidden')).toBeFalsy();
119119
expect(
120120
queryByDisplayValue('hidden', { includeHiddenElements: false })
121121
).toBeFalsy();

0 commit comments

Comments
 (0)