From 99c10375508fdade330f91eecf3e616693b68cad Mon Sep 17 00:00:00 2001 From: Vugar Bakhishov Date: Fri, 18 Sep 2026 16:08:40 +0400 Subject: [PATCH] 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 Co-authored-by: Eric Fennis --- .../tests/buildLucideIconForReact.spec.ts | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 packages/shared/tests/buildLucideIconForReact.spec.ts diff --git a/packages/shared/tests/buildLucideIconForReact.spec.ts b/packages/shared/tests/buildLucideIconForReact.spec.ts new file mode 100644 index 000000000..8207c5ec3 --- /dev/null +++ b/packages/shared/tests/buildLucideIconForReact.spec.ts @@ -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'); + }); +});