Интерфейсы, построенные на основе библиотеки Muuri, активно используют динамическое позиционирование элементов, анимации, фильтрацию, сортировку и drag-and-drop. Такие механизмы усложняют автоматическое тестирование, поскольку:
Тестовые утилиты позволяют проверять:
В крупных проектах тестирование Muuri обычно реализуется с помощью инструментов:
Однако для корректных тестов требуется набор вспомогательных утилит, позволяющих работать с асинхронностью, анимациями и DOM-изменениями.
Muuri работает исключительно с DOM-элементами, поэтому перед тестированием требуется подготовить тестовую разметку.
Пример минимальной структуры:
<div class="grid">
<div class="item"><div class="item-content">1</div></div>
<div class="item"><div class="item-content">2</div></div>
<div class="item"><div class="item-content">3</div></div>
</div>
Инициализация сетки:
import Muuri from 'muuri';
const grid = new Muuri('.grid', {
items: '.item'
});
В тестах DOM создаётся программно:
function createTestGrid() {
document.body.innerHTML = `
<div class="grid">
<div class="item"><div class="item-content">A</div></div>
<div class="item"><div class="item-content">B</div></div>
<div class="item"><div class="item-content">C</div></div>
</div>
`;
return new Muuri('.grid');
}
Такая функция становится базовой тестовой утилитой.
Muuri выполняет перерасчёт позиций асинхронно. Проверка расположения элементов должна происходить только после завершения layout.
Muuri предоставляет событие:
layoutEnd
Тестовая утилита ожидания:
function waitForLayout(grid) {
return new Promise(resolve => {
grid.once('layoutEnd', resolve);
});
}
Использование:
test('layout завершён', async () => {
const grid = createTestGrid();
grid.layout();
await waitForLayout(grid);
expect(grid.getItems().length).toBe(3);
});
Анимации усложняют тестирование, поскольку меняют время выполнения операций.
Muuri позволяет отключить их через настройки.
Тестовая конфигурация:
function createTestGrid() {
document.body.innerHTML = `
<div class="grid">
<div class="item"><div>1</div></div>
<div class="item"><div>2</div></div>
</div>
`;
return new Muuri('.grid', {
layoutDuration: 0,
dragReleaseDuration: 0,
showDuration: 0,
hideDuration: 0
});
}
Это превращает анимации в мгновенные операции.
После сортировки или drag-and-drop важно проверить порядок элементов.
Утилита:
function getItemOrder(grid) {
return grid.getItems().map(item => {
return item.getElement().textContent.trim();
});
}
Использование:
test('сортировка элементов', () => {
const grid = createTestGrid();
grid.sort((a, b) => {
return b.getElement().textContent - a.getElement().textContent;
});
const order = getItemOrder(grid);
expect(order).toEqual(['3', '2', '1']);
});
Muuri использует методы show() и hide() для
управления отображением.
Проверка состояния элемента:
function isVisible(item) {
return item.isVisible();
}
Пример теста:
test('скрытие элемента', () => {
const grid = createTestGrid();
const item = grid.getItems()[0];
grid.hide([item]);
expect(isVisible(item)).toBe(false);
});
Muuri поддерживает динамическую фильтрацию элементов.
Пример:
grid.filter(item => {
return item.getElement().textContent.includes('A');
});
Тестовая проверка:
test('фильтрация элементов', () => {
const grid = createTestGrid();
grid.filter(item => {
return item.getElement().textContent === 'A';
});
const visible = grid.getItems().filter(item => item.isVisible());
expect(visible.length).toBe(1);
});
Muuri активно использует событийную систему:
Тестовая утилита:
function waitForEvent(grid, eventName) {
return new Promise(resolve => {
grid.once(eventName, resolve);
});
}
Использование:
await waitForEvent(grid, 'layoutEnd');
Muuri поддерживает динамическое добавление элементов через
add().
Создание DOM-элемента:
function createItem(content) {
const el = document.createElement('div');
el.className = 'item';
el.innerHTML = `<div>${content}</div>`;
return el;
}
Тест:
test('добавление элемента', () => {
const grid = createTestGrid();
const item = createItem('D');
grid.add(item);
expect(grid.getItems().length).toBe(4);
});
Метод удаления:
grid.remove(items)
Тест:
test('удаление элемента', () => {
const grid = createTestGrid();
const item = grid.getItems()[0];
grid.remove(item);
expect(grid.getItems().length).toBe(2);
});
Drag-and-drop сложно тестировать напрямую, поэтому используются симулированные события.
Пример генерации события мыши:
function triggerMouseEvent(node, type, x, y) {
const event = new MouseEvent(type, {
clientX: x,
clientY: y,
bubbles: true
});
node.dispatchEvent(event);
}
Сценарий drag:
mousedown
mousemove
mousemove
mouseup
Пример:
const element = grid.getItems()[0].getElement();
triggerMouseEvent(element, 'mousedown', 100, 100);
triggerMouseEvent(document, 'mousemove', 200, 200);
triggerMouseEvent(document, 'mouseup', 200, 200);
Muuri управляет координатами элементов через transform.
Проверка позиции:
function getItemPosition(item) {
return item.getPosition();
}
Пример:
const position = getItemPosition(grid.getItems()[0]);
expect(position.left).toBeGreaterThanOrEqual(0);
expect(position.top).toBeGreaterThanOrEqual(0);
После каждого теста требуется очищать DOM.
afterEach(() => {
document.body.innerHTML = '';
});
Это предотвращает накопление элементов и конфликт тестов.
Muuri поддерживает пользовательскую функцию сортировки.
Пример:
grid.sort((itemA, itemB) => {
const a = itemA.getElement().textContent;
const b = itemB.getElement().textContent;
return a.localeCompare(b);
});
Тест:
test('алфавитная сортировка', () => {
const grid = createTestGrid();
grid.sort((a, b) => {
return a.getElement().textContent.localeCompare(
b.getElement().textContent
);
});
const order = getItemOrder(grid);
expect(order).toEqual(['A', 'B', 'C']);
});
Muuri использует размеры элементов для расчёта layout.
Получение размеров:
function getItemSize(item) {
const rect = item.getElement().getBoundingClientRect();
return {
width: rect.width,
height: rect.height
};
}
Пример проверки:
const size = getItemSize(grid.getItems()[0]);
expect(size.width).toBeGreaterThan(0);
expect(size.height).toBeGreaterThan(0);
Метод destroy() удаляет все обработчики и очищает
структуру Muuri.
test('уничтожение сетки', () => {
const grid = createTestGrid();
grid.destroy();
expect(grid._isDestroyed).toBe(true);
});
После destroy:
При сложных тестах полезно отслеживать последовательность событий.
function logEvents(grid, events) {
const log = [];
events.forEach(event => {
grid.on(event, () => {
log.push(event);
});
});
return log;
}
Использование:
const log = logEvents(grid, [
'layoutStart',
'layoutEnd'
]);
После выполнения операций можно проверить порядок:
expect(log).toEqual([
'layoutStart',
'layoutEnd'
]);
Muuri поддерживает перенос элементов между сетками.
Создание двух сеток:
const gridA = new Muuri('.grid-a');
const gridB = new Muuri('.grid-b');
Конфигурация drag:
dragEnabled: true,
dragSort: () => [gridA, gridB]
Тест проверяет:
Обычно тестовые утилиты выносятся в отдельный модуль:
test-utils/
createGrid.js
waitForLayout.js
dragSimulation.js
itemHelpers.js
Структура файла:
export {
createTestGrid,
waitForLayout,
getItemOrder,
triggerMouseEvent
};
Это делает тесты компактными и повторно используемыми.
1. Асинхронные layout операции
Решение — ожидание событий layoutEnd.
2. Анимации
Решение — отключение длительности анимаций.
3. Отсутствие реального layout в jsdom
jsdom не рассчитывает размеры элементов.
Решение:
4. Сложности drag-and-drop
Иногда требуется тестирование в реальном браузере.
Инструменты:
Проверяют:
Используют jsdom.
Проверяют:
Используют браузерные среды.
Проверяют реальные сценарии:
Инструменты:
Стандартная структура проекта:
src/
grid.js
tests/
grid.test.js
tests/utils/
createGrid.js
waitForLayout.js
domHelpers.js
Пример импорта:
import {
createTestGrid,
waitForLayout
} from './utils/createGrid';
Такой подход обеспечивает: