Компонент Grid в библиотеке Muuri отвечает за управление набором элементов: позиционирование, сортировку, фильтрацию, перемещение, добавление и удаление. Несмотря на то что Muuri активно работает с DOM, большая часть логики может проверяться изолированно.
Unit-тестирование позволяет проверять:
При разработке интерфейсов с большим количеством интерактивных элементов отсутствие тестов часто приводит к регрессиям — изменения в одной части логики могут нарушить работу сортировки, перетаскивания или позиционирования.
Unit-тесты позволяют:
Muuri — клиентская библиотека, поэтому тестирование чаще всего выполняется в JavaScript-среде с эмуляцией DOM.
Наиболее распространённый стек:
| Инструмент | Назначение |
|---|---|
| Jest | тестовый раннер и фреймворк |
| Vitest | современная альтернатива Jest |
| jsdom | виртуальная DOM-среда |
| Testing Library | удобная работа с DOM |
Пример установки среды тестирования:
npm install --save-dev jest jsdom
В конфигурации Jest необходимо указать использование jsdom:
{
"testEnvironment": "jsdom"
}
Это позволяет Muuri работать так же, как в браузере.
Muuri требует наличие контейнера и элементов внутри него.
Минимальная разметка:
<div class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
В тестах DOM создаётся программно:
document.body.innerHTML = `
<div class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
`;
После этого сетка инициализируется:
import Muuri from 'muuri';
const gridElement = document.querySelector('.grid');
const grid = new Muuri(gridElement);
Первый уровень unit-тестов — проверка корректного создания экземпляра.
test('Grid успешно инициализируется', () => {
document.body.innerHTML = `
<div class="grid">
<div class="item"></div>
<div class="item"></div>
</div>
`;
const grid = new Muuri('.grid');
expect(grid).toBeDefined();
});
Проверка количества элементов:
test('Grid содержит корректное количество элементов', () => {
document.body.innerHTML = `
<div class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
`;
const grid = new Muuri('.grid');
expect(grid.getItems().length).toBe(3);
});
Метод getItems() возвращает массив объектов элементов Muuri.
Muuri позволяет динамически добавлять элементы.
test('Добавление элемента в сетку', () => {
document.body.innerHTML = `
<div class="grid">
<div class="item"></div>
</div>
`;
const grid = new Muuri('.grid');
const newItem = document.createElement('div');
newItem.classList.add('item');
grid.add(newItem);
expect(grid.getItems().length).toBe(2);
});
test('Новый элемент добавляется в конец', () => {
const grid = new Muuri('.grid');
const item = document.createElement('div');
item.classList.add('item');
grid.add(item);
const items = grid.getItems();
const lastItem = items[items.length - 1];
expect(lastItem.getElement()).toBe(item);
});
Удаление элементов выполняется методом remove().
test('Удаление элемента из сетки', () => {
document.body.innerHTML = `
<div class="grid">
<div class="item"></div>
<div class="item"></div>
</div>
`;
const grid = new Muuri('.grid');
const items = grid.getItems();
grid.remove(items[0]);
expect(grid.getItems().length).toBe(1);
});
Удаление с DOM:
grid.remove(items[0], { removeElements: true });
Тест:
test('Элемент удаляется из DOM', () => {
const grid = new Muuri('.grid');
const items = grid.getItems();
grid.remove(items[0], { removeElements: true });
expect(document.querySelectorAll('.item').length).toBe(1);
});
Muuri поддерживает сортировку.
grid.sort((a, b) => {
return a.getElement().dataset.id - b.getElement().dataset.id;
});
Тест:
test('Сортировка элементов', () => {
document.body.innerHTML = `
<div class="grid">
<div class="item" data-id="3"></div>
<div class="item" data-id="1"></div>
<div class="item" data-id="2"></div>
</div>
`;
const grid = new Muuri('.grid');
grid.sort((a, b) => {
return a.getElement().dataset.id - b.getElement().dataset.id;
});
const items = grid.getItems();
const first = items[0].getElement().dataset.id;
expect(first).toBe("1");
});
Фильтрация используется для показа только определённых элементов.
Пример:
grid.filter(item => {
return item.getElement().dataset.category === "news";
});
Тест:
test('Фильтрация элементов', () => {
document.body.innerHTML = `
<div class="grid">
<div class="item" data-category="news"></div>
<div class="item" data-category="blog"></div>
</div>
`;
const grid = new Muuri('.grid');
grid.filter(item => {
return item.getElement().dataset.category === "news";
});
const visibleItems = grid.getItems().filter(item => item.isVisible());
expect(visibleItems.length).toBe(1);
});
Метод layout() запускает перерасчёт расположения элементов.
grid.layout();
Тест:
test('layout обновляет позиции элементов', () => {
const grid = new Muuri('.grid');
const layoutSpy = jest.spyOn(grid, 'layout');
grid.layout();
expect(layoutSpy).toHaveBeenCalled();
});
Muuri позволяет перемещать элементы внутри сетки.
grid.move(item, newIndex);
Тест:
test('Перемещение элемента', () => {
document.body.innerHTML = `
<div class="grid">
<div class="item" id="a"></div>
<div class="item" id="b"></div>
</div>
`;
const grid = new Muuri('.grid');
const items = grid.getItems();
grid.move(items[1], 0);
const newFirst = grid.getItems()[0].getElement().id;
expect(newFirst).toBe('b');
});
Muuri генерирует события.
Основные события:
| Событие | Назначение |
|---|---|
| add | добавление элемента |
| remove | удаление |
| move | перемещение |
| layoutStart | начало layout |
| layoutEnd | завершение layout |
test('Срабатывает событие add', () => {
const grid = new Muuri('.grid');
const handler = jest.fn();
grid.on('add', handler);
const item = document.createElement('div');
item.classList.add('item');
grid.add(item);
expect(handler).toHaveBeenCalled();
});
Некоторые операции Muuri зависят от браузерных API:
В тестах их часто приходится мокировать.
global.requestAnimationFrame = (cb) => {
return setTimeout(cb, 0);
};
HTMLElement.prototype.getBoundingClientRect = () => ({
width: 100,
height: 100,
top: 0,
left: 0,
bottom: 100,
right: 100
});
Это позволяет корректно тестировать layout.
Drag-операции сложно тестировать напрямую, потому что они зависят от пользовательских событий.
Подходы:
Пример эмуляции:
element.dispatchEvent(new MouseEvent('mousedown'));
element.dispatchEvent(new MouseEvent('mousemove'));
element.dispatchEvent(new MouseEvent('mouseup'));
Однако чаще проверяется итоговое состояние:
expect(grid.getItems()[0]).toBe(expectedItem);
Каждый тест должен работать в чистой среде.
Используется beforeEach.
beforeEach(() => {
document.body.innerHTML = `
<div class="grid">
<div class="item"></div>
<div class="item"></div>
</div>
`;
});
Создание grid:
let grid;
beforeEach(() => {
grid = new Muuri('.grid');
});
Некоторые операции Muuri выполняются асинхронно.
Например:
Используется async/await.
test('layout выполняется асинхронно', async () => {
const grid = new Muuri('.grid');
await new Promise(resolve => {
grid.once('layoutEnd', resolve);
grid.layout();
});
expect(grid.getItems().length).toBe(2);
});
Типичная структура проекта:
project
│
├─ src
│ └─ grid.js
│
├─ tests
│ └─ grid.test.js
│
├─ jest.config.js
└─ package.json
Пример тестового файла:
describe('Muuri Grid', () => {
test('инициализация', () => {});
test('добавление элементов', () => {});
test('удаление элементов', () => {});
test('сортировка', () => {});
test('фильтрация', () => {});
});
Минимизировать зависимость от реального DOM
Тестировать:
Избегать тестирования анимаций
Анимации — визуальный эффект, они редко требуют unit-тестов.
Фиксировать публичный API
Основные методы, которые должны быть покрыты тестами:
add()remove()move()sort()filter()layout()getItems()Использовать spy-функции
Позволяют проверять вызовы методов.
const spy = jest.spyOn(grid, 'layout');
Проверять события
Muuri активно использует event-модель.
Muuri активно использует:
Без mock-объектов тесты падают.
Позиции элементов часто меняются из-за layout-алгоритма.
Лучше проверять:
Это приводит к состоянию гонки.
Каждый тест должен создавать новый экземпляр.
Полный набор unit-тестов для Grid должен включать проверки:
| Категория | Что проверяется |
|---|---|
| Инициализация | создание сетки |
| Элементы | добавление, удаление |
| Управление | перемещение |
| Сортировка | правильный порядок |
| Фильтрация | видимость |
| Layout | перерасчёт |
| События | корректные триггеры |
| Асинхронность | layoutEnd, drag |
Такое покрытие позволяет безопасно развивать интерфейс, не опасаясь нарушить поведение сетки при изменениях кода.