index.test.js
12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
/**
* 搜索页面测试套件
* @description 测试搜索功能的各种场景,包括输入、返回、状态切换等
* @date 2026-01-31
*/
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import SearchPage from './index.vue'
// Mock Taro
vi.mock('@tarojs/taro', () => ({
default: {
showToast: vi.fn()
}
}))
// Mock hooks
vi.mock('@/hooks/useGo', () => ({
useGo: () => vi.fn()
}))
describe('搜索页面测试', () => {
let wrapper
beforeEach(() => {
wrapper = mount(SearchPage, {
global: {
components: {
NavHeader: { template: '<div>NavHeader</div>' },
IconFont: { template: '<div>IconFont</div>' },
SearchBar: {
template: '<input :value="modelValue" @input="$emit(\'update:modelValue\', $event.target.value)" @search="$emit(\'search\')" @clear="$emit(\'clear\')" @blur="$emit(\'blur\')" />',
props: ['modelValue'],
emits: ['update:modelValue', 'search', 'clear', 'blur']
}
}
}
})
})
describe('初始化状态测试', () => {
it('应该正确初始化 mock 数据', () => {
// 验证是否生成了 100 条数据(50 产品 + 50 资料)
expect(wrapper.vm.allData.length).toBe(100)
})
it('应该正确初始化分类数据', () => {
// 验证 3 个分类(全部、产品、资料)
expect(wrapper.vm.tabsData.length).toBe(3)
expect(wrapper.vm.tabsData[0].name).toBe('全部')
expect(wrapper.vm.tabsData[1].name).toBe('产品')
expect(wrapper.vm.tabsData[2].name).toBe('资料')
})
it('应该默认选中"全部"分类', () => {
expect(wrapper.vm.activeTabId).toBe('')
})
it('初始状态 shouldSearched 应该为 false', () => {
expect(wrapper.vm.hasSearched).toBe(false)
})
it('初始状态搜索结果应该为空数组', () => {
expect(wrapper.vm.searchResults.length).toBe(0)
})
})
describe('搜索功能测试', () => {
it('输入搜索关键词后,hasSearched 应该变为 true', async () => {
wrapper.vm.searchKeyword = '保险'
await wrapper.vm.$nextTick()
await wrapper.vm.handleSearch()
expect(wrapper.vm.hasSearched).toBe(true)
expect(wrapper.vm.searchKeyword).toBe('保险')
})
it('应该能搜索到包含关键词的产品', async () => {
wrapper.vm.searchKeyword = '保险'
await wrapper.vm.$nextTick()
await wrapper.vm.handleSearch()
const results = wrapper.vm.searchResults
expect(results.length).toBeGreaterThan(0)
expect(results[0].title.toLowerCase()).toContain('保险')
})
it('应该能搜索到包含关键词的资料', async () => {
wrapper.vm.searchKeyword = '培训'
await wrapper.vm.$nextTick()
await wrapper.vm.handleSearch()
const results = wrapper.vm.searchResults
expect(results.length).toBeGreaterThan(0)
expect(results[0].title.toLowerCase()).toContain('培训')
})
it('搜索不存在的关键词应该返回空结果', async () => {
wrapper.vm.searchKeyword = '不存在的内容xyz123'
await wrapper.vm.$nextTick()
await wrapper.vm.handleSearch()
expect(wrapper.vm.hasSearched).toBe(true)
expect(wrapper.vm.searchResults.length).toBe(0)
})
it('清空搜索关键词后 hasSearched 应该保持为 true', async () => {
// 先执行搜索
wrapper.vm.searchKeyword = '保险'
await wrapper.vm.$nextTick()
await wrapper.vm.handleSearch()
expect(wrapper.vm.hasSearched).toBe(true)
// 清空搜索
await wrapper.vm.clearSearch()
expect(wrapper.vm.searchKeyword).toBe('')
// ✅ 修复后:hasSearched 应该保持 true
expect(wrapper.vm.hasSearched).toBe(true)
})
})
describe('分类切换测试', () => {
beforeEach(async () => {
// 先执行一次搜索
wrapper.vm.searchKeyword = '保险'
await wrapper.vm.handleSearch()
})
it('切换到"产品"分类应该只显示产品', async () => {
await wrapper.vm.onTabClick('product')
expect(wrapper.vm.activeTabId).toBe('product')
const results = wrapper.vm.searchResults
results.forEach(item => {
expect(item.category).toBe('product')
})
})
it('切换到"资料"分类应该只显示资料', async () => {
await wrapper.vm.onTabClick('material')
expect(wrapper.vm.activeTabId).toBe('material')
const results = wrapper.vm.searchResults
results.forEach(item => {
expect(item.category).toBe('material')
})
})
it('切换到"全部"分类应该显示所有结果', async () => {
await wrapper.vm.onTabClick('')
expect(wrapper.vm.activeTabId).toBe('')
const results = wrapper.vm.searchResults
// 验证包含所有分类
const hasProduct = results.some(item => item.category === 'product')
const hasMaterial = results.some(item => item.category === 'material')
expect(hasProduct).toBe(true)
expect(hasMaterial).toBe(true)
})
it('切换分类后 listRenderKey 应该递增', async () => {
const oldKey = wrapper.vm.listRenderKey
await wrapper.vm.onTabClick('product')
expect(wrapper.vm.listRenderKey).toBe(oldKey + 1)
})
})
describe('实时搜索测试(watch searchKeyword)', () => {
it('输入关键词应该自动触发搜索', async () => {
wrapper.vm.searchKeyword = '医疗'
// 等待 watch 触发
await wrapper.vm.$nextTick()
expect(wrapper.vm.hasSearched).toBe(true)
expect(wrapper.vm.searchResults.length).toBeGreaterThan(0)
})
it('清空关键词应该自动重置搜索状态', async () => {
// 先输入
wrapper.vm.searchKeyword = '保险'
await wrapper.vm.$nextTick()
expect(wrapper.vm.hasSearched).toBe(true)
// 清空
wrapper.vm.searchKeyword = ''
await wrapper.vm.$nextTick()
expect(wrapper.vm.hasSearched).toBe(false)
expect(wrapper.vm.searchResults.length).toBe(0)
})
})
describe('边界情况测试', () => {
it('输入纯空格不应该触发搜索', async () => {
wrapper.vm.searchKeyword = ' '
await wrapper.vm.handleSearch()
expect(wrapper.vm.hasSearched).toBe(false)
})
it('输入特殊字符应该正常搜索', async () => {
wrapper.vm.searchKeyword = '!@#$%'
await wrapper.vm.handleSearch()
expect(wrapper.vm.hasSearched).toBe(true)
// 应该返回空结果,但不应该报错
expect(wrapper.vm.searchResults.length).toBe(0)
})
it('输入超长关键词应该正常处理', async () => {
const longKeyword = 'a'.repeat(1000)
wrapper.vm.searchKeyword = longKeyword
await wrapper.vm.handleSearch()
expect(wrapper.vm.hasSearched).toBe(true)
// 应该返回空结果,但不应该报错
expect(wrapper.vm.searchResults.length).toBe(0)
})
it('快速切换多次分类不应该出错', async () => {
wrapper.vm.searchKeyword = '保险'
await wrapper.vm.handleSearch()
// 快速切换分类
await wrapper.vm.onTabClick('product')
await wrapper.vm.onTabClick('material')
await wrapper.vm.onTabClick('')
await wrapper.vm.onTabClick('product')
// 不应该抛出错误
expect(wrapper.vm.activeTabId).toBe('product')
})
it('快速输入和删除关键词应该正常处理', async () => {
// 快速输入
wrapper.vm.searchKeyword = '保'
await wrapper.vm.$nextTick()
wrapper.vm.searchKeyword = '保险'
await wrapper.vm.$nextTick()
wrapper.vm.searchKeyword = '保险产品'
await wrapper.vm.$nextTick()
// 快速删除
wrapper.vm.searchKeyword = '保险'
await wrapper.vm.$nextTick()
wrapper.vm.searchKeyword = ''
await wrapper.vm.$nextTick()
// 最终状态应该是重置的
expect(wrapper.vm.hasSearched).toBe(false)
expect(wrapper.vm.searchResults.length).toBe(0)
})
})
describe('✅ 修复后的问题测试', () => {
it('问题1修复: 清空关键词后,应该显示"暂无搜索结果"而不是"初始状态"', async () => {
// 1. 先搜索一个关键词(有结果)
wrapper.vm.searchKeyword = '保险'
await wrapper.vm.handleSearch()
console.log('搜索"保险"后:')
console.log('- hasSearched:', wrapper.vm.hasSearched)
console.log('- results.length:', wrapper.vm.searchResults.length)
expect(wrapper.vm.hasSearched).toBe(true)
expect(wrapper.vm.searchResults.length).toBeGreaterThan(0)
// 2. 清空关键词
wrapper.vm.searchKeyword = ''
await wrapper.vm.$nextTick()
console.log('清空关键词后:')
console.log('- hasSearched:', wrapper.vm.hasSearched)
console.log('- results.length:', wrapper.vm.searchResults.length)
console.log('- isInitialState:', wrapper.vm.isInitialState)
// ✅ 修复后:hasSearched 保持 true
expect(wrapper.vm.hasSearched).toBe(true)
expect(wrapper.vm.isInitialState).toBe(false)
expect(wrapper.vm.searchResults.length).toBe(0)
console.log('✅ FIXED: 用户看到"暂无搜索结果"而不是"初始状态"')
})
it('问题2: 切换分类时,如果当前没有关键词,不应该显示结果', async () => {
// 不输入任何关键词,直接切换分类
await wrapper.vm.onTabClick('product')
// hasSearched 应该保持 false
expect(wrapper.vm.hasSearched).toBe(false)
// 结果应该为空
expect(wrapper.vm.searchResults.length).toBe(0)
})
it('问题3: 搜索关键词后切换分类,结果应该正确更新', async () => {
// 1. 搜索"保险"
wrapper.vm.searchKeyword = '保险'
await wrapper.vm.handleSearch()
const allResults = wrapper.vm.searchResults.length
console.log('全部分类搜索"保险"结果数:', allResults)
// 2. 切换到"产品"分类
await wrapper.vm.onTabClick('product')
const productResults = wrapper.vm.searchResults.length
console.log('产品分类搜索"保险"结果数:', productResults)
// 3. 切换到"资料"分类
await wrapper.vm.onTabClick('material')
const materialResults = wrapper.vm.searchResults.length
console.log('资料分类搜索"保险"结果数:', materialResults)
// 验证结果正确性
expect(productResults + materialResults).toBe(allResults)
})
})
})
describe('搜索结果 computed 属性详细测试', () => {
let wrapper
beforeEach(() => {
wrapper = mount(SearchPage, {
global: {
components: {
NavHeader: { template: '<div>NavHeader</div>' },
IconFont: { template: '<div>IconFont</div>' },
SearchBar: { template: '<div>SearchBar</div>' }
}
}
})
})
it('当 hasSearched 为 false 时,应该返回空数组', () => {
wrapper.vm.hasSearched = false
wrapper.vm.searchKeyword = '保险'
expect(wrapper.vm.searchResults.length).toBe(0)
})
it('当 hasSearched 为 true 但关键词为空时,应该返回当前分类的所有数据', () => {
wrapper.vm.hasSearched = true
wrapper.vm.searchKeyword = ''
wrapper.vm.activeTabId = ''
// 应该返回所有 100 条数据
expect(wrapper.vm.searchResults.length).toBe(100)
})
it('当 hasSearched 为 true 且有关键词时,应该返回过滤后的结果', () => {
wrapper.vm.hasSearched = true
wrapper.vm.searchKeyword = '保险'
wrapper.vm.activeTabId = ''
const results = wrapper.vm.searchResults
expect(results.length).toBeGreaterThan(0)
// 验证所有结果都包含关键词
results.forEach(item => {
expect(item.title.toLowerCase()).toContain('保险')
})
})
it('搜索应该不区分大小写', () => {
wrapper.vm.hasSearched = true
wrapper.vm.searchKeyword = 'BAOXIAN'
wrapper.vm.activeTabId = ''
const results = wrapper.vm.searchResults
// 应该能搜索到包含"保险"的内容
expect(results.length).toBeGreaterThan(0)
})
})