test: add comprehensive tests for CopyButton component (#7719)

* test: add comprehensive tests for CopyButton component

- Add tests for basic rendering and functionality
- Add clipboard API mocking and error handling
- Add tests for custom props (size, tooltip, label)
- Add edge case testing (empty text, special characters)
- Improve component test coverage

Signed-off-by: Jason Young <farion1231@gmail.com>

* fix: resolve linting issues in CopyButton tests

- Sort imports alphabetically
- Remove trailing whitespace
- Add final newline

Signed-off-by: Jason Young <farion1231@gmail.com>

* refactor: consolidate similar test cases in CopyButton tests

- Merge 'should render copy icon' and 'should render with basic structure'
- Merge 'should apply custom size to icon' and 'should apply custom size to label'
- Reduce test duplication while maintaining full coverage
- Address maintainer feedback for better test organization

Signed-off-by: Jason Young <farion1231@gmail.com>

---------

Signed-off-by: Jason Young <farion1231@gmail.com>
This commit is contained in:
Jason Young 2025-07-01 23:37:44 +08:00 committed by GitHub
parent ba21a2c5fa
commit f58378daa0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -0,0 +1,164 @@
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import CopyButton from '../CopyButton'
// Mock navigator.clipboard
const mockWriteText = vi.fn()
const mockClipboard = {
writeText: mockWriteText
}
// Mock window.message
const mockMessage = {
success: vi.fn(),
error: vi.fn()
}
// Mock useTranslation
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => {
const translations: Record<string, string> = {
'message.copy.success': '复制成功',
'message.copy.failed': '复制失败'
}
return translations[key] || key
}
})
}))
describe('CopyButton', () => {
beforeEach(() => {
// Setup mocks
Object.assign(navigator, { clipboard: mockClipboard })
Object.assign(window, { message: mockMessage })
// Clear all mocks
vi.clearAllMocks()
})
it('should render with basic structure and copy icon', () => {
render(<CopyButton textToCopy="test text" />)
// Should have basic clickable container
const container = document.querySelector('div')
expect(container).toBeInTheDocument()
// Should render copy icon
const copyIcon = document.querySelector('.copy-icon')
expect(copyIcon).toBeInTheDocument()
})
it('should render label when provided', () => {
const labelText = 'Copy to clipboard'
render(<CopyButton textToCopy="test text" label={labelText} />)
expect(screen.getByText(labelText)).toBeInTheDocument()
})
it('should render tooltip when provided', async () => {
const tooltipText = 'Click to copy'
render(<CopyButton textToCopy="test text" tooltip={tooltipText} />)
// Check that the component structure includes tooltip
const container = document.querySelector('div')
expect(container).toBeInTheDocument()
// The tooltip should be rendered when hovered
const copyIcon = document.querySelector('.copy-icon')
expect(copyIcon).toBeInTheDocument()
})
it('should not render tooltip when not provided', () => {
render(<CopyButton textToCopy="test text" />)
// Should not have tooltip wrapper
expect(document.querySelector('.ant-tooltip')).not.toBeInTheDocument()
})
it('should copy text to clipboard on click', async () => {
const textToCopy = 'Hello World'
mockWriteText.mockResolvedValue(undefined)
render(<CopyButton textToCopy={textToCopy} />)
// Find the clickable element by using the copy icon as reference
const copyIcon = document.querySelector('.copy-icon')
const clickableElement = copyIcon?.parentElement
expect(clickableElement).toBeInTheDocument()
await userEvent.click(clickableElement!)
expect(mockWriteText).toHaveBeenCalledWith(textToCopy)
})
it('should show success message when copy succeeds', async () => {
mockWriteText.mockResolvedValue(undefined)
render(<CopyButton textToCopy="test text" />)
const copyIcon = document.querySelector('.copy-icon')
const clickableElement = copyIcon?.parentElement
await userEvent.click(clickableElement!)
expect(mockMessage.success).toHaveBeenCalledWith('复制成功')
expect(mockMessage.error).not.toHaveBeenCalled()
})
it('should show error message when copy fails', async () => {
mockWriteText.mockRejectedValue(new Error('Clipboard access denied'))
render(<CopyButton textToCopy="test text" />)
const copyIcon = document.querySelector('.copy-icon')
const clickableElement = copyIcon?.parentElement
await userEvent.click(clickableElement!)
expect(mockMessage.error).toHaveBeenCalledWith('复制失败')
expect(mockMessage.success).not.toHaveBeenCalled()
})
it('should apply custom size to icon and label', () => {
const customSize = 20
const labelText = 'Copy'
render(<CopyButton textToCopy="test text" size={customSize} label={labelText} />)
// Should apply custom size to icon
const copyIcon = document.querySelector('.copy-icon')
expect(copyIcon).toHaveAttribute('width', customSize.toString())
expect(copyIcon).toHaveAttribute('height', customSize.toString())
// Should apply custom size to label
const label = screen.getByText(labelText)
expect(label).toHaveStyle({ fontSize: `${customSize}px` })
})
it('should handle empty text', async () => {
const emptyText = ''
mockWriteText.mockResolvedValue(undefined)
render(<CopyButton textToCopy={emptyText} />)
const copyIcon = document.querySelector('.copy-icon')
const clickableElement = copyIcon?.parentElement
await userEvent.click(clickableElement!)
expect(mockWriteText).toHaveBeenCalledWith(emptyText)
})
it('should handle special characters', async () => {
const specialText = '特殊字符 🎉 @#$%^&*()'
mockWriteText.mockResolvedValue(undefined)
render(<CopyButton textToCopy={specialText} />)
const copyIcon = document.querySelector('.copy-icon')
const clickableElement = copyIcon?.parentElement
await userEvent.click(clickableElement!)
expect(mockWriteText).toHaveBeenCalledWith(specialText)
})
})