import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, it } from 'vitest'
import { axe } from 'vitest-axe'
import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from './preview-card'
function renderPreviewCard({
side,
body = 'A short preview of the linked page.',
}: {
side?: 'top' | 'bottom' | 'left' | 'right'
body?: string
} = {}) {
return render(
example.com
{body}
,
)
}
describe('PreviewCard', () => {
it('renders the trigger as a link tagged with data-slot', () => {
renderPreviewCard()
const trigger = screen.getByRole('link', { name: 'example.com' })
expect(trigger.getAttribute('data-slot')).toBe('preview-card-trigger')
})
it('does not render content before hover', () => {
renderPreviewCard({ body: 'Hidden until hover' })
expect(screen.queryByText('Hidden until hover')).toBeNull()
})
it('shows content on hover and hides on unhover', async () => {
const user = userEvent.setup()
renderPreviewCard()
const trigger = screen.getByRole('link', { name: 'example.com' })
await user.hover(trigger)
const body = await screen.findByText('A short preview of the linked page.')
const popup = body.closest('[data-slot="preview-card-content"]') as HTMLElement
expect(popup).not.toBeNull()
expect(popup.className).toContain('bg-background')
expect(popup.className).toContain('rounded-xl')
expect(popup.className).toContain('border-border-overlay')
await user.unhover(trigger)
await waitFor(() => {
expect(screen.queryByText('A short preview of the linked page.')).toBeNull()
})
})
it('renders the arrow svg inside the popup', async () => {
const user = userEvent.setup()
renderPreviewCard()
await user.hover(screen.getByRole('link', { name: 'example.com' }))
await screen.findByText('A short preview of the linked page.')
const arrow = document.querySelector('[data-slot="preview-card-arrow"]') as HTMLElement | null
expect(arrow).not.toBeNull()
expect(arrow!.querySelector('svg')).not.toBeNull()
})
it('omits the arrow and the thicker side border when arrow is false', async () => {
const user = userEvent.setup()
render(
link
No arrow here.
,
)
await user.hover(screen.getByRole('link', { name: 'link' }))
const body = await screen.findByText('No arrow here.')
expect(document.querySelector('[data-slot="preview-card-arrow"]')).toBeNull()
const popup = body.closest('[data-slot="preview-card-content"]') as HTMLElement
expect(popup.className).not.toMatch(/border-[a-z]-2/)
})
it('exposes createHandle for detached trigger patterns', () => {
expect(typeof PreviewCard.createHandle).toBe('function')
})
it('has no accessibility violations (closed state)', async () => {
const { container } = renderPreviewCard()
expect(await axe(container)).toHaveNoViolations()
})
})