Skip to content

Commit d4e78c4

Browse files
authoredApr 23, 2024··
Add ref callback test for cleanup fn vs null call (#28895)
Used this test scenario to clarify how callback refs work when detached based on the availability of a cleanup function to update documentation in reactjs/react.dev#6770 Checking it in for additional test coverage and test-based documentation
1 parent 699d03c commit d4e78c4

File tree

1 file changed

+97
-0
lines changed

1 file changed

+97
-0
lines changed
 

‎packages/react-dom/src/__tests__/refs-test.js

+97
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)
Please sign in to comment.