import React from 'react'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Tooltip } from '../tooltip'
// Mock next-themes
jest.mock('next-themes', () => ({
useTheme: () => ({
theme: 'light',
}),
}))
describe('Tooltip Components', () => {
describe('Tooltip Component', () => {
describe('Basic Rendering', () => {
it('renders correctly with default props', () => {
render(
)
expect(screen.getByRole('button')).toBeInTheDocument()
expect(screen.getByText('Hover me')).toBeInTheDocument()
})
it('shows tooltip on hover', async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
expect(screen.getByRole('tooltip')).toBeInTheDocument()
expect(screen.getByText('Test tooltip')).toBeInTheDocument()
})
})
it('hides tooltip on mouse leave', async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
expect(screen.getByRole('tooltip')).toBeInTheDocument()
})
await user.unhover(trigger)
await waitFor(() => {
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument()
})
})
it('applies custom className', () => {
render(
)
const wrapper = screen.getByRole('button').parentElement
expect(wrapper).toHaveClass('relative', 'inline-block')
})
it('has correct displayName', () => {
expect(Tooltip.displayName || Tooltip.name || 'Tooltip').toBe('Tooltip')
})
})
describe('Variants', () => {
const variants = ['primary', 'secondary', 'success', 'caution', 'destructive', 'info'] as const
variants.forEach((variant) => {
it(`renders ${variant} variant correctly`, async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
const tooltip = screen.getByRole('tooltip')
expect(tooltip).toBeInTheDocument()
expect(tooltip).toHaveClass(`bg-${variant}`)
})
})
})
})
describe('Sizes', () => {
const sizes = ['sm', 'md', 'lg'] as const
sizes.forEach((size) => {
it(`renders ${size} size correctly`, async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
const tooltip = screen.getByRole('tooltip')
expect(tooltip).toBeInTheDocument()
// Size classes are applied through tooltipVariants
expect(tooltip).toHaveAttribute('class')
})
})
})
})
describe('Radius', () => {
const radiusOptions = ['none', 'sm', 'md', 'lg', 'full'] as const
radiusOptions.forEach((radius) => {
it(`renders ${radius} radius correctly`, async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
const tooltip = screen.getByRole('tooltip')
expect(tooltip).toBeInTheDocument()
expect(tooltip).toHaveClass(`rounded-${radius === 'none' ? 'none' : radius}`)
})
})
})
})
describe('Shadow', () => {
const shadowOptions = ['none', 'sm', 'md', 'lg'] as const
shadowOptions.forEach((shadow) => {
it(`renders ${shadow} shadow correctly`, async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
const tooltip = screen.getByRole('tooltip')
expect(tooltip).toBeInTheDocument()
expect(tooltip).toHaveClass(`shadow-${shadow}`)
})
})
})
})
describe('Positioning', () => {
const sides = ['top', 'right', 'bottom', 'left'] as const
sides.forEach((side) => {
it(`renders ${side} position correctly`, async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
const tooltip = screen.getByRole('tooltip')
expect(tooltip).toBeInTheDocument()
// Position classes are applied through sideClasses
expect(tooltip).toHaveAttribute('class')
})
})
})
const alignments = ['start', 'center', 'end'] as const
alignments.forEach((align) => {
it(`renders ${align} alignment correctly`, async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
const tooltip = screen.getByRole('tooltip')
expect(tooltip).toBeInTheDocument()
// Alignment classes are applied through alignClasses
expect(tooltip).toHaveAttribute('class')
})
})
})
})
describe('Delay Duration', () => {
it('respects custom delay duration', async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
// Should appear after 100ms
await waitFor(() => {
expect(screen.getByRole('tooltip')).toBeInTheDocument()
}, { timeout: 200 })
})
it('works with zero delay', async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
// Should appear immediately
await waitFor(() => {
expect(screen.getByRole('tooltip')).toBeInTheDocument()
})
})
})
describe('Side Offset', () => {
it('applies custom side offset', async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
const tooltip = screen.getByRole('tooltip')
expect(tooltip).toBeInTheDocument()
expect(tooltip).toHaveStyle({ marginBottom: '10px' })
})
})
})
describe('Content Types', () => {
it('renders text content', async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
expect(screen.getByText('Simple text')).toBeInTheDocument()
})
})
it('renders JSX content', async () => {
const user = userEvent.setup()
render(
JSX Content}>
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
expect(screen.getByTestId('jsx-content')).toBeInTheDocument()
})
})
it('renders complex content', async () => {
const user = userEvent.setup()
const complexContent = (
)
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
expect(screen.getByText('Title')).toBeInTheDocument()
expect(screen.getByText('Description')).toBeInTheDocument()
})
})
})
describe('Focus Events', () => {
it('shows tooltip on focus', async () => {
render(
)
const trigger = screen.getByRole('button')
fireEvent.focus(trigger)
await waitFor(() => {
expect(screen.getByRole('tooltip')).toBeInTheDocument()
})
})
it('hides tooltip on blur', async () => {
render(
)
const trigger = screen.getByRole('button')
fireEvent.focus(trigger)
await waitFor(() => {
expect(screen.getByRole('tooltip')).toBeInTheDocument()
})
fireEvent.blur(trigger)
await waitFor(() => {
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument()
})
})
})
describe('Complex Combinations', () => {
it('renders with all props', async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
const tooltip = screen.getByRole('tooltip')
expect(tooltip).toBeInTheDocument()
expect(tooltip).toHaveClass('bg-success')
expect(tooltip).toHaveClass('rounded-lg')
expect(tooltip).toHaveClass('shadow-lg')
expect(tooltip).toHaveStyle({ marginTop: '8px' })
})
})
it('works with different trigger elements', async () => {
const user = userEvent.setup()
render(
)
// Test button
await user.hover(screen.getByTestId('tooltip-button'))
await waitFor(() => {
expect(screen.getByText('Button tooltip')).toBeInTheDocument()
})
await user.unhover(screen.getByTestId('tooltip-button'))
await waitFor(() => {
expect(screen.queryByText('Button tooltip')).not.toBeInTheDocument()
})
// Test link
await user.hover(screen.getByRole('link'))
await waitFor(() => {
expect(screen.getByText('Link tooltip')).toBeInTheDocument()
})
})
})
describe('Edge Cases', () => {
it('handles empty content', async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
const tooltip = screen.getByRole('tooltip')
expect(tooltip).toBeInTheDocument()
expect(tooltip).toBeEmptyDOMElement()
})
})
it('handles null content', async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
const tooltip = screen.getByRole('tooltip')
expect(tooltip).toBeInTheDocument()
})
})
it('handles undefined content', async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
const tooltip = screen.getByRole('tooltip')
expect(tooltip).toBeInTheDocument()
})
})
it('handles single child requirement', () => {
// Tooltip component expects single child due to React.Children.only
render(
)
// Should render both buttons wrapped in div
expect(screen.getByText('Button 1')).toBeInTheDocument()
expect(screen.getByText('Button 2')).toBeInTheDocument()
})
it('passes through HTML attributes', async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
const tooltip = screen.getByRole('tooltip')
expect(tooltip).toBeInTheDocument()
expect(tooltip).toHaveAttribute('data-testid', 'tooltip-wrapper')
expect(tooltip).toHaveAttribute('aria-label', 'Custom tooltip')
})
})
it('handles rapid hover/unhover', async () => {
const user = userEvent.setup()
render(
)
const trigger = screen.getByRole('button')
// Rapid hover/unhover
await user.hover(trigger)
await user.unhover(trigger)
await user.hover(trigger)
await waitFor(() => {
expect(screen.getByRole('tooltip')).toBeInTheDocument()
})
})
it('handles very long content', async () => {
const user = userEvent.setup()
const longContent = 'This is a very long tooltip content that should wrap properly and not break the layout. '.repeat(10)
render(
)
const trigger = screen.getByRole('button')
await user.hover(trigger)
await waitFor(() => {
const tooltip = screen.getByRole('tooltip')
expect(tooltip).toBeInTheDocument()
// Check if content contains the expected text (ignoring whitespace differences)
expect(tooltip.textContent?.trim()).toContain('This is a very long tooltip content')
})
})
})
})
})