Vue-test-utils challenge

vue test utils

В данном челендже, дается задача, дается минималистичная подсказка, и решение. В данном челендже я постараюсь пройти наиболее популярные задача Vue-test-utils и vitest.

Рекомендуется для усвоения материала, запустить vue приложение, и копируя компоненты, писать для них тесты. указанные в заданиях. Попутно читая в документации о методах и ранее не известных функциях.

1: Монтирование компонента и доступ к DOM

Компонент:

<template>
  <div>{{ message }}</div>
</template>

<script setup>
defineProps({
  message: String
});
</script>

Задание:

  1. Протестируй, что компонент отображает переданное значение message.
  2. Проверь, что если message не передано, то компонент будет работать.

Подсказка:

При проверке теста можно использовать: .toBe() .toEqual() .toContain()

  • Используйте .toBe() для примитивов (строки, числа, булевы) и ссылочного равенства
  • Используйте .toEqual() для объектов, массивов и глубокого сравнения
  • Используйте .toContain() для проверки наличия элемента в массиве или подстроки в строке

Используй mount, .text(), .exists().

  • text() - возвращает текстовое содержимое компонента
  • exists() - проверяет существует ли элемент
  • mount()mount() - монтирует Vue компонент в изолированное DOM-дерево

Решение:

import { mount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest'
import mountComponent from '@/components/mount.vue';

describe('mount.vue', () => {
    it('Протестируй, что компонент отображает переданное значение message.', () => {
        const wrapper = mount(mountComponent, {
            props: {
                message: 'message'
            }
        });

        expect(wrapper.find('[data-test-message]').text()).toBe('message')
    })
    it('Проверь, что если message не передано, то ничего не падает', () => {
        const wrapper = mount(mountComponent);
        expect(wrapper.exists()).toBe(true)
    })
})


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

Компонент:

<template>
  <button @click="count++">{{ count }}</button>
</template>

<script setup>
import { ref } from 'vue';
const count = ref(0);
</script>

Задание:

  • Проверь, что начальное значение count равно 0.
  • Проверь, что при клике значение увеличивается.

Подсказка:

Используй wrapper.get(), .trigger(), .text().

  • wrapper.get() - получает элемент с проверкой существования, аналог find(), но выбрасывает ошибку, если элемент не найден, используется когда элемент должен существовать.
  • wrapper.trigger() - симулирует DOM события, имитирует события на элементе (click, input, submit и т.д.)

Решение:

import { mount, trigger } from '@vue/test-utils';
import { describe, expect, it } from 'vitest'
import Counter from '@/components/Counter.vue';

describe('Counter',()=>{
    it('Проверь, что начальное значение count равно 0.',()=>{
        const wrapper = mount(Counter);
        expect(wrapper.find('button').text()).toBe('0');
    });
    it('Проверь, что при клике 1 раз значение увеличивается.', async() => {
        const wrapper = mount(Counter);
        const button = wrapper.find('button');

        await button.trigger('click');

        expect(wrapper.find('button').text()).toBe('1');
    });

    it('Проверь, что при клике 3 раза значение увеличивается.', async () => {
        const wrapper = mount(Counter);
        const button = wrapper.find('button');

        await button.trigger('click');
        await button.trigger('click');
        await button.trigger('click');

        expect(wrapper.find('button').text()).toBe('3');
    });
});


3: Тестирование emit’ов

Компонент:

<template>
  <button @click="$emit('custom-event')">Click me</button>
</template>

<script setup>
defineEmits(['custom-event']);
</script>

Задание:

  • Проверь, что при клике "эмитится" событие custom-event.

Подсказка:

Используй wrapper.emitted().

wrapper.emitted() - отслеживает кастомные события, которые эмиттил компонент, возвращает объект со всеми событиями, которые были вызваны с помощью $emit() внутри компонента.

Решение:

import { mount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest'
import Emits from '@/components/Emits.vue';

describe('Emits.vue',()=>{
    it('Проверь, что при клике эмитится событие custom-event.', async ()=>{
        const wrapper = mount(Emits);
        const button = wrapper.find('button');

        await button.trigger('click');

        expect(wrapper.emitted('custom-event')).toBeTruthy();
        expect(wrapper.emitted('custom-event')).toHaveLength(1)  // доп проверка на колличество эмитов
    });
})


4: Тестирование v-model

Компонент:

<template>
  <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">
</template>

<script setup>
defineProps(['modelValue']);
defineEmits(['update:modelValue']);
</script>

Задание:

  • Проверь, что инпут отображает переданное значение.
  • Проверь, что при вводе эмитится обновление modelValue.

Подсказка:

Используй wrapper.find('selector').setValue('value').

Решение:

import { mount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest'
import Vmodel from '@/components/Vmodel.vue';

describe('Vmodel', () => {
    it('Проверь, что инпут отображает переданное значение', async () => {
        const wrapper = mount(Vmodel);
        const input = wrapper.find('input');

        await input.setValue('test');

        expect(input.element.value).toBe('test');
    });
    it('Проверь, что при вводе эмитится обновление modelValue', async () => {
        const wrapper = mount(Vmodel);
        const input = wrapper.find('input');
        await input.setValue('test');

        expect(wrapper.emitted('update:modelValue')).toBeTruthy();

        expect(wrapper.emitted('update:modelValue')).toHaveLength(1);

        expect(wrapper.emitted('update:modelValue')[0]).toBe(['test']);
    })
})


5: Тестирование computed свойства

Компонент:

<template>
  <div>{{ fullName }}</div>
</template>

<script setup>
import { computed } from 'vue';
const firstName = defineModel('firstName');
const lastName = defineModel('lastName');

const fullName = computed(() => `${firstName.value} ${lastName.value}`);
</script>

Вызвать компонент так:

<script setup lang="ts">
import {ref} from 'vue';
import Computed from './components/Computed.vue';
const first = ref('Nick');
const last = ref('Mart');
</script>

<template>
  <div>
   <Computed v-model:firstName="first" v-model:lastName="last" />
  </div>
</template>

Задание:

  • Проверь, что fullName корректно формируется.
  • Измените firstName и проверьте, что fullName обновился.

Подсказка:

Используйте wrapper.setProps() и nextTick().

Имитирует изменение props, переданных от родительского компонента.

wrapper.setProps() - обновляет props компонента, имитирует изменение props, переданных от родительского компонента.

Пример использования: await wrapper.setProps({ propName: newValue })

Решение:

import { mount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest'
import Computed from '@/components/Computed.vue';

describe('Computed.vue', () => {
    it('Проверь, что fullName корректно формируется.', () => {
        const wrapper = mount(Computed, {
            props: {
                firstName: 'Nick',
                lastName: 'Tester'
            }
        });

        expect(wrapper.find('[data-test="title"]').text()).toBe('Nick Tester')
    });

    it('Измените firstName и проверьте, что fullName обновился', async () => {
        const wrapper = mount(Computed, {
            props: {
                firstName: 'Nick',
                lastName: 'Tester'
            }
        });
        
        await wrapper.setProps({
            firstName: 'Vladimir',
            lastName: 'Programmer'
        });

        expect(wrapper.find('[data-test="title"]').text()).toBe('Vladimir Programmer')
    });
});


6: Тестирование watch и watchEffect

Компонент:

<template>
  <div>{{ log }}</div>
</template>

<script setup>
import { ref, watch } from 'vue';
const count = ref(0);
const log = ref('');

watch(count, (val) => {
  log.value = `Count changed to ${val}`;
});
</script>

Задание:

  • Проверь, что при изменении count обновляется log.

Подсказка:

Используй прямой доступ к vm. Через vm изменить данные можно так: wrapper.vm.count = 10, и после этого нужен await wrapper.vm.$nextTick()

Решение:

import { mount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest'
import Watch from '@/components/Watch.vue';

describe('Watch.vue', () => {
    it('Проверь, что при изменении count обновляется log.',async()=>{
        const wrapper = mount(Watch);

        wrapper.vm.count = 1;

        await wrapper.vm.$nextTick()

        expect(wrapper.text()).toBe('Count changed to 1')
    })
}) 


7: Использование stubs и mocks

Компонент:

<template>
  <ChildComponent :data="someData" />
</template>

<script setup>
import ChildComponent from './ChildComponent.vue';
const someData = 'hello';
</script>

Задание:

  • "Застабь" ChildComponent и проверь, что он получил data.

stubs - заглушки для дочерних компонентов. stubs заменяют дочерние компоненты простыми заглушками во время тестирования. Это нужно для изоляции тестируемого компонента.

import { mount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest'
import Stub from '@/components/Stub.vue';

describe('Stub.vue', ()=>{
    it('Делаю заглушку child и передаю внутрь props', async() =>{
        const wrapper = mount(Stub,{
            global:{
                stubs: ['ChildComponent'],
            }
        });

        const child = wrapper.find('child-component-stub');
        expect(child.attributes('data')).toBe('hello');
       
    })
});

Также можно вообще все дочерние компоненты заглушить через :

const wrapper = mount(TestComponent, {
  shallow: true // Автоматически заглушает все дочерние компоненты
})

Или более новый и читабельный вариант shallowMount:

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

const wrapper = shallowMount(TestComponent, {
  // опции конфигурации
})


8: Тестирование асинхронных данных

Компонент:

<template>
  <div v-if="loading">Loading...</div>
  <div v-else>{{ data }}</div>
</template>

<script setup>
import { ref, onMounted } from 'vue';

const loading = ref(true);
const data = ref(null);

onMounted(async () => {
  // simulate fetch
  await new Promise(resolve => setTimeout(resolve, 100));
  data.value = 'Loaded!';
  loading.value = false;
});
</script>

Задание:

  • Проверь, что сначала отображается "Loading...".
  • После загрузки отображается "Loaded!".

Подсказка:

Используй flushPromises() и vi.advanceTimersByTime().

flushPromises - чтобы дождаться промиса, а vi.advanceTimersByTime чтобы перемотать setTimeout.

flushPromises() - "Прогоняет" все ожидающие Promise до их завершения. Заставляет JavaScript обработать все микрозадачи (microtasks) в очереди. По сути их закрывает.

Что НЕ делает flushPromises():

  • Не ждет setTimeout/setInterval
  • Не ждет requestAnimationFrame
  • Не ждет HTTP-запросы (только Promise, которые уже разрешаются)

vi.advanceTimersByTime() - Перемещает "виртуальное время" вперед на указанное количество миллисекунд, срабатывая для всех таймеров (setTimeout, setInterval). Пример:

import { vi } from 'vitest'
// Включаем фейковые таймеры
vi.useFakeTimers();
setTimeout(() => console.log('Done!'), 1000);

vi.advanceTimersByTime(1000); // Прошло 1 секунда "виртуального" времени
// Выведет: "Done!"

flushPromises() - для .then()async/await

vi.advanceTimersByTime() - для setTimeoutsetInterval

Решение:

import { mount, flushPromises } from '@vue/test-utils';
import { describe, it, expect, vi } from 'vitest';
import Async from '@/components/Async.vue';

describe('Async.vue', () => {
    it('Проверь, что сначала отображается "Loading..."', () => {
        const wrapper = mount(Async);

        expect(wrapper.text()).toBe('Loading...')
    });

    it('После загрузки отображается "Loaded!"', async () => {
        vi.useFakeTimers();
        const wrapper = mount(Async);

        vi.advanceTimersByTime(1000);
        await flushPromises();

        expect(wrapper.text()).toBe('Loaded!')
    });
})


9: Тестирование HTTP-запросов (моки)

Компонент:

<template>
  <div>{{ user.name || 'No user' }}</div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import axios from 'axios';

const user = ref({});

onMounted(async () => {
  const response = await axios.get('https://jsonplaceholder.typicode.com/users/1');
  user.value = response.data;
});
</script>

Задание:

  • Замокай axios и верни фейкового юзера.
  • Проверь, что имя отображается.

Подсказка:

Используй vi.mock('axios') и axios.get.mockResolvedValue

import { mount, flushPromises } from '@vue/test-utils';
import { vi, expect, describe, it } from 'vitest';
import HTTP from '@/components/HTTP.vue';
import axios from 'axios';

vi.mock('axios');

describe('HTTP', () => {
    it('Замокай axios и верни фейкового юзера.', async () => {
        const user = { name: "Nick Molodets" };

        axios.get.mockResolvedValue({
            data: user,
            status: 200
        });

        const wrapper = mount(HTTP);
        await flushPromises();
        await wrapper.vm.$nextTick();

        expect(wrapper.text()).toBe('Nick Molodets');
    });
});


10: Тестирование форм и валидации

Компонент:

<template>
  <form @submit.prevent="submit">
    <input v-model="email" type="email" required />
    <span v-if="error">{{ error }}</span>
    <button type="submit">Submit</button>
  </form>
</template>

<script setup>
import { ref } from 'vue';

const email = ref('');
const error = ref('');

const submit = () => {
  if (!email.value.includes('@')) {
    error.value = 'Invalid email';
  }
};
</script>

Задание:

  • Проверь, что при неверном email появляется ошибка.
  • Проверь, что при правильном email ошибка исчезает.

Решение:

import { mount } from '@vue/test-utils';
import { expect, describe, it } from 'vitest';
import Form from '@/components/Form.vue';

describe('Form.vue', ()=>{
    it('Проверь, что при неверном email появляется ошибка', async()=>{
        const wrapper = mount(Form);

        const input = wrapper.find('input');
        const form = wrapper.find('form'); 

        await input.setValue('test');
        await form.trigger('submit');

       expect(wrapper.find('span').text()).toBe('Invalid email')
    });
    it('Проверь, что при правильном email ошибка исчезает.', async () => {
        const wrapper = mount(Form);
        const input = wrapper.find('input');
        const form = wrapper.find('form'); 
        
        await input.setValue('test@mail.ru');
        await form.trigger('submit');

        expect(wrapper.find('span').exists()).toBe(false);
    })
})


11: Тестирование глобальных свойств (например, $t)

Создайте файл /src/plugins/i18n.js

import { createI18n } from 'vue-i18n'

const messages = {
    en: {
        welcome: 'Welcome!',
        greeting: 'Hello, {name}!'
    },
    ru: {
        welcome: 'Добро пожаловать!',
        greeting: 'Привет, {name}!'
    }
}

const i18n = createI18n({
    legacy: false,
    locale: 'ru',
    fallbackLocale: 'en',
    messages
})

export default i18n

Подключите в main.js/main.ts

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import i18n from './plugins/i18n'
createApp(App).use(i18n).mount('#app')

Компонент:

<template>
  <h1>{{ $t('welcome') }}</h1>
</template>

Задание:

  • Замокай $t и проверь, что отображается правильный текст.

Подсказка: Используй global.mocks.

Решение:

import { mount } from '@vue/test-utils';
import { expect, describe, it } from 'vitest';
import MockT from '@/components/MockT.vue';

describe('MockT', () => {
    it('Замокай $t и проверь, что отображается правильный текст.', () => {
        const wrapper = mount(MockT, {
            global:{
                mocks:{
                    $t:()=> 'Hello Nick'
                }
            }
        });

        expect(wrapper.text()).toBe('Hello Nick');
    })
})


12: Тестирование provide / inject

Компонент:

<template>
  <div>{{ injectedValue }}</div>
</template>

<script setup>
import { inject } from 'vue';

const injectedValue = inject('myKey');
</script>

Задание:

  • Проверь, что компонент получает значение через provide.

Подсказка:

Используй global.provide.

Решение:

import { mount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest'
import Provide from '@/components/Provide.vue';

describe('Provide',()=>{
    it('Проверь, что компонент получает значение через provide.',()=>{
        const wrapper = mount(Provide,{
            global:{
                provide:{
                    myKey:'test'
                }
            }
        });
        expect(wrapper.text()).toBe('test')
    })
})


13: Тестирование Pinia store

Компонент:

<template>
  <div>{{ counterStore.count }}</div>
</template>

<script setup>
import { useCounterStore } from '@/stores/counter';
const counterStore = useCounterStore();
</script>

Задание:

  • Замокай store и проверь, что отображается нужное значение.

Подсказка:

Используй vi.mock('@/stores/counter').

vi.fn() создает "заглушку" функции, которая:

  • Заменяет реальную функцию
  • Позволяет отслеживать её вызовы
  • Может возвращать заданные значения

Решение:

import { mount } from '@vue/test-utils';
import { describe, expect, it, vi } from 'vitest'
import Pinia from '@/components/Pinia.vue';
import { useCounterStore } from '@/stores/counter'

describe('Pinia', () => {
    it('Замокай store и проверь, что отображается нужное значение.', () => {
        
        vi.mock('@/stores/counter', ()=>({
            useCounterStore:vi.fn(()=>({
                count:54
            }))
        }))

        const wrapper = mount(Pinia);
        
        expect(wrapper.text()).toBe('54')
    })
})


14: Тестирование маршрутов (Router)

Компонент:

<template>
  <div>{{ $route.params.id }}</div>
</template>

Задание:

  • Замокай $route и проверь отображение параметра.

Подсказка:

Используй global.mocks.

Подготовка:

// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Router from '@/components/Router.vue'; // твой компонент

const routes = [
    {
        path: '/test/:id', // :id — динамический параметр
        name: 'ItemDetail',
        component: Router,
    },
];

export const router = createRouter({
    history: createWebHistory(),
    routes,
});

//main.js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

import { router } from './router';


createApp(App).use(router).mount('#app')

И теперь по урлу - /test/88 на странице будет текст -88

Решение:

import { mount } from '@vue/test-utils';
import { vi, it, describe, expect } from 'vitest';
import Router from '@/components/Router.vue';

describe('Router.vue', () => {
    it('амокай $route и проверь отображение параметра.', () => {
        const wrapper = mount(Router, {
            global: {
                mocks: {
                    $route: {
                        params: { id: '12345' }
                    }
                }
            }
        });

        expect(wrapper.text()).toBe('12345');
    })
})


15: Тестирование условного рендеринга

Компонент:

<template>
  <div v-if="show">Visible</div>
  <div v-else>Hidden</div>
</template>

<script setup>
defineProps(['show']);
</script>

Задание:

  • Проверь, что при show=true видно "Visible".
  • При show=false — "Hidden".

Подсказка:

Используй setProps.

Решение:

import { mount } from '@vue/test-utils';
import { expect, describe, it } from 'vitest';
import Vif from '@/components/V-if.vue';

describe('V-if.vue', () => {
    it('Проверь, что при show=true видно "Visible"', () => {
        const wrapper = mount(Vif, {
            props: {
                show: true
            }
        });

        expect(wrapper.text()).toBe('Visible');
    });
    it('При show=false — "Hidden".', () => {
        const wrapper = mount(Vif, {
            props: {
                show: false
            }
        });

        expect(wrapper.text()).toBe('Hidden');
    })
})


16: Тестирование списков (v-for)

Компонент:

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

<script setup>
defineProps(['items']);
</script>

Задание:

  • Проверь, что отображаются все элементы списка.

Подсказка:

Используй wrapper.findAll.

import { mount } from '@vue/test-utils';
import { expect, describe, it } from 'vitest';
import vfor from '@/components/v-for.vue';

describe('v-for.vue', () => {
    const fakeItems = [{ id: 1, name: 'vfor1' }, { id: 2, name: 'vfor2' }];

    it('Проверь, что отображаются все элементы списка.', () => {
        const wrapper = mount(vfor, {
            props: {
                items: fakeItems
            }
        });

        const items = wrapper.findAll('li');

        expect(items).toHaveLength(2);
        console.log(items);
        expect(items[0].text()).toBe('vfor1');
    })
})


17: Тестирование scoped slots

Компонент:

<template>
  <slot name="header" :title="title"></slot>
</template>

<script setup>
const title = 'My Title';
</script>

Задание:

  • Проверь, что слот получает title.

Подсказка:

Используй slots.

test('passes title to slot', () => {
  const wrapper = mount(MyComponent, {
    slots: {
      header: `<template #header="{ title }">{{ title }}</template>`
    }
  });
  expect(wrapper.text()).toBe('My Title');
});


18: Тестирование lifecycle hooks

Компонент:

<template>
  <div>{{ mounted }}</div>
</template>

<script setup>
import { ref, onMounted } from 'vue';

const mounted = ref(false);

onMounted(() => {
  mounted.value = true;
});
</script>

Задание:

  • Проверь, что mounted становится true после монтирования.

Подсказка:

Это происходит автоматически при mount, а достучаться до переменной можно через wrapper.vm.mounted

Решение:

import { mount } from '@vue/test-utils';
import { expect, describe, it } from 'vitest';
import Life from '@/components/Life.vue';

describe('Life.vue', () => {
    it('mounted становится true после монтирования', () => {
        const wrapper = mount(Life);

        expect(wrapper.vm.mounted).toBe(true);
    })
})


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

Компонент:

<template>
  <div>{{ data }}</div>
</template>

<script setup>
import { ref } from 'vue';

const data = ref('initial');

// Async hook
setTimeout(() => {
  data.value = 'async data';
}, 100);
</script>

Задание:

  • Проверь, что data меняется после setTimeout.

Подсказка:

Используй vi.advanceTimersByTime. Этот метод будет запускать каждый инициированный таймер до тех пор, пока не истечет указанное количество миллисекунд или пока очередь не опустеет — в зависимости от того, что произойдет раньше.

Решение:

import { mount } from '@vue/test-utils';
import { expect, describe, it, vi } from 'vitest';
import AsyncHook from '@/components/AsyncHook.vue';


describe('AsyncHook.vue', () => {

    vi.useFakeTimers();

    it('data меняется после setTimeout.', async () => {

        const wrapper = mount(AsyncHook);

        await vi.advanceTimersByTime(100);

        expect(wrapper.text()).toBe('async data');
    });
})


20: Тестирование с использованием composables

Компонент:

<template>
  <div>{{ useMyComposable().value }}</div>
</template>

<script setup>
import { useMyComposable } from '@/composables/myComposable';
</script>

// composables/myComposable.ts
import { ref } from 'vue'

export function useMyComposable() {
  // Создаем реактивную переменную
  const message = ref('Привет от композабла!')
  
  // Возвращаем объект с данными
  return message
}

Задание:

  • Замокай composable и проверь его возвращаемое значение.

Подсказка:

Используй vi.mock.

Решение:

import { mount } from '@vue/test-utils';
import { expect, describe, it } from 'vitest';
import Composable from '@/components/Composable.vue';

describe('Composable.vue', () => {
    it('Замокай composable и проверь его возвращаемое значение', () => {

        vi.mock('@/composables/myComposable', () => ({
            useMyComposable: () => ({
                value: 'Привет от замоканного компосаблa'
            });
        }));

        const wrapper = mount(Composable);
        expect(wrapper.text()).toBe('Привет от замоканного компосаблa');

    })
})


21: Тестирование событий мыши/клавиатуры

Компонент:

<template>
    <div @keydown="handleKeyDown" tabindex="0">-->{{ key }}</div>
</template>

<script setup>
import { ref } from 'vue';

const key = ref('');

const handleKeyDown = (e) => {
    key.value = e.key;
};
</script>

Задание:

  • Симулируй нажатие клавиши и проверь, что она отображается.

Подсказка:

Используй trigger('keydown', { key: 'Enter' }).

Решение:

import { mount } from '@vue/test-utils';
import { expect, describe, it } from 'vitest';
import KeyDown from '@/components/KeyDown.vue';

describe('KeyDown.vue', () => {
    it('Симулируй нажатие клавиши и проверь, что она отображается.', async () => {

        const wrapper = mount(KeyDown);

        await wrapper.trigger('keydown', { key: 'Enter' });

        expect(wrapper.text()).toBe('-->Enter');
    });
})


22: Тестирование Drag & Drop

Компонент:

<template>
  <div @dragstart="dragStart">Drag me</div>
</template>

<script setup>
const dragStart = (e) => {
  e.dataTransfer.setData('text/plain', 'dragged data');
};
</script>

Задание:

  • Проверь, что при драге устанавливается правильный тип и данные.

Подсказка:

Нужно замокать dataTransfer.

Решение:

import { mount } from '@vue/test-utils';
import { expect, describe, it } from 'vitest';
import drag from '@/components/drag.vue';

describe('drag.vue', () => {
    it('при драге устанавливается правильный тип и данные.', async () => {
        const dataTransfer = {
            setData: vi.fn()
        };


        const wrapper = mount(drag);
        const div = wrapper.find('div');
        div.trigger('dragstart', {
            dataTransfer
        });

        expect(dataTransfer.setData).toHaveBeenCalledTimes(1)
        expect(dataTransfer.setData).toHaveBeenCalledWith('text/plain', 'dragged data')
    });
})


23: Тестирование ошибок (try/catch)

Компонент:

<template>
  <div v-if="error">Error: {{ error.message }}</div>
  <div v-else>Success</div>
</template>

<script setup>
import { ref } from 'vue';

const error = ref(null);

try {
  throw new Error('Test error');
} catch (e) {
  error.value = e;
}
</script>

Задание:

  • Проверь, что отображается сообщение об ошибке.

import { mount } from '@vue/test-utils';
import { expect, describe, it } from 'vitest';
import tryComp from '@/components/try.vue';

describe('tryComp.vue', () => {
    it('Проверь, что отображается сообщение об ошибке.', async () => {
        const wrapper = mount(tryComp);
        expect(wrapper.text()).toContain('Error: Test error');
    });
})


24: Тестирование Watch с deep и immediate

Компонент:

<template>
  <div>{{ log }}</div>
</template>

<script setup>
import { ref, reactive, watch } from 'vue';

const obj = reactive({ nested: { value: 1 } });
const log = ref('');

watch(
  () => obj.nested,
  (val) => {
    log.value = `Changed to ${val.value}`;
  },
  { deep: true, immediate: true }
);
</script>

Задание:

  • Проверь, что срабатывает сразу и при изменении obj.nested.value.

Подсказка:

Используй nextTick

Решение:

import { mount } from '@vue/test-utils';
import { expect, describe, it } from 'vitest';
import watch from '@/components/watch.vue';

describe('watch.vue', () => {
    it('Проверь, что отображается сообщение об ошибке.', async () => {
        const wrapper = mount(drag);

        wrapper.vm.obj.nested.value = '3';

        await wrapper.vm.$nextTick();
        
        expect(wrapper.text()).toBe('Changed to 3');
    });
})


25: Тестирование компонента с Teleport

Компонент:

<template>
  <Teleport to="#modal">
    <div>Modal content</div>
  </Teleport>
</template>

//App.vue
<script setup lang="ts">

import {ref} from 'vue';
import Tele from '@/components/Teleport.vue';

</script>

<template>
  <div id="modal"></div>
  <div>
    <Tele />
    
  </div>
  
  
</template>

Задание:

  • Проверь, что содержимое попало в нужный контейнер.

Подсказка:

Создай div с id="modal", передай в attachTo.

import { mount } from '@vue/test-utils';
import { expect, describe, it } from 'vitest';
import Tele from '@/components/Teleport.vue';

describe('Teleport.vue', () => {
    it('Проверь, что содержимое попало в нужный контейнер.', async () => {
        document.body.innerHTML = '<div id="modal"></div>';
       
        const wrapper = mount(Tele, { attachTo: document.body });
        console.log(document.body.innerHTML); //<div id="modal"><div>Modal content</div></div><div data-v-app=""><!--teleport start--><!--teleport end--></div>
        expect(document.querySelector('#modal div').textContent).toBe('Modal content');
    });
});


26: Тестирование с асинхронным компонентом

Компонент:

<template>
    <Suspense>
        <AsyncComponent />
    </Suspense>
</template>

<script setup>
import { defineAsyncComponent } from 'vue';

const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'));
</script>

//AsyncComponent.vue 
<script setup>
    import {ref} from 'vue';
    const text = ref('hello')
</script>
<template>
    {{ text }}
</template>

Задание:

  • Проверь, что компонент загружается.

Подсказка:

Мокай импорт через Stub и дождись всех промисов через flushPromises.

import { mount, flushPromises } from '@vue/test-utils';
import { describe, it, expect, vi } from 'vitest';
import Acomponent from '@/components/Acomponent.vue';

describe('Acomponent.vue', () => {
    it('Проверь, что компонент загружается', async () => {
        const wrapper = mount(Acomponent, {
            global: {
                stubs: {
                    // Автоматически создаст стаб для компонента
                    AsyncComponent: {
                        template: '<div>Stubbed async component</div>'
                    }
                }
            }
        });

        await flushPromises();

        expect(wrapper.text()).toBe('Stubbed async component');
    });
});


27: Тестирование с использованием describe и beforeEach

Компонент:

<template>
  <div>{{ value }}</div>
</template>

<script setup>
defineProps(['value']);
</script>

Задание:

  • Напиши несколько тестов с общими настройками.

Подсказка:

Используй beforeEach для подготовки wrapper.

import { mount, flushPromises } from '@vue/test-utils';
import { describe, it, expect, vi } from 'vitest';
import Desc from '@/components/describe.vue';

describe('describe.vue', () => {
    let wrapper;
    beforeEach(() => {
        wrapper = mount(Desc, { props: { value: 'default' } });
    });

    it('значение по умолчанию', () => {
        expect(wrapper.text()).toBe('default')
    });
    it('меняем значение по умолчанию', async () => {
        await wrapper.setProps({ value: 'ttest2' });
        expect(wrapper.text()).toBe('ttest2');
    })
})