diff --git a/.changeset/public-bags-retire.md b/.changeset/public-bags-retire.md new file mode 100644 index 00000000000..f6844a30dad --- /dev/null +++ b/.changeset/public-bags-retire.md @@ -0,0 +1,17 @@ +--- +"@primer/react": minor +--- + +Add data-component attributes and associated tests for: + +Radio +RadioGroup +RelativeTime +ScrollableRegion +SegmentedControl +Select +SideNav +SkeletonBox +SkeletonAvatar +SkeletonText +Spinner diff --git a/packages/react/src/Radio/Radio.test.tsx b/packages/react/src/Radio/Radio.test.tsx index 43635bbc10a..1a7b9756dcd 100644 --- a/packages/react/src/Radio/Radio.test.tsx +++ b/packages/react/src/Radio/Radio.test.tsx @@ -23,6 +23,12 @@ describe('Radio', () => { expect(radio).toBeDefined() }) + it('renders data-component attribute', () => { + const {getByRole} = render() + + expect(getByRole('radio')).toHaveAttribute('data-component', 'Radio') + }) + it('renders an unchecked radio by default', () => { const {getByRole} = render() diff --git a/packages/react/src/Radio/Radio.tsx b/packages/react/src/Radio/Radio.tsx index aef591a7bde..5ea79439a9f 100644 --- a/packages/react/src/Radio/Radio.tsx +++ b/packages/react/src/Radio/Radio.tsx @@ -80,6 +80,7 @@ const Radio = React.forwardRef( onChange={handleOnChange} className={clsx(className, sharedClasses.Input, classes.Radio)} {...rest} + data-component="Radio" /> ) }, diff --git a/packages/react/src/RadioGroup/RadioGroup.test.tsx b/packages/react/src/RadioGroup/RadioGroup.test.tsx index 1b6fc91ffb3..a1583e786e1 100644 --- a/packages/react/src/RadioGroup/RadioGroup.test.tsx +++ b/packages/react/src/RadioGroup/RadioGroup.test.tsx @@ -28,6 +28,25 @@ describe('RadioGroup', () => { vi.clearAllMocks() }) + it('renders data-component attributes', () => { + const {getByRole, getByText} = render( + + Choices + Pick one + Selection required + + + Choice one + + , + ) + + expect(getByRole('group')).toHaveAttribute('data-component', 'RadioGroup') + expect(getByText('Choices')).toHaveAttribute('data-component', 'RadioGroup.Label') + expect(getByText('Pick one')).toHaveAttribute('data-component', 'RadioGroup.Caption') + expect(document.querySelector('[data-component="RadioGroup.Validation"]')).toHaveTextContent('Selection required') + }) + it('renders a disabled group of inputs', () => { const {getAllByRole, getByRole} = render( diff --git a/packages/react/src/RadioGroup/RadioGroup.tsx b/packages/react/src/RadioGroup/RadioGroup.tsx index a86c58eb90e..0c107648f3d 100644 --- a/packages/react/src/RadioGroup/RadioGroup.tsx +++ b/packages/react/src/RadioGroup/RadioGroup.tsx @@ -44,7 +44,7 @@ const RadioGroup: FC> = ({children, dis }, }} > - + {children} diff --git a/packages/react/src/RelativeTime/RelativeTime.test.tsx b/packages/react/src/RelativeTime/RelativeTime.test.tsx index 4315a8532a0..ba9f48430f5 100644 --- a/packages/react/src/RelativeTime/RelativeTime.test.tsx +++ b/packages/react/src/RelativeTime/RelativeTime.test.tsx @@ -10,6 +10,13 @@ describe('RelativeTime', () => { expect(container.firstChild?.nodeName.toLowerCase()).toEqual('relative-time') }) + it('renders data-component attribute', () => { + const date = new Date('2024-03-07T12:22:48.123Z') + const {container} = render() + + expect(container.firstChild).toHaveAttribute('data-component', 'RelativeTime') + }) + it('renders a date inside', () => { const date = new Date('2024-03-07T12:22:48.123Z') const {container} = render() diff --git a/packages/react/src/RelativeTime/RelativeTime.tsx b/packages/react/src/RelativeTime/RelativeTime.tsx index 65ca48fd7d0..b42612beef2 100644 --- a/packages/react/src/RelativeTime/RelativeTime.tsx +++ b/packages/react/src/RelativeTime/RelativeTime.tsx @@ -8,7 +8,7 @@ const localeOptions: Intl.DateTimeFormatOptions = {month: 'short', day: 'numeric function RelativeTime({date, datetime, children, noTitle, ...props}: RelativeTimeProps) { if (datetime) date = new Date(datetime) return ( - + {children || date?.toLocaleDateString('en', localeOptions) || ''} ) diff --git a/packages/react/src/ScrollableRegion/ScrollableRegion.test.tsx b/packages/react/src/ScrollableRegion/ScrollableRegion.test.tsx index 0107b335721..d2823a5a79f 100644 --- a/packages/react/src/ScrollableRegion/ScrollableRegion.test.tsx +++ b/packages/react/src/ScrollableRegion/ScrollableRegion.test.tsx @@ -30,6 +30,16 @@ describe('ScrollableRegion', () => { window.ResizeObserver = originalResizeObserver }) + test('renders data-component attribute', () => { + render( + + Example content + , + ) + + expect(screen.getByTestId('container')).toHaveAttribute('data-component', 'ScrollableRegion') + }) + test('does not render with region props by default', () => { render( diff --git a/packages/react/src/ScrollableRegion/ScrollableRegion.tsx b/packages/react/src/ScrollableRegion/ScrollableRegion.tsx index b177d76f399..6f464aea9a3 100644 --- a/packages/react/src/ScrollableRegion/ScrollableRegion.tsx +++ b/packages/react/src/ScrollableRegion/ScrollableRegion.tsx @@ -34,7 +34,13 @@ function ScrollableRegion({ : {} return ( -
+
{children}
) diff --git a/packages/react/src/SegmentedControl/SegmentedControl.test.tsx b/packages/react/src/SegmentedControl/SegmentedControl.test.tsx index 4c99c940cdc..351d58a0d22 100644 --- a/packages/react/src/SegmentedControl/SegmentedControl.test.tsx +++ b/packages/react/src/SegmentedControl/SegmentedControl.test.tsx @@ -34,6 +34,44 @@ const segmentData = [ describe('SegmentedControl', () => { implementsClassName(props => , classes.SegmentedControl) + it('renders data-component attribute on the root', () => { + const {getByRole} = render( + + {segmentData.map(({label}) => ( + {label} + ))} + , + ) + + expect(getByRole('list')).toHaveAttribute('data-component', 'SegmentedControl') + }) + + it('renders data-component attribute on segmented control buttons', () => { + const {getByRole} = render( + + Preview + , + ) + + expect(getByRole('button', {name: 'Preview'}).closest('li')).toHaveAttribute( + 'data-component', + 'SegmentedControl.Button', + ) + }) + + it('renders data-component attribute on segmented control icon buttons', () => { + const {getByRole} = render( + + } aria-label="Preview" /> + , + ) + + expect(getByRole('button', {name: 'Preview'}).closest('li')).toHaveAttribute( + 'data-component', + 'SegmentedControl.IconButton', + ) + }) + it('renders with a selected segment - controlled', () => { const {getByText} = render( diff --git a/packages/react/src/SegmentedControl/SegmentedControl.tsx b/packages/react/src/SegmentedControl/SegmentedControl.tsx index fa2c7e0614f..707530b01f8 100644 --- a/packages/react/src/SegmentedControl/SegmentedControl.tsx +++ b/packages/react/src/SegmentedControl/SegmentedControl.tsx @@ -168,6 +168,7 @@ const Root: React.FC> = ({ {...getResponsiveAttributes('variant', variant)} data-size={size} {...rest} + data-component="SegmentedControl" > {React.Children.map(children, (child, index) => { // Not a valid child element - skip rendering child diff --git a/packages/react/src/SegmentedControl/SegmentedControlButton.tsx b/packages/react/src/SegmentedControl/SegmentedControlButton.tsx index 075bc8f3e6e..287099e1d1f 100644 --- a/packages/react/src/SegmentedControl/SegmentedControlButton.tsx +++ b/packages/react/src/SegmentedControl/SegmentedControlButton.tsx @@ -44,7 +44,11 @@ const SegmentedControlButton: FCWithSlotMarker +