Files
v10/packages/react/src/utils/tests/use-composed-refs.test.tsx
T

128 lines
3.7 KiB
TypeScript

import type { MutableRefObject, RefObject } from 'react';
import { render } from '@testing-library/react';
import { useRef } from 'react';
import { describe, expect, it, vi } from 'vitest';
import { composeRefs, useComposedRefs } from '../use-composed-refs';
// Helper to create a mutable ref object for testing (without using deprecated createRef)
function createMutableRef<T>(initialValue: T | null = null): MutableRefObject<T | null> {
return { current: initialValue };
}
describe('composeRefs', () => {
it('sets value on callback ref', () => {
const callbackRef = vi.fn();
const composed = composeRefs(callbackRef);
composed('test-value');
expect(callbackRef).toHaveBeenCalledWith('test-value');
});
it('sets value on RefObject', () => {
const refObject = createMutableRef<string>();
const composed = composeRefs(refObject);
composed('test-value');
expect(refObject.current).toBe('test-value');
});
it('sets value on multiple refs', () => {
const callbackRef = vi.fn();
const refObject = createMutableRef<string>();
const composed = composeRefs(callbackRef, refObject);
composed('test-value');
expect(callbackRef).toHaveBeenCalledWith('test-value');
expect(refObject.current).toBe('test-value');
});
it('handles undefined refs', () => {
const callbackRef = vi.fn();
const composed = composeRefs(undefined, callbackRef, undefined);
composed('test-value');
expect(callbackRef).toHaveBeenCalledWith('test-value');
});
it('returns cleanup function when callback ref returns one', () => {
const cleanup = vi.fn();
const callbackRef = vi.fn().mockReturnValue(cleanup);
const composed = composeRefs(callbackRef);
const returnedCleanup = composed('test-value') as (() => void) | void;
expect(returnedCleanup).toBeTypeOf('function');
if (typeof returnedCleanup === 'function') {
returnedCleanup();
}
expect(cleanup).toHaveBeenCalled();
});
it('clears RefObject on cleanup', () => {
const cleanup = vi.fn();
const callbackRef = vi.fn().mockReturnValue(cleanup);
const refObject = createMutableRef<string>();
const composed = composeRefs(callbackRef, refObject);
composed('test-value');
expect(refObject.current).toBe('test-value');
const returnedCleanup = composed('test-value') as (() => void) | void;
if (typeof returnedCleanup === 'function') {
returnedCleanup();
}
expect(refObject.current).toBeNull();
});
});
describe('useComposedRefs', () => {
it('returns a stable callback ref', () => {
let composedRef1: ((value: HTMLDivElement | null) => void) | null = null;
let composedRef2: ((value: HTMLDivElement | null) => void) | null = null;
function TestComponent() {
const ref1 = useRef<HTMLDivElement>(null);
const ref2 = useRef<HTMLDivElement>(null);
const composed = useComposedRefs(ref1, ref2);
if (!composedRef1) {
composedRef1 = composed;
} else {
composedRef2 = composed;
}
return <div ref={composed}>Test</div>;
}
const { rerender } = render(<TestComponent />);
rerender(<TestComponent />);
// Same refs should produce same composed ref
expect(composedRef1).toBe(composedRef2);
});
it('works with forwardRef pattern', () => {
const externalRef = createMutableRef<HTMLDivElement>();
function TestComponent({ forwardedRef }: { forwardedRef: RefObject<HTMLDivElement | null> }) {
const internalRef = useRef<HTMLDivElement>(null);
const composedRef = useComposedRefs(forwardedRef, internalRef);
return <div ref={composedRef}>Test</div>;
}
render(<TestComponent forwardedRef={externalRef} />);
expect(externalRef.current).toBeInstanceOf(HTMLDivElement);
});
});