В контексте JavaScript и работы с библиотекой Floating UI, моки и стабы используются для имитации поведения элементов DOM и API браузера, чтобы тестировать позиционирование всплывающих элементов без необходимости реального рендеринга в браузере. Мок (mock) — это объект, который имитирует поведение настоящего объекта, включая методы и их возвращаемые значения. Стаб (stub) — частный случай мока, обычно подменяющий конкретную функцию или метод для контроля её результата.
document.createElement и подмена размеровFloating UI активно использует размеры и расположение элементов через
свойства DOM, такие как offsetWidth,
offsetHeight, getBoundingClientRect. В тестах
необходимо создавать стабы для этих свойств:
const reference = document.createElement('div');
Object.defineProperty(reference, 'offsetWidth', { value: 100 });
Object.defineProperty(reference, 'offsetHeight', { value: 50 });
const floating = document.createElement('div');
Object.defineProperty(floating, 'offsetWidth', { value: 80 });
Object.defineProperty(floating, 'offsetHeight', { value: 40 });
При этом getBoundingClientRect можно замокать, чтобы
возвращать контролируемые координаты:
reference.getBoundingClientRect = () => ({
width: 100,
height: 50,
top: 100,
left: 200,
right: 300,
bottom: 150,
});
Эти подходы позволяют полностью контролировать вычисления Floating UI, включая размещение тултипов, поповеров и других плавающих элементов.
Для корректного тестирования поведения Floating UI на странице с
прокруткой необходимо мокировать свойства window и
document.documentElement:
Object.defineProperty(window, 'scrollX', { value: 50 });
Object.defineProperty(window, 'scrollY', { value: 100 });
Object.defineProperty(document.documentElement, 'clientWidth', { value: 1024 });
Object.defineProperty(document.documentElement, 'clientHeight', { value: 768 });
Такой подход обеспечивает предсказуемое поведение алгоритмов flip, shift и offset, применяемых библиотекой для динамического позиционирования.
Библиотеки Jest и Sinon позволяют создавать
сложные моки DOM API с минимальными усилиями. Пример подмены метода с
Jest:
const getBoundingClientRectMock = jest.fn(() => ({
width: 120,
height: 60,
top: 150,
left: 250,
right: 370,
bottom: 210,
}));
const reference = document.createElement('div');
reference.getBoundingClientRect = getBoundingClientRectMock;
С Sinon можно дополнительно проверять, сколько раз вызвана функция, с какими аргументами:
const stub = sinon.stub(reference, 'getBoundingClientRect').returns({
width: 120,
height: 60,
top: 150,
left: 250,
});
Такой контроль позволяет симулировать разные сценарии позиционирования и тестировать edge-cases, которые сложно воспроизвести вручную.
Floating UI реагирует на события scroll,
resize, pointermove. Для тестов создаются
стабы для событий:
const event = new Event('scroll');
window.dispatchEvent(event);
const resizeEvent = new Event('resize');
window.dispatchEvent(resizeEvent);
Можно также мокать слушатели событий, чтобы перехватывать их вызовы без фактического изменения DOM:
const addEventListenerStub = jest.spyOn(window, 'addEventListener');
Это позволяет проверять, корректно ли Floating UI подписывается на события и вызывает соответствующие колбэки.
Для полноценного тестирования компонентов с Floating UI рекомендуется создавать контролируемую виртуальную среду:
getBoundingClientRect и
свойства offsetWidth, offsetHeight.window.scrollX/Y и размеры
viewport для тестирования смещений.Этот подход позволяет полностью симулировать среду, в которой Floating UI рассчитывает позиции элементов, без необходимости рендеринга настоящего DOM.
Важно следить за тем, чтобы моки и стабы не оставались после теста, иначе они могут повлиять на последующие проверки. Для Jest и Sinon существуют методы очистки:
jest.restoreAllMocks();
sinon.restore();
Это гарантирует, что любые изменения DOM API или методов будут отменены, и среда вернется в исходное состояние.
import { computePosition } from '@floating-ui/dom';
test('проверка позиции тултипа', () => {
const reference = document.createElement('div');
Object.defineProperty(reference, 'offsetWidth', { value: 100 });
Object.defineProperty(reference, 'offsetHeight', { value: 50 });
reference.getBoundingClientRect = () => ({ width: 100, height: 50, top: 100, left: 200, right: 300, bottom: 150 });
const floating = document.createElement('div');
Object.defineProperty(floating, 'offsetWidth', { value: 80 });
Object.defineProperty(floating, 'offsetHeight', { value: 40 });
const result = computePosition(reference, floating, { placement: 'top' });
expect(result.x).toBe(210); // пример ожидаемого значения с учетом offset
expect(result.y).toBe(60);
});
В этом тесте полностью контролируется окружение: размеры элементов, позиции и даже результаты вычислений Floating UI. Такой подход позволяет создавать воспроизводимые тесты для сложных UI-сценариев.