import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react'
import '@testing-library/jest-dom'
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverAnchor,
PopoverClose,
PopoverSeparator,
PopoverHeader,
PopoverFooter,
popoverContentVariants,
} from '../popover'
// Mock Radix UI Popover components
jest.mock('@radix-ui/react-popover', () => {
const mockForwardRef = (component: any) => {
const forwardedComponent = (props: any) => component(props, null)
forwardedComponent.displayName = component.displayName || component.name
return forwardedComponent
}
return {
Root: ({ children, ...props }: any) =>
{children}
,
Trigger: mockForwardRef(({ children, ...props }: any, ref: any) => (
)),
Content: mockForwardRef(({ children, className, onInteractOutside, ...props }: any, ref: any) => (
onInteractOutside?.(e)}
{...props}
>
{children}
)),
Anchor: mockForwardRef(({ children, ...props }: any, ref: any) => (
{children}
)),
Close: mockForwardRef(({ children, ...props }: any, ref: any) => (
)),
}
})
describe('Popover Components', () => {
describe('Popover Component', () => {
it('renders correctly with default props', () => {
render(
Open
Content
)
// Popover Root doesn't render a DOM element, just manages state
expect(screen.getByTestId('popover-trigger')).toBeInTheDocument()
expect(screen.getByTestId('popover-content')).toBeInTheDocument()
})
it('passes through HTML attributes', () => {
render(
Open
)
const popover = screen.getByTestId('popover-root')
expect(popover).toHaveAttribute('data-custom', 'test')
expect(popover).toHaveAttribute('aria-label', 'Custom popover')
})
})
describe('PopoverTrigger Component', () => {
it('renders correctly with default props', () => {
render(
Open Popover
)
const trigger = screen.getByTestId('popover-trigger')
expect(trigger).toBeInTheDocument()
expect(trigger).toHaveTextContent('Open Popover')
})
it('handles click events', () => {
const onClick = jest.fn()
render(
Open
)
const trigger = screen.getByTestId('popover-trigger')
fireEvent.click(trigger)
expect(onClick).toHaveBeenCalled()
})
it('passes through HTML attributes', () => {
render(
Open
)
const trigger = screen.getByTestId('popover-trigger')
expect(trigger).toHaveAttribute('data-custom', 'test')
expect(trigger).toHaveAttribute('aria-label', 'Open popover')
})
})
describe('PopoverContent Component', () => {
it('renders correctly with default props', () => {
render(
Popover content
)
const content = screen.getByTestId('popover-content')
expect(content).toBeInTheDocument()
expect(content).toHaveTextContent('Popover content')
})
it('applies custom className', () => {
render(
Content
)
const content = screen.getByTestId('popover-content')
expect(content).toHaveClass('custom-class')
})
it('renders default variant correctly', () => {
render(
Content
)
const content = screen.getByTestId('popover-content')
expect(content).toBeInTheDocument()
})
it('renders destructive variant correctly', () => {
render(
Content
)
const content = screen.getByTestId('popover-content')
expect(content).toBeInTheDocument()
})
it('renders outline variant correctly', () => {
render(
Content
)
const content = screen.getByTestId('popover-content')
expect(content).toBeInTheDocument()
})
it('renders subtle variant correctly', () => {
render(
Content
)
const content = screen.getByTestId('popover-content')
expect(content).toBeInTheDocument()
})
it('renders sm size correctly', () => {
render(
Content
)
const content = screen.getByTestId('popover-content')
expect(content).toBeInTheDocument()
})
it('renders default size correctly', () => {
render(
Content
)
const content = screen.getByTestId('popover-content')
expect(content).toBeInTheDocument()
})
it('renders lg size correctly', () => {
render(
Content
)
const content = screen.getByTestId('popover-content')
expect(content).toBeInTheDocument()
})
it('renders different radius options correctly', () => {
const radiusOptions = ['none', 'sm', 'default', 'lg', 'full'] as const
radiusOptions.forEach((radius) => {
render(
Content
)
expect(screen.getByTestId(`content-${radius}`)).toBeInTheDocument()
})
})
it('renders different shadow options correctly', () => {
const shadowOptions = ['none', 'sm', 'default', 'md', 'lg', 'xl'] as const
shadowOptions.forEach((shadow) => {
render(
Content
)
expect(screen.getByTestId(`content-${shadow}`)).toBeInTheDocument()
})
})
it('handles backdrop prop', () => {
render(
Content
)
const content = screen.getByTestId('popover-content')
expect(content).toBeInTheDocument()
})
it('handles overlayBackdrop prop', () => {
render(
Content
)
const content = screen.getByTestId('popover-content')
expect(content).toBeInTheDocument()
// Overlay backdrop creates a div with fixed positioning
expect(document.querySelector('.fixed.inset-0')).toBeInTheDocument()
})
it('handles closeOnInteractOutside prop', () => {
const onInteractOutside = jest.fn()
render(
Content
)
const content = screen.getByTestId('popover-content')
fireEvent.click(content)
// Should prevent default when closeOnInteractOutside is false
expect(content).toBeInTheDocument()
})
it('forwards ref correctly', () => {
const ref = React.createRef()
render(
Content
)
expect(ref.current).toBeInstanceOf(HTMLDivElement)
})
it('maintains displayName', () => {
expect(PopoverContent.displayName).toBeDefined()
})
it('passes through HTML attributes', () => {
render(
Content
)
const content = screen.getByTestId('popover-content')
expect(content).toHaveAttribute('data-custom', 'test')
expect(content).toHaveAttribute('aria-label', 'Popover content')
})
})
describe('PopoverAnchor Component', () => {
it('renders correctly with default props', () => {
render(
Anchor
)
const anchor = screen.getByTestId('popover-anchor')
expect(anchor).toBeInTheDocument()
expect(anchor).toHaveTextContent('Anchor')
})
it('passes through HTML attributes', () => {
render(
Anchor
)
const anchor = screen.getByTestId('popover-anchor')
expect(anchor).toHaveAttribute('data-custom', 'test')
})
})
describe('PopoverClose Component', () => {
it('renders correctly with default props', () => {
render(
Close
)
const close = screen.getByTestId('popover-close')
expect(close).toBeInTheDocument()
expect(close).toHaveTextContent('Close')
})
it('handles click events', () => {
const onClick = jest.fn()
render(
Close
)
const close = screen.getByTestId('popover-close')
fireEvent.click(close)
expect(onClick).toHaveBeenCalled()
})
it('passes through HTML attributes', () => {
render(
Close
)
const close = screen.getByTestId('popover-close')
expect(close).toHaveAttribute('data-custom', 'test')
expect(close).toHaveAttribute('aria-label', 'Close popover')
})
})
describe('PopoverSeparator Component', () => {
it('renders correctly with default props', () => {
render()
const separator = screen.getByTestId('popover-separator')
expect(separator).toBeInTheDocument()
expect(separator).toHaveClass('my-2', 'h-px', 'bg-border')
})
it('applies custom className', () => {
render()
const separator = screen.getByTestId('separator')
expect(separator).toHaveClass('custom-separator')
expect(separator).toHaveClass('my-2', 'h-px', 'bg-border')
})
it('maintains displayName', () => {
expect(PopoverSeparator.displayName).toBe('PopoverSeparator')
})
it('passes through HTML attributes', () => {
render()
const separator = screen.getByTestId('separator')
expect(separator).toHaveAttribute('data-custom', 'test')
})
})
describe('PopoverHeader Component', () => {
it('renders correctly with default props', () => {
render(Header)
const header = screen.getByTestId('popover-header')
expect(header).toBeInTheDocument()
expect(header).toHaveTextContent('Header')
expect(header).toHaveClass('-mx-4', '-mt-4', 'mb-3', 'px-4', 'pt-4', 'pb-3', 'border-b', 'border-border')
})
it('applies custom className', () => {
render(Header)
const header = screen.getByTestId('header')
expect(header).toHaveClass('custom-header')
expect(header).toHaveClass('-mx-4', '-mt-4', 'mb-3')
})
it('maintains displayName', () => {
expect(PopoverHeader.displayName).toBe('PopoverHeader')
})
it('passes through HTML attributes', () => {
render(Header)
const header = screen.getByTestId('header')
expect(header).toHaveAttribute('data-custom', 'test')
})
})
describe('PopoverFooter Component', () => {
it('renders correctly with default props', () => {
render(Footer)
const footer = screen.getByTestId('popover-footer')
expect(footer).toBeInTheDocument()
expect(footer).toHaveTextContent('Footer')
expect(footer).toHaveClass('-mx-4', '-mb-4', 'mt-3', 'px-4', 'pt-3', 'pb-4', 'border-t', 'border-border')
})
it('applies custom className', () => {
render(Footer)
const footer = screen.getByTestId('footer')
expect(footer).toHaveClass('custom-footer')
expect(footer).toHaveClass('-mx-4', '-mb-4', 'mt-3')
})
it('maintains displayName', () => {
expect(PopoverFooter.displayName).toBe('PopoverFooter')
})
it('passes through HTML attributes', () => {
render(Footer)
const footer = screen.getByTestId('footer')
expect(footer).toHaveAttribute('data-custom', 'test')
})
})
describe('Variant Functions', () => {
it('popoverContentVariants function works correctly', () => {
const variants = popoverContentVariants({
variant: 'destructive',
size: 'lg',
radius: 'full',
shadow: 'xl'
})
expect(typeof variants).toBe('string')
expect(variants.length).toBeGreaterThan(0)
})
})
describe('Complex Combinations', () => {
it('renders complete popover with all components', () => {
render(
Open
Anchor
Header
Content
Footer
Close
)
expect(screen.getByTestId('popover-root')).toBeInTheDocument()
expect(screen.getByTestId('popover-trigger')).toBeInTheDocument()
expect(screen.getByTestId('popover-anchor')).toBeInTheDocument()
expect(screen.getByTestId('popover-content')).toBeInTheDocument()
expect(screen.getByTestId('popover-close')).toBeInTheDocument()
expect(screen.getByText('Header')).toBeInTheDocument()
expect(screen.getByText('Content')).toBeInTheDocument()
expect(screen.getByText('Footer')).toBeInTheDocument()
})
it('handles all content variants and sizes together', () => {
render(
Destructive Small
Outline Large
)
expect(screen.getByText('Destructive Small')).toBeInTheDocument()
expect(screen.getByText('Outline Large')).toBeInTheDocument()
})
it('handles backdrop and overlay combinations', () => {
render(
Complex Content
)
expect(screen.getByText('Complex Content')).toBeInTheDocument()
expect(document.querySelector('.fixed.inset-0')).toBeInTheDocument()
})
})
describe('Edge Cases', () => {
it('handles empty popover', () => {
render()
expect(screen.getByTestId('popover-root')).toBeInTheDocument()
})
it('handles popover without content', () => {
render(
Open
)
expect(screen.getByTestId('popover-trigger')).toBeInTheDocument()
})
it('handles null and undefined children', () => {
render(
{null}
{undefined}
Valid content
)
expect(screen.getByText('Valid content')).toBeInTheDocument()
})
it('handles complex nested content', () => {
render(
Complex Header
With multiple elements
Cancel
)
expect(screen.getByText('Complex Header')).toBeInTheDocument()
expect(screen.getByText('With multiple elements')).toBeInTheDocument()
expect(screen.getByText('Item 1')).toBeInTheDocument()
expect(screen.getByText('Item 2')).toBeInTheDocument()
expect(screen.getByText('Action 1')).toBeInTheDocument()
expect(screen.getByText('Cancel')).toBeInTheDocument()
})
it('handles multiple popovers on same page', () => {
render(
First Trigger
First Content
Second Trigger
Second Content
)
expect(screen.getByText('First Trigger')).toBeInTheDocument()
expect(screen.getByText('First Content')).toBeInTheDocument()
expect(screen.getByText('Second Trigger')).toBeInTheDocument()
expect(screen.getByText('Second Content')).toBeInTheDocument()
})
})
})