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

Юнит-тестирование анимаций в JavaScript с использованием anime.js требует понимания, как библиотека управляет временем и состоянием объектов. Анимации в anime.js не мгновенны — они изменяют свойства объектов постепенно, поэтому тестирование должно учитывать временные промежутки и события жизненного цикла анимации.

Настройка среды тестирования

Для начала необходимо подключить anime.js в проект. В юнит-тестах чаще всего используется среда Jest, так как она поддерживает мокирование времени с помощью jest.useFakeTimers() и позволяет контролировать асинхронные процессы:

import anime from 'animejs/lib/anime.es.js';

jest.useFakeTimers();

Мокирование таймеров критично для тестирования, так как anime.js использует requestAnimationFrame или setTimeout для обновления состояния анимации. Контроль времени позволяет “прокрутить” анимацию вперед без реального ожидания.

Проверка свойств объекта

Основная цель юнит-теста анимации — убедиться, что свойства объекта меняются корректно. Пример для анимации opacity элемента:

const obj = { opacity: 0 };

const animation = anime({
  targets: obj,
  opacity: 1,
  duration: 1000,
  easing: 'linear'
});

// Начальное состояние
expect(obj.opacity).toBe(0);

// "Продвигаем" время на 500 мс
jest.advanceTimersByTime(500);

// Проверка промежуточного состояния
expect(obj.opacity).toBeCloseTo(0.5, 1);

// Завершение анимации
jest.advanceTimersByTime(500);
expect(obj.opacity).toBe(1);

Ключевой момент: toBeCloseTo необходим для проверки чисел с плавающей точкой, так как анимации изменяют свойства постепенно, а не дискретно.

Тестирование событий жизненного цикла

Anime.js поддерживает события begin, update, complete, которые можно использовать для проверки вызовов коллбэков:

const onBe gin = jest.fn();
const onUpd ate = jest.fn();
const onCompl ete = jest.fn();

anime({
  targets: obj,
  opacity: 1,
  duration: 1000,
  easing: 'linear',
  begin: onBegin,
  update: onUpdate,
  complete: onComplete
});

jest.advanceTimersByTime(1000);

expect(onBegin).toHaveBeenCalledTimes(1);
expect(onUpdate).toHaveBeenCalled();
expect(onComplete).toHaveBeenCalledTimes(1);

События позволяют убедиться, что анимация запускается, обновляется и завершается как ожидается.

Тестирование последовательных и параллельных анимаций

Для сложных анимационных сценариев с последовательностью (timeline) важно проверять каждый шаг:

const timeline = anime.timeline({ autoplay: false });

timeline
  .add({
    targets: obj,
    x: 100,
    duration: 500
  })
  .add({
    targets: obj,
    y: 200,
    duration: 500
  });

timeline.play();

// Проверка первого шага
jest.advanceTimersByTime(500);
expect(obj.x).toBe(100);
expect(obj.y).toBe(0);

// Проверка второго шага
jest.advanceTimersByTime(500);
expect(obj.y).toBe(200);

Тестирование timeline особенно важно, когда анимации влияют на последовательное изменение нескольких свойств или объектов.

Тестирование циклических и бесконечных анимаций

Анимации с опцией loop или direction: 'alternate' требуют особого внимания:

const loopAnimation = anime({
  targets: obj,
  x: [0, 100],
  duration: 1000,
  loop: 2,
  easing: 'linear'
});

loopAnimation.play();
jest.advanceTimersByTime(2000);
expect(obj.x).toBe(100); // После двух циклов

Важно учитывать количество циклов и направление, чтобы корректно проверять финальное состояние.

Практические советы

  • Использовать fake timers для контроля времени.
  • Проверять промежуточные состояния свойств, а не только конечное.
  • Использовать toBeCloseTo для свойств с плавающей точкой.
  • Мокировать коллбэки событий жизненного цикла для проверки их вызова.
  • Для timeline проверять каждый добавленный шаг.
  • Для бесконечных или циклических анимаций фиксировать количество циклов в тесте.

Юнит-тестирование анимаций с anime.js повышает надёжность интерфейсов и позволяет автоматизировать проверку динамических эффектов без необходимости визуальной инспекции.