Skip to content

Commit 04c7ee4

Browse files
feat: support Image with alt prop as label (#1665)
* refactor: detect Image host component name * feat: support Image alt prop in *ByLabelText * feat: support *ByRole queries * feat: support toHaveAccessibleName matcher * chore: fix typecheck * refactor: self code review * refactor: adjust finding to experiment results * chore: add missing tests * refactor: self code review * refactor: self code review * refactor: self-code review
1 parent f69d1d5 commit 04c7ee4

14 files changed

+255
-122
lines changed

src/__tests__/config.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ test('resetToDefaults() resets internal config to defaults', () => {
3333
hostComponentNames: {
3434
text: 'A',
3535
textInput: 'A',
36+
image: 'A',
3637
switch: 'A',
3738
scrollView: 'A',
3839
modal: 'A',
@@ -41,6 +42,7 @@ test('resetToDefaults() resets internal config to defaults', () => {
4142
expect(getConfig().hostComponentNames).toEqual({
4243
text: 'A',
4344
textInput: 'A',
45+
image: 'A',
4446
switch: 'A',
4547
scrollView: 'A',
4648
modal: 'A',

src/__tests__/host-component-names.test.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ describe('getHostComponentNames', () => {
1414
hostComponentNames: {
1515
text: 'banana',
1616
textInput: 'banana',
17+
image: 'banana',
1718
switch: 'banana',
1819
scrollView: 'banana',
1920
modal: 'banana',
@@ -23,6 +24,7 @@ describe('getHostComponentNames', () => {
2324
expect(getHostComponentNames()).toEqual({
2425
text: 'banana',
2526
textInput: 'banana',
27+
image: 'banana',
2628
switch: 'banana',
2729
scrollView: 'banana',
2830
modal: 'banana',
@@ -37,6 +39,7 @@ describe('getHostComponentNames', () => {
3739
expect(hostComponentNames).toEqual({
3840
text: 'Text',
3941
textInput: 'TextInput',
42+
image: 'Image',
4043
switch: 'RCTSwitch',
4144
scrollView: 'RCTScrollView',
4245
modal: 'Modal',
@@ -67,6 +70,7 @@ describe('configureHostComponentNamesIfNeeded', () => {
6770
expect(getConfig().hostComponentNames).toEqual({
6871
text: 'Text',
6972
textInput: 'TextInput',
73+
image: 'Image',
7074
switch: 'RCTSwitch',
7175
scrollView: 'RCTScrollView',
7276
modal: 'Modal',
@@ -78,6 +82,7 @@ describe('configureHostComponentNamesIfNeeded', () => {
7882
hostComponentNames: {
7983
text: 'banana',
8084
textInput: 'banana',
85+
image: 'banana',
8186
switch: 'banana',
8287
scrollView: 'banana',
8388
modal: 'banana',
@@ -89,13 +94,14 @@ describe('configureHostComponentNamesIfNeeded', () => {
8994
expect(getConfig().hostComponentNames).toEqual({
9095
text: 'banana',
9196
textInput: 'banana',
97+
image: 'banana',
9298
switch: 'banana',
9399
scrollView: 'banana',
94100
modal: 'banana',
95101
});
96102
});
97103

98-
test('throw an error when autodetection fails', () => {
104+
test('throw an error when auto-detection fails', () => {
99105
const mockCreate = jest.spyOn(TestRenderer, 'create') as jest.Mock;
100106
const renderer = TestRenderer.create(<View />);
101107

src/__tests__/react-native-api.test.tsx

Lines changed: 119 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import * as React from 'react';
2-
import { FlatList, ScrollView, Switch, Text, TextInput, View } from 'react-native';
2+
import { FlatList, Image, Modal, ScrollView, Switch, Text, TextInput, View } from 'react-native';
33
import { render, screen } from '..';
44

55
/**
66
* Tests in this file are intended to give us an proactive warning that React Native behavior has
77
* changed in a way that may impact our code like queries or event handling.
88
*/
99

10-
test('React Native API assumption: <View> renders single host element', () => {
10+
test('React Native API assumption: <View> renders a single host element', () => {
1111
render(<View testID="test" />);
1212

1313
expect(screen.toJSON()).toMatchInlineSnapshot(`
@@ -17,7 +17,7 @@ test('React Native API assumption: <View> renders single host element', () => {
1717
`);
1818
});
1919

20-
test('React Native API assumption: <Text> renders single host element', () => {
20+
test('React Native API assumption: <Text> renders a single host element', () => {
2121
render(<Text testID="test">Hello</Text>);
2222

2323
expect(screen.toJSON()).toMatchInlineSnapshot(`
@@ -29,7 +29,7 @@ test('React Native API assumption: <Text> renders single host element', () => {
2929
`);
3030
});
3131

32-
test('React Native API assumption: nested <Text> renders single host element', () => {
32+
test('React Native API assumption: nested <Text> renders a single host element', () => {
3333
render(
3434
<Text testID="test">
3535
<Text testID="before">Before</Text>
@@ -63,7 +63,7 @@ test('React Native API assumption: nested <Text> renders single host element', (
6363
`);
6464
});
6565

66-
test('React Native API assumption: <TextInput> renders single host element', () => {
66+
test('React Native API assumption: <TextInput> renders a single host element', () => {
6767
render(
6868
<TextInput
6969
testID="test"
@@ -102,7 +102,7 @@ test('React Native API assumption: <TextInput> with nested Text renders single h
102102
`);
103103
});
104104

105-
test('React Native API assumption: <Switch> renders single host element', () => {
105+
test('React Native API assumption: <Switch> renders a single host element', () => {
106106
render(<Switch testID="test" value={true} onChange={jest.fn()} />);
107107

108108
expect(screen.toJSON()).toMatchInlineSnapshot(`
@@ -123,7 +123,117 @@ test('React Native API assumption: <Switch> renders single host element', () =>
123123
`);
124124
});
125125

126-
test('React Native API assumption: aria-* props render on host View', () => {
126+
test('React Native API assumption: <Image> renders a single host element', () => {
127+
render(<Image testID="test" source={{ uri: 'https://fake.url/image.jpg' }} alt="Alt text" />);
128+
129+
expect(screen.toJSON()).toMatchInlineSnapshot(`
130+
<Image
131+
alt="Alt text"
132+
source={
133+
{
134+
"uri": "https://fake.url/image.jpg",
135+
}
136+
}
137+
testID="test"
138+
/>
139+
`);
140+
});
141+
142+
test('React Native API assumption: <ScrollView> renders a single host element', () => {
143+
render(
144+
<ScrollView testID="scrollView">
145+
<View testID="view" />
146+
</ScrollView>,
147+
);
148+
149+
expect(screen.toJSON()).toMatchInlineSnapshot(`
150+
<RCTScrollView
151+
testID="scrollView"
152+
>
153+
<View>
154+
<View
155+
testID="view"
156+
/>
157+
</View>
158+
</RCTScrollView>
159+
`);
160+
});
161+
162+
test('React Native API assumption: <FlatList> renders a single host <ScrollView> element', () => {
163+
render(
164+
<FlatList testID="flatList" data={[1, 2]} renderItem={({ item }) => <Text>{item}</Text>} />,
165+
);
166+
167+
expect(screen.toJSON()).toMatchInlineSnapshot(`
168+
<RCTScrollView
169+
data={
170+
[
171+
1,
172+
2,
173+
]
174+
}
175+
getItem={[Function]}
176+
getItemCount={[Function]}
177+
keyExtractor={[Function]}
178+
onContentSizeChange={[Function]}
179+
onLayout={[Function]}
180+
onMomentumScrollBegin={[Function]}
181+
onMomentumScrollEnd={[Function]}
182+
onScroll={[Function]}
183+
onScrollBeginDrag={[Function]}
184+
onScrollEndDrag={[Function]}
185+
removeClippedSubviews={false}
186+
renderItem={[Function]}
187+
scrollEventThrottle={0.0001}
188+
stickyHeaderIndices={[]}
189+
testID="flatList"
190+
viewabilityConfigCallbackPairs={[]}
191+
>
192+
<View>
193+
<View
194+
onFocusCapture={[Function]}
195+
onLayout={[Function]}
196+
style={null}
197+
>
198+
<Text>
199+
1
200+
</Text>
201+
</View>
202+
<View
203+
onFocusCapture={[Function]}
204+
onLayout={[Function]}
205+
style={null}
206+
>
207+
<Text>
208+
2
209+
</Text>
210+
</View>
211+
</View>
212+
</RCTScrollView>
213+
`);
214+
});
215+
216+
test('React Native API assumption: <Modal> renders a single host element', () => {
217+
render(
218+
<Modal testID="test">
219+
<Text>Modal Content</Text>
220+
</Modal>,
221+
);
222+
223+
expect(screen.toJSON()).toMatchInlineSnapshot(`
224+
<Modal
225+
hardwareAccelerated={false}
226+
testID="test"
227+
visible={true}
228+
>
229+
<Text>
230+
Modal Content
231+
</Text>
232+
</Modal>
233+
`);
234+
});
235+
236+
test('React Native API assumption: aria-* props render directly on host View', () => {
127237
render(
128238
<View
129239
testID="test"
@@ -171,7 +281,7 @@ test('React Native API assumption: aria-* props render on host View', () => {
171281
`);
172282
});
173283

174-
test('React Native API assumption: aria-* props render on host Text', () => {
284+
test('React Native API assumption: aria-* props render directly on host Text', () => {
175285
render(
176286
<Text
177287
testID="test"
@@ -219,7 +329,7 @@ test('React Native API assumption: aria-* props render on host Text', () => {
219329
`);
220330
});
221331

222-
test('React Native API assumption: aria-* props render on host TextInput', () => {
332+
test('React Native API assumption: aria-* props render directly on host TextInput', () => {
223333
render(
224334
<TextInput
225335
testID="test"
@@ -266,77 +376,3 @@ test('React Native API assumption: aria-* props render on host TextInput', () =>
266376
/>
267377
`);
268378
});
269-
270-
test('ScrollView renders correctly', () => {
271-
render(
272-
<ScrollView testID="scrollView">
273-
<View testID="view" />
274-
</ScrollView>,
275-
);
276-
277-
expect(screen.toJSON()).toMatchInlineSnapshot(`
278-
<RCTScrollView
279-
testID="scrollView"
280-
>
281-
<View>
282-
<View
283-
testID="view"
284-
/>
285-
</View>
286-
</RCTScrollView>
287-
`);
288-
});
289-
290-
test('FlatList renders correctly', () => {
291-
render(
292-
<FlatList testID="flatList" data={[1, 2]} renderItem={({ item }) => <Text>{item}</Text>} />,
293-
);
294-
295-
expect(screen.toJSON()).toMatchInlineSnapshot(`
296-
<RCTScrollView
297-
data={
298-
[
299-
1,
300-
2,
301-
]
302-
}
303-
getItem={[Function]}
304-
getItemCount={[Function]}
305-
keyExtractor={[Function]}
306-
onContentSizeChange={[Function]}
307-
onLayout={[Function]}
308-
onMomentumScrollBegin={[Function]}
309-
onMomentumScrollEnd={[Function]}
310-
onScroll={[Function]}
311-
onScrollBeginDrag={[Function]}
312-
onScrollEndDrag={[Function]}
313-
removeClippedSubviews={false}
314-
renderItem={[Function]}
315-
scrollEventThrottle={0.0001}
316-
stickyHeaderIndices={[]}
317-
testID="flatList"
318-
viewabilityConfigCallbackPairs={[]}
319-
>
320-
<View>
321-
<View
322-
onFocusCapture={[Function]}
323-
onLayout={[Function]}
324-
style={null}
325-
>
326-
<Text>
327-
1
328-
</Text>
329-
</View>
330-
<View
331-
onFocusCapture={[Function]}
332-
onLayout={[Function]}
333-
style={null}
334-
>
335-
<Text>
336-
2
337-
</Text>
338-
</View>
339-
</View>
340-
</RCTScrollView>
341-
`);
342-
});

src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export type ConfigAliasOptions = {
2323
export type HostComponentNames = {
2424
text: string;
2525
textInput: string;
26+
image: string;
2627
switch: string;
2728
scrollView: string;
2829
modal: string;

0 commit comments

Comments
 (0)