Условный рендеринг разных компонентов

Условный рендеринг — ключевой механизм в Vue, позволяющий отображать разные компоненты в зависимости от состояния приложения или переданных пропсов. При тестировании компонентов с помощью Vue Test Utils важно проверять как правильность рендеринга, так и корректность взаимодействия между условиями, компонентами и их состояниями.

Основы условного рендеринга

В Vue условный рендеринг чаще всего реализуется с помощью директив v-if, v-else-if, v-else или v-show.

<template>
  <div>
    <ComponentA v-if="type === 'A'" />
    <ComponentB v-else-if="type === 'B'" />
    <ComponentC v-else />
  </div>
</template>

<script>
export default {
  props: {
    type: {
      type: String,
      required: true
    }
  },
  components: { ComponentA, ComponentB, ComponentC }
}
</script>

В данном примере компонент, который будет отрендерен, полностью зависит от значения пропса type. Это накладывает требования на тестирование: необходимо проверять все возможные ветви условного рендеринга.

Монтирование компонентов с Vue Test Utils

Для тестирования условного рендеринга используется функция mount или shallowMount из Vue Test Utils. Разница между ними в том, что shallowMount рендерит компоненты только поверхностно, заменяя дочерние компоненты заглушками.

import { mount } from '@vue/test-utils'
import ParentComponent from '@/components/ParentComponent.vue'
import ComponentA from '@/components/ComponentA.vue'
import ComponentB from '@/components/ComponentB.vue'
import ComponentC from '@/components/ComponentC.vue'

describe('ParentComponent', () => {
  it('рендерит ComponentA при type A', () => {
    const wrapper = mount(ParentComponent, {
      props: { type: 'A' }
    })
    expect(wrapper.findComponent(ComponentA).exists()).toBe(true)
    expect(wrapper.findComponent(ComponentB).exists()).toBe(false)
    expect(wrapper.findComponent(ComponentC).exists()).toBe(false)
  })

  it('рендерит ComponentB при type B', () => {
    const wrapper = mount(ParentComponent, {
      props: { type: 'B' }
    })
    expect(wrapper.findComponent(ComponentB).exists()).toBe(true)
    expect(wrapper.findComponent(ComponentA).exists()).toBe(false)
    expect(wrapper.findComponent(ComponentC).exists()).toBe(false)
  })

  it('рендерит ComponentC при других значениях', () => {
    const wrapper = mount(ParentComponent, {
      props: { type: 'C' }
    })
    expect(wrapper.findComponent(ComponentC).exists()).toBe(true)
    expect(wrapper.findComponent(ComponentA).exists()).toBe(false)
    expect(wrapper.findComponent(ComponentB).exists()).toBe(false)
  })
})

Тестирование динамического изменения состояния

Компоненты с условным рендерингом часто меняют вид в процессе работы приложения. Vue Test Utils позволяет эмулировать такие изменения через реактивные свойства или изменение пропсов с использованием setProps.

it('должен менять рендер при изменении пропса', async () => {
  const wrapper = mount(ParentComponent, { props: { type: 'A' } })
  expect(wrapper.findComponent(ComponentA).exists()).toBe(true)

  await wrapper.setProps({ type: 'B' })
  expect(wrapper.findComponent(ComponentB).exists()).toBe(true)
  expect(wrapper.findComponent(ComponentA).exists()).toBe(false)
})

Использование await важно, так как Vue обновляет DOM асинхронно, и без него проверка может происходить до фактического ререндеринга.

Комбинированные условия

Иногда рендер зависит от нескольких условий, например пропсов и состояния компонента (data или computed). В таких случаях тесты должны учитывать все комбинации условий.

<template>
  <div>
    <ComponentX v-if="isActive && type === 'X'" />
    <ComponentY v-else-if="!isActive && type === 'Y'" />
    <ComponentZ v-else />
  </div>
</template>

Тестирование всех веток:

it('рендерит ComponentX только если isActive и type X', async () => {
  const wrapper = mount(ParentComponent, { 
    props: { type: 'X' }, 
    data: () => ({ isActive: true }) 
  })
  expect(wrapper.findComponent(ComponentX).exists()).toBe(true)

  await wrapper.setData({ isActive: false })
  expect(wrapper.findComponent(ComponentZ).exists()).toBe(true)
})

Использование findComponent и селекторов

Для проверки рендеринга разных компонентов можно использовать не только компоненты, но и классы, атрибуты или селекторы.

expect(wrapper.find('.component-x').exists()).toBe(true)
expect(wrapper.find('[data-test="component-y"]').exists()).toBe(false)

Использование атрибутов data-test особенно полезно для тестов, так как они не зависят от структуры CSS и менее подвержены изменениям верстки.

Мокирование дочерних компонентов

При глубоком тестировании условного рендеринга часто необходимо изолировать родительский компонент. Для этого можно использовать shallowMount или моки:

const wrapper = shallowMount(ParentComponent, {
  props: { type: 'A' },
  global: {
    stubs: {
      ComponentA: true,
      ComponentB: true,
      ComponentC: true
    }
  }
})

Это позволяет проверять только логику рендеринга без влияния внутренней реализации дочерних компонентов.

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

Сложные компоненты могут использовать слоты с условным содержимым:

<template>
  <div>
    <slot v-if="showHeader" name="header" />
    <slot />
  </div>
</template>

Тестирование слотов:

const wrapper = mount(ParentComponent, {
  props: { showHeader: true },
  slots: { header: '<h1>Заголовок</h1>', default: '<p>Контент</p>' }
})
expect(wrapper.html()).toContain('<h1>Заголовок</h1>')
expect(wrapper.html()).toContain('<p>Контент</p>')

Если showHeader становится false, слот с именем header не должен рендериться.

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

  1. Разделение тестов по веткам рендеринга — каждая ветка v-if, v-else-if, v-else должна иметь отдельный тест.
  2. Проверка динамических изменений — любые изменения пропсов или состояния должны сопровождаться проверкой соответствующего рендера.
  3. Использование моков и shallowMount для ускорения тестов и изоляции логики.
  4. Селекторы и data-test для точного таргетинга элементов.

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