Button 封装
vue
<template>
<button
ref="_ref"
class="h-button"
:class="{
[`h-button--${type}`]: type,
[`h-button--${size}`]: size,
'is-plain': plain,
'is-round': round,
'is-circle': circle,
'is-disabled': disabled,
'is-loading': loading
}"
:disabled="disabled || loading"
:autofocus="autofocus"
type="button"
>
<!-- 加载状态 -->
<Icon icon="spinner" spin v-if="loading" />
<!-- 图标 -->
<Icon :icon="icon" v-if="icon" />
<span>
<slot />
</span>
</button>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ButtonProps } from './types'
defineOptions({
name: 'HButton'
})
const _ref = ref<HTMLButtonElement>()
defineExpose({
ref: _ref
})
</script>
<style lang="less" scoped></style>
Button 类型定义
typescript
export type ButtonType = 'primary'| 'success'| 'warning'| 'danger'| 'info'
export type ButtonSize = 'large' | 'small'
export interface ButtonProps {
type?: ButtonType;
size?: ButtonSize;
plain?: boolean;
round?: boolean;
circle?: boolean;
disabled?: boolean;
autofocus?: boolean;
icon?: string;
loading?: boolean;
}
Button 入口文件
typescript
import type { App } from 'vue'
import Button from './Button.vue'
Button.install = (app: App) => {
app.component(Button.name, Button)
}
export default Button
export * from './types'
使用 Vitest 对 Button 进行单元测试
typescript
/**
* describe 是 vitest 中的一个函数,用于描述一组测试用例。
* test 是 vitest 中的一个函数,用于定义一个测试用例。
* expect 是 vitest 中的一个函数,用于断言测试结果。
* mount 是 vue-test-utils 中的一个函数,用于挂载组件。
*/
describe('Button.vue', () => {
// 测试一
test('test1', () => { /* -------- */})
// 测试一
test('test2', () => { /* -------- */})
})
基本功能测试
typescript
test('test1', () => {
// 生成一个 Button 组件的实例
const wrapper = mount(Button as any, {
props: {
type: 'primary'
},
// 组件的插槽
slots: {
// 插入文本 button test
default: 'button test'
}
})
// wrapper.classes() 获取组件的 class 列表;toContain 判断 class 列表中是否包含指定的 class
expect(wrapper.classes()).toContain('h-button--primary')
// wrapper.get('button') 获取组件中的 button 元素
// wrapper.get('button').text() 获取 button 元素的文本内容; toBe 判断文本内容是否等于指定的文本内容
expect(wrapper.get('button').text()).toBe('button test')
// trigger('click') 触发 button 元素的 click 事件
wrapper.get('button').trigger('click')
// toHaveProperty 判断事件列表中是否包含指定的事件;
expect(wrapper.emitted()).toHaveProperty('click')
})
TIP
mount
:原来模拟页面渲染组件get
和find
都是查找组件中是否有某个原生dom元素 ;区别于findComponent
;wrapper.emitted()
:获取组件触发的事件列表wrapper.html()
: 获取组件的 html 字符串
属性验证测试(禁止点击为例)
typescript
test('test2', () => {
const wrapper = mount(Button as any, {
props: {
disabled: true
},
slots: {
default: 'disabled'
}
})
// * wrapper.attributes() 获取组件的属性列表;
// * toBeDefined 判断属性列表中是否包含指定的属性;
expect(wrapper.attributes('disabled')).toBeDefined()
// 判断 button 元素是否禁用;
expect(wrapper.find('button').element.disabled).toBeDefined()
// 触发点击事件;
wrapper.get('button').trigger('click')
// 事件列表没有 click 事件;
expect(wrapper.emitted()).not.toHaveProperty('click')
})
Tip
wrapper.attributes('disabled')
:获取组件 disabled 属性wrapper.find('button').element.disabled
:原生 button 是否被禁止了
加载状态测试
typescript
test('test4', () => {
const wrapper = mount(Button as any, {
props: {
loading: true
},
slots: {
default: 'loading'
},
global: {
stubs: ['Icon']
}
})
// 查找组件中的 Icon 组件
const iconElement = wrapper.findComponent(Icon)
expect(iconElement.exists()).toBeTruthy()
expect(iconElement.attributes('icon')).toBe('spinner')
// 组件是否禁用
expect(wrapper.attributes('disabled')).toBeDefined()
})
组件引用测试
typescript
test('test3', () => {
const wrapper = mount(Button as any, {
props: {
// 传入图标名称
icon: 'arrow-up'
},
slots: {
default: 'icon'
},
global: {
// 创建一个虚拟的 FontAwesomeIcon 组件
stubs: ['FontAwesomeIcon']
}
})
// 查找组件中的 FontAwesomeIcon 组件 (icon 组件内部使用了 FontAwesomeIcon)
const iconElement = wrapper.findComponent(FontAwesomeIcon)
// 断言组件是否存在
expect(iconElement.exists()).toBeTruthy()
// 断言组件的属性是否正确
expect(iconElement.attributes('icon')).toBe('arrow-up')
})