From fc8159eb130eb7c46e349451e1e87caa057d606c Mon Sep 17 00:00:00 2001 From: sahillpaul Date: Wed, 28 Oct 2026 10:15:06 +0530 Subject: [PATCH 1/2] feat(icons): add PinIcon and OutlinedPinIcon Signed-off-by: sahillpaul --- src/__testing__/PinIcon.test.tsx | 67 +++++++++++++++++++++++++++++++ src/icons/Pin/OutlinedPinIcon.tsx | 25 ++++++++++++ src/icons/Pin/PinIcon.tsx | 25 ++++++++++++ src/icons/Pin/index.ts | 2 + src/icons/index.ts | 1 + 5 files changed, 120 insertions(+) create mode 100644 src/__testing__/PinIcon.test.tsx create mode 100644 src/icons/Pin/OutlinedPinIcon.tsx create mode 100644 src/icons/Pin/PinIcon.tsx create mode 100644 src/icons/Pin/index.ts diff --git a/src/__testing__/PinIcon.test.tsx b/src/__testing__/PinIcon.test.tsx new file mode 100644 index 000000000..e6acb3557 --- /dev/null +++ b/src/__testing__/PinIcon.test.tsx @@ -0,0 +1,67 @@ +import { render } from '@testing-library/react'; +import React from 'react'; +import { OutlinedPinIcon, PinIcon } from '../icons'; +import { SistentThemeProviderWithoutBaseLine } from '../theme'; + +function renderWithTheme(ui: React.ReactElement) { + return render({ui}); +} + +const icons = [ + { name: 'PinIcon', Component: PinIcon }, + { name: 'OutlinedPinIcon', Component: OutlinedPinIcon } +]; + +describe.each(icons)('$name', ({ Component }) => { + it('renders an with viewBox "0 0 24 24"', () => { + const { container } = renderWithTheme(); + const svg = container.querySelector('svg'); + expect(svg).not.toBeNull(); + expect(svg!.getAttribute('viewBox')).toBe('0 0 24 24'); + }); + + it('renders with default width and height "24", and default fill "currentColor"', () => { + const { container } = renderWithTheme(); + const svg = container.querySelector('svg'); + expect(svg).not.toBeNull(); + expect(svg!.getAttribute('width')).toBe('24'); + expect(svg!.getAttribute('height')).toBe('24'); + expect(svg!.getAttribute('fill')).toBe('currentColor'); + }); + + it('applies custom width, height, and fill props', () => { + const { container } = renderWithTheme(); + const svg = container.querySelector('svg'); + expect(svg).not.toBeNull(); + expect(svg!.getAttribute('width')).toBe('32'); + expect(svg!.getAttribute('height')).toBe('32'); + expect(svg!.getAttribute('fill')).toBe('#ff0000'); + }); + + it('forwards extra props like aria-hidden', () => { + const { container } = renderWithTheme(); + const svg = container.querySelector('svg'); + expect(svg).not.toBeNull(); + expect(svg!.getAttribute('aria-hidden')).toBe('true'); + }); +}); + +describe('PinIcon and OutlinedPinIcon path comparison', () => { + it('renders different path "d" attributes for the two icons', () => { + const { container: pinContainer } = renderWithTheme(); + const { container: outlinedContainer } = renderWithTheme(); + + const pinPath = pinContainer.querySelector('path'); + const outlinedPath = outlinedContainer.querySelector('path'); + + expect(pinPath).not.toBeNull(); + expect(outlinedPath).not.toBeNull(); + + const pinD = pinPath!.getAttribute('d'); + const outlinedD = outlinedPath!.getAttribute('d'); + + expect(pinD).toBeTruthy(); + expect(outlinedD).toBeTruthy(); + expect(pinD).not.toBe(outlinedD); + }); +}); diff --git a/src/icons/Pin/OutlinedPinIcon.tsx b/src/icons/Pin/OutlinedPinIcon.tsx new file mode 100644 index 000000000..8759c6bca --- /dev/null +++ b/src/icons/Pin/OutlinedPinIcon.tsx @@ -0,0 +1,25 @@ +import { DEFAULT_FILL_NONE, DEFAULT_HEIGHT, DEFAULT_WIDTH } from '../../constants/constants'; +import { IconProps } from '../types'; + +export const OutlinedPinIcon = ({ + width = DEFAULT_WIDTH, + height = DEFAULT_HEIGHT, + fill = DEFAULT_FILL_NONE, + ...props +}: IconProps): JSX.Element => { + return ( + + + + ); +}; + +export default OutlinedPinIcon; + diff --git a/src/icons/Pin/PinIcon.tsx b/src/icons/Pin/PinIcon.tsx new file mode 100644 index 000000000..e0c5949be --- /dev/null +++ b/src/icons/Pin/PinIcon.tsx @@ -0,0 +1,25 @@ +import { DEFAULT_FILL_NONE, DEFAULT_HEIGHT, DEFAULT_WIDTH } from '../../constants/constants'; +import { IconProps } from '../types'; + +export const PinIcon = ({ + width = DEFAULT_WIDTH, + height = DEFAULT_HEIGHT, + fill = DEFAULT_FILL_NONE, + ...props +}: IconProps): JSX.Element => { + return ( + + + + ); +}; + +export default PinIcon; + diff --git a/src/icons/Pin/index.ts b/src/icons/Pin/index.ts new file mode 100644 index 000000000..617020a24 --- /dev/null +++ b/src/icons/Pin/index.ts @@ -0,0 +1,2 @@ +export { default as OutlinedPinIcon } from './OutlinedPinIcon'; +export { default as PinIcon } from './PinIcon'; diff --git a/src/icons/index.ts b/src/icons/index.ts index 8299cb93e..a465d29ed 100644 --- a/src/icons/index.ts +++ b/src/icons/index.ts @@ -149,6 +149,7 @@ export * from './Pattern'; export * from './People'; export * from './Performance'; export * from './Person'; +export * from './Pin'; export * from './PlayArrow'; export * from './Pod'; export * from './Poll'; From 8ebcc8731ceda810bab50ba8f4656174000d974e Mon Sep 17 00:00:00 2001 From: sahillpaul Date: Thu, 29 Oct 2026 08:59:41 +0530 Subject: [PATCH 2/2] fix(icons): forward refs to the svg in PinIcon and OutlinedPinIcon Signed-off-by: sahillpaul --- src/__testing__/PinIcon.test.tsx | 25 ++++++++++++++++++- src/icons/Pin/OutlinedPinIcon.tsx | 40 +++++++++++++++---------------- src/icons/Pin/PinIcon.tsx | 40 +++++++++++++++---------------- 3 files changed, 64 insertions(+), 41 deletions(-) diff --git a/src/__testing__/PinIcon.test.tsx b/src/__testing__/PinIcon.test.tsx index e6acb3557..d9e2c114b 100644 --- a/src/__testing__/PinIcon.test.tsx +++ b/src/__testing__/PinIcon.test.tsx @@ -1,5 +1,6 @@ -import { render } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import React from 'react'; +import { Tooltip } from '../base/Tooltip'; import { OutlinedPinIcon, PinIcon } from '../icons'; import { SistentThemeProviderWithoutBaseLine } from '../theme'; @@ -44,6 +45,28 @@ describe.each(icons)('$name', ({ Component }) => { expect(svg).not.toBeNull(); expect(svg!.getAttribute('aria-hidden')).toBe('true'); }); + + it('attaches a forwarded ref to the rendered element', () => { + const ref = React.createRef(); + const { container } = renderWithTheme(); + const svg = container.querySelector('svg'); + expect(ref.current).toBeInstanceOf(SVGSVGElement); + expect(ref.current).toBe(svg); + }); + + it('opens MUI Tooltip when the icon is its direct child', async () => { + const { container } = renderWithTheme( + + + + ); + const svg = container.querySelector('svg'); + expect(svg).not.toBeNull(); + + fireEvent.mouseOver(svg!); + const tooltipText = await screen.findByText('Pin tooltip'); + expect(tooltipText).not.toBeNull(); + }); }); describe('PinIcon and OutlinedPinIcon path comparison', () => { diff --git a/src/icons/Pin/OutlinedPinIcon.tsx b/src/icons/Pin/OutlinedPinIcon.tsx index 8759c6bca..0d664f951 100644 --- a/src/icons/Pin/OutlinedPinIcon.tsx +++ b/src/icons/Pin/OutlinedPinIcon.tsx @@ -1,25 +1,25 @@ +import React from 'react'; import { DEFAULT_FILL_NONE, DEFAULT_HEIGHT, DEFAULT_WIDTH } from '../../constants/constants'; import { IconProps } from '../types'; -export const OutlinedPinIcon = ({ - width = DEFAULT_WIDTH, - height = DEFAULT_HEIGHT, - fill = DEFAULT_FILL_NONE, - ...props -}: IconProps): JSX.Element => { - return ( - - - - ); -}; +export const OutlinedPinIcon = React.forwardRef( + ({ width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT, fill = DEFAULT_FILL_NONE, ...props }, ref) => { + return ( + + + + ); + } +); -export default OutlinedPinIcon; +OutlinedPinIcon.displayName = 'OutlinedPinIcon'; +export default OutlinedPinIcon; diff --git a/src/icons/Pin/PinIcon.tsx b/src/icons/Pin/PinIcon.tsx index e0c5949be..37de4762a 100644 --- a/src/icons/Pin/PinIcon.tsx +++ b/src/icons/Pin/PinIcon.tsx @@ -1,25 +1,25 @@ +import React from 'react'; import { DEFAULT_FILL_NONE, DEFAULT_HEIGHT, DEFAULT_WIDTH } from '../../constants/constants'; import { IconProps } from '../types'; -export const PinIcon = ({ - width = DEFAULT_WIDTH, - height = DEFAULT_HEIGHT, - fill = DEFAULT_FILL_NONE, - ...props -}: IconProps): JSX.Element => { - return ( - - - - ); -}; +export const PinIcon = React.forwardRef( + ({ width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT, fill = DEFAULT_FILL_NONE, ...props }, ref) => { + return ( + + + + ); + } +); -export default PinIcon; +PinIcon.displayName = 'PinIcon'; +export default PinIcon;