test(packages/shared): cover buildLucideIconForReact (#4872)

buildLucideIconForReact is the entry point both lucide-react and
lucide-react-native use to build an icon, and its whole job is renaming
the hyphenated SVG attributes to the spellings React expects. Nothing
tested that rename directly: the other build helpers have specs in
packages/icons/tests, and this one had none anywhere in the repo.

The spec pins the five renames it applies, checks that attributes React
already accepts are left alone, that aria-hidden stays hyphenated, that
vector-effect is renamed on child nodes for a non-scaling stroke, and
that a caller's own attributeNames are merged without displacing the
React spellings.

Verified that the spec is not vacuous: removing the class -> className
entry from the rename map fails two of the five tests.

Co-authored-by: Vugar Bakhishov <vugarbbakhishov-hub@users.noreply.github.com>
Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
This commit is contained in:
Vugar Bakhishov
2026-09-18 14:08:40 +02:00
committed by GitHub
co-authored by Vugar Bakhishov Eric Fennis
parent e32a4a2c10
commit 99c1037550
@@ -0,0 +1,68 @@
import { describe, expect, it } from 'vitest';
import buildLucideIconForReact from '../src/build/buildLucideIconForReact';
import defaultReactAttributes from '../src/build/defaultReactAttributes';
import type { LucideIconData, LucideIconNode, SVGProps } from '../src/build/types';
const icon: LucideIconData = {
name: 'house',
size: 24,
node: [['path', { d: 'M0 0h1' }]],
};
const childrenOf = (node: LucideIconNode) => (node.at(2) ?? []) as LucideIconNode[];
describe('buildLucideIconForReact', () => {
it('uses the React spelling for the hyphenated SVG attributes', () => {
const [, attributes] = buildLucideIconForReact(icon);
expect(attributes).toMatchObject({
strokeWidth: defaultReactAttributes.strokeWidth,
strokeLinecap: defaultReactAttributes.strokeLinecap,
strokeLinejoin: defaultReactAttributes.strokeLinejoin,
className: 'lucide lucide-house',
});
expect(attributes).not.toHaveProperty('stroke-width');
expect(attributes).not.toHaveProperty('stroke-linecap');
expect(attributes).not.toHaveProperty('stroke-linejoin');
expect(attributes).not.toHaveProperty('class');
});
it('leaves the attributes React already accepts untouched', () => {
const [, attributes] = buildLucideIconForReact(icon);
expect(attributes).toMatchObject({
xmlns: defaultReactAttributes.xmlns,
width: defaultReactAttributes.width,
height: defaultReactAttributes.height,
viewBox: defaultReactAttributes.viewBox,
fill: defaultReactAttributes.fill,
stroke: defaultReactAttributes.stroke,
});
});
it('keeps aria-hidden hyphenated, which is how React expects it', () => {
const [, attributes] = buildLucideIconForReact(icon, { hasA11yProp: false });
expect(attributes['aria-hidden']).toBe('true');
expect(attributes).not.toHaveProperty('ariaHidden');
});
it('renames vector-effect on the child nodes for a non-scaling stroke', () => {
const children = childrenOf(buildLucideIconForReact(icon, { nonScalingStroke: true }));
const childAttributes = (children[0]?.[1] ?? {}) as SVGProps;
expect(childAttributes.vectorEffect).toBe('non-scaling-stroke');
expect(childAttributes).not.toHaveProperty('vector-effect');
});
it('merges caller attribute names but keeps its own React spellings', () => {
const [, attributes] = buildLucideIconForReact(icon, {
attributeNames: { fill: 'fillColor', class: 'cssClass' },
});
expect(attributes.fillColor).toBe(defaultReactAttributes.fill);
expect(attributes.className).toBe('lucide lucide-house');
expect(attributes).not.toHaveProperty('cssClass');
});
});