Анимации и переходы

Vue.js предоставляет мощный механизм для работы с анимациями и переходами через встроенные компоненты <transition> и <transition-group>. Тестирование таких элементов требует понимания того, как Vue Test Utils взаимодействует с DOM и асинхронными процессами анимации. Важно помнить, что по умолчанию анимации не выполняются в тестовой среде так, как в браузере, поэтому методы тестирования должны учитывать виртуальное выполнение переходов.


Компоненты <transition> и <transition-group>

<transition> используется для анимации одиночного элемента при его появлении и исчезновении из DOM. Ключевые моменты для тестирования:

  • Классы перехода: Vue автоматически добавляет классы v-enter, v-enter-active, v-leave, v-leave-active. Тест должен проверять присутствие этих классов в нужные моменты.
  • События перехода: before-enter, enter, after-enter, before-leave, leave, after-leave. Они могут быть замоканы через Jest или другой фреймворк, чтобы проверить последовательность вызовов.

<transition-group> используется для анимации списка элементов. Отличия в тестировании:

  • Элементы получают динамические ключи (key), что критично для правильной работы анимаций.
  • Анимация элементов может происходить параллельно, поэтому важно проверять состояние каждого элемента отдельно.
  • Методы Vue Test Utils, такие как findAll, позволяют проверить, какие элементы присутствуют в DOM и какие классы перехода применяются.

Симуляция анимаций в тестах

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

1. Мокирование CSS-анимаций:

import { mount } from '@vue/test-utils'
import MyComponent from '@/components/MyComponent.vue'

jest.mock('vue', () => ({
  ...jest.requireActual('vue'),
  nextTick: (fn) => fn(),
}))

test('проверка классов transition', async () => {
  const wrapper = mount(MyComponent)
  wrapper.find('button').trigger('click')
  await wrapper.vm.$nextTick()
  expect(wrapper.find('.element').classes()).toContain('v-enter-active')
})
  • nextTick используется для ожидания обновления DOM после изменения состояния.
  • Проверка классов позволяет убедиться, что переходы активируются корректно.

2. Асинхронное тестирование событий:

test('проверка событий transition', async () => {
  const beforeEnter = jest.fn()
  const afterEnter = jest.fn()

  const wrapper = mount(MyComponent, {
    props: { beforeEnter, afterEnter }
  })

  wrapper.setData({ visible: true })
  await wrapper.vm.$nextTick()

  expect(beforeEnter).toHaveBeenCalled()
  await wrapper.vm.$nextTick()
  expect(afterEnter).toHaveBeenCalled()
})
  • Асинхронные вызовы $nextTick гарантируют, что все события анимации успели произойти.
  • Мок-функции позволяют проверять последовательность вызовов.

Тестирование сложных сценариев

1. Анимации списков с <transition-group>

Для тестов списка элементов важно проверять добавление, удаление и перестановку элементов. Пример:

const wrapper = mount({
  template: `
    <transition-group name="list" tag="ul">
      <li v-for="item in items" :key="item">{{ item }}</li>
    </transition-group>
  `,
  data() {
    return { items: ['A', 'B', 'C'] }
  }
})

wrapper.setData({ items: ['B', 'C', 'D'] })
await wrapper.vm.$nextTick()

const listItems = wrapper.findAll('li')
expect(listItems.map(li => li.text())).toEqual(['B', 'C', 'D'])
listItems.forEach(li => {
  expect(li.classes()).toContain('list-move')
})
  • Проверяется правильная перестановка элементов и присвоение классов анимации list-move.
  • Асинхронная обработка гарантирует корректность DOM перед проверкой.

2. Комбинированные анимации

Если компонент использует несколько переходов одновременно (например, <transition> внутри <transition-group>), тестирование должно учитывать последовательность:

  • Включение и отключение элементов через v-if.
  • Проверка событий before-* и after-* для каждого слоя анимации.
  • Использование await wrapper.vm.$nextTick() после каждого изменения состояния.

Практические рекомендации

  • Использовать trigger для имитации пользовательских событий, вызывающих анимацию.
  • Мокировать длинные анимации с помощью jest.useFakeTimers() для ускорения тестов.
  • Проверять классы перехода, а не реальные визуальные эффекты.
  • Использовать await flushPromises() или $nextTick() для корректного ожидания завершения анимаций.
  • При тестировании <transition-group> учитывать динамические ключи и проверять, что порядок элементов в DOM соответствует ожиданиям.

Особенности Vue 3

  • Vue 3 использует Composition API, что позволяет выносить логику анимаций в отдельные функции, упрощая тестирование.
  • Компоненты <Transition> и <TransitionGroup> стали более предсказуемыми: все асинхронные хуки возвращают промисы, которые удобно ожидать в тестах.
  • Рекомендуется использовать mount вместо shallowMount, если требуется тестировать реальные классы анимации, так как shallowMount не рендерит дочерние компоненты полностью.