Что делает attachTo?
attachTo позволяет монтировать компонент в реальный DOM-элемент, а не в изолированную песочницу. Это критически важно для тестирования поведения, которое зависит от реальной DOM-структуры. По умолчанию Vue Test Utils не подключает компонент к document.body а просто изолирует в некой обертке.
Зачем это нужно? Основные случаи использования:
- Тестирование модальных окон, тултипов, dropdown-меню
- Тестирование поведения с
position: fixed - Тестирование фокуса и событий клавиатуры
- Тестирование с
getBoundingClientRect()
Важно:
- Очистка после тестов (Если не очищать, элементы накапливаются в DOM)
- Использование уникального элемента (Лучше создавать отдельный элемент для каждого теста)
Компонент (Modal.vue):
<template>
<Teleport to="body">
<div v-if="isOpen" class="modal-overlay" @click="close">
<div class="modal-content" @click.stop>
<p>Это модальное окно</p>
<button @click.stop="close">Закрыть</button>
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue';
const props = defineProps({
isOpen: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['close']);
const close = () => {
emit('close');
};
</script>
<style scoped>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
/* Убедитесь, что поверх других элементов */
}
.modal-content {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
text-align: center;
/* Стили для содержимого модального окна */
}
</style>
Компонент App.vue
<script setup lang="ts">
import { ref } from 'vue';
import Modal from './components/ModalOutside.vue';
const isOpen = ref(false);
</script>
<template>
<div>
<button @click="isOpen = true">Открыть модалку</button>
<Modal :isOpen="isOpen" @close="isOpen = false" />
</div>
</template>
В начале надо очищать весь наш виртуальный body, чтобы избежать влияния одного теста на другой.
Может возникнуть вопрос, почему использовать attachTo нужно при teleport to="body"?
Когда Vue встречает <Teleport to="body">, он пытается переместить содержимое в document.body. Но:
- Если компонент не подключён к DOM, то
document.bodyне существует в тестовой среде как настоящий DOM-узел. - Vue Test Utils не подключает компонент к
document.bodyпо умолчанию — он живёт в "изолированной" обёртке. - Без
attachToTeleportне сможет найтиdocument.bodyи ничего не переместит.
Задание:
Создайте тест для компонента Modal.vue.
Используйте опцию attachTo при вызове mount, чтобы прикрепить компонент к document.body. В данном примере teleport вынесет модальное окно в другое место, потому нам и надо использовать attachTo.
Убедитесь, что когда isOpen равно true, модальное окно отображается (.not.toBeNull()), а когда isOpen равно false, оно не отображается (.toBeNull()). Не забудьте вызвать wrapper.unmount() после теста
Решение
import { mount } from "@vue/test-utils";
import { expect, test } from "vitest";
import ModalOutside from '@/components/ModalOutside.vue';
afterEach(() => {
document.body.innerHTML = ''
})
test('ModalOutside проверка на существование при пропс = true', () => {
const div = document.createElement('div');
document.body.appendChild(div);
const wrapper = mount(ModalOutside, {
props: {
isOpen: true
},
attachTo: div
});
expect(document.querySelector('.modal-content')).not.toBeNull();
wrapper.unmount();
});
test('ModalOutside проверка на существование при пропс = false', () => {
const div = document.createElement('div');
document.body.appendChild(div);
const wrapper = mount(ModalOutside, {
props: {
isOpen: false
},
attachTo: div
});
expect(document.querySelector('.modal-content')).toBeNull();
wrapper.unmount();
})