diff --git a/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.test.ts b/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.test.ts index e8c5e0a8b9..cfe6236b95 100644 --- a/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.test.ts +++ b/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createElement } from 'react'; +import { createElement, memo, forwardRef } from 'react'; import { DefaultIconsApi } from './DefaultIconsApi'; describe('DefaultIconsApi', () => { @@ -107,4 +107,31 @@ describe('DefaultIconsApi', () => { expect(warnSpy).not.toHaveBeenCalled(); }); + + it('should treat React.memo components as IconComponent', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const MemoIcon = memo(() => createElement('svg')); + const api = new DefaultIconsApi({ myIcon: MemoIcon }); + + const el = api.icon('myIcon'); + expect(el).toBeTruthy(); + // @ts-expect-error accessing internal React element structure + expect(el.type).toBe(MemoIcon); + + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('myIcon')); + }); + + it('should treat React.forwardRef components as IconComponent', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const RefIcon = forwardRef(() => createElement('svg')); + // @ts-expect-error forwardRef is not strictly IconComponent but should be handled + const api = new DefaultIconsApi({ myIcon: RefIcon }); + + const el = api.icon('myIcon'); + expect(el).toBeTruthy(); + // @ts-expect-error accessing internal React element structure + expect(el.type).toBe(RefIcon); + + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('myIcon')); + }); }); diff --git a/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.ts b/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.ts index 49fde51bf2..cc5ef69b6f 100644 --- a/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.ts +++ b/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.ts @@ -19,7 +19,7 @@ import { IconElement, IconsApi, } from '@backstage/frontend-plugin-api'; -import { createElement } from 'react'; +import { createElement, isValidElement } from 'react'; /** * Implementation for the {@link IconsApi} @@ -35,11 +35,11 @@ export class DefaultIconsApi implements IconsApi { this.#icons = new Map( Object.entries(icons).map(([key, value]) => { - if (typeof value === 'function') { - deprecatedKeys.push(key); - return [key, createElement(value)]; + if (value === null || isValidElement(value)) { + return [key, value]; } - return [key, value]; + deprecatedKeys.push(key); + return [key, createElement(value as IconComponent)]; }), );