Бесконечный скролл (infinite scroll)

Бесконечный скролл — это популярная техника в веб-разработке, позволяющая подгружать новые данные по мере прокрутки страницы пользователем, создавая эффект бесконечной загрузки контента. Эта техника часто используется в социальных сетях, новостных сайтах, интернет-магазинах и других веб-приложениях, где требуется динамически обновлять контент без перезагрузки страницы.

Когда речь заходит о тестировании компонентов с бесконечным скроллом в React, важно не только проверить работоспособность самого механизма скроллинга, но и корректность отображения данных, загрузки новых элементов, а также проверку асинхронных операций.

Компоненты и хуки для реализации

Для реализации бесконечного скролла в React часто используется комбинация различных хуков и методов, таких как useState, useEffect, а также библиотеки для работы с виртуализацией (например, react-window или react-virtualized). Основной логикой является отслеживание текущей позиции скролла и подгрузка новых данных, когда пользователь достигает нижней границы видимой области.

Пример простого компонента с бесконечным скроллом:

import React, { useState, useEffect } from 'react';

const InfiniteScroll = ({ fetchData, hasMoreData }) => {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const handleScroll = () => {
      if (
        window.innerHeight + document.documentElement.scrollTop
        === document.documentElement.offsetHeight
        && !loading && hasMoreData
      ) {
        setLoading(true);
        fetchData().then(newData => {
          setData(prevData => [...prevData, ...newData]);
          setLoading(false);
        });
      }
    };

    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [loading, fetchData, hasMoreData]);

  return (
    <div>
      {data.map((item, index) => (
        <div key={index}>{item}</div>
      ))}
      {loading && <div>Loading...</div>}
    </div>
  );
};

export default InfiniteScroll;

В этом примере компонент загружает данные по мере прокрутки до конца страницы, добавляя их в состояние и отображая в списке.

Тестирование компонентов с бесконечным скроллом

Тестирование компонентов, использующих бесконечный скролл, требует особого подхода. Основные задачи, которые нужно решить в тестах:

  1. Проверка подгрузки новых данных: необходимо проверить, что при прокрутке страницы данные загружаются.
  2. Асинхронность: компоненты с бесконечным скроллом зависят от асинхронных вызовов для подгрузки новых элементов. Это должно быть правильно протестировано.
  3. Обработка состояния загрузки: важно удостовериться, что состояние загрузки отображается корректно.

Для тестирования компонентов React с использованием React Testing Library важно уделить внимание событиям, состояниям и асинхронным операциям.

Основные шаги для тестирования

1. Настройка среды тестирования

Для работы с асинхронными данными важно правильно настроить среду. Для имитации асинхронных вызовов можно использовать jest.mock() для подмены реальных API-запросов или же использовать фиктивные данные. Также полезно добавить обработку времени с помощью jest.useFakeTimers().

2. Прокрутка страницы

React Testing Library предоставляет удобные инструменты для имитации событий прокрутки. Важно прокачать тест таким образом, чтобы скроллинг был проверен с учетом динамической загрузки контента.

Пример теста с прокруткой:

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import InfiniteScroll from './InfiniteScroll';

test('загружает новые данные при прокрутке до конца', async () => {
  const fetchData = jest.fn().mockResolvedValue([1, 2, 3]);
  const { container } = render(<InfiniteScroll fetchData={fetchData} hasMoreData={true} />);

  // Прокрутка до конца страницы
  fireEvent.scroll(container, { target: { scrollY: 1000 } });

  // Ожидание завершения асинхронной загрузки
  await waitFor(() => expect(fetchData).toHaveBeenCalledTimes(1));

  // Проверка отображения загруженных данных
  expect(screen.getByText('1')).toBeInTheDocument();
  expect(screen.getByText('2')).toBeInTheDocument();
  expect(screen.getByText('3')).toBeInTheDocument();
});

В этом тесте проверяется, что при прокрутке страницы вызывается функция fetchData для подгрузки новых данных. После этого проверяется, что новые данные отображаются на экране.

3. Проверка состояния загрузки

Для тестирования состояния загрузки можно использовать элементы UI, такие как индикатор загрузки. Например, можно проверить, что индикатор появляется при подгрузке новых данных.

Пример:

test('показывает индикатор загрузки при загрузке новых данных', async () => {
  const fetchData = jest.fn().mockResolvedValue([4, 5, 6]);
  render(<InfiniteScroll fetchData={fetchData} hasMoreData={true} />);

  // Прокрутка до конца
  fireEvent.scroll(window, { target: { scrollY: 1000 } });

  // Проверка, что индикатор загрузки появился
  expect(screen.getByText('Loading...')).toBeInTheDocument();

  // Ожидание завершения асинхронной загрузки
  await waitFor(() => expect(fetchData).toHaveBeenCalledTimes(1));

  // Проверка, что индикатор загрузки исчез
  expect(screen.queryByText('Loading...')).toBeNull();
});

Здесь проверяется, что во время подгрузки данных появляется индикатор “Loading…”, а после завершения загрузки — исчезает.

4. Асинхронность

Так как данные подгружаются асинхронно, важно использовать waitFor или findBy из React Testing Library для корректного ожидания завершения асинхронных операций.

test('проверка асинхронной загрузки данных', async () => {
  const fetchData = jest.fn().mockResolvedValue([7, 8, 9]);
  render(<InfiniteScroll fetchData={fetchData} hasMoreData={true} />);

  // Прокрутка до конца
  fireEvent.scroll(window, { target: { scrollY: 1000 } });

  // Ожидание завершения асинхронного вызова
  const item7 = await screen.findByText('7');
  const item8 = await screen.findByText('8');
  const item9 = await screen.findByText('9');

  expect(item7).toBeInTheDocument();
  expect(item8).toBeInTheDocument();
  expect(item9).toBeInTheDocument();
});

В этом тесте используется findByText для ожидания появления загруженных элементов.

Рекомендации по улучшению тестов

  1. Тестирование различных состояний: помимо проверки успешной загрузки, полезно добавить тесты для случаев, когда данные не подгружаются, либо когда происходит ошибка при загрузке.
  2. Проверка оптимизации: для компонентов с большим объемом данных важно тестировать производительность и поведение при большом числе элементов, чтобы убедиться в отсутствии лагов.
  3. Мокирование зависимостей: при тестировании асинхронных операций важно правильно замокать запросы, чтобы контролировать данные, с которыми работает компонент.

Правильное тестирование компонентов с бесконечным скроллом помогает обеспечить их стабильность и предсказуемость поведения в реальных условиях.