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

Анимации в интерфейсе — это не просто визуальные эффекты, а часть логики взаимодействия. В случае использования библиотеки mo.js тестирование осложняется асинхронностью, зависимостью от времени и случайными параметрами (например, delay, duration, easing, random). Это требует особого подхода к проверке корректности поведения.

Ключевые сложности:

  • зависимость от времени выполнения
  • отсутствие «фиксированного» состояния между кадрами
  • влияние производительности среды
  • взаимодействие с DOM и CSS

Подходы к тестированию анимаций

1. Разделение логики и визуализации

Основная логика анимации (расчёт параметров, генерация конфигураций) должна быть отделена от непосредственного вызова mo.js. Это позволяет тестировать вычисления независимо от рендеринга.

Пример:

function getBurstConfig(x, y) {
  return {
    left: x,
    top: y,
    count: 10,
    radius: { 0: 100 },
    duration: 500
  };
}

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

test('генерация конфигурации burst', () => {
  const config = getBurstConfig(50, 100);
  expect(config.left).toBe(50);
  expect(config.top).toBe(100);
  expect(config.count).toBe(10);
});

2. Контроль времени (Mock таймеров)

Анимации в mo.js используют requestAnimationFrame и внутренние таймеры. Для тестирования используется подмена времени.

В среде тестирования (например, Jest):

jest.useFakeTimers();

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

test('анимация запускается через delay', () => {
  const burst = new mojs.Burst({ delay: 200 });
  const spy = jest.spyOn(burst, 'play');

  burst.replay();

  jest.advanceTimersByTime(200);

  expect(spy).toHaveBeenCalled();
});

3. Проверка состояний объектов mo.js

Объекты анимации содержат внутреннее состояние, которое можно анализировать:

  • _progress — текущий прогресс
  • _props — параметры анимации
  • _isRunning — статус выполнения

Пример:

test('анимация меняет состояние при запуске', () => {
  const tween = new mojs.Tween({ duration: 300 });

  tween.play();
  expect(tween._isRunning).toBe(true);

  tween.stop();
  expect(tween._isRunning).toBe(false);
});

4. Тестирование колбэков

Анимации часто используют события:

  • onStart
  • onUpdate
  • onComplete

Проверка вызова:

test('вызывается onComplete', () => {
  const onCompl ete = jest.fn();

  const tween = new mojs.Tween({
    duration: 100,
    onComplete
  });

  tween.play();
  jest.advanceTimersByTime(100);

  expect(onComplete).toHaveBeenCalled();
});

5. Стабилизация случайности

mo.js поддерживает генерацию случайных значений:

radius: { 0: 'rand(50, 100)' }

Для тестирования необходимо:

  • либо отключить случайность
  • либо зафиксировать генератор случайных чисел

Пример замены:

Math.random = jest.fn(() => 0.5);

Это делает поведение детерминированным.

Тестирование DOM-эффектов

Анимации часто изменяют DOM:

  • стили (transform, opacity)
  • позиционирование
  • добавление/удаление элементов

Использование jsdom

В тестовой среде DOM эмулируется через jsdom.

Пример:

test('элемент получает стиль transform', () => {
  document.body.innerHTML = '<div id="el"></div>';
  const el = document.getElementById('el');

  const tween = new mojs.Tween({
    duration: 100,
    onUpdate: (progress) => {
      el.style.transform = `scale(${progress})`;
    }
  });

  tween.play();
  jest.advanceTimersByTime(50);

  expect(el.style.transform).toContain('scale');
});

Проверка классов и атрибутов

test('класс добавляется при старте', () => {
  const el = document.createElement('div');

  const tween = new mojs.Tween({
    onStart: () => el.classList.add('active')
  });

  tween.play();

  expect(el.classList.contains('active')).toBe(true);
});

Снимки (snapshot testing)

Для сложных конфигураций анимации удобно использовать снапшоты:

test('конфигурация burst соответствует ожиданиям', () => {
  const burst = new mojs.Burst({
    count: 5,
    radius: { 0: 50 }
  });

  expect(burst._props).toMatchSnapshot();
});

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

Интеграционное тестирование

Проверяет взаимодействие анимации с другими частями приложения.

Пример сценария:

  • пользователь кликает кнопку
  • запускается анимация
  • меняется состояние UI
test('клик запускает анимацию', () => {
  document.body.innerHTML = '<button id="btn"></button>';
  const btn = document.getElementById('btn');

  const playSpy = jest.fn();

  const burst = new mojs.Burst({});
  burst.play = playSpy;

  btn.addEventListener('click', () => burst.play());

  btn.click();

  expect(playSpy).toHaveBeenCalled();
});

Визуальное тестирование

Автоматические тесты не всегда могут гарантировать корректность визуального результата. Используются:

  • сравнение скриншотов
  • инструменты визуальной регрессии (например, Percy, Chromatic)

Подход:

  1. фиксируется состояние страницы
  2. запускается анимация
  3. делается снимок
  4. сравнивается с эталоном

Производительность и нагрузка

Анимации могут влиять на FPS и производительность.

Методы тестирования:

  • замер времени выполнения
  • анализ количества перерисовок
  • профилирование

Пример:

test('анимация выполняется за допустимое время', () => {
  const start = performance.now();

  const tween = new mojs.Tween({ duration: 100 });
  tween.play();

  jest.advanceTimersByTime(100);

  const end = performance.now();

  expect(end - start).toBeLessThan(200);
});

Типичные ошибки и способы их выявления

1. Анимация не запускается

  • проверка вызова play()
  • проверка delay

2. Анимация не завершается

  • тестирование onComplete
  • проверка duration

3. Неправильные значения параметров

  • snapshot-тесты
  • unit-тесты генерации конфигураций

4. Рассинхронизация анимаций

  • тестирование нескольких tween-объектов
  • контроль времени

Пример:

test('анимации синхронизированы', () => {
  const a = new mojs.Tween({ duration: 100 });
  const b = new mojs.Tween({ duration: 100 });

  a.play();
  b.play();

  jest.advanceTimersByTime(100);

  expect(a._progress).toBe(1);
  expect(b._progress).toBe(1);
});

Тестирование цепочек анимаций (Timeline)

mo.js поддерживает Timeline для последовательностей.

const timeline = new mojs.Timeline();
timeline.add(tween1, tween2);

Тест:

test('timeline запускает все анимации', () => {
  const t1 = new mojs.Tween({ duration: 100 });
  const t2 = new mojs.Tween({ duration: 100 });

  const timeline = new mojs.Timeline();
  timeline.add(t1, t2);

  timeline.play();

  expect(t1._isRunning).toBe(true);
  expect(t2._isRunning).toBe(true);
});

Рекомендации по организации тестов

  • изолировать анимации от DOM, где возможно
  • избегать реального времени — использовать mock
  • фиксировать случайные значения
  • тестировать не только запуск, но и завершение
  • покрывать ключевые сценарии взаимодействия

Инструменты

  • Jest — для unit и интеграционных тестов
  • jsdom — для эмуляции DOM
  • Cypress / Playwright — для e2e и визуальных тестов
  • инструменты визуальной регрессии — для контроля внешнего вида

Грамотное тестирование анимаций в mo.js позволяет избежать нестабильного поведения интерфейса, упрощает рефакторинг и обеспечивает предсказуемость пользовательского опыта даже при сложных визуальных эффектах.