Unit тесты для позиционирования

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


Настройка тестового окружения

Для unit-тестирования рекомендуется использовать Jest или Vitest, а также JSDOM для имитации DOM-среды. Floating UI требует, чтобы элементы имели реальные размеры и позиции, поэтому часто необходимо мокировать размеры и координаты элементов через свойства getBoundingClientRect:

const reference = document.createElement('div');
const floating = document.createElement('div');

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

Такой подход позволяет тестировать позиционирование без необходимости рендерить элементы в реальном браузере.


Тестирование вычисления позиции

Floating UI предоставляет функции computePosition, которые возвращают объект с координатами x и y и информацией о стратегии позиционирования (placement, middlewareData). В unit-тестах важно проверить:

  1. Корректность вычисления базовой позиции для разных placement:
const {computePosition} = require('@floating-ui/dom');

test('позиция floating элемента с placement top', async () => {
  const {x, y, placement} = await computePosition(reference, floating, {placement: 'top'});
  expect(placement).toBe('top');
  expect(y).toBeLessThan(reference.getBoundingClientRect().top);
});
  1. Применение middleware, таких как offset, flip и shift:
import {offset, flip, shift} from '@floating-ui/dom';

test('middleware offset корректно смещает элемент', async () => {
  const {x, y} = await computePosition(reference, floating, {
    placement: 'bottom',
    middleware: [offset(10)]
  });
  expect(y).toBe(reference.getBoundingClientRect().bottom + 10);
});
  1. Реакция на крайние условия (например, при переполнении viewport или пересечении с границами документа):
test('flip middleware меняет placement при нехватке места', async () => {
  const {placement} = await computePosition(reference, floating, {
    placement: 'top',
    middleware: [flip()]
  });
  expect(['top', 'bottom']).toContain(placement);
});

Мокирование размеров и скролла

В реальных приложениях floating-элемент может зависеть от размеров окна и позиции скролла. В тестах эти данные можно замокировать:

Object.defineProperty(window, 'innerWidth', {value: 800});
Object.defineProperty(window, 'innerHeight', {value: 600});
window.scrollX = 50;
window.scrollY = 100;

При этом функции computePosition будут использовать эти значения для расчёта x и y.


Тестирование реакции на изменения DOM

Floating UI поддерживает динамическое позиционирование через middleware autoUpdate. Для unit-тестов важно симулировать изменение размеров элементов и проверять, что новые координаты пересчитываются корректно:

import {autoUpdate, computePosition} from '@floating-ui/dom';

let cleanup;
beforeEach(() => {
  cleanup = autoUpdate(reference, floating, ({x, y}) => {
    floating.style.transform = `translate(${x}px, ${y}px)`;
  });
});

afterEach(() => {
  cleanup();
});

test('обновление позиции при изменении размеров reference', async () => {
  reference.getBoundingClientRect = jest.fn(() => ({
    width: 200,
    height: 100,
    top: 100,
    left: 100,
    right: 300,
    bottom: 200,
  }));
  
  const {x, y} = await computePosition(reference, floating, {placement: 'bottom'});
  expect(y).toBe(200);
});

Практические советы по написанию unit-тестов

  • Разделять middleware: проверять каждый middleware отдельно, чтобы точно локализовать ошибки.
  • Проверять все варианты placement: top, bottom, left, right, а также комбинации с start и end.
  • Использовать моковые размеры: без них JSDOM не предоставляет реальные значения размеров.
  • Тестировать реакцию на крайние сценарии: элементы на границе viewport, отрицательные координаты, перекрытия другими элементами.
  • Избегать зависимости от CSS: unit-тесты должны проверять только вычисления координат, а не визуальное отображение.

Валидация координат и стратегий

Floating UI возвращает стратегию позиционирования absolute или fixed. В тестах важно убедиться, что:

  • absolute корректно учитывает offset родительского контейнера.
  • fixed игнорирует скролл страницы.
test('fixed strategy игнорирует скролл', async () => {
  const {x, y} = await computePosition(reference, floating, {strategy: 'fixed'});
  expect(y).toBe(reference.getBoundingClientRect().top);
});

Проверка middleware shift и flip

Middleware shift корректирует позицию floating-элемента при пересечении с границами контейнера, а flip меняет placement. Unit-тесты должны проверять оба эффекта:

test('shift корректирует координаты при выходе за viewport', async () => {
  reference.getBoundingClientRect = jest.fn(() => ({
    width: 100,
    height: 50,
    top: -20,
    left: 50,
    right: 150,
    bottom: 30,
  }));

  const {x, y} = await computePosition(reference, floating, {middleware: [shift()]});
  expect(y).toBeGreaterThanOrEqual(0);
});

Тестирование цепочек middleware

Floating UI позволяет комбинировать middleware для сложного поведения. В unit-тестах важно проверять цепочку в целом:

test('offset + flip + shift middleware корректно взаимодействуют', async () => {
  const {x, y, placement} = await computePosition(reference, floating, {
    placement: 'top',
    middleware: [offset(10), flip(), shift()]
  });

  expect(['top', 'bottom']).toContain(placement);
  expect(x).toBeGreaterThanOrEqual(0);
  expect(y).toBeGreaterThanOrEqual(0);
});

Unit-тесты для Floating UI концентрируются на точности вычислений координат, правильности применения middleware и устойчивости к изменениям размеров и скролла. Такой подход обеспечивает надежность позиционирования даже при сложных сценариях интерфейса.