Тестирование middleware

Middleware в Floating UI представляет собой цепочку функций, которые обрабатывают позиционирование всплывающих элементов (popper, tooltip, dropdown) перед окончательным рендером. Каждая функция middleware получает текущее состояние позиции и возвращает модифицированное состояние. Middleware позволяет управлять смещением, предотвращать выход за границы окна, применять сдвиги и другие трансформации.

Структура middleware выглядит следующим образом:

import { offset, flip, shift } from '@floating-ui/dom';

const middleware = [
  offset(10),
  flip(),
  shift({ padding: 5 })
];

Здесь каждая функция выполняет свою задачу в цепочке, а результат передаётся следующему middleware.


Типы и функциональность middleware

  1. offset – добавляет смещение от целевого элемента. Параметры:

    • mainAxis – смещение по основной оси.
    • crossAxis – смещение по перекрёстной оси.
    offset({ mainAxis: 8, crossAxis: 4 });

    Это полезно для создания отступов между trigger-элементом и всплывающим окном.

  2. flip – автоматически меняет позицию всплывающего элемента, если она выходит за пределы viewport. Параметры:

    • fallbackPlacements – массив альтернативных позиций.
    • padding – отступ от границ окна.
    flip({ fallbackPlacements: ['top', 'right'], padding: 10 });

    Flip гарантирует, что элемент останется видимым вне зависимости от ограничений контейнера.

  3. shift – корректирует позицию при перекрытии краёв viewport. Параметры:

    • padding – внутренний отступ от границ окна.
    shift({ padding: 5 });

    Этот middleware особенно важен при динамически изменяемых размерах окна.

  4. size – позволяет управлять размером всплывающего элемента относительно целевого. Параметры:

    • apply – функция для изменения стилей.
    • padding – отступ для вычислений.
    size({
      apply({ rects, availableHeight, elements }) {
        elements.floating.style.maxHeight = `${availableHeight}px`;
      },
    });

Создание кастомного middleware

Custom middleware позволяет реализовать уникальные правила позиционирования:

function customShift({ x, y, elements }) {
  return {
    x: x + 20,
    y: y,
  };
}

const middleware = [customShift];

Функция middleware получает объект:

  • x, y – текущие координаты элемента.
  • placement – выбранная позиция.
  • rects – размеры target и floating.
  • elements – DOM-элементы target и floating.

Возвращаемое значение может включать:

  • x, y – новые координаты.
  • data – объект с дополнительной информацией для следующих middleware.
  • reset – флаг перезапуска цепочки с новой позицией.

Последовательность работы middleware

  1. Floating UI вычисляет базовые координаты floating-элемента.
  2. Эти координаты передаются первому middleware.
  3. Каждый middleware получает текущее состояние и возвращает изменённое.
  4. После обработки последнего middleware координаты применяются к DOM.

Пример с цепочкой middleware:

const middlewareChain = [
  offset(10),
  flip({ fallbackPlacements: ['bottom', 'left'] }),
  shift({ padding: 8 }),
  size({
    apply({ elements, availableHeight }) {
      elements.floating.style.maxHeight = `${availableHeight}px`;
    },
  }),
];

Тестирование middleware

Тестирование middleware требует проверки:

  • Корректности расчёта координат при различных позициях target.
  • Взаимодействия цепочки middleware, чтобы один не ломал работу другого.
  • Событий изменения окна, чтобы shift и flip работали корректно.
  • Применения стилей, например, middleware size должен корректно ограничивать размеры.

Юнит-тесты middleware

Использование Jest или Vitest позволяет тестировать функции изолированно:

import { offset } from '@floating-ui/dom';

test('offset middleware добавляет смещение', () => {
  const result = offset({ mainAxis: 10 })({ x: 0, y: 0 });
  expect(result.x).toBe(0);
  expect(result.y).toBe(10);
});

Можно тестировать цепочку middleware через мок объектов rects и elements:

import { flip } from '@floating-ui/dom';

const mockState = {
  x: 0,
  y: 0,
  placement: 'top',
  rects: {
    reference: { width: 50, height: 50, top: 0, left: 0 },
    floating: { width: 100, height: 100 },
  },
  elements: {
    reference: document.createElement('div'),
    floating: document.createElement('div'),
  },
};

test('flip middleware корректно меняет позицию', () => {
  const result = flip({ fallbackPlacements: ['bottom'] })(mockState);
  expect(result.placement).toBe('bottom');
});

Интеграционные тесты

Для проверки middleware в реальных условиях:

  • Создать тестовую страницу с floating-элементом и trigger.
  • Изменять размеры окна и позицию target.
  • Проверять, что элемент остаётся видимым и применяет смещения и размеры.
const floating = document.createElement('div');
document.body.appendChild(floating);

computePosition(trigger, floating, { middleware: middlewareChain });

Интеграционные тесты позволяют выявить баги в цепочках middleware, которые не видны при юнит-тестах, например конфликты offset и shift или неправильная работа flip при нескольких fallbackPlacements.


Практические рекомендации по тестированию middleware

  • Проверять крайние случаи: target у границ окна, маленькие размеры floating.
  • Использовать snapshot-тесты для DOM-изменений.
  • Разделять логику: custom middleware тестировать отдельно от стандартных.
  • Всегда учитывать порядок middleware: flip перед shift может давать другой результат, чем наоборот.
  • Использовать мок объекты для rects и elements, чтобы тесты были стабильными и независимыми от реального DOM.

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