🍀封装个 Button 组件,使用 vitest 来测试一下

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:原来模拟页面渲染组件
  • getfind 都是查找组件中是否有某个原生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')
  })
相关推荐
程序猿小泓几秒前
从 Claude Code 学 Agent Harness:一个前端工程师的 AI Agent 学习笔记
前端·人工智能·学习
柒和远方41 分钟前
从审计日志到可下载证据包:事务型 Outbox、租约 fencing 与保留水位
前端·后端·架构
minglie11 小时前
npm ali-oss库依赖冲突
前端·npm
CryptoPP1 小时前
BSE股票K线数据接入实战:从接口调用到前端图表展示
大数据·前端·网络·人工智能·websocket·网络协议
没落英雄3 小时前
5. 从零开始搭建一个 AI Agent —— 人机协作与中断恢复
前端·人工智能·架构
品尚公益团队4 小时前
C#在asp.net网页开发中的带cookie登录实现
前端·c#·asp.net
没有了遇见5 小时前
AI Agent 是什么?—— 一文理解 LLM、Memory、Skills、Tools、MCP、Workflow,Context
android·前端·程序员
后台开发者Ethan5 小时前
setState组件更新
前端·javascript·react
谙忆10245 小时前
大文件上传实战:前端分片、断点续传、秒传与并发控制怎么落地
前端
轻动琴弦6 小时前
VoltTool —— 一站式在线开发者工具平台,69+实用工具免费使用
前端·chrome