diff --git a/.changeset/quick-ants-lead.md b/.changeset/quick-ants-lead.md new file mode 100644 index 00000000000..2189997b67c --- /dev/null +++ b/.changeset/quick-ants-lead.md @@ -0,0 +1,5 @@ +--- +"@primer/react": minor +--- + +Add data-component attributes and associated tests for ActionMenu, AnchoredOverlay, Autocomplete, and NavList diff --git a/packages/react/src/ActionMenu/ActionMenu.test.tsx b/packages/react/src/ActionMenu/ActionMenu.test.tsx index 09303b7923b..0d7885785ee 100644 --- a/packages/react/src/ActionMenu/ActionMenu.test.tsx +++ b/packages/react/src/ActionMenu/ActionMenu.test.tsx @@ -180,6 +180,19 @@ function ExampleWithSubmenus(): JSX.Element { describe('ActionMenu', () => { implementsClassName(ActionMenu.Button) + it('renders data-component attributes for ActionMenu parts', async () => { + const component = HTMLRender() + const user = userEvent.setup() + + const trigger = component.getByRole('button', {name: 'Toggle Menu'}) + expect(trigger).toHaveAttribute('data-component', 'ActionMenu.Button') + + await user.click(trigger) + + expect(component.baseElement.querySelector('[data-component="ActionMenu.Overlay"]')).not.toBeNull() + expect(component.baseElement.querySelector('[data-component="AnchoredOverlay"]')).toBeNull() + }) + it('should open Menu on MenuButton click', async () => { const component = HTMLRender() const button = component.getByRole('button') @@ -778,7 +791,7 @@ describe('ActionMenu', () => { const initialAnchor = component.getByRole('button', {name: 'Open menu'}) await user.click(initialAnchor) - const overlay = component.baseElement.querySelector('[data-component="AnchoredOverlay"]') as HTMLElement + const overlay = component.baseElement.querySelector('[data-component="ActionMenu.Overlay"]') as HTMLElement expect(overlay).not.toBeNull() const initialAnchorName = initialAnchor.style.getPropertyValue('anchor-name') diff --git a/packages/react/src/ActionMenu/ActionMenu.tsx b/packages/react/src/ActionMenu/ActionMenu.tsx index 7762d0307ee..99291b1888c 100644 --- a/packages/react/src/ActionMenu/ActionMenu.tsx +++ b/packages/react/src/ActionMenu/ActionMenu.tsx @@ -245,6 +245,7 @@ const Anchor: WithSlotMarker< className: clsx(anchorProps.className, child.props.className), onClick: onButtonClick, onKeyDown: onButtonKeyDown, + 'data-component': child.props['data-component'] ?? 'ActionMenu.Anchor', })} ) @@ -256,7 +257,7 @@ export type ActionMenuButtonProps = ButtonProps const MenuButton = React.forwardRef(({...props}, anchorRef) => { return ( - } + variant={{regular: 'anchored', narrow: 'fullscreen'}} + > +
content
+ + + , + ) + + expect(baseElement.querySelector('[data-component="AnchoredOverlay.Anchor"]')).toBeInTheDocument() + expect(baseElement.querySelector('[data-component="AnchoredOverlay.CloseButton"]')).toBeInTheDocument() + }) + it('should support a `ref` through `overlayProps` on the overlay element', () => { const ref = createRef() diff --git a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx index a507313e784..d87ff3efc77 100644 --- a/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx +++ b/packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx @@ -60,6 +60,14 @@ export type AnchoredOverlayWrapperAnchorProps = | AnchoredOverlayPropsWithoutAnchor interface AnchoredOverlayBaseProps extends Pick { + /** + * Props that will be merged into the props passed to `renderAnchor`. + * Useful for overriding the anchor `data-component` in composite components. + */ + anchorProps?: Omit, 'aria-label' | 'aria-labelledby'> & { + 'data-component'?: string + } + /** * Determines whether the overlay portion of the component should be shown or not */ @@ -143,6 +151,7 @@ const defaultCloseButtonProps: Partial = {} */ export const AnchoredOverlay: React.FC> = ({ renderAnchor, + anchorProps, anchorRef: externalAnchorRef, anchorId: externalAnchorId, children, @@ -357,6 +366,7 @@ export const AnchoredOverlay: React.FC { )) + + it('renders data-component attributes for Autocomplete parts when menu is shown', async () => { + const user = userEvent.setup() + const {container} = render( + , + ) + + const input = container.querySelector('#autocompleteInput') as HTMLInputElement + expect(input).toHaveAttribute('data-component', 'Autocomplete.Input') + + await user.type(input, 'z') + + expect(container.querySelector('[data-component="Autocomplete.Overlay"]')).toBeInTheDocument() + expect(container.querySelector('[data-component="Autocomplete.Menu"]')).toBeInTheDocument() + }) + it('calls onChange', async () => { const user = userEvent.setup() const onChangeMock = vi.fn() diff --git a/packages/react/src/Autocomplete/AutocompleteInput.tsx b/packages/react/src/Autocomplete/AutocompleteInput.tsx index ef1278451a1..c12898f15f4 100644 --- a/packages/react/src/Autocomplete/AutocompleteInput.tsx +++ b/packages/react/src/Autocomplete/AutocompleteInput.tsx @@ -170,6 +170,7 @@ const AutocompleteInput = React.forwardRef( autoComplete="off" id={id} {...props} + data-component="Autocomplete.Input" /> ) }, diff --git a/packages/react/src/Autocomplete/AutocompleteMenu.tsx b/packages/react/src/Autocomplete/AutocompleteMenu.tsx index 1e53386750b..9d088ece9e9 100644 --- a/packages/react/src/Autocomplete/AutocompleteMenu.tsx +++ b/packages/react/src/Autocomplete/AutocompleteMenu.tsx @@ -359,6 +359,7 @@ function AutocompleteMenu(props: AutocompleteMe
{allItemsToRender.length ? ( {children} diff --git a/packages/react/src/NavList/NavList.test.tsx b/packages/react/src/NavList/NavList.test.tsx index 05f92549307..0ece5c490e8 100644 --- a/packages/react/src/NavList/NavList.test.tsx +++ b/packages/react/src/NavList/NavList.test.tsx @@ -23,6 +23,27 @@ const NextJSLikeLink = React.forwardRef( describe('NavList', () => { implementsClassName(NavList) + it('renders data-component attributes for NavList and NavList.SubNav', () => { + const {container} = render( + + Item 1 + + Item 2 + + Sub Item 1 + + + , + ) + + const nav = container.querySelector('nav') + expect(nav).toBeInTheDocument() + expect(nav).toHaveAttribute('data-component', 'NavList') + + const subNav = container.querySelector('[data-component="NavList.SubNav"]') + expect(subNav).toBeInTheDocument() + }) + it('supports TrailingAction', async () => { const {getByRole} = render( diff --git a/packages/react/src/NavList/NavList.tsx b/packages/react/src/NavList/NavList.tsx index 524281f4018..b86a48da3a6 100644 --- a/packages/react/src/NavList/NavList.tsx +++ b/packages/react/src/NavList/NavList.tsx @@ -29,7 +29,7 @@ export type NavListProps = { const Root = React.forwardRef(({children, ...props}, ref) => { return ( -