Библиотека Muuri реализует систему перетаскивания
элементов через встроенный механизм dragEnabled. При
активации этой опции каждый элемент сетки получает внутренний контроллер
перетаскивания, который отслеживает:
pointer, mouse,
touch)Перетаскивание в Muuri состоит из нескольких этапов:
E2E-тестирование (End-to-End) проверяет весь этот процесс полностью: от пользовательского жеста до итогового состояния DOM.
Unit-тесты проверяют только отдельные функции:
Однако drag and drop — это пользовательское взаимодействие, которое зависит от:
Поэтому проверка корректной работы требует E2E-тестов, имитирующих реальное поведение пользователя.
Основные аспекты, которые проверяются:
Наиболее распространённые инструменты:
| Инструмент | Особенности |
|---|---|
| Playwright | точная эмуляция pointer-событий |
| Cypress | удобные команды тестирования интерфейса |
| WebdriverIO | работа через WebDriver API |
| Puppeteer | управление браузером через DevTools |
На практике чаще всего используются:
Минимальная конфигурация Muuri:
const grid = new Muuri('.grid', {
dragEnabled: true
});
HTML структура:
<div class="grid">
<div class="item">A</div>
<div class="item">B</div>
<div class="item">C</div>
<div class="item">D</div>
</div>
Каждый элемент должен иметь контейнер:
.grid {
position: relative;
}
.item {
width: 100px;
height: 100px;
}
Muuri использует несколько ключевых событий:
| Событие | Описание |
|---|---|
dragStart |
начало перетаскивания |
dragMove |
движение элемента |
dragEnd |
завершение |
move |
изменение порядка |
Пример обработки:
grid.on('dragStart', item => {
console.log('start', item);
});
grid.on('move', data => {
console.log('moved', data);
});
grid.on('dragEnd', item => {
console.log('end', item);
});
E2E-тесты проверяют, что после пользовательского взаимодействия эти события происходят в правильной последовательности.
Playwright поддерживает команду dragTo.
Пример теста:
import { test, expect } from '@playwright/test';
test('drag item changes order', async ({ page }) => {
await page.goto('http://localhost:3000');
const first = page.locator('.item').nth(0);
const third = page.locator('.item').nth(2);
await first.dragTo(third);
const items = await page.locator('.item').allTextContents();
expect(items[2]).toBe('A');
});
Тест выполняет следующие действия:
Иногда встроенная команда dragTo работает нестабильно с
кастомными drag-системами. В таких случаях используются ручные
pointer-события.
Пример:
await page.mouse.move(100, 100);
await page.mouse.down();
await page.mouse.move(300, 100, { steps: 10 });
await page.mouse.up();
Такой подход максимально приближен к реальному поведению пользователя.
Muuri физически меняет порядок элементов внутри массива grid.items, а затем обновляет DOM.
Проверка может выполняться двумя способами.
const items = await page.$$eval('.item', nodes =>
nodes.map(n => n.textContent)
);
expect(items).toEqual(['B', 'C', 'A', 'D']);
Иногда порядок DOM не меняется, а элементы перестраиваются через позиционирование.
Проверка позиции:
const box = await page.locator('.item').nth(0).boundingBox();
expect(box.x).toBeGreaterThan(200);
Muuri активно использует анимацию при перестановке элементов.
Если тест проверяет состояние слишком рано, возникает ошибка.
Решение — ожидание завершения анимации.
await page.waitForFunction(() => {
return document.querySelector('.grid').classList.contains('muuri-ready');
});
await page.waitForTimeout(500);
Предпочтителен первый вариант.
E2E-тест может проверять, что событие move действительно
произошло.
Пример:
await page.exposeFunction('onMove', () => {
window.__moved = true;
});
await page.evaluate(() => {
grid.on('move', () => window.onMove());
});
После перетаскивания:
const moved = await page.evaluate(() => window.__moved);
expect(moved).toBe(true);
Muuri поддерживает параметр dragContainer.
Пример конфигурации:
const grid = new Muuri('.grid', {
dragEnabled: true,
dragContainer: document.body
});
E2E-тест должен убедиться, что элемент может покидать пределы сетки.
Проверка:
const box = await page.locator('.item').nth(0).boundingBox();
expect(box.y).toBeGreaterThan(300);
Muuri может сортировать элементы между несколькими сетками.
Конфигурация:
const gridA = new Muuri('.grid-a', {
dragEnabled: true,
dragSort: () => [gridA, gridB]
});
const gridB = new Muuri('.grid-b', {
dragEnabled: true
});
E2E-тест проверяет перенос элемента.
await source.dragTo(target);
const countA = await page.locator('.grid-a .item').count();
const countB = await page.locator('.grid-b .item').count();
expect(countA).toBe(3);
expect(countB).toBe(1);
Перетаскивание может быть ограничено только частью элемента.
Конфигурация:
const grid = new Muuri('.grid', {
dragEnabled: true,
dragHandle: '.handle'
});
HTML:
<div class="item">
<div class="handle">⇅</div>
<div class="content">Item</div>
</div>
E2E-тест:
const handle = page.locator('.handle').first();
await handle.dragTo(page.locator('.item').nth(2));
Попытка перетаскивания за .content должна завершаться
без изменения позиции.
Muuri может анимировать возврат элемента.
Опция:
dragRelease: {
duration: 300
}
E2E-тест должен учитывать время анимации.
await page.waitForTimeout(350);
После этого выполняется проверка позиции.
Drag-and-drop активно используется на touch-устройствах.
Playwright позволяет эмулировать мобильное устройство:
test.use({
viewport: { width: 375, height: 812 },
hasTouch: true
});
Тест выполняет жесты:
await page.touchscreen.tap(100, 100);
И движение:
await page.mouse.move(200, 200);
1. Нестабильность координат
Причина:
Решение:
test.use({
viewport: { width: 1280, height: 720 }
});
2. Анимации
Muuri активно анимирует перемещения.
Решение — отключение анимации в тестах.
const grid = new Muuri('.grid', {
layoutDuration: 0,
dragRelease: {
duration: 0
}
});
3. Перекрытие элементов
Если элементы перекрываются, dragTo может выбрать
неправильную цель.
Используется точное указание координат:
await page.mouse.move(150, 150);
Часто порядок элементов сохраняется в базе данных.
Пример функции:
function saveOrder() {
const order = grid.getItems().map(item =>
item.getElement().textContent
);
localStorage.setItem('order', JSON.stringify(order));
}
E2E-тест:
await page.reload();
const order = await page.evaluate(() =>
JSON.parse(localStorage.getItem('order'))
);
expect(order[0]).toBe('B');
Большие сетки могут содержать сотни элементов.
E2E-тест измеряет время операции.
const start = Date.now();
await item.dragTo(target);
const duration = Date.now() - start;
expect(duration).toBeLessThan(1000);
Это помогает выявлять деградацию производительности.
Оптимальный набор тестов:
Такая структура покрывает практически все сценарии использования Muuri drag-and-drop.