@@ -4,6 +4,40 @@ import { Text, View } from 'react-native';
44import { render , screen } from '..' ;
55import { _console , logger } from '../helpers/logger' ;
66
7+ function MaybeSuspend ( {
8+ children,
9+ promise,
10+ suspend,
11+ } : {
12+ children : React . ReactNode ;
13+ promise : Promise < unknown > ;
14+ suspend : boolean ;
15+ } ) {
16+ if ( suspend ) {
17+ React . use ( promise ) ;
18+ }
19+
20+ return children ;
21+ }
22+
23+ function TestSuspenseWrapper ( {
24+ children,
25+ promise,
26+ suspend,
27+ } : {
28+ children : React . ReactNode ;
29+ promise : Promise < unknown > ;
30+ suspend : boolean ;
31+ } ) {
32+ return (
33+ < React . Suspense fallback = { < Text > Loading...</ Text > } >
34+ < MaybeSuspend promise = { promise } suspend = { suspend } >
35+ { children }
36+ </ MaybeSuspend >
37+ </ React . Suspense >
38+ ) ;
39+ }
40+
741test ( 'renders a simple component' , async ( ) => {
842 const TestComponent = ( ) => (
943 < View testID = "container" >
@@ -77,6 +111,203 @@ describe('render options', () => {
77111 } ) ;
78112} ) ;
79113
114+ describe ( 'hidden instance props' , ( ) => {
115+ test ( 'does not retain hidden UI when the component suspends on initial render' , async ( ) => {
116+ const promise = new Promise < unknown > ( ( ) => { } ) ;
117+
118+ await render (
119+ < TestSuspenseWrapper promise = { promise } suspend >
120+ < View testID = "hidden-target" >
121+ < Text > Ready</ Text >
122+ </ View >
123+ </ TestSuspenseWrapper > ,
124+ ) ;
125+
126+ expect ( screen . getByText ( 'Loading...' ) ) . toBeOnTheScreen ( ) ;
127+ expect ( screen . queryByTestId ( 'hidden-target' ) ) . not . toBeOnTheScreen ( ) ;
128+ expect ( screen . queryByTestId ( 'hidden-target' , { includeHiddenElements : true } ) ) . toBeNull ( ) ;
129+ expect ( screen . toJSON ( ) ) . toMatchInlineSnapshot ( `
130+ <Text>
131+ Loading...
132+ </Text>
133+ ` ) ;
134+ } ) ;
135+
136+ test ( 'sets hidden suspended elements with no style to display none' , async ( ) => {
137+ const promise = new Promise < unknown > ( ( ) => { } ) ;
138+
139+ await render (
140+ < TestSuspenseWrapper promise = { promise } suspend = { false } >
141+ < View testID = "hidden-target" >
142+ < Text > Ready</ Text >
143+ </ View >
144+ </ TestSuspenseWrapper > ,
145+ ) ;
146+
147+ expect ( screen . getByText ( 'Ready' ) ) . toBeOnTheScreen ( ) ;
148+
149+ await screen . rerender (
150+ < TestSuspenseWrapper promise = { promise } suspend >
151+ < View testID = "hidden-target" >
152+ < Text > Ready</ Text >
153+ </ View >
154+ </ TestSuspenseWrapper > ,
155+ ) ;
156+
157+ expect ( screen . getByText ( 'Loading...' ) ) . toBeOnTheScreen ( ) ;
158+ expect (
159+ screen . getByTestId ( 'hidden-target' , { includeHiddenElements : true } ) . props . style ,
160+ ) . toEqual ( {
161+ display : 'none' ,
162+ } ) ;
163+ expect ( screen . toJSON ( ) ) . toMatchInlineSnapshot ( `
164+ <>
165+ <View
166+ style={
167+ {
168+ "display": "none",
169+ }
170+ }
171+ testID="hidden-target"
172+ >
173+ <Text>
174+ Ready
175+ </Text>
176+ </View>
177+ <Text>
178+ Loading...
179+ </Text>
180+ </>
181+ ` ) ;
182+ } ) ;
183+
184+ test ( 'appends display none when suspending an element with existing style' , async ( ) => {
185+ const promise = new Promise < unknown > ( ( ) => { } ) ;
186+
187+ await render (
188+ < TestSuspenseWrapper promise = { promise } suspend = { false } >
189+ < View style = { { opacity : 0.5 } } testID = "hidden-target" >
190+ < Text > Ready</ Text >
191+ </ View >
192+ </ TestSuspenseWrapper > ,
193+ ) ;
194+
195+ expect ( screen . getByText ( 'Ready' ) ) . toBeOnTheScreen ( ) ;
196+
197+ await screen . rerender (
198+ < TestSuspenseWrapper promise = { promise } suspend >
199+ < View style = { { opacity : 0.5 } } testID = "hidden-target" >
200+ < Text > Ready</ Text >
201+ </ View >
202+ </ TestSuspenseWrapper > ,
203+ ) ;
204+
205+ expect ( screen . getByText ( 'Loading...' ) ) . toBeOnTheScreen ( ) ;
206+ expect (
207+ screen . getByTestId ( 'hidden-target' , { includeHiddenElements : true } ) . props . style ,
208+ ) . toEqual ( [ { opacity : 0.5 } , { display : 'none' } ] ) ;
209+ expect ( screen . toJSON ( ) ) . toMatchInlineSnapshot ( `
210+ <>
211+ <View
212+ style={
213+ [
214+ {
215+ "opacity": 0.5,
216+ },
217+ {
218+ "display": "none",
219+ },
220+ ]
221+ }
222+ testID="hidden-target"
223+ >
224+ <Text>
225+ Ready
226+ </Text>
227+ </View>
228+ <Text>
229+ Loading...
230+ </Text>
231+ </>
232+ ` ) ;
233+ } ) ;
234+
235+ test ( 'applies hidden styles to multiple direct child views when suspending' , async ( ) => {
236+ const promise = new Promise < unknown > ( ( ) => { } ) ;
237+
238+ await render (
239+ < TestSuspenseWrapper promise = { promise } suspend = { false } >
240+ < View testID = "hidden-target-1" >
241+ < Text > First</ Text >
242+ </ View >
243+ < View style = { { opacity : 0.5 } } testID = "hidden-target-2" >
244+ < Text > Second</ Text >
245+ </ View >
246+ </ TestSuspenseWrapper > ,
247+ ) ;
248+
249+ expect ( screen . getByText ( 'First' ) ) . toBeOnTheScreen ( ) ;
250+ expect ( screen . getByText ( 'Second' ) ) . toBeOnTheScreen ( ) ;
251+
252+ await screen . rerender (
253+ < TestSuspenseWrapper promise = { promise } suspend >
254+ < View testID = "hidden-target-1" >
255+ < Text > First</ Text >
256+ </ View >
257+ < View style = { { opacity : 0.5 } } testID = "hidden-target-2" >
258+ < Text > Second</ Text >
259+ </ View >
260+ </ TestSuspenseWrapper > ,
261+ ) ;
262+
263+ expect ( screen . getByText ( 'Loading...' ) ) . toBeOnTheScreen ( ) ;
264+ expect (
265+ screen . getByTestId ( 'hidden-target-1' , { includeHiddenElements : true } ) . props . style ,
266+ ) . toEqual ( {
267+ display : 'none' ,
268+ } ) ;
269+ expect (
270+ screen . getByTestId ( 'hidden-target-2' , { includeHiddenElements : true } ) . props . style ,
271+ ) . toEqual ( [ { opacity : 0.5 } , { display : 'none' } ] ) ;
272+ expect ( screen . toJSON ( ) ) . toMatchInlineSnapshot ( `
273+ <>
274+ <View
275+ style={
276+ {
277+ "display": "none",
278+ }
279+ }
280+ testID="hidden-target-1"
281+ >
282+ <Text>
283+ First
284+ </Text>
285+ </View>
286+ <View
287+ style={
288+ [
289+ {
290+ "opacity": 0.5,
291+ },
292+ {
293+ "display": "none",
294+ },
295+ ]
296+ }
297+ testID="hidden-target-2"
298+ >
299+ <Text>
300+ Second
301+ </Text>
302+ </View>
303+ <Text>
304+ Loading...
305+ </Text>
306+ </>
307+ ` ) ;
308+ } ) ;
309+ } ) ;
310+
80311describe ( 'component rendering' , ( ) => {
81312 test ( 'render accepts RCTText component' , async ( ) => {
82313 await render ( React . createElement ( 'RCTText' , { testID : 'text' } , 'Hello' ) ) ;
0 commit comments