import React from 'react';
import { render, screen } from '@testing-library/react';
import { createGlyphComponent } from './createGlyphComponent';
import { createIconComponent } from './createIconComponent';
import * as generatedGlyphs from './generated';
import { Size } from './glyphCommon';
import { isComponentGlyph } from './isComponentGlyph';
import {
AnotherCustomGlyph,
createTestSVGRComponent,
CustomSVGRGlyph,
} from './testUtils';
// Create glyph components from the SVGR components
const customGlyphs = {
CustomGlyph: createGlyphComponent('CustomGlyph', CustomSVGRGlyph),
AnotherGlyph: createGlyphComponent('AnotherGlyph', AnotherCustomGlyph),
};
describe('packages/Icon/createIconComponent', () => {
describe('basic functionality', () => {
const IconComponent = createIconComponent(customGlyphs);
test('returns a function', () => {
expect(typeof IconComponent).toBe('function');
});
test('returned function has the displayName: "Icon"', () => {
expect(IconComponent.displayName).toBe('Icon');
});
test('returned function has the property: `isGlyph`', () => {
expect(IconComponent).toHaveProperty('isGlyph');
expect(IconComponent.isGlyph).toBeTruthy();
});
test('returned function passes `isComponentGlyph`', () => {
expect(isComponentGlyph(IconComponent)).toBeTruthy();
});
});
describe('rendering glyphs', () => {
const IconComponent = createIconComponent(customGlyphs);
test('renders the correct glyph when passed a valid glyph name', () => {
render();
const glyph = screen.getByTestId('custom-svgr-glyph');
expect(glyph).toBeInTheDocument();
expect(glyph.nodeName.toLowerCase()).toBe('svg');
});
test('renders different glyphs based on glyph prop', () => {
const { rerender } = render();
expect(screen.getByTestId('custom-svgr-glyph')).toBeInTheDocument();
rerender();
expect(screen.getByTestId('another-custom-glyph')).toBeInTheDocument();
});
test('logs an error and renders nothing when glyph does not exist', () => {
const consoleSpy = jest
.spyOn(console, 'error')
.mockImplementation(() => {});
const { container } = render();
// Should log an error
expect(consoleSpy).toHaveBeenCalledWith(
'Error in Icon',
'Could not find glyph named "NonExistentGlyph" in the icon set.',
undefined,
);
// Should not render an SVG
const svg = container.querySelector('svg');
expect(svg).not.toBeInTheDocument();
consoleSpy.mockRestore();
});
test('suggests near match when glyph name has incorrect casing', () => {
const consoleSpy = jest
.spyOn(console, 'error')
.mockImplementation(() => {});
render();
expect(consoleSpy).toHaveBeenCalledWith(
'Error in Icon',
'Could not find glyph named "custom-glyph" in the icon set.',
'Did you mean "CustomGlyph?"',
);
consoleSpy.mockRestore();
});
});
describe('custom SVG support', () => {
const RawSVGGlyph = createTestSVGRComponent('raw-svg-glyph');
const customSVGGlyphs = {
RawSVG: createGlyphComponent('RawSVG', RawSVGGlyph),
};
const IconComponent = createIconComponent(customSVGGlyphs);
test('renders custom SVG components correctly', () => {
render();
const glyph = screen.getByTestId('raw-svg-glyph');
expect(glyph).toBeInTheDocument();
expect(glyph.nodeName.toLowerCase()).toBe('svg');
});
test('applies size prop to custom SVGs', () => {
render();
const glyph = screen.getByTestId('raw-svg-glyph');
expect(glyph).toHaveAttribute('height', '32');
expect(glyph).toHaveAttribute('width', '32');
});
test('applies Size enum to custom SVGs', () => {
render();
const glyph = screen.getByTestId('raw-svg-glyph');
expect(glyph).toHaveAttribute('height', '20');
expect(glyph).toHaveAttribute('width', '20');
});
});
describe('raw SVGR component support (auto-wrapped)', () => {
// Pass raw SVGR components directly without wrapping with createGlyphComponent
const RawSVGRGlyph = createTestSVGRComponent('raw-svgr-glyph');
const IconComponent = createIconComponent({ RawSVGR: RawSVGRGlyph });
test('automatically wraps raw SVGR components with createGlyphComponent', () => {
render();
const glyph = screen.getByTestId('raw-svgr-glyph');
expect(glyph).toBeInTheDocument();
expect(glyph.nodeName.toLowerCase()).toBe('svg');
});
test('applies size prop to auto-wrapped SVGR components', () => {
render();
const glyph = screen.getByTestId('raw-svgr-glyph');
expect(glyph).toHaveAttribute('height', '28');
expect(glyph).toHaveAttribute('width', '28');
});
test('applies Size enum to auto-wrapped SVGR components', () => {
render();
const glyph = screen.getByTestId('raw-svgr-glyph');
expect(glyph).toHaveAttribute('height', '24');
expect(glyph).toHaveAttribute('width', '24');
});
test('applies className to auto-wrapped SVGR components', () => {
render();
const glyph = screen.getByTestId('raw-svgr-glyph');
expect(glyph).toHaveClass('my-raw-class');
});
test('applies fill to auto-wrapped SVGR components', () => {
render();
const glyph = screen.getByTestId('raw-svgr-glyph');
// Fill is applied as a CSS color via an emotion-generated class
expect(glyph.classList.length).toBeGreaterThan(0);
});
test('applies accessibility props to auto-wrapped SVGR components', () => {
render();
const glyph = screen.getByTestId('raw-svgr-glyph');
expect(glyph).toHaveAttribute('role', 'img');
// Default aria-label is generated from glyph name
expect(glyph).toHaveAttribute('aria-label', 'Raw SVGR Icon');
});
});
describe('className prop', () => {
const IconComponent = createIconComponent(customGlyphs);
test('applies className to glyph SVG element', () => {
render();
const glyph = screen.getByTestId('custom-svgr-glyph');
expect(glyph).toHaveClass('custom-class');
});
});
describe('className prop with custom SVGs', () => {
const RawSVGGlyph = createTestSVGRComponent('raw-svg-for-class');
const customSVGGlyphs = {
RawSVG: createGlyphComponent('RawSVG', RawSVGGlyph),
};
const IconComponent = createIconComponent(customSVGGlyphs);
test('applies className to custom SVG components', () => {
render();
const glyph = screen.getByTestId('raw-svg-for-class');
expect(glyph).toHaveClass('my-custom-class');
});
});
describe('size prop', () => {
const IconComponent = createIconComponent(customGlyphs);
test('applies numeric size to glyph', () => {
render();
const glyph = screen.getByTestId('custom-svgr-glyph');
expect(glyph).toHaveAttribute('height', '24');
expect(glyph).toHaveAttribute('width', '24');
});
test('applies Size.Small correctly', () => {
render();
const glyph = screen.getByTestId('custom-svgr-glyph');
expect(glyph).toHaveAttribute('height', '14');
expect(glyph).toHaveAttribute('width', '14');
});
test('applies Size.Default correctly', () => {
render();
const glyph = screen.getByTestId('custom-svgr-glyph');
expect(glyph).toHaveAttribute('height', '16');
expect(glyph).toHaveAttribute('width', '16');
});
test('applies Size.Large correctly', () => {
render();
const glyph = screen.getByTestId('custom-svgr-glyph');
expect(glyph).toHaveAttribute('height', '20');
expect(glyph).toHaveAttribute('width', '20');
});
test('applies Size.XLarge correctly', () => {
render();
const glyph = screen.getByTestId('custom-svgr-glyph');
expect(glyph).toHaveAttribute('height', '24');
expect(glyph).toHaveAttribute('width', '24');
});
test('uses default size when size prop is not provided', () => {
render();
const glyph = screen.getByTestId('custom-svgr-glyph');
expect(glyph).toHaveAttribute('height', '16');
expect(glyph).toHaveAttribute('width', '16');
});
});
describe('accessibility props', () => {
const IconComponent = createIconComponent(customGlyphs);
test('applies role="img" by default', () => {
render();
const glyph = screen.getByTestId('custom-svgr-glyph');
expect(glyph).toHaveAttribute('role', 'img');
});
test('applies role="presentation" when specified', () => {
render();
const glyph = screen.getByTestId('custom-svgr-glyph');
expect(glyph).toHaveAttribute('role', 'presentation');
expect(glyph).toHaveAttribute('aria-hidden', 'true');
});
test('generates default aria-label when no accessibility props provided', () => {
render();
const glyph = screen.getByTestId('custom-svgr-glyph');
expect(glyph).toHaveAttribute('aria-label', 'Custom Glyph Icon');
});
test('applies custom aria-label when provided', () => {
render(
,
);
const glyph = screen.getByTestId('custom-svgr-glyph');
expect(glyph).toHaveAttribute('aria-label', 'My Custom Label');
});
test('applies aria-labelledby when provided', () => {
render(
,
);
const glyph = screen.getByTestId('custom-svgr-glyph');
expect(glyph).toHaveAttribute('aria-labelledby', 'my-label-id');
});
});
describe('fill prop', () => {
const IconComponent = createIconComponent(customGlyphs);
test('applies fill as CSS color via className', () => {
render();
const glyph = screen.getByTestId('custom-svgr-glyph');
// Fill is applied as a CSS color via an emotion-generated class
expect(glyph).toHaveAttribute('class');
expect(glyph.classList.length).toBeGreaterThan(0);
});
test('applies fill alongside className', () => {
render(
,
);
const glyph = screen.getByTestId('custom-svgr-glyph');
expect(glyph).toHaveClass('custom-class');
// Fill adds an emotion class in addition to custom-class
expect(glyph.classList.length).toBeGreaterThan(1);
});
});
describe('fill prop with generated glyphs', () => {
const IconComponent = createIconComponent(generatedGlyphs);
test('applies fill as CSS color to generated glyph', () => {
render();
const glyph = screen.getByRole('img');
// Fill is applied as a CSS color via an emotion-generated class
expect(glyph).toHaveAttribute('class');
expect(glyph.classList.length).toBeGreaterThan(0);
});
});
describe('combined props with generated glyphs', () => {
const IconComponent = createIconComponent(generatedGlyphs);
test('applies all props correctly together', () => {
render(
,
);
const glyph = screen.getByRole('img');
expect(glyph).toHaveAttribute('height', '24');
expect(glyph).toHaveAttribute('width', '24');
expect(glyph).toHaveClass('combined-class');
// Check title
const titleElement = glyph.querySelector('title');
expect(titleElement).toBeInTheDocument();
expect(titleElement?.textContent).toBe('Combined Title');
// Check aria-labelledby points to title
expect(glyph.getAttribute('aria-labelledby')).toBe(titleElement?.id);
// Fill adds an emotion class in addition to combined-class
expect(glyph.classList.length).toBeGreaterThan(1);
});
});
describe('combined props with custom SVGR glyphs', () => {
const IconComponent = createIconComponent(customGlyphs);
test('applies size, className, and fill together', () => {
render(
,
);
const glyph = screen.getByTestId('custom-svgr-glyph');
expect(glyph).toHaveAttribute('height', '32');
expect(glyph).toHaveAttribute('width', '32');
expect(glyph).toHaveClass('combined-custom-class');
// Fill adds an emotion class in addition to combined-custom-class
expect(glyph.classList.length).toBeGreaterThan(1);
});
test('applies accessibility props with className', () => {
render(
,
);
const glyph = screen.getByTestId('custom-svgr-glyph');
expect(glyph).toHaveClass('accessible-class');
expect(glyph).toHaveAttribute('aria-label', 'Accessible Custom Icon');
});
});
});