import React , { useEffect , useState , useRef , RefObject , FunctionComponent , RefCallback , } from "react" ; import useResizeObserver from "../" ; import { createComponentHandler , Observed , render , HandlerReceiver , ObservedSize , MultiHandlerResolverComponentProps , ComponentHandler , HandlerResolverComponentProps , } from "./utils" ; import awaitNextFrame from "./utils/awaitNextFrame" ; describe ( "Vanilla tests" , ( ) => { it ( "should render with undefined sizes at first" , async ( ) => { const handler = await render ( Observed ) ; handler . assertDefaultSize ( ) ; } ) ; it ( "should render with custom defaults" , async ( ) => { const { assertSize } = await render ( Observed , { } , { defaultWidth : 24 , defaultHeight : 42 , } ) ; assertSize ( { width : 24 , height : 42 } ) ; } ) ; it ( "should follow size changes correctly with appropriate render count and without sub-pixels as they're used in CSS" , async ( ) => { const { setAndAssertSize , setSize , assertSize , assertRenderCount } = await render ( Observed , { waitForFirstMeasurement : true } ) ; assertRenderCount ( 2 ) ; await setAndAssertSize ( { width : 100 , height : 200 } ) ; assertRenderCount ( 3 ) ; setSize ( { width : 321 , height : 456 } ) ; await awaitNextFrame ( ) ; assertSize ( { width : 321 , height : 456 } ) ; assertRenderCount ( 4 ) ; } ) ; it ( "should handle multiple instances" , async ( ) => { const Test = ( { resolveHandler , } ) => { let resolveHandler1 = ( ) => { } ; let resolveHandler2 = ( ) => { } ; const handlersPromise = Promise . all ( [ new Promise < ComponentHandler > ( ( resolve ) => ( resolveHandler1 = resolve as HandlerReceiver ) ) , new Promise < ComponentHandler > ( ( resolve ) => ( resolveHandler2 = resolve as HandlerReceiver ) ) , ] ) ; useEffect ( ( ) => { handlersPromise . then ( ( [ handler1 , handler2 ] ) => { resolveHandler ( [ handler1 , handler2 ] ) ; } ) ; } , [ ] ) ; return ( < > < Observed resolveHandler = { resolveHandler1 } / > < Observed resolveHandler = { resolveHandler2 } /> < />); } ; const [ handler1 , handler2 ] = await render ( Test , { waitForFirstMeasurement : true , } ) ; await handler1 . setAndAssertSize ( { width : 100 , height : 200 } ) ; await handler2 . setAndAssertSize ( { width : 300 , height : 400 } ) ; handler1 . assertRenderCount ( 3 ) ; handler2 . assertRenderCount ( 3 ) ; await handler2 . setAndAssertSize ( { width : 321 , height : 456 } ) ; handler1 . assertRenderCount ( 3 ) ; handler2 . assertRenderCount ( 4 ) ; handler1 . assertSize ( { width : 100 , height : 200 } ) ; } ) ; it ( "should handle custom refs" , async ( ) => { const Test = ( { resolveHandler , } ) => { const ref = useRef ( null ) ; const { width , height } = useResizeObserver ( { ref } ) ; const currentSizeRef = useRef < ObservedSize > ( { } as ObservedSize ) ; currentSizeRef . current . height = height ; currentSizeRef . current . width = width ; useEffect ( ( ) => { resolveHandler ( createComponentHandler ( { currentSizeRef } ) ) ; } , [ ] ) ; return ( < div ref = { ref } style = { { width : 100 , height : 200 } } > { width } x { height } < /div>); } ; const handler = await render ( Test ) ; handler . assertDefaultSize ( ) ; await awaitNextFrame ( ) ; handler . assertSize ( { width : 100 , height : 200 } ) ; } ) ; it ( "should be able to reuse the same ref to measure different elements" , async ( ) => { let switchRefs = ( ) : void => { throw new Error ( `"switchRefs" should've been implemented by now.` ) ; } ; const Test = ( { resolveHandler } ) => { const ref1 = useRef < HTMLDivElement > ( null ) ; const ref2 = useRef < HTMLDivElement > ( null ) ; const [ stateRef , setStateRef ] = useState < RefObject < HTMLDivElement > > ( ref1 ) ; const sizeRef = useRef ( null ) ; const { width , height } = useResizeObserver ( { ref : stateRef } ) ; const currentSizeRef = useRef < ObservedSize > ( { width : undefined , height : undefined , } ) ; currentSizeRef . current . width = width ; currentSizeRef . current . height = height ; useEffect ( ( ) => { switchRefs = ( ) => setStateRef ( ref2 ) ; resolveHandler ( createComponentHandler ( { currentSizeRef } ) ) ; } , [ ] ) ; return ( < > < div ref = { sizeRef } > { width } x { height } < /div> < div ref = { ref1 } style = { { width : 100 , height : 200 } } /> < div ref = { ref2 } style = { { width : 150 , height : 250 } } /> < />); } ; const handler = await render ( Test ) ; handler . assertDefaultSize ( ) ; await awaitNextFrame ( ) ; handler . assertSize ( { width : 100 , height : 200 } ) ; switchRefs ( ) ; await awaitNextFrame ( ) ; handler . assertSize ( { width : 150 , height : 250 } ) ; } ) ; it ( "should be able to render without mock defaults" , async ( ) => { const handler = await render ( Observed ) ; handler . assertDefaultSize ( ) ; handler . setSize ( { width : 100 , height : 100 } ) ; await awaitNextFrame ( ) ; handler . assertSize ( { width : 100 , height : 100 } ) ; } ) ; it ( "should not trigger unnecessary renders with the same width or height" , async ( ) => { const handler = await render ( Observed ) ; handler . assertDefaultSize ( ) ; await awaitNextFrame ( ) ; handler . assertRenderCount ( 2 ) ; handler . setSize ( { width : 100 , height : 102 } ) ; await awaitNextFrame ( ) ; handler . assertSize ( { width : 100 , height : 102 } ) ; handler . assertRenderCount ( 3 ) ; handler . setSize ( { width : 100.4 , height : 102.4 } ) ; await awaitNextFrame ( ) ; handler . assertSize ( { width : 100 , height : 102 } ) ; handler . assertRenderCount ( 3 ) ; } ) ; it ( "should keep the same response instance between renders if nothing changed" , async ( ) => { let assertSameInstance = ( ) : void => { throw new Error ( `"assertSameInstance" should've been implemented by now.` ) ; } ; const Test = ( { resolveHandler , } ) => { const previousResponseRef = useRef < ( { ref : RefCallback < HTMLElement > ; } & ObservedSize ) | null > ( null ) ; const response = useResizeObserver < HTMLDivElement > ( ) ; const [ state , setState ] = useState ( false ) ; const sameInstance = previousResponseRef . current === response ; previousResponseRef . current = response ; useEffect ( ( ) => { if ( response . width && response . height ) { setState ( true ) ; } } , [ response ] ) ; useEffect ( ( ) => { if ( ! state ) { return ; } assertSameInstance = ( ) => { expect ( sameInstance ) . toBe ( true ) ; } ; resolveHandler ( { } ) ; } , [ state ] ) ; return < div ref = { response . ref } > { String ( sameInstance ) } < /div>; } ; await render ( Test ) ; assertSameInstance ( ) ; } ) ; it ( "should ignore invalid custom refs" , async ( ) => { const Test = ( { resolveHandler , } ) => { const { width , height } = useResizeObserver ( { ref : { } as RefObject < HTMLDivElement > , } ) ; const currentSizeRef = useRef < ObservedSize > ( { } as ObservedSize ) ; currentSizeRef . current . width = width ; currentSizeRef . current . height = height ; useEffect ( ( ) => { resolveHandler ( createComponentHandler ( { currentSizeRef } ) ) ; } , [ ] ) ; return ( < div > { width } x { height } < /div>); } ; const handler = await render ( Test ) ; await awaitNextFrame ( ) ; handler . assertDefaultSize ( ) ; } ) ; it ( "should be able to work with onResize instead of rendering the values" , async ( ) => { const observations = [ ] ; const handler = await render ( Observed , { } , { onResize : ( size ) => observations . push ( size ) } ) ; handler . setSize ( { width : 100 , height : 200 } ) ; await awaitNextFrame ( ) ; handler . setSize ( { width : 101 , height : 201 } ) ; await awaitNextFrame ( ) ; handler . assertDefaultSize ( ) ; expect ( observations . length ) . toBe ( 2 ) ; expect ( observations [ 0 ] ) . toEqual ( { width : 100 , height : 200 } ) ; expect ( observations [ 1 ] ) . toEqual ( { width : 101 , height : 201 } ) ; handler . assertRenderCount ( 1 ) ; } ) ; it ( "should handle if the onResize handler changes properly with the correct render counts" , async ( ) => { let changeOnResizeHandler = ( ) => { } ; const Test = ( { resolveHandler , ... props } ) => { const [ onResizeHandler , setOnResizeHandler ] = useState ( ( ) => ( ) => { } ) ; changeOnResizeHandler = ( handler ) => setOnResizeHandler ( ( ) => handler ) ; return ( < Observed { ... props } onResize = { onResizeHandler } resolveHandler = { resolveHandler } / > ) ; } ; const { assertRenderCount , setSize } = await render ( Test , { waitForFirstMeasurement : true , } ) ; assertRenderCount ( 1 ) ; const observations1 = [ ] ; const observations2 = [ ] ; setSize ( { width : 1 , height : 1 } ) ; await awaitNextFrame ( ) ; assertRenderCount ( 1 ) ; changeOnResizeHandler ( ( size ) => observations1 . push ( size ) ) ; await awaitNextFrame ( ) ; setSize ( { width : 1 , height : 2 } ) ; await awaitNextFrame ( ) ; setSize ( { width : 3 , height : 4 } ) ; assertRenderCount ( 2 ) ; await awaitNextFrame ( ) ; changeOnResizeHandler ( ( size ) => observations2 . push ( size ) ) ; await awaitNextFrame ( ) ; setSize ( { width : 5 , height : 6 } ) ; await awaitNextFrame ( ) ; setSize ( { width : 7 , height : 8 } ) ; await awaitNextFrame ( ) ; assertRenderCount ( 3 ) ; expect ( observations1 . length ) . toBe ( 2 ) ; expect ( observations1 [ 0 ] ) . toEqual ( { width : 1 , height : 2 } ) ; expect ( observations1 [ 1 ] ) . toEqual ( { width : 3 , height : 4 } ) ; expect ( observations2 . length ) . toBe ( 2 ) ; expect ( observations2 [ 0 ] ) . toEqual ( { width : 5 , height : 6 } ) ; expect ( observations2 [ 1 ] ) . toEqual ( { width : 7 , height : 8 } ) ; } ) ; } ) ;