Skip to content

Commit 4ec4248

Browse files
authored
data-component adr part 5 (#7867)
1 parent e3f655c commit 4ec4248

13 files changed

Lines changed: 121 additions & 6 deletions

File tree

.changeset/quick-ants-lead.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/react": minor
3+
---
4+
5+
Add data-component attributes and associated tests for ActionMenu, AnchoredOverlay, Autocomplete, and NavList

packages/react/src/ActionMenu/ActionMenu.test.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,19 @@ function ExampleWithSubmenus(): JSX.Element {
180180
describe('ActionMenu', () => {
181181
implementsClassName(ActionMenu.Button)
182182

183+
it('renders data-component attributes for ActionMenu parts', async () => {
184+
const component = HTMLRender(<Example />)
185+
const user = userEvent.setup()
186+
187+
const trigger = component.getByRole('button', {name: 'Toggle Menu'})
188+
expect(trigger).toHaveAttribute('data-component', 'ActionMenu.Button')
189+
190+
await user.click(trigger)
191+
192+
expect(component.baseElement.querySelector('[data-component="ActionMenu.Overlay"]')).not.toBeNull()
193+
expect(component.baseElement.querySelector('[data-component="AnchoredOverlay"]')).toBeNull()
194+
})
195+
183196
it('should open Menu on MenuButton click', async () => {
184197
const component = HTMLRender(<Example />)
185198
const button = component.getByRole('button')
@@ -778,7 +791,7 @@ describe('ActionMenu', () => {
778791
const initialAnchor = component.getByRole('button', {name: 'Open menu'})
779792
await user.click(initialAnchor)
780793

781-
const overlay = component.baseElement.querySelector('[data-component="AnchoredOverlay"]') as HTMLElement
794+
const overlay = component.baseElement.querySelector('[data-component="ActionMenu.Overlay"]') as HTMLElement
782795
expect(overlay).not.toBeNull()
783796

784797
const initialAnchorName = initialAnchor.style.getPropertyValue('anchor-name')

packages/react/src/ActionMenu/ActionMenu.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ export type ActionMenuButtonProps = ButtonProps
256256
const MenuButton = React.forwardRef(({...props}, anchorRef) => {
257257
return (
258258
<Anchor ref={anchorRef}>
259-
<Button type="button" trailingAction={TriangleDownIcon} {...props} />
259+
<Button data-component="ActionMenu.Button" type="button" trailingAction={TriangleDownIcon} {...props} />
260260
</Anchor>
261261
)
262262
}) as PolymorphicForwardRefComponent<'button', ActionMenuButtonProps>
@@ -355,7 +355,10 @@ const Overlay: FCWithSlotMarker<React.PropsWithChildren<MenuOverlayProps>> = ({
355355
onClose={handleClose}
356356
align={align}
357357
side={side ?? (isSubmenu ? 'outside-right' : 'outside-bottom')}
358-
overlayProps={overlayProps}
358+
overlayProps={{
359+
...overlayProps,
360+
'data-component': 'ActionMenu.Overlay',
361+
}}
359362
focusZoneSettings={isNarrowFullscreen ? {disabled: true} : {focusOutBehavior: 'wrap'}}
360363
onPositionChange={onPositionChange}
361364
variant={variant}

packages/react/src/AnchoredOverlay/AnchoredOverlay.test.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,26 @@ describe.each([true, false])(
201201
})
202202
})
203203

204+
it('renders data-component attributes for AnchoredOverlay parts when shown', () => {
205+
const {baseElement} = render(
206+
<FeatureFlags flags={{primer_react_css_anchor_positioning: true}}>
207+
<BaseStyles>
208+
<AnchoredOverlay
209+
open={true}
210+
onOpen={() => {}}
211+
onClose={() => {}}
212+
renderAnchor={props => <Button {...props}>Anchor Button</Button>}
213+
variant={{regular: 'anchored', narrow: 'fullscreen'}}
214+
>
215+
<div>content</div>
216+
</AnchoredOverlay>
217+
</BaseStyles>
218+
</FeatureFlags>,
219+
)
220+
expect(baseElement.querySelector('[data-component="AnchoredOverlay"]')).toBeInTheDocument()
221+
expect(baseElement.querySelector('[data-component="AnchoredOverlay.CloseButton"]')).toBeInTheDocument()
222+
})
223+
204224
it('should support a `ref` through `overlayProps` on the overlay element', () => {
205225
const ref = createRef<HTMLDivElement>()
206226

packages/react/src/AnchoredOverlay/AnchoredOverlay.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@ export const AnchoredOverlay: React.FC<React.PropsWithChildren<AnchoredOverlayPr
443443
<div className={classes.ResponsiveCloseButtonContainer}>
444444
<IconButton
445445
{...(closeButtonProps as IconButtonProps)}
446+
data-component="AnchoredOverlay.CloseButton"
446447
type="button"
447448
variant="invisible"
448449
icon={XIcon}

packages/react/src/Autocomplete/Autocomplete.test.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,24 @@ describe('Autocomplete', () => {
5555
<Autocomplete.Input {...props} />
5656
</Autocomplete>
5757
))
58+
59+
it('renders data-component attributes for Autocomplete parts when menu is shown', async () => {
60+
const user = userEvent.setup()
61+
const {container} = render(
62+
<LabelledAutocomplete
63+
menuProps={{items: mockItems, selectedItemIds: [], ['aria-labelledby']: 'autocompleteLabel'}}
64+
/>,
65+
)
66+
67+
const input = container.querySelector('#autocompleteInput') as HTMLInputElement
68+
expect(input).toHaveAttribute('data-component', 'Autocomplete.Input')
69+
70+
await user.type(input, 'z')
71+
72+
expect(container.querySelector('[data-component="Autocomplete.Overlay"]')).toBeInTheDocument()
73+
expect(container.querySelector('[data-component="Autocomplete.Menu"]')).toBeInTheDocument()
74+
})
75+
5876
it('calls onChange', async () => {
5977
const user = userEvent.setup()
6078
const onChangeMock = vi.fn()

packages/react/src/Autocomplete/AutocompleteInput.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ const AutocompleteInput = React.forwardRef(
188188
autoComplete="off"
189189
id={id}
190190
{...props}
191+
data-component="Autocomplete.Input"
191192
/>
192193
)
193194
},

packages/react/src/Autocomplete/AutocompleteMenu.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ function AutocompleteMenu<T extends AutocompleteItemProps>(props: AutocompleteMe
359359
<div ref={listContainerRef}>
360360
{allItemsToRender.length ? (
361361
<ActionList
362+
data-component="Autocomplete.Menu"
362363
selectionVariant={selectionVariant} // TODO: make this configurable
363364
role="listbox"
364365
id={`${id}-listbox`}

packages/react/src/Autocomplete/AutocompleteOverlay.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ function AutocompleteOverlay({
7878
left={position?.left}
7979
className={clsx(classes.Overlay, className)}
8080
{...overlayProps}
81+
data-component="Autocomplete.Overlay"
8182
>
8283
{children}
8384
</Overlay>

packages/react/src/NavList/NavList.test.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,42 @@ const NextJSLikeLink = React.forwardRef<HTMLAnchorElement, NextJSLinkProps>(
2323
describe('NavList', () => {
2424
implementsClassName(NavList)
2525

26+
it('renders data-component attributes for NavList, NavList.Item, and NavList.SubNav', () => {
27+
const {container, getByRole} = render(
28+
<NavList>
29+
<NavList.Item href="#">Item 1</NavList.Item>
30+
<NavList.Item>
31+
Item 2
32+
<NavList.SubNav>
33+
<NavList.Item href="#">Sub Item 1</NavList.Item>
34+
</NavList.SubNav>
35+
</NavList.Item>
36+
</NavList>,
37+
)
38+
39+
const nav = container.querySelector('nav')
40+
expect(nav).toBeInTheDocument()
41+
expect(nav).toHaveAttribute('data-component', 'NavList')
42+
43+
const item1Link = getByRole('link', {name: 'Item 1'})
44+
expect(item1Link).toBeInTheDocument()
45+
expect(item1Link).toHaveAttribute('data-component', 'NavList.Item')
46+
47+
const item2Button = getByRole('button', {name: 'Item 2'})
48+
expect(item2Button).toBeInTheDocument()
49+
expect(item2Button).toHaveAttribute('data-component', 'NavList.Item')
50+
51+
const subNav = container.querySelector('[data-component="NavList.SubNav"]')
52+
expect(subNav).toBeInTheDocument()
53+
54+
// Expand so nested links are in the accessible tree
55+
fireEvent.click(item2Button)
56+
57+
const subItem1Link = getByRole('link', {name: 'Sub Item 1'})
58+
expect(subItem1Link).toBeInTheDocument()
59+
expect(subItem1Link).toHaveAttribute('data-component', 'NavList.Item')
60+
})
61+
2662
it('supports TrailingAction', async () => {
2763
const {getByRole} = render(
2864
<NavList>

0 commit comments

Comments
 (0)