Unit тестирование модификаторов

Popper.js предоставляет мощный механизм для позиционирования всплывающих элементов, таких как тултипы, дропдауны и поповеры. Центральным элементом этой системы являются модификаторы, которые позволяют изменять поведение и внешний вид всплывающего элемента. Unit-тестирование модификаторов помогает гарантировать их корректную работу при различных конфигурациях.


Подготовка окружения для тестирования

Для unit-тестирования используется сочетание инструментов, таких как Jest или Mocha. Основные задачи включают:

  1. Инициализацию Popper-инстанса с тестируемым модификатором.
  2. Симуляцию взаимодействия с DOM (например, изменение размеров или положения reference-элемента).
  3. Проверку ожидаемого состояния popper-а после выполнения модификатора.

Пример подключения необходимых инструментов:

import { createPopper } from '@popperjs/core';

Структура теста модификатора

Каждый тест состоит из нескольких этапов:

  1. Создание DOM-элементов: reference и popper.
  2. Инициализация Popper с нужными опциями, включая модификатор.
  3. Вызов жизненного цикла Popper для применения модификатора.
  4. Проверка результата через DOM или объект state.

Пример шаблона:

test('custom modifier correctly updates popper offsets', () => {
  const reference = document.createElement('div');
  const popper = document.createElement('div');

  document.body.appendChild(reference);
  document.body.appendChild(popper);

  const customModifier = {
    name: 'customOffset',
    enabled: true,
    phase: 'main',
    fn({ state }) {
      state.styles.popper.top = `${parseInt(state.styles.popper.top || 0) + 10}px`;
    }
  };

  const popperInstance = createPopper(reference, popper, {
    modifiers: [customModifier]
  });

  popperInstance.update();

  expect(parseInt(popper.style.top)).toBe(10);
});

Тестирование встроенных модификаторов

Popper.js содержит стандартные модификаторы, такие как offset, preventOverflow и flip. Тестирование встроенных модификаторов требует проверки их конфигурационных параметров и эффекта на позиции popper-а.

Проверка модификатора offset

test('offset modifier shifts popper position', () => {
  const reference = document.createElement('button');
  const popper = document.createElement('div');

  document.body.appendChild(reference);
  document.body.appendChild(popper);

  const popperInstance = createPopper(reference, popper, {
    modifiers: [
      { name: 'offset', options: { offset: [0, 20] } }
    ]
  });

  popperInstance.update();

  const computedTop = parseFloat(popper.style.top);
  expect(computedTop).toBeGreaterThan(0);
});

Проверка модификатора preventOverflow

preventOverflow гарантирует, что popper не выйдет за пределы указанных границ:

test('preventOverflow keeps popper within boundaries', () => {
  const reference = document.createElement('div');
  const popper = document.createElement('div');
  const boundary = document.createElement('div');

  boundary.style.position = 'absolute';
  boundary.style.top = '0';
  boundary.style.left = '0';
  boundary.style.width = '100px';
  boundary.style.height = '100px';
  document.body.appendChild(boundary);

  document.body.appendChild(reference);
  document.body.appendChild(popper);

  const popperInstance = createPopper(reference, popper, {
    modifiers: [
      { name: 'preventOverflow', options: { boundary } }
    ]
  });

  popperInstance.update();

  const popperRect = popper.getBoundingClientRect();
  const boundaryRect = boundary.getBoundingClientRect();

  expect(popperRect.top).toBeGreaterThanOrEqual(boundaryRect.top);
  expect(popperRect.left).toBeGreaterThanOrEqual(boundaryRect.left);
});

Стратегии мокирования и симуляции

Для unit-тестов важно мокировать поведение браузера, если тест запускается в среде без полноценного DOM (например, Node.js):

  • Мокирование getBoundingClientRect для reference и popper.
  • Симуляция скролла и изменения размеров окна.
  • Использование jest-функций для отслеживания вызовов модификатора.

Пример мокирования getBoundingClientRect:

reference.getBoundingClientRect = jest.fn(() => ({
  width: 100,
  height: 50,
  top: 100,
  left: 100,
  right: 200,
  bottom: 150
}));

popper.getBoundingClientRect = jest.fn(() => ({
  width: 80,
  height: 40,
  top: 0,
  left: 0,
  right: 80,
  bottom: 40
}));

Проверка жизненного цикла модификаторов

Каждый модификатор в Popper.js выполняется на определённой фазе: read, main, write. Unit-тест должен проверять поведение на соответствующей фазе:

  • read — вычисление размеров и позиции элементов.
  • main — изменение state и стилей.
  • write — запись изменений в DOM.

Пример проверки вызова модификатора на фазе main:

const mockFn = jest.fn(({ state }) => {
  state.attributes.popper['data-test'] = 'applied';
});

createPopper(reference, popper, {
  modifiers: [{ name: 'mockModifier', phase: 'main', fn: mockFn }]
}).update();

expect(popper.getAttribute('data-test')).toBe('applied');

Тестирование кастомных модификаторов с зависимостями

Иногда модификатор зависит от других модификаторов, указанных в requires или requiresIfExists. Unit-тест должен:

  1. Обеспечить наличие необходимых зависимостей.
  2. Проверить правильный порядок вызова.
  3. Убедиться, что state обновляется корректно.
const baseModifier = {
  name: 'base',
  enabled: true,
  phase: 'read',
  fn({ state }) { state.modifierData.baseValue = 42; }
};

const dependentModifier = {
  name: 'dependent',
  enabled: true,
  phase: 'main',
  requires: ['base'],
  fn({ state }) { state.styles.popper.top = `${state.modifierData.baseValue}px`; }
};

createPopper(reference, popper, { modifiers: [baseModifier, dependentModifier] }).update();

expect(popper.style.top).toBe('42px');

Автоматизация тестов

  • Unit-тесты для модификаторов рекомендуется запускать на каждом коммите, чтобы обнаруживать регрессии.
  • Интеграция с CI/CD позволяет контролировать стабильность пользовательских компонентов, зависящих от Popper.js.
  • Тесты должны охватывать все критические варианты: изменение размеров, скролл, различные позиции reference, отключенные модификаторы.

Unit-тестирование модификаторов Popper.js обеспечивает высокую надёжность работы всплывающих элементов, выявляет ошибки взаимодействия между модификаторами и гарантирует стабильное поведение интерфейса при любых изменениях.