React предоставляет механизм ref для получения прямого
доступа к DOM-элементам или экземплярам компонентов. Этот механизм
полезен, когда необходимо взаимодействовать с элементами вне обычного
потока данных через props и state. Однако, несмотря на свою полезность,
использование ref в тестах требует особого подхода, чтобы
корректно взаимодействовать с компонентами, которые используют
ref или forwardRef.
ref
в Reactref позволяет React-элементам или компонентам сохранять
ссылку на DOM-элементы или экземпляры классовых компонентов. Это
необходимо, когда требуется доступ к этим элементам для манипуляции с их
состоянием или выполнения операций, которые не могут быть выполнены
через обычное взаимодействие с состоянием и props.
Для функциональных компонентов доступ к ref можно
реализовать с помощью React.forwardRef. Этот метод
позволяет передавать ref из родительского компонента в
дочерний компонент. Применяется это в случаях, когда нужно обернуть
компонент, предоставив ему возможность передавать ссылки на
DOM-элементы.
refТестирование компонентов с ref имеет свои особенности,
так как большинство библиотек для тестирования (включая React Testing
Library) фокусируются на взаимодействии с компонентами через их
публичные API (например, с помощью props и событий). Однако когда
компонент использует ref для доступа к DOM-элементам,
возникает необходимость тестировать, как эти элементы взаимодействуют в
рамках UI.
refПредположим, что у нас есть компонент, который использует
ref для фокусировки на элементе ввода:
import React, { useRef, useEffect } from 'react';
const InputWithFocusButton = React.forwardRef((props, ref) => {
const inputRef = useRef();
const focusInput = () => {
inputRef.current.focus();
};
useEffect(() => {
if (ref) {
ref.current = inputRef.current;
}
}, [ref]);
return (
<div>
<input ref={inputRef} />
<button onCl ick={focusInput}>Focus the input</button>
</div>
);
});
export default InputWithFocusButton;
В этом примере InputWithFocusButton использует как
внутренний useRef, так и переданный ref для
фокусировки на элементе ввода. Это довольно типичный случай
использования forwardRef, когда внешний компонент может
управлять ссылкой на внутренний элемент.
ref и forwardRefДля того чтобы протестировать такой компонент, нам нужно убедиться,
что элемент может быть сосредоточен с помощью вызова метода на
переданный ref.
Для тестирования компонента с ref, важно учитывать
следующие моменты:
ref корректно передается и ссылается на
DOM-элемент.Пример теста с использованием React Testing Library:
import { render, screen, fireEvent } from '@testing-library/react';
import InputWithFocusButton from './InputWithFocusButton';
test('input gets focus when button is clicked', () => {
const inputRef = { current: null };
render(<InputWithFocusButton ref={inputRef} />);
const button = screen.getByText('Focus the input');
fireEvent.click(button);
// Проверка, что input элемент получил фокус
expect(inputRef.current).toHaveFocus();
});
Здесь мы передаем объект inputRef в качестве
ref в компонент и проверяем, что после клика по кнопке поле
ввода действительно получает фокус.
forwardRefВажной особенностью при тестировании компонентов с
forwardRef является то, что forwardRef создает
внутреннюю обертку для компонента, что может затруднить тестирование.
Чтобы проверить, что ссылку на DOM-элемент можно правильно передать из
родителя в дочерний компонент, нужно не только проверять, что
ref ссылается на элемент, но и что элемент имеет доступ к
необходимым методам.
Если необходимо провести тестирование сложных компонентов с
использованием ref, важно, чтобы все асинхронные операции
(например, получение фокуса или изменение состояния) учитывались в
тестах с помощью соответствующих методов, таких как waitFor
в React Testing Library.
refsКогда компонент использует несколько ссылок, например, несколько
ref для разных DOM-элементов, то важно проверить, что
каждый из этих ref корректно ссылается на нужный
элемент.
Пример компонента с несколькими ref:
import React, { useRef, forwardRef } from 'react';
const MultiRefComponent = forwardRef((props, ref) => {
const inputRef = useRef();
const buttonRef = useRef();
React.useImperativeHandle(ref, () => ({
focusInput: () => inputRef.current.focus(),
focusButton: () => buttonRef.current.focus(),
}));
return (
<div>
<input ref={inputRef} />
<button ref={buttonRef}>Click Me</button>
</div>
);
});
export default MultiRefComponent;
В этом примере создается компонент, который имеет несколько ссылок на
разные элементы — инпут и кнопку. Мы также используем
useImperativeHandle, чтобы предоставить родительскому
компоненту возможность фокусировать эти элементы.
Для тестирования такого компонента можно написать следующий тест:
import { render, screen, fireEvent } from '@testing-library/react';
import MultiRefComponent from './MultiRefComponent';
test('focuses on input when focusInput is called', () => {
const ref = { current: null };
render(<MultiRefComponent ref={ref} />);
ref.current.focusInput();
const input = screen.getByRole('textbox');
expect(input).toHaveFocus();
});
test('focuses on button when focusButton is called', () => {
const ref = { current: null };
render(<MultiRefComponent ref={ref} />);
ref.current.focusButton();
const button = screen.getByRole('button');
expect(button).toHaveFocus();
});
В этих тестах проверяется фокусировка на разных элементах в
зависимости от вызова соответствующих методов, предоставленных через
ref.
Тестирование компонентов с использованием ref и
forwardRef требует особого подхода, особенно когда
компоненты имеют сложные взаимодействия с DOM-элементами. Использование
React Testing Library позволяет удобно работать с такими
компонентами, предоставляя методы для проверки фокуса, манипуляций с
элементами и других действий, которые обычно происходят через прямое
взаимодействие с DOM. Важно тщательно проверять, что ref
корректно передается и что компоненты реагируют на изменения состояния,
вызванные асинхронными операциями.