@@ -3,82 +3,290 @@ import { Text, View } from 'react-native';
33
44import { render , screen } from '..' ;
55
6- class Banana extends React . Component < any , { fresh : boolean } > {
7- state = {
8- fresh : false ,
9- } ;
10-
11- componentDidUpdate ( ) {
12- if ( this . props . onUpdate ) {
13- this . props . onUpdate ( ) ;
6+ test ( 'renders a simple component' , async ( ) => {
7+ const TestComponent = ( ) => (
8+ < View testID = "container" >
9+ < Text > Hello World</ Text >
10+ </ View >
11+ ) ;
12+
13+ await render ( < TestComponent /> ) ;
14+
15+ expect ( screen . getByTestId ( 'container' ) ) . toBeOnTheScreen ( ) ;
16+ expect ( screen . getByText ( 'Hello World' ) ) . toBeOnTheScreen ( ) ;
17+ } ) ;
18+
19+ describe ( 'render options' , ( ) => {
20+ test ( 'renders component with wrapper option' , async ( ) => {
21+ const TestComponent = ( ) => < Text testID = "inner" > Inner Content</ Text > ;
22+ const Wrapper = ( { children } : { children : React . ReactNode } ) => (
23+ < View testID = "wrapper" > { children } </ View >
24+ ) ;
25+
26+ await render ( < TestComponent /> , { wrapper : Wrapper } ) ;
27+
28+ expect ( screen . getByTestId ( 'wrapper' ) ) . toBeOnTheScreen ( ) ;
29+ expect ( screen . getByTestId ( 'inner' ) ) . toBeOnTheScreen ( ) ;
30+ expect ( screen . getByText ( 'Inner Content' ) ) . toBeOnTheScreen ( ) ;
31+ } ) ;
32+
33+ test ( 'createNodeMock option is passed to renderer' , async ( ) => {
34+ const TestComponent = ( ) => < View testID = "test" /> ;
35+ const mockNode = { testProperty : 'testValue' } ;
36+ const createNodeMock = jest . fn ( ( ) => mockNode ) ;
37+
38+ await render ( < TestComponent /> , { createNodeMock } ) ;
39+
40+ expect ( screen . getByTestId ( 'test' ) ) . toBeOnTheScreen ( ) ;
41+ } ) ;
42+ } ) ;
43+
44+ describe ( 'component rendering' , ( ) => {
45+ test ( 'render accepts RCTText component' , async ( ) => {
46+ await render ( React . createElement ( 'RCTText' , { testID : 'text' } , 'Hello' ) ) ;
47+ expect ( screen . getByTestId ( 'text' ) ) . toBeOnTheScreen ( ) ;
48+ expect ( screen . getByText ( 'Hello' ) ) . toBeOnTheScreen ( ) ;
49+ } ) ;
50+
51+ test ( 'render throws when text string is rendered without Text component' , async ( ) => {
52+ await expect ( render ( < View > Hello</ View > ) ) . rejects . toThrowErrorMatchingInlineSnapshot (
53+ `"Invariant Violation: Text strings must be rendered within a <Text> component. Detected attempt to render "Hello" string within a <View> component."` ,
54+ ) ;
55+ } ) ;
56+ } ) ;
57+
58+ describe ( 'rerender' , ( ) => {
59+ test ( 'rerender updates component with new props' , async ( ) => {
60+ interface TestComponentProps {
61+ message : string ;
62+ }
63+ const TestComponent = ( { message } : TestComponentProps ) => (
64+ < Text testID = "message" > { message } </ Text >
65+ ) ;
66+
67+ await render ( < TestComponent message = "Initial" /> ) ;
68+
69+ expect ( screen . getByText ( 'Initial' ) ) . toBeOnTheScreen ( ) ;
70+
71+ await screen . rerender ( < TestComponent message = "Updated" /> ) ;
72+
73+ expect ( screen . getByText ( 'Updated' ) ) . toBeOnTheScreen ( ) ;
74+ expect ( screen . queryByText ( 'Initial' ) ) . not . toBeOnTheScreen ( ) ;
75+ } ) ;
76+
77+ test ( 'rerender works with wrapper option' , async ( ) => {
78+ interface TestComponentProps {
79+ value : number ;
1480 }
81+ const TestComponent = ( { value } : TestComponentProps ) => < Text testID = "value" > { value } </ Text > ;
82+ const Wrapper = ( { children } : { children : React . ReactNode } ) => (
83+ < View testID = "wrapper" > { children } </ View >
84+ ) ;
85+
86+ await render ( < TestComponent value = { 1 } /> , {
87+ wrapper : Wrapper ,
88+ } ) ;
89+
90+ expect ( screen . getByText ( '1' ) ) . toBeOnTheScreen ( ) ;
91+
92+ await screen . rerender ( < TestComponent value = { 2 } /> ) ;
93+
94+ expect ( screen . getByText ( '2' ) ) . toBeOnTheScreen ( ) ;
95+ expect ( screen . getByTestId ( 'wrapper' ) ) . toBeOnTheScreen ( ) ;
96+ } ) ;
97+ } ) ;
98+
99+ test ( 'unmount removes component from tree' , async ( ) => {
100+ const TestComponent = ( ) => < Text testID = "content" > Content</ Text > ;
101+
102+ await render ( < TestComponent /> ) ;
103+
104+ expect ( screen . getByTestId ( 'content' ) ) . toBeOnTheScreen ( ) ;
105+
106+ await screen . unmount ( ) ;
107+
108+ expect ( screen . queryByTestId ( 'content' ) ) . not . toBeOnTheScreen ( ) ;
109+ } ) ;
110+
111+ test ( 'rerender calls componentDidUpdate and unmount calls componentWillUnmount' , async ( ) => {
112+ interface ClassComponentProps {
113+ onUpdate ?: ( ) => void ;
114+ onUnmount ?: ( ) => void ;
15115 }
116+ class ClassComponent extends React . Component < ClassComponentProps > {
117+ componentDidUpdate ( ) {
118+ if ( this . props . onUpdate ) {
119+ this . props . onUpdate ( ) ;
120+ }
121+ }
122+
123+ componentWillUnmount ( ) {
124+ if ( this . props . onUnmount ) {
125+ this . props . onUnmount ( ) ;
126+ }
127+ }
16128
17- componentWillUnmount ( ) {
18- if ( this . props . onUnmount ) {
19- this . props . onUnmount ( ) ;
129+ render ( ) {
130+ return < Text > Class Component</ Text > ;
20131 }
21132 }
22133
23- changeFresh = ( ) => {
24- this . setState ( ( state ) => ( {
25- fresh : ! state . fresh ,
26- } ) ) ;
27- } ;
134+ const onUpdate = jest . fn ( ) ;
135+ const onUnmount = jest . fn ( ) ;
136+ await render ( < ClassComponent onUpdate = { onUpdate } onUnmount = { onUnmount } /> ) ;
137+ expect ( onUpdate ) . toHaveBeenCalledTimes ( 0 ) ;
138+ expect ( onUnmount ) . toHaveBeenCalledTimes ( 0 ) ;
139+
140+ await screen . rerender ( < ClassComponent onUpdate = { onUpdate } onUnmount = { onUnmount } /> ) ;
141+ expect ( onUpdate ) . toHaveBeenCalledTimes ( 1 ) ;
142+ expect ( onUnmount ) . toHaveBeenCalledTimes ( 0 ) ;
143+
144+ await screen . unmount ( ) ;
145+ expect ( onUpdate ) . toHaveBeenCalledTimes ( 1 ) ;
146+ expect ( onUnmount ) . toHaveBeenCalledTimes ( 1 ) ;
147+ } ) ;
148+
149+ describe ( 'toJSON' , ( ) => {
150+ test ( 'toJSON returns null for empty children' , async ( ) => {
151+ const TestComponent = ( ) => null ;
28152
29- render ( ) {
30- return (
153+ await render ( < TestComponent /> ) ;
154+
155+ expect ( screen . toJSON ( ) ) . toMatchInlineSnapshot ( `null` ) ;
156+ } ) ;
157+
158+ test ( 'toJSON returns single child when only one child exists' , async ( ) => {
159+ const TestComponent = ( ) => (
31160 < View >
32- < Text > Is the banana fresh?</ Text >
33- < Text testID = "bananaFresh" > { this . state . fresh ? 'fresh' : 'not fresh' } </ Text >
161+ < Text testID = "single" > Single Child</ Text >
34162 </ View >
35163 ) ;
36- }
37- }
38164
39- test ( 'render renders component asynchronously' , async ( ) => {
40- await render ( < View testID = "test" /> ) ;
41- expect ( screen . getByTestId ( 'test' ) ) . toBeOnTheScreen ( ) ;
165+ await render ( < TestComponent /> ) ;
166+
167+ expect ( screen . toJSON ( ) ) . toMatchInlineSnapshot ( `
168+ <View>
169+ <Text
170+ testID="single"
171+ >
172+ Single Child
173+ </Text>
174+ </View>
175+ ` ) ;
176+ } ) ;
177+
178+ test ( 'toJSON returns full tree for React fragment with multiple children' , async ( ) => {
179+ const TestComponent = ( ) => (
180+ < >
181+ < Text testID = "first" > First</ Text >
182+ < Text testID = "second" > Second</ Text >
183+ </ >
184+ ) ;
185+
186+ await render ( < TestComponent /> ) ;
187+
188+ expect ( screen . toJSON ( ) ) . toMatchInlineSnapshot ( `
189+ <>
190+ <Text
191+ testID="first"
192+ >
193+ First
194+ </Text>
195+ <Text
196+ testID="second"
197+ >
198+ Second
199+ </Text>
200+ </>
201+ ` ) ;
202+ } ) ;
42203} ) ;
43204
44- test ( 'render with wrapper option' , async ( ) => {
45- const WrapperComponent = ( { children } : { children : React . ReactNode } ) => (
46- < View testID = "wrapper" > { children } </ View >
47- ) ;
205+ describe ( 'debug' , ( ) => {
206+ test ( 'debug outputs formatted component tree' , async ( ) => {
207+ const TestComponent = ( ) => (
208+ < View testID = "container" >
209+ < Text > Debug Test</ Text >
210+ </ View >
211+ ) ;
212+
213+ await render ( < TestComponent /> ) ;
48214
49- await render ( < View testID = "inner" /> , {
50- wrapper : WrapperComponent ,
215+ expect ( ( ) => {
216+ screen . debug ( ) ;
217+ } ) . not . toThrow ( ) ;
51218 } ) ;
52219
53- expect ( screen . getByTestId ( 'wrapper' ) ) . toBeTruthy ( ) ;
54- expect ( screen . getByTestId ( 'inner' ) ) . toBeTruthy ( ) ;
55- } ) ;
220+ test ( 'debug accepts options with message' , async ( ) => {
221+ const TestComponent = ( ) => < Text > Test</ Text > ;
56222
57- test ( 'rerender function updates component asynchronously' , async ( ) => {
58- const fn = jest . fn ( ) ;
59- await render ( < Banana onUpdate = { fn } /> ) ;
60- expect ( fn ) . toHaveBeenCalledTimes ( 0 ) ;
223+ await render ( < TestComponent /> ) ;
61224
62- await screen . rerender ( < Banana onUpdate = { fn } /> ) ;
63- expect ( fn ) . toHaveBeenCalledTimes ( 1 ) ;
225+ expect ( ( ) => {
226+ screen . debug ( { message : 'Custom message' } ) ;
227+ } ) . not . toThrow ( ) ;
228+ } ) ;
64229} ) ;
65230
66- test ( 'unmount function unmounts component asynchronously' , async ( ) => {
67- const fn = jest . fn ( ) ;
68- await render ( < Banana onUnmount = { fn } /> ) ;
231+ describe ( 'result getters' , ( ) => {
232+ test ( 'container getter returns renderer container' , async ( ) => {
233+ const TestComponent = ( ) => < Text testID = "content" > Content </ Text > ;
69234
70- await screen . unmount ( ) ;
71- expect ( fn ) . toHaveBeenCalled ( ) ;
72- } ) ;
235+ const result = await render ( < TestComponent /> ) ;
236+
237+ expect ( result . container ) . toMatchInlineSnapshot ( `
238+ <>
239+ <Text
240+ testID="content"
241+ >
242+ Content
243+ </Text>
244+ </>
245+ ` ) ;
246+ } ) ;
73247
74- test ( 'render accepts RCTText component' , async ( ) => {
75- await render ( React . createElement ( 'RCTText' , { testID : 'text' } , 'Hello' ) ) ;
76- expect ( screen . getByTestId ( 'text' ) ) . toBeOnTheScreen ( ) ;
77- expect ( screen . getByText ( 'Hello' ) ) . toBeOnTheScreen ( ) ;
248+ test ( 'root getter works correctly' , async ( ) => {
249+ const TestComponent = ( ) => < View testID = "test" /> ;
250+
251+ const result = await render ( < TestComponent /> ) ;
252+
253+ expect ( result . root ) . toMatchInlineSnapshot ( `
254+ <View
255+ testID="test"
256+ />
257+ ` ) ;
258+ } ) ;
78259} ) ;
79260
80- test ( 'render throws when text string is rendered without Text component' , async ( ) => {
81- await expect ( render ( < View > Hello</ View > ) ) . rejects . toThrowErrorMatchingInlineSnapshot (
82- `"Invariant Violation: Text strings must be rendered within a <Text> component. Detected attempt to render "Hello" string within a <View> component."` ,
83- ) ;
261+ describe ( 'screen integration' , ( ) => {
262+ test ( 'render sets screen queries' , async ( ) => {
263+ const TestComponent = ( ) => (
264+ < View >
265+ < Text testID = "text1" > First Text</ Text >
266+ < Text testID = "text2" > Second Text</ Text >
267+ </ View >
268+ ) ;
269+
270+ await render ( < TestComponent /> ) ;
271+
272+ expect ( screen . getByTestId ( 'text1' ) ) . toBeOnTheScreen ( ) ;
273+ expect ( screen . getByTestId ( 'text2' ) ) . toBeOnTheScreen ( ) ;
274+ expect ( screen . getByText ( 'First Text' ) ) . toBeOnTheScreen ( ) ;
275+ expect ( screen . getByText ( 'Second Text' ) ) . toBeOnTheScreen ( ) ;
276+ } ) ;
277+
278+ test ( 'screen queries work after rerender' , async ( ) => {
279+ interface TestComponentProps {
280+ label : string ;
281+ }
282+ const TestComponent = ( { label } : TestComponentProps ) => < Text testID = "label" > { label } </ Text > ;
283+
284+ await render ( < TestComponent label = "Initial" /> ) ;
285+
286+ expect ( screen . getByText ( 'Initial' ) ) . toBeOnTheScreen ( ) ;
287+
288+ await screen . rerender ( < TestComponent label = "Updated" /> ) ;
289+
290+ expect ( screen . getByText ( 'Updated' ) ) . toBeOnTheScreen ( ) ;
291+ } ) ;
84292} ) ;
0 commit comments