import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react'
import { Badge } from '../badge'
describe('Badge Component', () => {
it('renders correctly with default props', () => {
render(Default Badge)
const badge = screen.getByText('Default Badge').parentElement!
expect(badge).toBeInTheDocument()
expect(badge).toHaveClass('inline-flex')
expect(badge).toHaveClass('items-center')
expect(badge).toHaveClass('h-6') // md size
expect(badge).toHaveClass('rounded-full') // default radius
})
it('renders with custom text content', () => {
render(Custom Text)
expect(screen.getByText('Custom Text')).toBeInTheDocument()
})
it('applies custom className', () => {
render(Test)
const badge = screen.getByText('Test').parentElement!
expect(badge).toHaveClass('custom-badge')
})
describe('Variants', () => {
it('renders primary variant correctly (default)', () => {
render(Primary)
const badge = screen.getByText('Primary').parentElement!
expect(badge).toHaveClass('bg-primary')
expect(badge).toHaveClass('text-primary-foreground')
expect(badge).toHaveClass('border-transparent')
})
it('renders secondary variant correctly', () => {
render(Secondary)
const badge = screen.getByText('Secondary').parentElement!
expect(badge).toHaveClass('bg-secondary')
expect(badge).toHaveClass('text-secondary-foreground')
})
it('renders destructive variant correctly', () => {
render(Destructive)
const badge = screen.getByText('Destructive').parentElement!
expect(badge).toHaveClass('bg-error')
expect(badge).toHaveClass('text-white')
})
it('renders outline variant correctly', () => {
render(Outline)
const badge = screen.getByText('Outline').parentElement!
expect(badge).toHaveClass('border-gray-200')
expect(badge).toHaveClass('bg-transparent')
})
it('renders success variant correctly', () => {
render(Success)
const badge = screen.getByText('Success').parentElement!
expect(badge).toHaveClass('bg-success')
expect(badge).toHaveClass('text-white')
})
it('renders warning variant correctly', () => {
render(Warning)
const badge = screen.getByText('Warning').parentElement!
expect(badge).toHaveClass('bg-warning')
expect(badge).toHaveClass('text-white')
})
it('renders ghost variant correctly', () => {
render(Ghost)
const badge = screen.getByText('Ghost').parentElement!
expect(badge).toHaveClass('bg-transparent')
expect(badge).toHaveClass('text-foreground')
})
})
describe('Sizes', () => {
it('renders small size correctly', () => {
render(Small)
const badge = screen.getByText('Small').parentElement!
expect(badge).toHaveClass('h-5')
expect(badge).toHaveClass('px-2')
expect(badge).toHaveClass('text-xs')
})
it('renders medium size correctly (default)', () => {
render(Medium)
const badge = screen.getByText('Medium').parentElement!
expect(badge).toHaveClass('h-6')
expect(badge).toHaveClass('px-2.5')
expect(badge).toHaveClass('text-xs')
})
it('renders large size correctly', () => {
render(Large)
const badge = screen.getByText('Large').parentElement!
expect(badge).toHaveClass('h-7')
expect(badge).toHaveClass('px-3')
expect(badge).toHaveClass('text-sm')
})
})
describe('Radius', () => {
it('renders default radius correctly', () => {
render(Default Radius)
const badge = screen.getByText('Default Radius').parentElement!
expect(badge).toHaveClass('rounded-full')
})
it('renders small radius correctly', () => {
render(Small Radius)
const badge = screen.getByText('Small Radius').parentElement!
expect(badge).toHaveClass('rounded-md')
})
it('renders large radius correctly', () => {
render(Large Radius)
const badge = screen.getByText('Large Radius').parentElement!
expect(badge).toHaveClass('rounded-xl')
})
it('renders no radius correctly', () => {
render(No Radius)
const badge = screen.getByText('No Radius').parentElement!
expect(badge).toHaveClass('rounded-none')
})
})
describe('Dot Feature', () => {
it('renders with dot when withDot is true', () => {
render(With Dot)
const badge = screen.getByText('With Dot').parentElement!
const dot = badge.querySelector('span[aria-hidden="true"]')
expect(dot).toBeInTheDocument()
expect(dot).toHaveClass('h-2')
expect(dot).toHaveClass('w-2')
expect(dot).toHaveClass('rounded-full')
})
it('does not render dot when withDot is false', () => {
render(Without Dot)
const badge = screen.getByText('Without Dot').parentElement!
const dot = badge.querySelector('span[aria-hidden="true"]')
expect(dot).not.toBeInTheDocument()
})
it('renders dot with custom color', () => {
render(Custom Dot)
const badge = screen.getByText('Custom Dot').parentElement!
const dot = badge.querySelector('span[aria-hidden="true"]')
expect(dot).toHaveClass('bg-red-500')
})
it('renders dot with variant-specific default colors', () => {
const { rerender } = render(Destructive)
let dot = screen.getByText('Destructive').parentElement!.querySelector('span[aria-hidden="true"]')
expect(dot).toHaveClass('bg-white')
rerender(Success)
dot = screen.getByText('Success').parentElement!.querySelector('span[aria-hidden="true"]')
expect(dot).toHaveClass('bg-white')
rerender(Primary)
dot = screen.getByText('Primary').parentElement!.querySelector('span[aria-hidden="true"]')
expect(dot).toHaveClass('bg-primary-foreground')
})
})
describe('Icons', () => {
const TestIcon = () => Icon
it('renders with left icon', () => {
render(}>With Left Icon)
expect(screen.getByTestId('test-icon')).toBeInTheDocument()
expect(screen.getByText('With Left Icon')).toBeInTheDocument()
})
it('renders with right icon', () => {
render(}>With Right Icon)
expect(screen.getByTestId('test-icon')).toBeInTheDocument()
expect(screen.getByText('With Right Icon')).toBeInTheDocument()
})
it('renders with both left and right icons', () => {
const LeftIcon = () => Left
const RightIcon = () => Right
render(
} rightIcon={}>
Both Icons
)
expect(screen.getByTestId('left-icon')).toBeInTheDocument()
expect(screen.getByTestId('right-icon')).toBeInTheDocument()
expect(screen.getByText('Both Icons')).toBeInTheDocument()
})
})
describe('Removable Feature', () => {
it('renders remove button when removable is true and onRemove is provided', () => {
const onRemove = jest.fn()
render(Removable)
const removeButton = screen.getByRole('button', { name: 'Remove badge' })
expect(removeButton).toBeInTheDocument()
expect(removeButton).toHaveAttribute('aria-label', 'Remove badge')
})
it('does not render remove button when removable is false', () => {
const onRemove = jest.fn()
render(Not Removable)
const removeButton = screen.queryByRole('button', { name: 'Remove badge' })
expect(removeButton).not.toBeInTheDocument()
})
it('does not render remove button when onRemove is not provided', () => {
render(No Remove Handler)
const removeButton = screen.queryByRole('button', { name: 'Remove badge' })
expect(removeButton).not.toBeInTheDocument()
})
it('calls onRemove when remove button is clicked', () => {
const onRemove = jest.fn()
render(Removable)
const removeButton = screen.getByRole('button', { name: 'Remove badge' })
fireEvent.click(removeButton)
expect(onRemove).toHaveBeenCalledTimes(1)
})
it('stops propagation when remove button is clicked', () => {
const onRemove = jest.fn()
const onBadgeClick = jest.fn()
render(
Removable
)
const removeButton = screen.getByRole('button', { name: 'Remove badge' })
fireEvent.click(removeButton)
expect(onRemove).toHaveBeenCalledTimes(1)
expect(onBadgeClick).not.toHaveBeenCalled()
})
it('sets data-removable attribute when removable is true', () => {
const onRemove = jest.fn()
render(Removable)
const badge = screen.getByText('Removable').parentElement!
expect(badge).toHaveAttribute('data-removable', '')
})
})
describe('Complex Combinations', () => {
it('renders with all features combined', () => {
const onRemove = jest.fn()
const LeftIcon = () => L
const RightIcon = () => R
render(
}
rightIcon={}
removable
onRemove={onRemove}
className="custom-badge"
>
Complex Badge
)
const badge = screen.getByText('Complex Badge').parentElement!
// Check variant, size, radius
expect(badge).toHaveClass('bg-success')
expect(badge).toHaveClass('h-7')
expect(badge).toHaveClass('rounded-md')
expect(badge).toHaveClass('custom-badge')
// Check dot
const dot = badge.querySelector('span[aria-hidden="true"]')
expect(dot).toHaveClass('bg-yellow-400')
// Check icons
expect(screen.getByTestId('left-icon')).toBeInTheDocument()
expect(screen.getByTestId('right-icon')).toBeInTheDocument()
// Check remove button
const removeButton = screen.getByRole('button', { name: 'Remove badge' })
expect(removeButton).toBeInTheDocument()
// Test remove functionality
fireEvent.click(removeButton)
expect(onRemove).toHaveBeenCalledTimes(1)
})
})
describe('Accessibility', () => {
it('has proper ARIA attributes for remove button', () => {
const onRemove = jest.fn()
render(Accessible)
const removeButton = screen.getByRole('button', { name: 'Remove badge' })
expect(removeButton).toHaveAttribute('aria-label', 'Remove badge')
expect(removeButton).toHaveAttribute('type', 'button')
})
it('has aria-hidden on dot element', () => {
render(With Dot)
const dot = screen.getByText('With Dot').parentElement!.querySelector('span[aria-hidden="true"]')
expect(dot).toHaveAttribute('aria-hidden', 'true')
})
it('has aria-hidden on remove icon', () => {
const onRemove = jest.fn()
render(Removable)
const removeIcon = screen.getByRole('button').querySelector('svg')
expect(removeIcon).toHaveAttribute('aria-hidden', 'true')
})
it('truncates long text content', () => {
render(Very long badge text that should be truncated)
const textSpan = screen.getByText('Very long badge text that should be truncated')
expect(textSpan).toHaveClass('truncate')
})
})
describe('Event Handling', () => {
it('handles click events on badge', () => {
const onClick = jest.fn()
render(Clickable)
const badge = screen.getByText('Clickable').parentElement!
fireEvent.click(badge)
expect(onClick).toHaveBeenCalledTimes(1)
})
it('handles focus events', () => {
const onFocus = jest.fn()
render(Focusable)
const badge = screen.getByText('Focusable').parentElement!
fireEvent.focus(badge)
expect(onFocus).toHaveBeenCalledTimes(1)
})
})
})