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() }) }) })