Слоты с областью видимости (scoped slots)

Scoped slots в Vue позволяют передавать данные из дочернего компонента в шаблон родителя через слот, обеспечивая гибкость в отображении. В тестировании компонентов с Vue Test Utils понимание и правильная обработка scoped slots является критически важным для точной проверки поведения компонентов.

Основные концепции

Scoped slot — это слот, который предоставляет объект с данными (props) родителю. В компоненте это выглядит так:

<template>
  <div>
    <slot :user="userData"></slot>
  </div>
</template>

<script>
export default {
  data() {
    return {
      userData: { name: 'Alice', age: 30 }
    };
  }
};
</script>

В родительском компоненте можно использовать эти данные так:

<Child>
  <template #default="{ user }">
    <p>{{ user.name }} - {{ user.age }}</p>
  </template>
</Child>

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

Монтирование scoped slots

Для тестов используется метод mount или shallowMount из Vue Test Utils, где слот передается через опцию slots. При этом scoped slot передается в виде функции, которая возвращает шаблон или компонент:

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

test('Scoped slot получает данные', () => {
  const wrapper = mount(Child, {
    slots: {
      default: `<template #default="{ user }">
                  <span class="username">{{ user.name }}</span>
                </template>`
    }
  });

  expect(wrapper.find('.username').text()).toBe('Alice');
});

Важный момент: использование строки в слоте автоматически компилируется Vue и позволяет использовать объект props, переданный дочерним компонентом.

Передача функции для слота

Можно передавать scoped slot как функцию, что особенно полезно для динамических значений:

const wrapper = mount(Child, {
  slots: {
    default: props => `<span class="username">${props.user.name}</span>`
  }
});

expect(wrapper.find('.username').text()).toBe('Alice');

Такой подход обеспечивает более точный контроль над передаваемыми props и позволяет проверять поведение компонента при различных данных.

Тестирование изменений данных

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

import { nextTick } from 'vue';

test('Scoped slot реагирует на изменения данных', async () => {
  const wrapper = mount(Child, {
    slots: {
      default: props => `<span class="username">${props.user.name}</span>`
    }
  });

  wrapper.vm.userData.name = 'Bob';
  await nextTick();

  expect(wrapper.find('.username').text()).toBe('Bob');
});

Использование nextTick() важно для того, чтобы дождаться обновления DOM после изменения реактивных данных.

Сложные слоты с несколькими props

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

<slot :user="userData" :status="status"></slot>

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

const wrapper = mount(Child, {
  slots: {
    default: ({ user, status }) => `
      <span class="username">${user.name}</span>
      <span class="status">${status}</span>
    `
  }
});

expect(wrapper.find('.username').text()).toBe('Alice');
expect(wrapper.find('.status').text()).toBe('active');

Взаимодействие со слотами внутри компонентов

Scoped slots часто используются внутри компонентов с циклической структурой, например, списки:

<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <slot :item="item"></slot>
    </li>
  </ul>
</template>

Для тестирования необходимо обеспечить корректную генерацию слота для каждого элемента:

const items = [{ id: 1, name: 'A' }, { id: 2, name: 'B' }];

const wrapper = mount(ListComponent, {
  props: { items },
  slots: {
    default: ({ item }) => `<span class="item-name">${item.name}</span>`
  }
});

const renderedItems = wrapper.findAll('.item-name').map(n => n.text());
expect(renderedItems).toEqual(['A', 'B']);

Подводные моменты

  • При использовании shallowMount слоты работают так же, но дочерние компоненты могут быть замоканы, что влияет на передаваемые props.
  • Scoped slots нельзя тестировать через обычный .text() родителя без явного монтирования слота.
  • Передача слотов как функций позволяет создавать динамические тесты с разными наборами данных.

Scoped slots в Vue Test Utils — это инструмент, который требует внимательности при монтировании и проверке. Корректное использование слотов обеспечивает точное тестирование реактивных и динамических компонентов, что критично для надежного покрытия тестами.