import * as React from 'react'
import { render, screen } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import { axe } from 'vitest-axe'
import { ColorSwatch, type ColorSwatchProps } from './color-swatch'
import { parseColor } from '../../lib/color'
function renderSwatch(props: ColorSwatchProps) {
return render()
}
const swatch = () => screen.getByTestId('swatch')
describe('ColorSwatch', () => {
it('paints an opaque color as a flat fill, with no checkerboard behind it', () => {
renderSwatch({ color: '#3b82f6' })
expect(swatch().style.backgroundColor).toBe('rgb(59, 130, 246)')
expect(swatch().style.backgroundImage).toBe('')
})
it('accepts a Color object as well as a string', () => {
renderSwatch({ color: parseColor('hsl(217, 91%, 60%)') })
expect(swatch().style.backgroundColor).toBe('rgb(60, 131, 246)')
})
it('draws a translucent color over a checkerboard so the transparency reads', () => {
renderSwatch({ color: 'rgba(59, 130, 246, 0.5)' })
expect(swatch().style.backgroundImage).toContain('linear-gradient(rgba(59, 130, 246, 0.5)')
expect(swatch().style.backgroundImage).toContain('repeating-conic-gradient')
// The tile size has to be its own declaration: `background-image` rejects the
// `0 0 / size` shorthand and drops the whole rule.
expect(swatch().style.backgroundSize).toBe('auto, 0.3em 0.3em')
})
it('drops the checkerboard so a translucent color can blend into the surface', () => {
renderSwatch({ color: 'rgba(59, 130, 246, 0.5)', checkerboard: false })
expect(swatch().style.backgroundImage).toBe('')
expect(swatch().style.backgroundColor).toBe('rgba(59, 130, 246, 0.5)')
})
it('names the color for assistive technology', () => {
renderSwatch({ color: '#3b82f6' })
expect(screen.getByRole('img')).toHaveAccessibleName('vivid blue')
})
it('describes grays and the extremes without reaching for a hue', () => {
const { rerender } = renderSwatch({ color: '#000000' })
expect(swatch()).toHaveAccessibleName('black')
rerender()
expect(swatch()).toHaveAccessibleName('white')
rerender()
expect(swatch()).toHaveAccessibleName('gray')
rerender()
expect(swatch()).toHaveAccessibleName('transparent')
})
it('reports the opacity as part of the name', () => {
renderSwatch({ color: 'rgba(59, 130, 246, 0.5)' })
expect(swatch()).toHaveAccessibleName('vivid blue, 50% opacity')
})
it('lets colorName replace the description and aria-label add context', () => {
renderSwatch({ color: '#ff0000', colorName: 'Fire truck red', 'aria-label': 'Background color' })
expect(swatch()).toHaveAccessibleName('Fire truck red, Background color')
})
it('sizes from a preset or an exact pixel number', () => {
const { rerender } = renderSwatch({ color: '#3b82f6' })
// The box is 1em, so the preset scale and a number both land on font-size.
expect(swatch()).toHaveClass('text-[2.5rem]')
expect(swatch()).toHaveClass('size-[1em]')
rerender()
expect(swatch().style.fontSize).toBe('20px')
expect(swatch()).not.toHaveClass('text-[2.5rem]')
})
it('takes a size class override on the root', () => {
renderSwatch({ color: '#3b82f6', className: 'size-10' })
expect(swatch()).toHaveClass('size-10')
expect(swatch()).not.toHaveClass('size-[1em]')
})
it('rounds the corners by default and goes fully round on request', () => {
const { rerender } = renderSwatch({ color: '#3b82f6' })
expect(swatch().className).toContain('rounded-[calc(tan(atan2(var(--radius-md),2.5rem))*100%)]')
rerender()
expect(swatch()).toHaveClass('rounded-full')
})
it('drops the color when disabled, so the flat fill can show', () => {
renderSwatch({ color: 'rgba(59, 130, 246, 0.5)', disabled: true })
// An inline background would paint straight over the disabled class.
expect(swatch()).toHaveAttribute('data-disabled')
expect(swatch().style.backgroundImage).toBe('')
expect(swatch().style.backgroundColor).toBe('')
})
it('carries the shared border overlay', () => {
renderSwatch({ color: '#3b82f6' })
expect(swatch().querySelector('[data-slot="color-swatch-surface"]')).toBeInTheDocument()
})
it('renders children over the color', () => {
renderSwatch({ color: '#3b82f6', children: ✓ })
expect(screen.getByTestId('check')).toBeInTheDocument()
})
it('has no accessibility violations', async () => {
const { container } = renderSwatch({ color: 'rgba(59, 130, 246, 0.5)' })
expect(await axe(container)).toHaveNoViolations()
})
})