import { describe, it, expect, vi } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import { Button } from '../../basic/button'
import { createRef } from 'react'
describe('Button', () => {
// ============================================================
// 渲染测试
// ============================================================
describe('渲染', () => {
it('应正确渲染 children 文本', () => {
render()
expect(screen.getByRole('button')).toHaveTextContent('点击我')
})
it('应渲染为原生 button 元素', () => {
render()
expect(screen.getByRole('button').tagName).toBe('BUTTON')
})
it('应设置正确的 displayName', () => {
expect(Button.displayName).toBe('Button')
})
})
// ============================================================
// Variant 变体测试
// ============================================================
describe('variant 变体', () => {
it('默认应使用 ghost 变体', () => {
render()
expect(screen.getByRole('button')).toHaveClass('bg-transparent')
})
it('variant="primary" 应应用主色样式', () => {
render()
expect(screen.getByRole('button')).toHaveClass('bg-primary')
})
it('variant="secondary" 应应用次级样式', () => {
render()
expect(screen.getByRole('button')).toHaveClass('bg-bg-highlight')
})
it('variant="tertiary" 应应用三级样式', () => {
render()
expect(screen.getByRole('button')).toHaveClass('bg-fill-secondary')
})
it('variant="text" 应应用纯文字样式', () => {
render()
expect(screen.getByRole('button')).toHaveClass('bg-transparent')
expect(screen.getByRole('button')).toHaveClass('text-text-secondary')
})
})
// ============================================================
// Size 尺寸测试
// ============================================================
describe('size 尺寸', () => {
it('默认应使用 md 尺寸', () => {
render()
expect(screen.getByRole('button')).toHaveClass('h-[var(--control-height-md)]')
})
it('size="sm" 应应用小尺寸样式', () => {
render()
expect(screen.getByRole('button')).toHaveClass('h-[var(--control-height-sm)]')
expect(screen.getByRole('button')).toHaveClass('text-[length:var(--control-font-size-sm)]')
})
it('size="md" 应应用中等尺寸样式', () => {
render()
expect(screen.getByRole('button')).toHaveClass('h-[var(--control-height-md)]')
expect(screen.getByRole('button')).toHaveClass('text-[length:var(--control-font-size-md)]')
})
it('size="lg" 应应用大尺寸样式', () => {
render()
expect(screen.getByRole('button')).toHaveClass('h-[var(--control-height-lg)]')
expect(screen.getByRole('button')).toHaveClass('text-[length:var(--control-font-size-lg)]')
})
})
// ============================================================
// Rounded 圆角测试
// ============================================================
describe('rounded 圆角', () => {
it('默认应使用 square 圆角', () => {
render()
expect(screen.getByRole('button')).toHaveClass('rounded')
expect(screen.getByRole('button')).not.toHaveClass('rounded-full')
})
it('rounded="pill" 应应用全圆角样式', () => {
render()
expect(screen.getByRole('button')).toHaveClass('rounded-full')
})
})
// ============================================================
// textButton 模式测试
// ============================================================
describe('textButton 模式', () => {
it('textButton=true 应移除高度和内边距', () => {
render()
const button = screen.getByRole('button')
expect(button).toHaveClass('h-auto')
expect(button).toHaveClass('px-[var(--control-text-padding-x)]')
})
it('textButton=true 应强制使用 text 变体样式', () => {
render()
const button = screen.getByRole('button')
// 即使传入 primary,textButton 也会覆盖为 text 变体
expect(button).toHaveClass('bg-transparent')
})
it('textButton 应保留正确的字体大小', () => {
const { rerender } = render()
expect(screen.getByRole('button')).toHaveClass('text-[length:var(--control-font-size-sm)]')
rerender()
expect(screen.getByRole('button')).toHaveClass('text-[length:var(--control-font-size-md)]')
rerender()
expect(screen.getByRole('button')).toHaveClass('text-[length:var(--control-font-size-lg)]')
})
})
// ============================================================
// 图标测试
// ============================================================
describe('图标', () => {
it('应渲染 prefixIcon', () => {
render(
)
expect(screen.getByTestId('prefix')).toBeInTheDocument()
expect(screen.getByTestId('prefix')).toHaveTextContent('←')
})
it('应渲染 suffixIcon', () => {
render(
)
expect(screen.getByTestId('suffix')).toBeInTheDocument()
expect(screen.getByTestId('suffix')).toHaveTextContent('→')
})
it('应同时渲染前后置图标', () => {
render(
)
expect(screen.getByTestId('prefix')).toBeInTheDocument()
expect(screen.getByTestId('suffix')).toBeInTheDocument()
})
it('无图标时不应渲染图标包裹元素', () => {
const { container } = render()
// 检查没有图标包裹的 span 元素
const iconWrappers = container.querySelectorAll('button > span.inline-flex')
expect(iconWrappers).toHaveLength(0)
})
})
// ============================================================
// 交互测试
// ============================================================
describe('交互', () => {
it('点击时应触发 onClick 回调', () => {
const handleClick = vi.fn()
render()
fireEvent.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
it('多次点击应触发相应次数的回调', () => {
const handleClick = vi.fn()
render()
const button = screen.getByRole('button')
fireEvent.click(button)
fireEvent.click(button)
fireEvent.click(button)
expect(handleClick).toHaveBeenCalledTimes(3)
})
})
// ============================================================
// Disabled 状态测试
// ============================================================
describe('disabled 状态', () => {
it('disabled=true 应设置 disabled 属性', () => {
render()
expect(screen.getByRole('button')).toBeDisabled()
})
it('disabled 时应应用禁用样式', () => {
render()
expect(screen.getByRole('button')).toHaveClass('disabled:opacity-50')
expect(screen.getByRole('button')).toHaveClass('disabled:cursor-not-allowed')
})
it('disabled 时点击不应触发 onClick', () => {
const handleClick = vi.fn()
render()
fireEvent.click(screen.getByRole('button'))
expect(handleClick).not.toHaveBeenCalled()
})
it('默认不应禁用', () => {
render()
expect(screen.getByRole('button')).not.toBeDisabled()
})
})
// ============================================================
// Ref 转发测试
// ============================================================
describe('ref 转发', () => {
it('应正确转发 ref 到 button 元素', () => {
const ref = createRef()
render()
expect(ref.current).toBeInstanceOf(HTMLButtonElement)
})
it('ref.current 应指向渲染的 button', () => {
const ref = createRef()
render()
expect(ref.current).toBe(screen.getByRole('button'))
})
it('callback ref 应被正确调用', () => {
const callbackRef = vi.fn()
render()
expect(callbackRef).toHaveBeenCalled()
expect(callbackRef.mock.calls[0][0]).toBeInstanceOf(HTMLButtonElement)
})
})
// ============================================================
// className 覆盖测试
// ============================================================
describe('className 覆盖', () => {
it('应合并自定义 className', () => {
render()
expect(screen.getByRole('button')).toHaveClass('custom-class')
})
it('自定义 className 应与默认类名共存', () => {
render()
const button = screen.getByRole('button')
expect(button).toHaveClass('my-custom')
expect(button).toHaveClass('bg-primary')
})
})
// ============================================================
// 原生属性透传测试
// ============================================================
describe('原生属性透传', () => {
it('应透传 type 属性', () => {
render()
expect(screen.getByRole('button')).toHaveAttribute('type', 'submit')
})
it('应透传 aria-label 属性', () => {
render()
expect(screen.getByRole('button')).toHaveAttribute('aria-label', '关闭按钮')
})
it('应透传 data-* 属性', () => {
render()
expect(screen.getByTestId('my-button')).toBeInTheDocument()
})
it('应透传 id 属性', () => {
render()
expect(screen.getByRole('button')).toHaveAttribute('id', 'unique-button')
})
})
// ============================================================
// 组合场景测试
// ============================================================
describe('组合场景', () => {
it('应支持所有 props 组合', () => {
const handleClick = vi.fn()
render(
)
const button = screen.getByTestId('combo-button')
expect(button).toHaveClass('bg-primary')
expect(button).toHaveClass('h-[var(--control-height-lg)]')
expect(button).toHaveClass('rounded-full')
expect(button).toHaveClass('extra-class')
expect(button).toHaveTextContent('🚀')
expect(button).toHaveTextContent('组合按钮')
expect(button).toHaveTextContent('✨')
fireEvent.click(button)
expect(handleClick).toHaveBeenCalled()
})
})
})