Integration тесты

Integration-тесты проверяют совместную работу нескольких модулей системы. В контексте библиотеки Shopify Draggable такие тесты подтверждают корректное взаимодействие:

  • ядра draggable-движка
  • DOM-среды браузера
  • пользовательских обработчиков событий
  • сторонних фреймворков (React, Vue и др.)
  • дополнительных плагинов библиотеки

Если unit-тесты изолированно проверяют отдельные функции, то integration-тесты воспроизводят реальное поведение интерфейса: захват элемента, перемещение, сортировку, обновление DOM и генерацию событий.

Основная цель — убедиться, что drag-and-drop сценарии корректно работают в браузерной среде и взаимодействуют с DOM так, как ожидается в пользовательском интерфейсе.


Архитектура тестируемых компонентов

При тестировании Draggable интеграция обычно включает несколько уровней:

1. Core-модуль

Базовый класс:

import {Draggable} from '@shopify/draggable';

Отвечает за:

  • регистрацию контейнеров
  • отслеживание pointer / mouse / touch событий
  • создание drag-сессии
  • генерацию lifecycle-событий

2. Плагины

Библиотека использует систему плагинов:

  • Sortable
  • Droppable
  • Swappable
  • Collidable
  • ResizeMirror

Integration-тесты часто проверяют совместную работу ядра и плагинов.

3. DOM-слой

Drag-операции напрямую изменяют DOM:

  • перемещение элементов
  • изменение порядка узлов
  • добавление CSS-классов
  • создание mirror-элемента

Тесты должны проверять фактические изменения DOM-структуры.


Среда выполнения integration-тестов

Для проверки drag-and-drop поведения используется тестовая среда браузера.

Наиболее распространённые инструменты:

  • Jest
  • Mocha
  • Playwright
  • Cypress
  • Puppeteer

Часто применяется комбинация:

Jest + jsdom (для логики)
Playwright/Cypress (для реального браузера)

Причина — jsdom не полностью поддерживает pointer-события и drag-поведение.


Подготовка DOM-окружения

Перед тестом необходимо создать структуру HTML, имитирующую реальный интерфейс.

Пример тестового DOM

document.body.innerHTML = `
  <ul class="list">
    <li class="item">A</li>
    <li class="item">B</li>
    <li class="item">C</li>
  </ul>
`;

Инициализация draggable-системы:

import {Sortable} from '@shopify/draggable';

const sortable = new Sortable(document.querySelectorAll('.list'), {
  draggable: '.item'
});

Теперь список поддерживает сортировку элементов.


Симуляция drag-операции

Integration-тест должен воспроизвести полный цикл перемещения элемента:

  1. pointerdown
  2. dragstart
  3. dragmove
  4. dragover
  5. drop
  6. dragstop

Пример симуляции событий

const item = document.querySelector('.item');

item.dispatchEvent(new MouseEvent('mousedown', {
  bubbles: true
}));

document.dispatchEvent(new MouseEvent('mousemove', {
  bubbles: true,
  clientX: 100,
  clientY: 100
}));

document.dispatchEvent(new MouseEvent('mouseup', {
  bubbles: true
}));

После выполнения сценария проверяется результат.


Проверка изменений DOM

Главная задача integration-теста — удостовериться, что DOM обновился правильно.

Пример

const items = [...document.querySelectorAll('.item')];
const order = items.map(el => el.textContent);

expect(order).toEqual(['B', 'A', 'C']);

Проверяется:

  • новый порядок элементов
  • наличие CSS-классов
  • создание mirror-элемента

Тестирование lifecycle-событий

Draggable генерирует большое количество событий.

Основные:

  • drag:start
  • drag:move
  • drag:stop
  • sortable:start
  • sortable:sorted
  • sortable:stop

Integration-тесты проверяют:

  • правильный порядок событий
  • корректные параметры event-объектов
  • возможность пользовательских обработчиков изменять поведение системы.

Пример

const startHandler = jest.fn();

sortable.on('drag:start', startHandler);

Запуск drag-сценария:

simulateDrag();

Проверка:

expect(startHandler).toHaveBeenCalled();

Проверка payload событий

Event-объекты содержат важные данные:

  • исходный элемент
  • контейнер
  • координаты
  • mirror-элемент

Пример теста:

sortable.on('drag:start', (event) => {
  expect(event.source.textContent).toBe('A');
});

Integration-тест подтверждает, что система передаёт корректный контекст операции.


Тестирование сортировки элементов

Для плагина Sortable критично проверять:

  • перемещение элементов внутри контейнера
  • перемещение между контейнерами
  • обновление порядка

Подготовка двух контейнеров

document.body.innerHTML = `
  <ul class="list-a">
    <li class="item">1</li>
  </ul>

  <ul class="list-b">
    <li class="item">2</li>
  </ul>
`;

Инициализация:

const sortable = new Sortable(
  document.querySelectorAll('.list-a, .list-b'),
  { draggable: '.item' }
);

Проверка перемещения

После drag-операции:

const containerB = document.querySelector('.list-b');

expect(containerB.children.length).toBe(2);

Проверка mirror-элемента

Во время drag-операции создаётся специальный DOM-элемент — mirror.

