Проверка вызова slot-scope параметров

В Vue.js слоты предоставляют возможность передачи контента из родительского компонента в дочерний. Scoped slots (слоты с областью видимости) позволяют передавать данные из дочернего компонента в родительский через slot props. Для юнит-тестирования компонентов с slot-scope параметрами важно не только убедиться, что слот отображается, но и проверить корректность передаваемых данных.


Основы scoped slots

Scoped slot определяется с помощью атрибута v-slot (в Vue 2.6+ используется синтаксис v-slot, в Vue 2.5 и ниже — slot-scope):

<ChildComponent>
  <template v-slot:default="slotProps">
    <p>{{ slotProps.text }}</p>
  </template>
</ChildComponent>

В данном примере дочерний компонент передает объект slotProps родителю, содержащий ключ text. В тестах важно убедиться, что данные, переданные через слот, соответствуют ожиданиям.


Монтирование компонента с scoped slot

Vue Test Utils предоставляет возможность замокать слот через объект slots при вызове mount или shallowMount. Для scoped slots используется свойство scopedSlots (в Vue 2) или можно передавать слот как функцию (в Vue 3):

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

const wrapper = mount(ChildComponent, {
  scopedSlots: {
    default: `<template slot-scope="props"><span>{{ props.text }}</span></template>`
  }
})

При таком подходе тест сможет отследить, что слот отрисован и что параметр props.text правильно передается.


Проверка передачи данных через слот

Для проверки данных, передаваемых в scoped slot, можно использовать следующие методы:

  1. Проверка текстового контента слота Если слот рендерит DOM-элементы, можно проверить содержимое через text():
expect(wrapper.text()).toContain('Ожидаемый текст');
  1. Использование функций слота для проверки аргументов В Vue Test Utils возможно передавать слот как функцию и проверять параметры напрямую:
const slotFn = jest.fn(props => `<span>${props.text}</span>`);

const wrapper = mount(ChildComponent, {
  scopedSlots: { default: slotFn }
});

expect(slotFn).toHaveBeenCalledWith(expect.objectContaining({ text: 'Hello World' }));

В этом примере используется jest.fn для отслеживания вызовов слота и проверки переданных параметров.


Комбинация shallowMount и scoped slots

shallowMount позволяет изолировать тестируемый компонент, игнорируя дочерние компоненты. Scoped slots при этом также можно замокать:

import { shallowMount } from '@vue/test-utils'

const wrapper = shallowMount(ChildComponent, {
  scopedSlots: {
    default: props => `<div>${props.message}</div>`
  }
})

expect(wrapper.html()).toContain('<div>Hello</div>');

Использование shallowMount ускоряет тестирование и снижает количество побочных эффектов от вложенных компонентов.


Работа с несколькими slot-scope параметрами

Если компонент передает несколько параметров через слот, проверку можно проводить через объект props:

const wrapper = mount(ChildComponent, {
  scopedSlots: {
    default: props => `<div>${props.text} - ${props.count}</div>`
  }
});

const slotDiv = wrapper.find('div');
expect(slotDiv.text()).toBe('Hello - 5');

Метод find позволяет локализовать конкретный DOM-элемент, созданный слотом, и проверить его содержимое.


Важные нюансы при тестировании scoped slots

  • Scoped slots всегда рендерятся в контексте родителя, но получают данные от дочернего компонента.
  • Использование функций слота позволяет проверять аргументы напрямую, а не только итоговый HTML.
  • Для Vue 3 предпочтительно передавать слот как функцию с параметрами вместо строки с шаблоном:
const wrapper = mount(ChildComponent, {
  slots: {
    default: ({ text }) => `<span>${text}</span>`
  }
});
  • При проверке вызова нескольких раз рекомендуется использовать jest.fn и проверять количество вызовов:
expect(slotFn).toHaveBeenCalledTimes(2);

Практический пример

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

const slotFn = jest.fn(props => `<li>${props.item}</li>`);

const wrapper = mount(ListComponent, {
  props: { items: ['A', 'B', 'C'] },
  scopedSlots: { default: slotFn }
});

expect(slotFn).toHaveBeenCalledTimes(3);
expect(slotFn.mock.calls[0][0]).toEqual({ item: 'A' });
expect(slotFn.mock.calls[1][0]).toEqual({ item: 'B' });
expect(slotFn.mock.calls[2][0]).toEqual({ item: 'C' });

В этом примере проверяется, что слот вызван для каждого элемента массива, а параметры item передаются корректно.


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

  • Изолировать тестируемый компонент через shallowMount, чтобы не тестировать внутренности дочерних компонентов.
  • Использовать функции слота для проверки props, а не только итогового HTML.
  • Проверять количество вызовов слота, если компонент рендерит его несколько раз.
  • Обращать внимание на версию Vue, так как синтаксис slot-scope устарел в пользу v-slot.

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