Моки и стабы

В Popper.js управление положением всплывающих элементов (popper) тесно связано с использованием моков (mocks) и стабов (stubs) при тестировании. Эти инструменты позволяют изолировать поведение элементов, проверить алгоритмы вычисления позиций и обеспечить корректное взаимодействие popper с reference-элементом без необходимости реально рендерить DOM.

Моки (Mocks)

Мок — это объект, имитирующий поведение реального элемента или функции. В контексте Popper.js моки часто применяются для:

  • Подмены DOM-элементов (reference или popper) при юнит-тестах.
  • Симуляции событий, таких как resize или scroll.
  • Проверки правильности вычисления placement и offset.

Пример создания мока reference-элемента:

const referenceMock = {
  getBoundingClientRect: jest.fn(() => ({
    width: 100,
    height: 50,
    top: 200,
    left: 300,
    right: 400,
    bottom: 250,
  })),
  clientWidth: 100,
  clientHeight: 50,
};

Здесь метод getBoundingClientRect возвращает заранее определённые размеры и координаты. Это позволяет тестировать функции Popper.js, которые зависят от положения reference, не создавая реальный DOM.

Моки также полезны при проверке модификаторов (modifiers). Например, offset можно протестировать без фактического изменения позиции в браузере:

const popperInstance = createPopper(referenceMock, popperMock, {
  placement: 'top',
  modifiers: [
    {
      name: 'offset',
      options: {
        offset: [0, 10],
      },
    },
  ],
});

popperInstance.update();
expect(popperInstance.state.modifiersData.offset).toEqual({ x: 0, y: 10 });

Стабы (Stubs)

Стаб — это объект или функция, заменяющая часть кода с целью контролировать её поведение. В отличие от моков, стабы чаще используют для контроля внутренних зависимостей Popper.js, а не имитации внешнего API.

Пример стаба модификатора:

const flipStub = jest.fn(({ state, options }) => {
  state.placement = 'bottom';
  return state;
});

const popperInstance = createPopper(referenceMock, popperMock, {
  placement: 'top',
  modifiers: [
    {
      name: 'flip',
      enabled: true,
      phase: 'main',
      fn: flipStub,
    },
  ],
});

popperInstance.update();
expect(flipStub).toHaveBeenCalled();
expect(popperInstance.state.placement).toBe('bottom');

Стабы позволяют:

  • Подменять поведение модификаторов (flip, preventOverflow, arrow).
  • Проверять взаимодействие модификаторов между собой.
  • Изолировать тестируемую логику от сложных сторонних зависимостей.

Отличия моков и стабов

Характеристика Мок Стаб
Назначение Имитировать внешний объект или API Подменять внутреннюю функцию или модуль
Контроль поведения Возвращает заранее заданные значения Управляет тем, как функция себя ведёт
Использование Юнит-тестирование элементов DOM Юнит-тестирование модификаторов и логики Popper
Примеры в Popper.js reference, popper, events flip, offset, preventOverflow

Практические рекомендации

  1. Использовать моки для reference и popper Это позволяет полностью контролировать их размеры и позицию без рендеринга страницы.

  2. Стабы для модификаторов Если нужно проверить корректность вызова или изменения состояния, стабы обеспечивают точный контроль.

  3. Комбинировать моки и стабы Для комплексного тестирования можно одновременно использовать моки для DOM и стабы для модификаторов. Это позволяет проверять полное поведение Popper.js в изоляции.

  4. Проверка фаз модификаторов Popper.js выполняет модификаторы в фазах read, main, write. Стабы позволяют проверить, что модификатор срабатывает именно в нужной фазе:

expect(flipStub.mock.calls[0][0].phase).toBe('main');
  1. Измерение влияния offset и flip Используя моки размеров и стаб для flip, можно моделировать ситуации, когда popper меняет положение автоматически:
const stateBefore = popperInstance.state.placement;
popperInstance.update();
const stateAfter = popperInstance.state.placement;
expect(stateBefore).not.toBe(stateAfter);

Моки и стабы позволяют строить надёжные тесты Popper.js без зависимости от визуального рендеринга, делая процесс проверки быстрым и предсказуемым.

Дополнительные техники

  • Мокаем событие scroll:
window.dispatchEvent(new Event('scroll'));
  • Стабим сложные вычисления:
const computeStylesStub = jest.spyOn(popperInstance.modifiersData, 'computeStyles').mockImplementation(() => {
  return { x: 10, y: 20 };
});
  • Комбинированная проверка state:
popperInstance.update();
expect(popperInstance.state.placement).toBe('bottom');
expect(popperInstance.state.modifiersData.offset).toEqual({ x: 0, y: 10 });

Эти подходы позволяют тестировать как отдельные части Popper.js, так и их взаимодействие, создавая полное покрытие поведения popper без необходимости работы с реальным DOM.