Ref и forwardRef тестирование

React предоставляет механизм ref для получения прямого доступа к DOM-элементам или экземплярам компонентов. Этот механизм полезен, когда необходимо взаимодействовать с элементами вне обычного потока данных через props и state. Однако, несмотря на свою полезность, использование ref в тестах требует особого подхода, чтобы корректно взаимодействовать с компонентами, которые используют ref или forwardRef.

Принцип работы с ref в React

ref позволяет 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, важно учитывать следующие моменты:

  1. Проверка на фокусировку: Мы должны удостовериться, что при вызове функции фокусировки (например, кнопки) элемент ввода получает фокус.
  2. Доступ к внутренним элементам: Мы должны убедиться, что внешний 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 корректно передается и что компоненты реагируют на изменения состояния, вызванные асинхронными операциями.