import type { ComponentType } from 'react'; import { render } from '@testing-library/react'; import { describe, expect, it } from 'vitest'; import type { IconProps } from './icon'; import * as kitIcons from './icons'; import { CheckIcon, SearchIcon } from './icons'; /** * `lucide-react` is a devDependency only — it is imported here to assert that * the inlined geometry still matches upstream. Nothing in `src/components/**` * may import it (ESLint enforces that), and it never reaches `dist/`. * * It is pinned to an **exact** version in `package.json` on purpose. With a * caret range a fresh install would pick up whatever upstream had redrawn since, * and a brand-new scaffold would greet its owner with a failing test it did * nothing to cause. Pinned, the comparison only speaks when someone * deliberately bumps the version — and then it is a decision to make, not a * break to paper over: copy the new geometry into `icons.tsx`, or keep ours and * say why here. */ import * as lucide from 'lucide-react'; /* Lucide's components are forwardRef exotics with their own prop type; all this comparison needs is "renders an SVG from these props". */ const upstreamIcons = lucide as unknown as Record>; /** Serialises the drawn shapes so two SVGs can be compared ignoring chrome. */ const geometryOf = (container: HTMLElement) => Array.from(container.querySelectorAll('svg > *')).map((node) => ({ tag: node.tagName.toLowerCase(), /* `key` is Lucide's internal React key and is not rendered, so whatever is left here is the actual drawing instruction. */ attrs: Object.fromEntries( Array.from(node.attributes).map((attr) => [attr.name, attr.value]) ), })); describe('icon set', () => { const names = Object.keys(kitIcons) as (keyof typeof kitIcons)[]; it('covers every icon the components import', () => { expect(names).toHaveLength(19); }); it.each(names)('%s draws the same geometry as lucide-react', (name) => { const KitIcon = kitIcons[name]; const LucideIcon = upstreamIcons[name]; expect(LucideIcon, `lucide-react has no ${name}`).toBeDefined(); const kit = render(); const upstream = render(); expect(geometryOf(kit.container)).toEqual(geometryOf(upstream.container)); }); it('renders the shared chassis attributes', () => { const { container } = render(); const svg = container.querySelector('svg')!; expect(svg).toHaveAttribute('viewBox', '0 0 24 24'); expect(svg).toHaveAttribute('stroke', 'currentColor'); expect(svg).toHaveAttribute('stroke-width', '2'); expect(svg).toHaveAttribute('fill', 'none'); expect(svg).toHaveAttribute('data-slot', 'icon'); }); it('is hidden from assistive tech by default', () => { const { container } = render(); expect(container.querySelector('svg')).toHaveAttribute('aria-hidden', 'true'); }); it.each([ ['aria-label', { 'aria-label': 'Search' }], ['role', { role: 'img' }], ])('drops aria-hidden once %s marks it meaningful', (_label, props) => { const { container } = render(); expect(container.querySelector('svg')).not.toHaveAttribute('aria-hidden'); }); it('accepts a numeric size and lets className through', () => { const { container } = render(); const svg = container.querySelector('svg')!; expect(svg).toHaveAttribute('width', '16'); expect(svg).toHaveAttribute('height', '16'); expect(svg).toHaveClass('size-4', 'text-primary'); }); });