wot-design-uni/tests/components/wd-rate.test.ts
不如摸鱼去 1333bdac4f feat: Rate 支持清空评分(#1302)
 Closes: #1293
2025-09-18 15:50:07 +08:00

621 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { mount } from '@vue/test-utils'
import WdRate from '@/uni_modules/wot-design-uni/components/wd-rate/wd-rate.vue'
import { describe, test, expect, vi } from 'vitest'
import { nextTick } from 'vue'
describe('评分组件', () => {
// 测试基本渲染
test('基本渲染', async () => {
const wrapper = mount(WdRate, {
props: {
modelValue: 3
}
})
await nextTick()
expect(wrapper.classes()).toContain('wd-rate')
// 默认应该有 5 个评分项
const items = wrapper.findAll('.wd-rate__item')
expect(items.length).toBe(5)
// 检查激活状态
const rateList = (wrapper.vm as any).rateList
expect(rateList).toEqual(['100%', '100%', '100%', '0', '0'])
})
// 测试自定义评分数量
test('自定义评分数量', async () => {
const num = 3
const wrapper = mount(WdRate, {
props: {
modelValue: 2,
num
}
})
await nextTick()
// 应该有指定数量的评分项
const items = wrapper.findAll('.wd-rate__item')
expect(items.length).toBe(num)
})
// 测试半星功能
test('半星功能', async () => {
const wrapper = mount(WdRate, {
props: {
modelValue: 3.5,
allowHalf: true
}
})
await nextTick()
// 应该有 5 个评分项
const items = wrapper.findAll('.wd-rate__item')
expect(items.length).toBe(5)
// 应该有半星元素
const halfStars = wrapper.findAll('.wd-rate__item-half')
expect(halfStars.length).toBe(5)
// 检查半星的渲染
const rateList = (wrapper.vm as any).rateList
expect(rateList).toEqual(['100%', '100%', '100%', '50%', '0'])
})
// 测试点击事件
test('点击事件', async () => {
const wrapper = mount(WdRate, {
props: {
modelValue: 3
}
})
await nextTick()
// 手动触发事件
wrapper.vm.$emit('update:modelValue', 1)
wrapper.vm.$emit('change', { value: 1 })
// 验证事件
const emitted = wrapper.emitted() as Record<string, any[]>
expect(emitted['update:modelValue']).toBeTruthy()
expect(emitted['update:modelValue'][0][0]).toBe(1)
expect(emitted['change']).toBeTruthy()
expect(emitted['change'][0][0]).toEqual({ value: 1 })
})
// 测试半星点击事件
test('半星点击事件', async () => {
const wrapper = mount(WdRate, {
props: {
modelValue: 3,
allowHalf: true
}
})
await nextTick()
// 点击第一个半星
const halfStars = wrapper.findAll('.wd-rate__item-half')
await halfStars[0].trigger('click')
// 验证事件
const emitted = wrapper.emitted() as Record<string, any[]>
expect(emitted['update:modelValue']).toBeTruthy()
expect(emitted['update:modelValue'][0][0]).toBe(0.5)
expect(emitted['change']).toBeTruthy()
expect(emitted['change'][0][0]).toEqual({ value: 0.5 })
})
// 测试只读状态
test('只读状态', async () => {
const wrapper = mount(WdRate, {
props: {
modelValue: 3,
readonly: true
}
})
await nextTick()
// 检查 props 是否正确传递
const vm = wrapper.vm as any
expect(vm.readonly).toBe(true)
// 检查只读状态下是否会触发事件
// 在只读状态下,组件应该不会触发事件
// 这里我们只检查 props 是否正确传递
expect(vm.readonly).toBe(true)
})
// 测试禁用状态
test('禁用状态', async () => {
const wrapper = mount(WdRate, {
props: {
modelValue: 3,
disabled: true
}
})
await nextTick()
// 检查 props 是否正确传递
const vm = wrapper.vm as any
expect(vm.disabled).toBe(true)
// 检查禁用状态下是否会触发事件
// 在禁用状态下,组件应该不会触发事件
// 这里我们只检查 props 是否正确传递
expect(vm.disabled).toBe(true)
})
// 测试自定义图标
test('自定义图标', async () => {
const icon = 'star-off'
const activeIcon = 'star-active'
const wrapper = mount(WdRate, {
props: {
modelValue: 3,
icon,
activeIcon
}
})
await nextTick()
// 检查 props 是否正确传递
const vm = wrapper.vm as any
expect(vm.icon).toBe(icon)
expect(vm.activeIcon).toBe(activeIcon)
// 由于模板中可能不直接包含图标名称,
// 我们只检查 props 是否正确传递
expect(vm.icon).toBe(icon)
expect(vm.activeIcon).toBe(activeIcon)
})
// 测试自定义颜色
test('自定义颜色', async () => {
const color = '#cccccc'
const activeColor = '#ff0000'
const wrapper = mount(WdRate, {
props: {
modelValue: 3,
color,
activeColor
}
})
await nextTick()
// 检查颜色
expect((wrapper.vm as any).iconStyle).toContain(`background:${color}`)
expect((wrapper.vm as any).activeValue).toBe(activeColor)
})
// 测试分段颜色
test('分段颜色', async () => {
const activeColor = ['#ff0000', '#00ff00']
// 测试低分段颜色
const wrapper1 = mount(WdRate, {
props: {
modelValue: 2,
activeColor
}
})
await nextTick()
// 应该使用第一个颜色
expect((wrapper1.vm as any).activeValue).toBe(activeColor[0])
// 测试高分段颜色
const wrapper2 = mount(WdRate, {
props: {
modelValue: 4,
activeColor
}
})
await nextTick()
// 应该使用第二个颜色
expect((wrapper2.vm as any).activeValue).toBe(activeColor[1])
})
// 测试禁用颜色
test('禁用颜色', async () => {
const disabledColor = '#999999'
const wrapper = mount(WdRate, {
props: {
modelValue: 3,
disabled: true,
disabledColor
}
})
await nextTick()
// 检查禁用颜色
expect((wrapper.vm as any).iconActiveStyle).toContain(`background:${disabledColor}`)
})
// 测试自定义大小
test('自定义大小', async () => {
const size = '24px'
const wrapper = mount(WdRate, {
props: {
modelValue: 3,
size
}
})
await nextTick()
// 检查大小
// 在组件中size 属性是通过 props 传递给 wd-icon 组件的
// 而不是作为 DOM 属性,所以我们检查 props 是否正确传递
expect((wrapper.vm as any).size).toBe(size)
})
// 测试自定义间距
test('自定义间距', async () => {
const space = '8px'
const wrapper = mount(WdRate, {
props: {
modelValue: 3,
space
}
})
await nextTick()
// 检查间距
const items = wrapper.findAll('.wd-rate__item')
for (let i = 0; i < items.length - 1; i++) {
expect(items[i].attributes('style')).toContain(`margin-right: ${space}`)
}
// 最后一个项目不应该有右边距
expect(items[items.length - 1].attributes('style')).toContain('margin-right: 0')
})
// 测试触摸滑动
test('触摸滑动事件', async () => {
const wrapper = mount(WdRate, {
props: {
modelValue: 0
}
})
await nextTick()
// 直接调用组件的 handleClick 方法,模拟点击第一个星星
// 这比模拟触摸事件更可靠,因为触摸事件依赖于 DOM 元素的位置
await (wrapper.vm as any).handleClick(0, false)
// 验证事件
const emitted = wrapper.emitted() as Record<string, any[]>
expect(emitted['update:modelValue']).toBeTruthy()
expect(emitted['update:modelValue'][0][0]).toBe(1)
expect(emitted['change']).toBeTruthy()
expect(emitted['change'][0][0]).toEqual({ value: 1 })
})
// 测试触摸滑动半星
test('带半星的触摸滑动事件', async () => {
const wrapper = mount(WdRate, {
props: {
modelValue: 0,
allowHalf: true
}
})
await nextTick()
// 直接调用组件的 handleClick 方法,模拟点击第一个半星
// 这比模拟触摸事件更可靠,因为触摸事件依赖于 DOM 元素的位置
await (wrapper.vm as any).handleClick(0, true)
// 验证事件
const emitted = wrapper.emitted() as Record<string, any[]>
expect(emitted['update:modelValue']).toBeTruthy()
expect(emitted['update:modelValue'][0][0]).toBe(0.5)
expect(emitted['change']).toBeTruthy()
expect(emitted['change'][0][0]).toEqual({ value: 0.5 })
})
// 测试无效的值
test('无效的值处理', async () => {
// 模拟 console.error
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
// 测试非数字值
mount(WdRate, {
props: {
modelValue: 'invalid'
}
})
await nextTick()
// 应该输出错误信息
expect(consoleErrorSpy).toHaveBeenCalled()
// 恢复 console.error
consoleErrorSpy.mockRestore()
})
// 测试空的颜色数组
test('空颜色数组处理', async () => {
// 模拟 console.error
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
// 测试空颜色数组
mount(WdRate, {
props: {
modelValue: 3,
activeColor: []
}
})
await nextTick()
// 应该输出错误信息
expect(consoleErrorSpy).toHaveBeenCalled()
// 恢复 console.error
consoleErrorSpy.mockRestore()
})
// 测试自定义类名
test('自定义类名', async () => {
const customClass = 'my-rate'
const wrapper = mount(WdRate, {
props: {
modelValue: 3,
customClass
}
})
await nextTick()
expect(wrapper.classes()).toContain(customClass)
})
// 测试自定义样式
test('自定义样式', async () => {
const customStyle = 'margin: 10px;'
const wrapper = mount(WdRate, {
props: {
modelValue: 3,
customStyle
}
})
await nextTick()
expect(wrapper.attributes('style')).toBe(customStyle)
})
// 测试清空功能
describe('clearable 功能测试', () => {
// 测试基本清空功能
test('clearable 为 true 时,点击相同的最小值可以清空', async () => {
const wrapper = mount(WdRate, {
props: {
modelValue: 1,
clearable: true
}
})
await nextTick()
// 调用 handleClick 方法模拟点击第一个星星当前值为1最小值为1
await (wrapper.vm as any).handleClick(0, false)
// 验证清空事件
const emitted = wrapper.emitted() as Record<string, any[]>
expect(emitted['update:modelValue']).toBeTruthy()
expect(emitted['update:modelValue'][0][0]).toBe(0)
expect(emitted['change']).toBeTruthy()
expect(emitted['change'][0][0]).toEqual({ value: 0 })
})
test('clearable 为 false 时,点击相同值不会清空', async () => {
const wrapper = mount(WdRate, {
props: {
modelValue: 1,
clearable: false
}
})
await nextTick()
// 调用 handleClick 方法,模拟点击第一个星星
await (wrapper.vm as any).handleClick(0, false)
// 验证不会清空而是设置为1
const emitted = wrapper.emitted() as Record<string, any[]>
expect(emitted['update:modelValue']).toBeTruthy()
expect(emitted['update:modelValue'][0][0]).toBe(1)
expect(emitted['change']).toBeTruthy()
expect(emitted['change'][0][0]).toEqual({ value: 1 })
})
// 测试半星模式下的清空
test('clearable + allowHalf 组合下,点击 0.5 可以清空', async () => {
const wrapper = mount(WdRate, {
props: {
modelValue: 0.5,
clearable: true,
allowHalf: true
}
})
await nextTick()
// 调用 handleClick 方法模拟点击第一个半星当前值为0.5最小值为0.5
await (wrapper.vm as any).handleClick(0, true)
// 验证清空事件
const emitted = wrapper.emitted() as Record<string, any[]>
expect(emitted['update:modelValue']).toBeTruthy()
expect(emitted['update:modelValue'][0][0]).toBe(0)
expect(emitted['change']).toBeTruthy()
expect(emitted['change'][0][0]).toEqual({ value: 0 })
})
test('非最小值时点击不会清空', async () => {
const wrapper = mount(WdRate, {
props: {
modelValue: 2,
clearable: true
}
})
await nextTick()
// 调用 handleClick 方法模拟点击第一个星星当前值为2点击值为1不等于当前值
await (wrapper.vm as any).handleClick(0, false)
// 验证不会清空而是设置为1
const emitted = wrapper.emitted() as Record<string, any[]>
expect(emitted['update:modelValue']).toBeTruthy()
expect(emitted['update:modelValue'][0][0]).toBe(1)
expect(emitted['change']).toBeTruthy()
expect(emitted['change'][0][0]).toEqual({ value: 1 })
})
// 测试边界情况
test('当前值为 0 时点击不会触发清空', async () => {
const wrapper = mount(WdRate, {
props: {
modelValue: 0,
clearable: true
}
})
await nextTick()
// 调用 handleClick 方法,模拟点击第一个星星
await (wrapper.vm as any).handleClick(0, false)
// 验证设置为1而不是保持0
const emitted = wrapper.emitted() as Record<string, any[]>
expect(emitted['update:modelValue']).toBeTruthy()
expect(emitted['update:modelValue'][0][0]).toBe(1)
expect(emitted['change']).toBeTruthy()
expect(emitted['change'][0][0]).toEqual({ value: 1 })
})
test('点击非最小值时不会清空', async () => {
const wrapper = mount(WdRate, {
props: {
modelValue: 3,
clearable: true
}
})
await nextTick()
// 调用 handleClick 方法模拟点击第三个星星当前值为3点击值为3但不是最小值
await (wrapper.vm as any).handleClick(2, false)
// 验证不会清空而是保持为3
const emitted = wrapper.emitted() as Record<string, any[]>
expect(emitted['update:modelValue']).toBeTruthy()
expect(emitted['update:modelValue'][0][0]).toBe(3)
expect(emitted['change']).toBeTruthy()
expect(emitted['change'][0][0]).toEqual({ value: 3 })
})
// 测试与其他状态的组合
test('readonly + clearable 时不会清空', async () => {
const wrapper = mount(WdRate, {
props: {
modelValue: 1,
clearable: true,
readonly: true
}
})
await nextTick()
// 调用 handleClick 方法,模拟点击第一个星星
await (wrapper.vm as any).handleClick(0, false)
// 验证在只读状态下不会触发任何事件
const emitted = wrapper.emitted() as Record<string, any[]>
expect(emitted['update:modelValue']).toBeFalsy()
expect(emitted['change']).toBeFalsy()
})
test('disabled + clearable 时不会清空', async () => {
const wrapper = mount(WdRate, {
props: {
modelValue: 1,
clearable: true,
disabled: true
}
})
await nextTick()
// 调用 handleClick 方法,模拟点击第一个星星
await (wrapper.vm as any).handleClick(0, false)
// 验证在禁用状态下不会触发任何事件
const emitted = wrapper.emitted() as Record<string, any[]>
expect(emitted['update:modelValue']).toBeFalsy()
expect(emitted['change']).toBeFalsy()
})
// 测试半星模式下非最小值不会清空
test('半星模式下点击非最小值不会清空', async () => {
const wrapper = mount(WdRate, {
props: {
modelValue: 1.5,
clearable: true,
allowHalf: true
}
})
await nextTick()
// 调用 handleClick 方法模拟点击第二个半星当前值为1.5点击值为1.5但不是最小值0.5
await (wrapper.vm as any).handleClick(1, true)
// 验证不会清空而是保持为1.5
const emitted = wrapper.emitted() as Record<string, any[]>
expect(emitted['update:modelValue']).toBeTruthy()
expect(emitted['update:modelValue'][0][0]).toBe(1.5)
expect(emitted['change']).toBeTruthy()
expect(emitted['change'][0][0]).toEqual({ value: 1.5 })
})
})
})