@@ -65,6 +65,360 @@ test('auto-height wrapper around a feed ScrollView stays bounded by the viewport
6565 expect ( feedContentRect . height ) . toBe ( FEED_CONTENT_HEIGHT ) ;
6666} ) ;
6767
68+ test ( 'updates flex-basis and intrinsic height in ScrollView content' , ( ) => {
69+ const root = Fantom . createRoot ( ) ;
70+ const candidateRef = createRef < HostInstance > ( ) ;
71+
72+ const render = ( flexBasis : number , intrinsicHeight : number ) => {
73+ Fantom . runTask ( ( ) => {
74+ root . render (
75+ < ScrollView >
76+ < View ref = { candidateRef } style = { { flexBasis} } >
77+ < View style = { { height : intrinsicHeight } } />
78+ </ View >
79+ </ ScrollView > ,
80+ ) ;
81+ } ) ;
82+ return nullthrows ( candidateRef . current ) . getBoundingClientRect ( ) . height ;
83+ } ;
84+
85+ expect ( render ( 40 , 40 ) ) . toBe ( 40 ) ;
86+
87+ expect ( render ( 80 , 20 ) ) . toBe ( 20 ) ;
88+ } ) ;
89+
90+ test ( 'auto-height wrapper gives remaining ScrollView space to a flex-growing body' , ( ) => {
91+ const viewportHeight = 200 ;
92+ const headerHeight = 50 ;
93+ const root = Fantom . createRoot ( {
94+ viewportHeight,
95+ viewportWidth : 100 ,
96+ } ) ;
97+
98+ const wrapperRef = createRef < HostInstance > ( ) ;
99+ const growingBodyRef = createRef < HostInstance > ( ) ;
100+
101+ Fantom . runTask ( ( ) => {
102+ root . render (
103+ < ScrollView
104+ contentContainerStyle = { { alignItems : 'flex-start' , flexGrow : 1 } } >
105+ < View collapsable = { false } ref = { wrapperRef } >
106+ < View collapsable = { false } style = { { height : headerHeight , width : 50 } } />
107+ < View
108+ collapsable = { false }
109+ ref = { growingBodyRef }
110+ style = { { flexGrow : 1 , height : 40 , width : 50 } }
111+ />
112+ </ View >
113+ </ ScrollView > ,
114+ ) ;
115+ } ) ;
116+
117+ const wrapperRect = nullthrows ( wrapperRef . current ) . getBoundingClientRect ( ) ;
118+ const growingBodyRect = nullthrows (
119+ growingBodyRef . current ,
120+ ) . getBoundingClientRect ( ) ;
121+
122+ const layout = {
123+ growingBodyHeight : growingBodyRect . height ,
124+ wrapperHeight : wrapperRect . height ,
125+ } ;
126+
127+ expect ( layout ) . toEqual ( {
128+ growingBodyHeight : viewportHeight - headerHeight ,
129+ wrapperHeight : viewportHeight ,
130+ } ) ;
131+ } ) ;
132+
133+ test ( 'positive flex-basis preserves content height in an unbounded ScrollView axis' , ( ) => {
134+ const root = Fantom . createRoot ( {
135+ viewportHeight : 200 ,
136+ viewportWidth : 100 ,
137+ } ) ;
138+
139+ const wrapperRef = createRef < HostInstance > ( ) ;
140+ const contentRef = createRef < HostInstance > ( ) ;
141+
142+ Fantom . runTask ( ( ) => {
143+ root . render (
144+ < ScrollView >
145+ < View collapsable = { false } ref = { wrapperRef } style = { { flexBasis : 50 } } >
146+ < View collapsable = { false } ref = { contentRef } style = { { height : 100 } } />
147+ </ View >
148+ </ ScrollView > ,
149+ ) ;
150+ } ) ;
151+
152+ expect ( {
153+ contentHeight : nullthrows ( contentRef . current ) . getBoundingClientRect ( )
154+ . height ,
155+ wrapperHeight : nullthrows ( wrapperRef . current ) . getBoundingClientRect ( )
156+ . height ,
157+ } ) . toEqual ( {
158+ contentHeight : 100 ,
159+ wrapperHeight : 100 ,
160+ } ) ;
161+ } ) ;
162+
163+ test ( 'positive flex-basis larger than content preserves content height in an unbounded ScrollView axis' , ( ) => {
164+ const root = Fantom . createRoot ( {
165+ viewportHeight : 200 ,
166+ viewportWidth : 100 ,
167+ } ) ;
168+
169+ const wrapperRef = createRef < HostInstance > ( ) ;
170+ const contentRef = createRef < HostInstance > ( ) ;
171+
172+ Fantom . runTask ( ( ) => {
173+ root . render (
174+ < ScrollView >
175+ < View collapsable = { false } ref = { wrapperRef } style = { { flexBasis : 100 } } >
176+ < View collapsable = { false } ref = { contentRef } style = { { height : 40 } } />
177+ </ View >
178+ </ ScrollView > ,
179+ ) ;
180+ } ) ;
181+
182+ const layout = {
183+ contentHeight : nullthrows ( contentRef . current ) . getBoundingClientRect ( )
184+ . height ,
185+ wrapperHeight : nullthrows ( wrapperRef . current ) . getBoundingClientRect ( )
186+ . height ,
187+ } ;
188+
189+ expect ( layout ) . toEqual ( {
190+ contentHeight : 40 ,
191+ wrapperHeight : 40 ,
192+ } ) ;
193+ } ) ;
194+
195+ test ( 'nested padding respects an exhausted ScrollView height constraint' , ( ) => {
196+ const viewportHeight = 100 ;
197+ const root = Fantom . createRoot ( {
198+ viewportHeight,
199+ viewportWidth : 100 ,
200+ } ) ;
201+
202+ const wrapperRef = createRef < HostInstance > ( ) ;
203+ const nestedRef = createRef < HostInstance > ( ) ;
204+ const paddedRef = createRef < HostInstance > ( ) ;
205+
206+ Fantom . runTask ( ( ) => {
207+ root . render (
208+ < ScrollView contentContainerStyle = { { height : viewportHeight } } >
209+ < View
210+ collapsable = { false }
211+ ref = { wrapperRef }
212+ style = { { paddingBottom : 50 , paddingTop : 50 } } >
213+ < View collapsable = { false } ref = { nestedRef } >
214+ < View
215+ collapsable = { false }
216+ ref = { paddedRef }
217+ style = { { paddingBottom : 10 , paddingTop : 10 } }
218+ />
219+ </ View >
220+ </ View >
221+ </ ScrollView > ,
222+ ) ;
223+ } ) ;
224+
225+ expect ( {
226+ nestedHeight : nullthrows ( nestedRef . current ) . getBoundingClientRect ( ) . height ,
227+ paddedHeight : nullthrows ( paddedRef . current ) . getBoundingClientRect ( ) . height ,
228+ wrapperHeight : nullthrows ( wrapperRef . current ) . getBoundingClientRect ( )
229+ . height ,
230+ } ) . toEqual ( {
231+ nestedHeight : 0 ,
232+ paddedHeight : 20 ,
233+ wrapperHeight : viewportHeight ,
234+ } ) ;
235+ } ) ;
236+
237+ test ( 'clipped ScrollView inside a horizontal pager stays scrollable' , ( ) => {
238+ const root = Fantom . createRoot ( {
239+ viewportHeight : 150 ,
240+ viewportWidth : 320 ,
241+ } ) ;
242+
243+ const outerScrollRef = createRef < HostInstance > ( ) ;
244+ const boundedContainerRef = createRef < HostInstance > ( ) ;
245+ const autoHeightWrapperRef = createRef < HostInstance > ( ) ;
246+ const innerScrollRef = createRef < HostInstance > ( ) ;
247+
248+ Fantom . runTask ( ( ) => {
249+ root . render (
250+ < ScrollView horizontal pagingEnabled ref = { outerScrollRef } >
251+ < View
252+ collapsable = { false }
253+ ref = { boundedContainerRef }
254+ style = { { height : 150 , overflow : 'hidden' , width : 320 } } >
255+ < View collapsable = { false } ref = { autoHeightWrapperRef } >
256+ < ScrollView ref = { innerScrollRef } >
257+ < View style = { { height : 240 } } />
258+ </ ScrollView >
259+ </ View >
260+ </ View >
261+ < View style = { { height : 150 , width : 320 } } />
262+ </ ScrollView > ,
263+ ) ;
264+ } ) ;
265+
266+ const outerScroll = nullthrows ( outerScrollRef . current ) ;
267+ const innerScroll = nullthrows ( innerScrollRef . current ) ;
268+ const layout = {
269+ autoHeightWrapperHeight : nullthrows (
270+ autoHeightWrapperRef . current ,
271+ ) . getBoundingClientRect ( ) . height ,
272+ boundedContainerHeight : nullthrows (
273+ boundedContainerRef . current ,
274+ ) . getBoundingClientRect ( ) . height ,
275+ innerContentHeight : innerScroll . scrollHeight ,
276+ innerHeight : innerScroll . getBoundingClientRect ( ) . height ,
277+ innerScrollRange : innerScroll . scrollHeight - innerScroll . clientHeight ,
278+ outerHorizontalScrollRange :
279+ outerScroll . scrollWidth - outerScroll . clientWidth ,
280+ outerVerticalScrollRange :
281+ outerScroll . scrollHeight - outerScroll . clientHeight ,
282+ } ;
283+
284+ expect ( layout ) . toEqual ( {
285+ autoHeightWrapperHeight : 150 ,
286+ boundedContainerHeight : 150 ,
287+ innerContentHeight : 240 ,
288+ innerHeight : 150 ,
289+ innerScrollRange : 90 ,
290+ outerHorizontalScrollRange : 320 ,
291+ outerVerticalScrollRange : 0 ,
292+ } ) ;
293+ } ) ;
294+
295+ test ( 'percentage padding tracks an updated owner width' , ( ) => {
296+ const root = Fantom . createRoot ( {
297+ viewportHeight : 300 ,
298+ viewportWidth : 300 ,
299+ } ) ;
300+
301+ function render ( updated : boolean ) {
302+ Fantom . runTask ( ( ) => {
303+ root . render (
304+ < View
305+ collapsable = { false }
306+ style = { {
307+ height : 200 ,
308+ overflow : 'scroll' ,
309+ width : updated ? 200 : 100 ,
310+ } } >
311+ < View collapsable = { false } style = { updated ? { height : 150 } : undefined } >
312+ < View
313+ collapsable = { false }
314+ nativeID = "candidate"
315+ style = { { maxWidth : 50 , paddingLeft : '25%' } }
316+ />
317+ </ View >
318+ </ View > ,
319+ ) ;
320+ } ) ;
321+ }
322+
323+ render ( false ) ;
324+ render ( true ) ;
325+
326+ expect (
327+ root
328+ . getRenderedOutput ( {
329+ includeLayoutMetrics : true ,
330+ props : [ 'layoutMetrics-contentInsets' , 'nativeID' ] ,
331+ } )
332+ . toJSX ( ) ,
333+ ) . toEqual (
334+ < rn-view layoutMetrics-contentInsets = "{top:0,right:0,bottom:0,left:0}" >
335+ < rn-view layoutMetrics-contentInsets = "{top:0,right:0,bottom:0,left:0}" >
336+ < rn-view
337+ layoutMetrics-contentInsets = "{top:0,right:0,bottom:0,left:50}"
338+ nativeID = "candidate"
339+ />
340+ </ rn-view >
341+ </ rn-view > ,
342+ ) ;
343+ } ) ;
344+
345+ test ( 'gates native box sizing cache invalidation' , ( ) => {
346+ const root = Fantom . createRoot ( {
347+ viewportHeight : 280 ,
348+ viewportWidth : 240 ,
349+ } ) ;
350+ const scrollRef = createRef < HostInstance > ( ) ;
351+ const leafRef = createRef < HostInstance > ( ) ;
352+
353+ const render = ( siblingHeight : number ) => {
354+ Fantom . runTask ( ( ) => {
355+ root . render (
356+ < View
357+ collapsable = { false }
358+ style = { {
359+ alignItems : 'flex-start' ,
360+ flexDirection : 'row' ,
361+ height : 240 ,
362+ width : 181 ,
363+ } } >
364+ < View
365+ collapsable = { false }
366+ style = { { alignItems : 'flex-start' , alignSelf : 'flex-start' } } >
367+ < ScrollView ref = { scrollRef } style = { { height : 200 } } >
368+ < View collapsable = { false } style = { { height : 100 } } >
369+ < View
370+ collapsable = { false }
371+ style = { { flexBasis : 100 , flexShrink : 1 } } >
372+ < View collapsable = { false } >
373+ < View
374+ collapsable = { false }
375+ ref = { leafRef }
376+ style = { {
377+ borderLeftWidth : 1 ,
378+ borderRightWidth : 2 ,
379+ paddingHorizontal : 3 ,
380+ width : 83 ,
381+ } }
382+ />
383+ </ View >
384+ </ View >
385+ < View style = { { height : siblingHeight , width : 17 } } />
386+ </ View >
387+ </ ScrollView >
388+ </ View >
389+ </ View > ,
390+ ) ;
391+ } ) ;
392+ } ;
393+
394+ render ( 27 ) ;
395+ Fantom . runTask ( ( ) => {
396+ nullthrows ( leafRef . current ) . setNativeProps ( {
397+ style : { boxSizing : 'content-box' } ,
398+ } ) ;
399+ } ) ;
400+ render ( 40 ) ;
401+
402+ const layoutAfterFirstRerender = {
403+ leafWidth : nullthrows ( leafRef . current ) . getBoundingClientRect ( ) . width ,
404+ scrollWidth : nullthrows ( scrollRef . current ) . getBoundingClientRect ( ) . width ,
405+ } ;
406+ expect ( layoutAfterFirstRerender ) . toEqual ( {
407+ leafWidth : 83 ,
408+ scrollWidth : 83 ,
409+ } ) ;
410+
411+ render ( 53 ) ;
412+
413+ expect ( {
414+ leafWidth : nullthrows ( leafRef . current ) . getBoundingClientRect ( ) . width ,
415+ scrollWidth : nullthrows ( scrollRef . current ) . getBoundingClientRect ( ) . width ,
416+ } ) . toEqual ( {
417+ leafWidth : 92 ,
418+ scrollWidth : 92 ,
419+ } ) ;
420+ } ) ;
421+
68422const styles = StyleSheet . create ( {
69423 feed : {
70424 width : VIEWPORT_WIDTH ,
0 commit comments