Witaj na Platformie Startowej NOVA LAB! Rok 2087 - przed wysłaniem systemów na Marsa, każdy kod musi przejść rygorystyczne testy. Podobnie jak systemy rakietowe są sprawdzane przed startem, tak testujemy nasze aplikacje Vue przed wdrożeniem.
Vitest to nowoczesny framework testowy dla Vue:
1npm install -D vitest @vue/test-utils happy-dom1// vitest.config.js
2import { defineConfig } from 'vitest/config'
3import vue from '@vitejs/plugin-vue'
4
5export default defineConfig({
6 plugins: [vue()],
7 test: {
8 globals: true,
9 environment: 'happy-dom'
10 }
11})1// package.json
2{
3 "scripts": {
4 "test": "vitest",
5 "test:ui": "vitest --ui",
6 "test:coverage": "vitest --coverage"
7 }
8}1// components/Counter.spec.js
2import { describe, it, expect } from 'vitest'
3import { mount } from '@vue/test-utils'
4import Counter from './Counter.vue'
5
6describe('Counter', () => {
7 it('renders count', () => {
8 const wrapper = mount(Counter)
9 expect(wrapper.text()).toContain('Count: 0')
10 })
11
12 it('increments count on button click', async () => {
13 const wrapper = mount(Counter)
14
15 await wrapper.find('button.increment').trigger('click')
16
17 expect(wrapper.text()).toContain('Count: 1')
18 })
19
20 it('accepts initial count prop', () => {
21 const wrapper = mount(Counter, {
22 props: {
23 initialCount: 10
24 }
25 })
26
27 expect(wrapper.text()).toContain('Count: 10')
28 })
29})1<!-- Counter.vue -->
2<template>
3 <div class="counter">
4 <p>Count: {{ count }}</p>
5 <button class="increment" @click="count++">+</button>
6 <button class="decrement" @click="count--">-</button>
7 </div>
8</template>
9
10<script setup>
11import { ref } from 'vue'
12
13const props = defineProps({
14 initialCount: {
15 type: Number,
16 default: 0
17 }
18})
19
20const count = ref(props.initialCount)
21</script>1import { mount } from '@vue/test-utils'
2import ArtworkCard from './ArtworkCard.vue'
3
4describe('ArtworkCard', () => {
5 it('emits select event on click', async () => {
6 const artwork = {
7 id: 1,
8 title: 'Mona Lisa',
9 artist: 'Leonardo'
10 }
11
12 const wrapper = mount(ArtworkCard, {
13 props: { artwork }
14 })
15
16 await wrapper.trigger('click')
17
18 expect(wrapper.emitted()).toHaveProperty('select')
19 expect(wrapper.emitted('select')[0]).toEqual([artwork])
20 })
21
22 it('emits favorite event on heart click', async () => {
23 const wrapper = mount(ArtworkCard, {
24 props: {
25 artwork: { id: 1, title: 'Test' }
26 }
27 })
28
29 await wrapper.find('.favorite-btn').trigger('click')
30
31 expect(wrapper.emitted('favorite')).toBeTruthy()
32 })
33})1import { mount } from '@vue/test-utils'
2import BaseCard from './BaseCard.vue'
3
4describe('BaseCard', () => {
5 it('renders default slot', () => {
6 const wrapper = mount(BaseCard, {
7 slots: {
8 default: '<p>Test content</p>'
9 }
10 })
11
12 expect(wrapper.html()).toContain('Test content')
13 })
14
15 it('renders named slots', () => {
16 const wrapper = mount(BaseCard, {
17 slots: {
18 header: '<h2>Header</h2>',
19 default: '<p>Body</p>',
20 footer: '<footer>Footer</footer>'
21 }
22 })
23
24 expect(wrapper.find('h2').text()).toBe('Header')
25 expect(wrapper.find('p').text()).toBe('Body')
26 expect(wrapper.find('footer').text()).toBe('Footer')
27 })
28})