Skip to content

Commit 2f6ff7d

Browse files
author
root
committed
7.11. Задание: написание тестов для проекта Vue-Pizza
Реализованы тесты для: 1. Утилиты (helpers): - Тесты для image.js (getImageUrl, getUserAvatar, getIngredientImage, getPizzaImage) - Проверка корректности формирования путей к изображениям - Тесты обработки edge cases (пустые значения, разные форматы URL) 2. Store (Pinia): - Тесты для pizza store - Проверка инициализации состояния - Тесты действий (selectDough, selectSize, selectSauce, addIngredient, removeIngredient) - Тесты геттеров (hasMinimalComponents, getIngredientQuantity) - Тест сброса конфигурации пиццы 3. Компоненты: - Тесты для DoughSelector (рендеринг, выбор, события) - Тесты для AppButton (props, disabled, loading, события) - Проверка корректности эмита событий - Тесты применения CSS классов
1 parent 4e45ae1 commit 2f6ff7d

File tree

4 files changed

+454
-0
lines changed

4 files changed

+454
-0
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { describe, it, expect } from 'vitest'
2+
import { mount } from '@vue/test-utils'
3+
import AppButton from '../AppButton.vue'
4+
5+
describe('AppButton', () => {
6+
it('должен рендериться с текстом из слота', () => {
7+
const wrapper = mount(AppButton, {
8+
slots: {
9+
default: 'Нажми меня'
10+
}
11+
})
12+
13+
expect(wrapper.text()).toBe('Нажми меня')
14+
})
15+
16+
it('должен иметь тип button по умолчанию', () => {
17+
const wrapper = mount(AppButton)
18+
19+
expect(wrapper.find('button').attributes('type')).toBe('button')
20+
})
21+
22+
it('должен применять переданный тип', () => {
23+
const wrapper = mount(AppButton, {
24+
props: {
25+
type: 'submit'
26+
}
27+
})
28+
29+
expect(wrapper.find('button').attributes('type')).toBe('submit')
30+
})
31+
32+
it('должен применять CSS класс для размера', () => {
33+
const wrapper = mount(AppButton, {
34+
props: {
35+
size: 'large'
36+
}
37+
})
38+
39+
expect(wrapper.find('.app-button--large').exists()).toBe(true)
40+
})
41+
42+
it('должен применять CSS класс для варианта', () => {
43+
const wrapper = mount(AppButton, {
44+
props: {
45+
variant: 'secondary'
46+
}
47+
})
48+
49+
expect(wrapper.find('.app-button--secondary').exists()).toBe(true)
50+
})
51+
52+
it('должен быть отключен когда disabled=true', () => {
53+
const wrapper = mount(AppButton, {
54+
props: {
55+
disabled: true
56+
}
57+
})
58+
59+
expect(wrapper.find('button').element.disabled).toBe(true)
60+
expect(wrapper.find('.app-button--disabled').exists()).toBe(true)
61+
})
62+
63+
it('должен эмитить событие click при клике', async () => {
64+
const wrapper = mount(AppButton)
65+
66+
await wrapper.find('button').trigger('click')
67+
68+
expect(wrapper.emitted()).toHaveProperty('click')
69+
expect(wrapper.emitted().click).toHaveLength(1)
70+
})
71+
72+
it('не должен эмитить событие click когда disabled', async () => {
73+
const wrapper = mount(AppButton, {
74+
props: {
75+
disabled: true
76+
}
77+
})
78+
79+
await wrapper.find('button').trigger('click')
80+
81+
expect(wrapper.emitted().click).toBeUndefined()
82+
})
83+
84+
it('должен показывать спиннер в состоянии загрузки', () => {
85+
const wrapper = mount(AppButton, {
86+
props: {
87+
loading: true
88+
},
89+
slots: {
90+
default: 'Загрузка'
91+
}
92+
})
93+
94+
expect(wrapper.find('.app-button__spinner').exists()).toBe(true)
95+
expect(wrapper.text()).not.toBe('Загрузка')
96+
})
97+
98+
it('должен быть отключен в состоянии загрузки', () => {
99+
const wrapper = mount(AppButton, {
100+
props: {
101+
loading: true
102+
}
103+
})
104+
105+
expect(wrapper.find('button').element.disabled).toBe(true)
106+
})
107+
108+
it('не должен эмитить событие click в состоянии загрузки', async () => {
109+
const wrapper = mount(AppButton, {
110+
props: {
111+
loading: true
112+
}
113+
})
114+
115+
await wrapper.find('button').trigger('click')
116+
117+
expect(wrapper.emitted().click).toBeUndefined()
118+
})
119+
})
120+
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { describe, it, expect } from 'vitest'
2+
import { mount } from '@vue/test-utils'
3+
import DoughSelector from '../DoughSelector.vue'
4+
5+
describe('DoughSelector', () => {
6+
const mockDoughType = {
7+
id: 1,
8+
name: 'Тонкое',
9+
description: 'Из твердых сортов пшеницы',
10+
price: 300
11+
}
12+
13+
it('должен рендериться с корректными данными', () => {
14+
const wrapper = mount(DoughSelector, {
15+
props: {
16+
doughType: mockDoughType,
17+
selectedId: null
18+
}
19+
})
20+
21+
expect(wrapper.find('b').text()).toBe('Тонкое')
22+
expect(wrapper.find('span').text()).toBe('Из твердых сортов пшеницы')
23+
})
24+
25+
it('должен отображать выбранное состояние', () => {
26+
const wrapper = mount(DoughSelector, {
27+
props: {
28+
doughType: mockDoughType,
29+
selectedId: 1
30+
}
31+
})
32+
33+
const input = wrapper.find('input[type="radio"]')
34+
expect(input.element.checked).toBe(true)
35+
})
36+
37+
it('должен отображать невыбранное состояние', () => {
38+
const wrapper = mount(DoughSelector, {
39+
props: {
40+
doughType: mockDoughType,
41+
selectedId: 2
42+
}
43+
})
44+
45+
const input = wrapper.find('input[type="radio"]')
46+
expect(input.element.checked).toBe(false)
47+
})
48+
49+
it('должен эмитить событие change при клике', async () => {
50+
const wrapper = mount(DoughSelector, {
51+
props: {
52+
doughType: mockDoughType,
53+
selectedId: null
54+
}
55+
})
56+
57+
const input = wrapper.find('input[type="radio"]')
58+
await input.trigger('change')
59+
60+
expect(wrapper.emitted()).toHaveProperty('change')
61+
expect(wrapper.emitted().change[0]).toEqual([mockDoughType])
62+
})
63+
64+
it('должен эмитить событие update:selectedId при клике', async () => {
65+
const wrapper = mount(DoughSelector, {
66+
props: {
67+
doughType: mockDoughType,
68+
selectedId: null
69+
}
70+
})
71+
72+
const input = wrapper.find('input[type="radio"]')
73+
await input.trigger('change')
74+
75+
expect(wrapper.emitted()).toHaveProperty('update:selectedId')
76+
expect(wrapper.emitted()['update:selectedId'][0]).toEqual([1])
77+
})
78+
79+
it('должен применять правильный CSS класс для тонкого теста', () => {
80+
const wrapper = mount(DoughSelector, {
81+
props: {
82+
doughType: { ...mockDoughType, id: 1 },
83+
selectedId: null
84+
}
85+
})
86+
87+
expect(wrapper.find('.dough-selector--light').exists()).toBe(true)
88+
})
89+
90+
it('должен применять правильный CSS класс для толстого теста', () => {
91+
const wrapper = mount(DoughSelector, {
92+
props: {
93+
doughType: { ...mockDoughType, id: 2, name: 'Толстое' },
94+
selectedId: null
95+
}
96+
})
97+
98+
expect(wrapper.find('.dough-selector--large').exists()).toBe(true)
99+
})
100+
})
101+
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { describe, it, expect } from 'vitest'
2+
import { getImageUrl, getUserAvatar, getIngredientImage, getPizzaImage } from '../image'
3+
4+
describe('Image Helpers', () => {
5+
describe('getImageUrl', () => {
6+
it('должен добавлять слеш в начало если его нет', () => {
7+
const result = getImageUrl('test.jpg')
8+
expect(result).toBe('/test.jpg')
9+
})
10+
11+
it('должен возвращать путь как есть если слеш уже есть', () => {
12+
const result = getImageUrl('/img/test.jpg')
13+
expect(result).toBe('/img/test.jpg')
14+
})
15+
16+
it('должен заменять /public/ на /', () => {
17+
const result = getImageUrl('/public/img/test.jpg')
18+
expect(result).toBe('/img/test.jpg')
19+
})
20+
21+
it('должен возвращать URL как есть если это http/https', () => {
22+
const result = getImageUrl('https://example.com/image.jpg')
23+
expect(result).toBe('https://example.com/image.jpg')
24+
})
25+
26+
it('должен возвращать пустую строку для пустого пути', () => {
27+
const result = getImageUrl('')
28+
expect(result).toBe('')
29+
})
30+
})
31+
32+
describe('getUserAvatar', () => {
33+
it('должен возвращать путь к аватару через getImageUrl', () => {
34+
const result = getUserAvatar('/img/users/user1.jpg')
35+
expect(result).toBe('/img/users/user1.jpg')
36+
})
37+
38+
it('должен возвращать дефолтный аватар если путь не указан', () => {
39+
const result = getUserAvatar('')
40+
expect(result).toBe('/img/users/default-avatar.svg')
41+
})
42+
})
43+
44+
describe('getIngredientImage', () => {
45+
it('должен возвращать путь к изображению ингредиента', () => {
46+
const result = getIngredientImage('/img/filling/tomatoes.svg')
47+
expect(result).toBe('/img/filling/tomatoes.svg')
48+
})
49+
50+
it('должен возвращать пустую строку для пустого пути', () => {
51+
const result = getIngredientImage('')
52+
expect(result).toBe('')
53+
})
54+
})
55+
56+
describe('getPizzaImage', () => {
57+
it('должен возвращать путь к изображению пиццы', () => {
58+
const result = getPizzaImage('/img/pizzas/pepperoni.png')
59+
expect(result).toBe('/img/pizzas/pepperoni.png')
60+
})
61+
62+
it('должен возвращать дефолтное изображение для пустого пути', () => {
63+
const result = getPizzaImage('')
64+
expect(result).toBe('/img/product.svg')
65+
})
66+
})
67+
})
68+

0 commit comments

Comments
 (0)