Он отображает перетаскиваемый элемент.

Integration-тест должен проверить:

  • создание mirror
  • корректные координаты
  • удаление после завершения drag

Проверка создания

const mirror = document.querySelector('.draggable-mirror');

expect(mirror).not.toBeNull();

Проверка удаления

expect(document.querySelector('.draggable-mirror')).toBeNull();

Тестирование плагинов

Collidable

Плагин определяет столкновения элементов.

Integration-тест:

  • создаёт перекрывающиеся элементы
  • инициирует drag
  • проверяет событие collision
draggable.on('collidable:in', handler);

Проверка:

expect(handler).toHaveBeenCalled();

Droppable

Позволяет бросать элементы в drop-зоны.

DOM-структура:

<div class="dropzone"></div>

Инициализация:

new Droppable(containers, {
  draggable: '.item',
  dropzone: '.dropzone'
});

Integration-тест проверяет:

  • корректное определение drop-зоны
  • вызов событий droppable:dropped

Тестирование пользовательских обработчиков

Большинство приложений используют собственные обработчики событий.

Integration-тест должен подтверждать, что такие обработчики получают правильные данные.

Пример:

sortable.on('sortable:sorted', (event) => {
  saveNewOrder(event.oldIndex, event.newIndex);
});

Тест:

expect(saveNewOrder).toHaveBeenCalledWith(0, 1);

Тестирование отмены операций

Система поддерживает отмену действий через event.cancel().

Пример:

sortable.on('drag:start', (event) => {
  event.cancel();
});

Integration-тест:

simulateDrag();

const order = getListOrder();

expect(order).toEqual(['A', 'B', 'C']);

Порядок элементов не изменился — операция успешно отменена.


Проверка производительности drag-сценариев

Некоторые integration-тесты проверяют нагрузку на систему.

Сценарий:

  • список из 500 элементов
  • многократные drag-операции

Тест оценивает:

  • время выполнения
  • отсутствие утечек DOM-узлов
  • корректное удаление mirror-элементов

Интеграция с React-приложениями

В приложениях на React integration-тесты должны учитывать реактивное обновление DOM.

Основные проблемы:

  • React может перерисовывать элементы
  • draggable хранит ссылки на DOM-узлы
  • возникает рассинхронизация

Тест проверяет:

  • корректное обновление состояния
  • отсутствие дублирования элементов
  • стабильность ключей React.

Интеграция с Vue

В среде Vue.js важно тестировать:

  • реактивные списки
  • изменение массива данных
  • синхронизацию DOM и модели.

Integration-тест должен подтверждать, что после drag-операции:

  • массив данных обновился
  • DOM соответствует модели.

Стратегии написания integration-тестов

Эффективные integration-тесты для Draggable обладают несколькими характеристиками.

Реалистичные сценарии

Тесты должны имитировать реальные действия пользователя:

  • drag элемента
  • сортировка списка
  • перемещение между колонками.

Минимальная изоляция

В отличие от unit-тестов, интеграционные проверки используют настоящий DOM и реальные события.

Проверка конечного состояния

Тест должен проверять:

  • итоговую структуру DOM
  • состояние приложения
  • вызовы пользовательских функций.

Структура типичного integration-теста

Стандартная структура:

describe()
  beforeEach()
  it()
  simulateDrag()
  assertions()

Пример:

describe('Sortable integration', () => {

  beforeEach(() => {
    setupDOM();
    initSortable();
  });

  it('reorders elements', () => {
    simulateDrag();
    expect(getOrder()).toEqual(['B', 'A', 'C']);
  });

});

Такой подход делает тесты:

  • воспроизводимыми
  • независимыми
  • читаемыми.

Частые ошибки при integration-тестировании

Недостаточная симуляция событий

Некоторые тесты вызывают только mousedown.

Drag-система требует полный цикл событий.


Проверка только событий

Иногда тесты проверяют только вызов handlers.

Это недостаточно — необходимо проверять изменения DOM.


Использование jsdom для сложных drag-сценариев

jsdom не поддерживает:

  • layout
  • реальные координаты
  • pointer events

Для сложных сценариев предпочтительны:

  • Playwright
  • Cypress

Автоматизация drag-тестов в браузере

В инструментах вроде Playwright drag-операции можно выполнять напрямую.

Пример:

await page.dragAndDrop(
  '.item:nth-child(1)',
  '.item:nth-child(3)'
);

После этого выполняется проверка DOM.


Поддержка CI

Integration-тесты часто запускаются в системах непрерывной интеграции:

  • GitHub Actions
  • GitLab CI
  • CircleCI

Для браузерных тестов используется headless-режим:

  • Chromium
  • Firefox
  • WebKit

Это позволяет автоматически проверять drag-функциональность после каждого коммита.


Метрики качества integration-тестов

Качественный набор integration-тестов для Shopify Draggable должен покрывать:

  • drag lifecycle
  • сортировку
  • перемещение между контейнерами
  • события
  • плагины
  • взаимодействие с фреймворками
  • DOM-изменения
  • пользовательские обработчики

Такие тесты обеспечивают стабильную работу drag-and-drop интерфейсов даже при масштабных изменениях кода.