Skip to content

Commit 47b125c

Browse files
refactor(breaking): rename container to UNSAFE_root; introduce root host element (#1298)
1 parent 1e8b3ba commit 47b125c

File tree

12 files changed

+383
-14
lines changed

12 files changed

+383
-14
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`toJSON renders host output 1`] = `
4+
<View
5+
accessibilityState={
6+
{
7+
"busy": undefined,
8+
"checked": undefined,
9+
"disabled": undefined,
10+
"expanded": undefined,
11+
"selected": undefined,
12+
}
13+
}
14+
accessibilityValue={
15+
{
16+
"max": undefined,
17+
"min": undefined,
18+
"now": undefined,
19+
"text": undefined,
20+
}
21+
}
22+
accessible={true}
23+
collapsable={false}
24+
focusable={true}
25+
onBlur={[Function]}
26+
onClick={[Function]}
27+
onFocus={[Function]}
28+
onResponderGrant={[Function]}
29+
onResponderMove={[Function]}
30+
onResponderRelease={[Function]}
31+
onResponderTerminate={[Function]}
32+
onResponderTerminationRequest={[Function]}
33+
onStartShouldSetResponder={[Function]}
34+
>
35+
<Text>
36+
press me
37+
</Text>
38+
</View>
39+
`;

src/__tests__/__snapshots__/render.test.tsx.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`toJSON 1`] = `
3+
exports[`toJSON renders host output 1`] = `
44
<View
55
accessibilityState={
66
{
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
/** This is a copy of regular tests with `useBreakingChanges` flag turned on. */
2+
3+
/* eslint-disable no-console */
4+
import * as React from 'react';
5+
import { View, Text, TextInput, Pressable } from 'react-native';
6+
import { render, screen, fireEvent, RenderAPI } from '..';
7+
import { configureInternal } from '../config';
8+
9+
beforeEach(() => {
10+
configureInternal({ useBreakingChanges: true });
11+
});
12+
13+
const PLACEHOLDER_FRESHNESS = 'Add custom freshness';
14+
const PLACEHOLDER_CHEF = 'Who inspected freshness?';
15+
const INPUT_FRESHNESS = 'Custom Freshie';
16+
const INPUT_CHEF = 'I inspected freshie';
17+
const DEFAULT_INPUT_CHEF = 'What did you inspect?';
18+
const DEFAULT_INPUT_CUSTOMER = 'What banana?';
19+
20+
class MyButton extends React.Component<any> {
21+
render() {
22+
return (
23+
<Pressable onPress={this.props.onPress}>
24+
<Text>{this.props.children}</Text>
25+
</Pressable>
26+
);
27+
}
28+
}
29+
30+
class Banana extends React.Component<any, { fresh: boolean }> {
31+
state = {
32+
fresh: false,
33+
};
34+
35+
componentDidUpdate() {
36+
if (this.props.onUpdate) {
37+
this.props.onUpdate();
38+
}
39+
}
40+
41+
componentWillUnmount() {
42+
if (this.props.onUnmount) {
43+
this.props.onUnmount();
44+
}
45+
}
46+
47+
changeFresh = () => {
48+
this.setState((state) => ({
49+
fresh: !state.fresh,
50+
}));
51+
};
52+
53+
render() {
54+
const test = 0;
55+
return (
56+
<View>
57+
<Text>Is the banana fresh?</Text>
58+
<Text testID="bananaFresh">
59+
{this.state.fresh ? 'fresh' : 'not fresh'}
60+
</Text>
61+
<TextInput
62+
testID="bananaCustomFreshness"
63+
placeholder={PLACEHOLDER_FRESHNESS}
64+
value={INPUT_FRESHNESS}
65+
/>
66+
<TextInput
67+
testID="bananaChef"
68+
placeholder={PLACEHOLDER_CHEF}
69+
value={INPUT_CHEF}
70+
defaultValue={DEFAULT_INPUT_CHEF}
71+
/>
72+
<TextInput defaultValue={DEFAULT_INPUT_CUSTOMER} />
73+
<TextInput defaultValue={'hello'} value="" />
74+
<MyButton onPress={this.changeFresh} type="primary">
75+
Change freshness!
76+
</MyButton>
77+
<Text testID="duplicateText">First Text</Text>
78+
<Text testID="duplicateText">Second Text</Text>
79+
<Text>{test}</Text>
80+
</View>
81+
);
82+
}
83+
}
84+
85+
test('UNSAFE_getAllByType, UNSAFE_queryAllByType', () => {
86+
const { UNSAFE_getAllByType, UNSAFE_queryAllByType } = render(<Banana />);
87+
const [text, status, button] = UNSAFE_getAllByType(Text);
88+
const InExistent = () => null;
89+
90+
expect(text.props.children).toBe('Is the banana fresh?');
91+
expect(status.props.children).toBe('not fresh');
92+
expect(button.props.children).toBe('Change freshness!');
93+
expect(() => UNSAFE_getAllByType(InExistent)).toThrow('No instances found');
94+
95+
expect(UNSAFE_queryAllByType(Text)[1]).toBe(status);
96+
expect(UNSAFE_queryAllByType(InExistent)).toHaveLength(0);
97+
});
98+
99+
test('UNSAFE_getByProps, UNSAFE_queryByProps', () => {
100+
const { UNSAFE_getByProps, UNSAFE_queryByProps } = render(<Banana />);
101+
const primaryType = UNSAFE_getByProps({ type: 'primary' });
102+
103+
expect(primaryType.props.children).toBe('Change freshness!');
104+
expect(() => UNSAFE_getByProps({ type: 'inexistent' })).toThrow(
105+
'No instances found'
106+
);
107+
108+
expect(UNSAFE_queryByProps({ type: 'primary' })).toBe(primaryType);
109+
expect(UNSAFE_queryByProps({ type: 'inexistent' })).toBeNull();
110+
});
111+
112+
test('UNSAFE_getAllByProp, UNSAFE_queryAllByProps', () => {
113+
const { UNSAFE_getAllByProps, UNSAFE_queryAllByProps } = render(<Banana />);
114+
const primaryTypes = UNSAFE_getAllByProps({ type: 'primary' });
115+
116+
expect(primaryTypes).toHaveLength(1);
117+
expect(() => UNSAFE_getAllByProps({ type: 'inexistent' })).toThrow(
118+
'No instances found'
119+
);
120+
121+
expect(UNSAFE_queryAllByProps({ type: 'primary' })).toEqual(primaryTypes);
122+
expect(UNSAFE_queryAllByProps({ type: 'inexistent' })).toHaveLength(0);
123+
});
124+
125+
test('update', () => {
126+
const fn = jest.fn();
127+
const { getByText, update, rerender } = render(<Banana onUpdate={fn} />);
128+
129+
fireEvent.press(getByText('Change freshness!'));
130+
131+
update(<Banana onUpdate={fn} />);
132+
rerender(<Banana onUpdate={fn} />);
133+
134+
expect(fn).toHaveBeenCalledTimes(3);
135+
});
136+
137+
test('unmount', () => {
138+
const fn = jest.fn();
139+
const { unmount } = render(<Banana onUnmount={fn} />);
140+
unmount();
141+
expect(fn).toHaveBeenCalled();
142+
});
143+
144+
test('unmount should handle cleanup functions', () => {
145+
const cleanup = jest.fn();
146+
const Component = () => {
147+
React.useEffect(() => cleanup);
148+
return null;
149+
};
150+
151+
const { unmount } = render(<Component />);
152+
153+
unmount();
154+
155+
expect(cleanup).toHaveBeenCalledTimes(1);
156+
});
157+
158+
test('toJSON renders host output', () => {
159+
const { toJSON } = render(<MyButton>press me</MyButton>);
160+
expect(toJSON()).toMatchSnapshot();
161+
});
162+
163+
test('renders options.wrapper around node', () => {
164+
type WrapperComponentProps = { children: React.ReactNode };
165+
const WrapperComponent = ({ children }: WrapperComponentProps) => (
166+
<View testID="wrapper">{children}</View>
167+
);
168+
169+
const { toJSON, getByTestId } = render(<View testID="inner" />, {
170+
wrapper: WrapperComponent,
171+
});
172+
173+
expect(getByTestId('wrapper')).toBeTruthy();
174+
expect(toJSON()).toMatchInlineSnapshot(`
175+
<View
176+
testID="wrapper"
177+
>
178+
<View
179+
testID="inner"
180+
/>
181+
</View>
182+
`);
183+
});
184+
185+
test('renders options.wrapper around updated node', () => {
186+
type WrapperComponentProps = { children: React.ReactNode };
187+
const WrapperComponent = ({ children }: WrapperComponentProps) => (
188+
<View testID="wrapper">{children}</View>
189+
);
190+
191+
const { toJSON, getByTestId, rerender } = render(<View testID="inner" />, {
192+
wrapper: WrapperComponent,
193+
});
194+
195+
rerender(
196+
<View testID="inner" accessibilityLabel="test" accessibilityHint="test" />
197+
);
198+
199+
expect(getByTestId('wrapper')).toBeTruthy();
200+
expect(toJSON()).toMatchInlineSnapshot(`
201+
<View
202+
testID="wrapper"
203+
>
204+
<View
205+
accessibilityHint="test"
206+
accessibilityLabel="test"
207+
testID="inner"
208+
/>
209+
</View>
210+
`);
211+
});
212+
213+
test('returns host root', () => {
214+
const { root } = render(<View testID="inner" />);
215+
216+
expect(root).toBeDefined();
217+
expect(root.type).toBe('View');
218+
expect(root.props.testID).toBe('inner');
219+
});
220+
221+
test('returns composite UNSAFE_root', () => {
222+
const { UNSAFE_root } = render(<View testID="inner" />);
223+
224+
expect(UNSAFE_root).toBeDefined();
225+
expect(UNSAFE_root.type).toBe(View);
226+
expect(UNSAFE_root.props.testID).toBe('inner');
227+
});
228+
229+
test('container displays deprecation', () => {
230+
const view = render(<View testID="inner" />);
231+
232+
expect(() => view.container).toThrowErrorMatchingInlineSnapshot(`
233+
"'container' property has been renamed to 'UNSAFE_root'.
234+
235+
Consider using 'root' property which returns root host element."
236+
`);
237+
expect(() => screen.container).toThrowErrorMatchingInlineSnapshot(`
238+
"'container' property has been renamed to 'UNSAFE_root'.
239+
240+
Consider using 'root' property which returns root host element."
241+
`);
242+
});
243+
244+
test('RenderAPI type', () => {
245+
render(<Banana />) as RenderAPI;
246+
expect(true).toBeTruthy();
247+
});

src/__tests__/render.test.tsx

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import * as React from 'react';
33
import { View, Text, TextInput, Pressable, SafeAreaView } from 'react-native';
44
import { render, fireEvent, RenderAPI } from '..';
55

6+
type ConsoleLogMock = jest.Mock<typeof console.log>;
7+
8+
beforeEach(() => {
9+
jest.spyOn(console, 'warn').mockImplementation(() => {});
10+
});
11+
612
const PLACEHOLDER_FRESHNESS = 'Add custom freshness';
713
const PLACEHOLDER_CHEF = 'Who inspected freshness?';
814
const INPUT_FRESHNESS = 'Custom Freshie';
@@ -148,7 +154,7 @@ test('unmount should handle cleanup functions', () => {
148154
expect(cleanup).toHaveBeenCalledTimes(1);
149155
});
150156

151-
test('toJSON', () => {
157+
test('toJSON renders host output', () => {
152158
const { toJSON } = render(<MyButton>press me</MyButton>);
153159

154160
expect(toJSON()).toMatchSnapshot();
@@ -204,17 +210,40 @@ test('renders options.wrapper around updated node', () => {
204210
`);
205211
});
206212

213+
test('returns host root', () => {
214+
const { root } = render(<View testID="inner" />);
215+
216+
expect(root).toBeDefined();
217+
expect(root.type).toBe('View');
218+
expect(root.props.testID).toBe('inner');
219+
});
220+
221+
test('returns composite UNSAFE_root', () => {
222+
const { UNSAFE_root } = render(<View testID="inner" />);
223+
224+
expect(UNSAFE_root).toBeDefined();
225+
expect(UNSAFE_root.type).toBe(View);
226+
expect(UNSAFE_root.props.testID).toBe('inner');
227+
});
228+
207229
test('returns container', () => {
208230
const { container } = render(<View testID="inner" />);
209231

232+
const mockCalls = (console.warn as any as ConsoleLogMock).mock.calls;
233+
expect(mockCalls[0][0]).toMatchInlineSnapshot(`
234+
"'container' property is deprecated and has been renamed to 'UNSAFE_root'.
235+
236+
Consider using 'root' property which returns root host element."
237+
`);
238+
210239
expect(container).toBeDefined();
211240
// `View` composite component is returned. This behavior will break if we
212241
// start returning only host components.
213242
expect(container.type).toBe(View);
214243
expect(container.props.testID).toBe('inner');
215244
});
216245

217-
test('returns wrapped component as container', () => {
246+
test('returns wrapper component as container', () => {
218247
type WrapperComponentProps = { children: React.ReactNode };
219248
const WrapperComponent = ({ children }: WrapperComponentProps) => (
220249
<SafeAreaView testID="wrapper">{children}</SafeAreaView>

src/__tests__/screen.test.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ test('screen works with nested re-mounting rerender', () => {
5252
});
5353

5454
test('screen throws without render', () => {
55+
expect(() => screen.root).toThrow('`render` method has not been called');
56+
expect(() => screen.UNSAFE_root).toThrow(
57+
'`render` method has not been called'
58+
);
5559
expect(() => screen.container).toThrow('`render` method has not been called');
5660
expect(() => screen.debug()).toThrow('`render` method has not been called');
5761
expect(() => screen.debug.shallow()).toThrow(

src/queries/__tests__/displayValue.breaking.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/** This is a copy of regular tests with `useBreakingChanges` flag turned on. */
2+
13
import * as React from 'react';
24
import { View, TextInput } from 'react-native';
35

src/queries/__tests__/placeholderText.breaking.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/** This is a copy of regular tests with `useBreakingChanges` flag turned on. */
2+
13
import * as React from 'react';
24
import { View, TextInput } from 'react-native';
35
import { render } from '../..';

0 commit comments

Comments
 (0)