import { render, screen } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import { axe } from 'vitest-axe'
import { BackgroundPattern } from './background-pattern'
describe('BackgroundPattern', () => {
it('renders its children inside a labeled wrapper', () => {
render(
Content
,
)
expect(screen.getByText('Content')).toBeInTheDocument()
expect(screen.getByText('Content').closest('[data-slot="background-pattern"]')).not.toBeNull()
})
it('paints a single decorative pattern layer when static', () => {
const { container } = render()
const layers = container.querySelectorAll('[data-slot="background-pattern-layer"]')
expect(layers).toHaveLength(1)
layers.forEach((layer) => expect(layer).toHaveAttribute('aria-hidden'))
expect(container.querySelector('[data-slot="background-pattern-highlight"]')).toBeNull()
})
it('mounts a cursor-following highlight when spotlight is enabled', () => {
const { container } = render()
const highlight = container.querySelector('[data-slot="background-pattern-highlight"]')
expect(highlight).not.toBeNull()
expect(highlight).toHaveAttribute('aria-hidden')
expect(container.querySelectorAll('[data-slot="background-pattern-layer"]')).toHaveLength(2)
})
it('marks the highlight persistent only when requested', () => {
const { container, rerender } = render()
expect(container.querySelector('[data-slot="background-pattern-highlight"]')).not.toHaveAttribute('data-persistent')
rerender()
expect(container.querySelector('[data-slot="background-pattern-highlight"]')).toHaveAttribute('data-persistent')
})
it('treats the highlight as persistent under prefers-reduced-motion', () => {
const original = window.matchMedia
window.matchMedia = ((query: string) =>
({
matches: query.includes('prefers-reduced-motion'),
media: query,
onchange: null,
addEventListener: () => {},
removeEventListener: () => {},
addListener: () => {},
removeListener: () => {},
dispatchEvent: () => false,
}) as unknown as MediaQueryList) as typeof window.matchMedia
try {
const { container } = render()
// No `persistent` prop, but reduced motion forces the steady, fade-free path.
expect(container.querySelector('[data-slot="background-pattern-highlight"]')).toHaveAttribute('data-persistent')
} finally {
window.matchMedia = original
}
})
it('resolves the spotlight size from a number, string, or object', () => {
const { container, rerender } = render()
const root = () => container.querySelector('[data-slot="background-pattern"]') as HTMLElement
expect(root().style.getPropertyValue('--spotlight-size')).toBe('300px')
rerender()
expect(root().style.getPropertyValue('--spotlight-size')).toBe('10rem')
rerender()
expect(root().style.getPropertyValue('--spotlight-size')).toBe('18px')
})
it('merges a consumer className over the default surface', () => {
const { container } = render()
const root = container.querySelector('[data-slot="background-pattern"]')!
expect(root).toHaveClass('bg-white')
expect(root).not.toHaveClass('bg-background-subtle')
})
it('forwards cellSize as a CSS variable', () => {
const { container } = render()
const root = container.querySelector('[data-slot="background-pattern"]') as HTMLElement
expect(root.style.getPropertyValue('--pattern-cell')).toBe('20px')
})
it('has no accessibility violations (decorative layers are hidden)', async () => {
const { container } = render(
Readable content
,
)
expect(await axe(container)).toHaveNoViolations()
})
})