@@ -706,4 +706,101 @@ describe('refs return clean up function', () => {
706706 expect ( setup ) . toHaveBeenCalledTimes ( 1 ) ;
707707 expect ( cleanUp ) . toHaveBeenCalledTimes ( 1 ) ;
708708 } ) ;
709+
710+ it ( 'handles detaching refs with either cleanup function or null argument' , async ( ) => {
711+ const container = document . createElement ( 'div' ) ;
712+ const cleanUp = jest . fn ( ) ;
713+ const setup = jest . fn ( ) ;
714+ const setup2 = jest . fn ( ) ;
715+ const nullHandler = jest . fn ( ) ;
716+
717+ function _onRefChangeWithCleanup ( _ref ) {
718+ if ( _ref ) {
719+ setup ( _ref . id ) ;
720+ } else {
721+ nullHandler ( ) ;
722+ }
723+ return cleanUp ;
724+ }
725+
726+ function _onRefChangeWithoutCleanup ( _ref ) {
727+ if ( _ref ) {
728+ setup2 ( _ref . id ) ;
729+ } else {
730+ nullHandler ( ) ;
731+ }
732+ }
733+
734+ const root = ReactDOMClient . createRoot ( container ) ;
735+ await act ( ( ) => {
736+ root . render ( < div id = "test-div" ref = { _onRefChangeWithCleanup } /> ) ;
737+ } ) ;
738+
739+ expect ( setup ) . toBeCalledWith ( 'test-div' ) ;
740+ expect ( setup ) . toHaveBeenCalledTimes ( 1 ) ;
741+ expect ( cleanUp ) . toHaveBeenCalledTimes ( 0 ) ;
742+
743+ await act ( ( ) => {
744+ root . render ( < div id = "test-div2" ref = { _onRefChangeWithoutCleanup } /> ) ;
745+ } ) ;
746+
747+ // Existing setup call was not called again
748+ expect ( setup ) . toHaveBeenCalledTimes ( 1 ) ;
749+ // No null call because cleanup is returned
750+ expect ( nullHandler ) . toHaveBeenCalledTimes ( 0 ) ;
751+ // Now we have a cleanup
752+ expect ( cleanUp ) . toHaveBeenCalledTimes ( 1 ) ;
753+
754+ // New ref is setup
755+ expect ( setup2 ) . toBeCalledWith ( 'test-div2' ) ;
756+ expect ( setup2 ) . toHaveBeenCalledTimes ( 1 ) ;
757+
758+ // Now, render with the original ref again
759+ await act ( ( ) => {
760+ root . render ( < div id = "test-div3" ref = { _onRefChangeWithCleanup } /> ) ;
761+ } ) ;
762+
763+ // Setup was not called again
764+ expect ( setup2 ) . toBeCalledWith ( 'test-div2' ) ;
765+ expect ( setup2 ) . toHaveBeenCalledTimes ( 1 ) ;
766+
767+ // Null handler hit because no cleanup is returned
768+ expect ( nullHandler ) . toHaveBeenCalledTimes ( 1 ) ;
769+
770+ // Original setup hit one more time
771+ expect ( setup ) . toHaveBeenCalledTimes ( 2 ) ;
772+ } ) ;
773+
774+ it ( 'calls cleanup function on unmount' , async ( ) => {
775+ const container = document . createElement ( 'div' ) ;
776+ const cleanUp = jest . fn ( ) ;
777+ const setup = jest . fn ( ) ;
778+ const nullHandler = jest . fn ( ) ;
779+
780+ function _onRefChangeWithCleanup ( _ref ) {
781+ if ( _ref ) {
782+ setup ( _ref . id ) ;
783+ } else {
784+ nullHandler ( ) ;
785+ }
786+ return cleanUp ;
787+ }
788+
789+ const root = ReactDOMClient . createRoot ( container ) ;
790+ await act ( ( ) => {
791+ root . render ( < div id = "test-div" ref = { _onRefChangeWithCleanup } /> ) ;
792+ } ) ;
793+
794+ expect ( setup ) . toHaveBeenCalledTimes ( 1 ) ;
795+ expect ( cleanUp ) . toHaveBeenCalledTimes ( 0 ) ;
796+ expect ( nullHandler ) . toHaveBeenCalledTimes ( 0 ) ;
797+
798+ root . unmount ( ) ;
799+
800+ expect ( setup ) . toHaveBeenCalledTimes ( 1 ) ;
801+ // Now cleanup has been called
802+ expect ( cleanUp ) . toHaveBeenCalledTimes ( 1 ) ;
803+ // Ref callback never called with null when cleanup is returned
804+ expect ( nullHandler ) . toHaveBeenCalledTimes ( 0 ) ;
805+ } ) ;
709806} ) ;
0 commit comments