Skip to content

Commit a05474e

Browse files
add test cases for tooltip on action menu both for v1 and v2
1 parent b56fb96 commit a05474e

File tree

5 files changed

+216
-21
lines changed

5 files changed

+216
-21
lines changed

src/ActionMenu/ActionMenu.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,13 @@ const Menu: React.FC<React.PropsWithChildren<ActionMenuProps>> = ({
5454
// with additional props for accessibility
5555
// 🚨 Accounting for Tooltip wrapping ActionMenu.Button or being a direct child of ActionMenu.Anchor.
5656
const contents = React.Children.map(children, child => {
57-
// Is ActionMenu.Button wrapped with Tooltip? If this is the case, our anchor is the tooltip's trigger
57+
// Is ActionMenu.Button wrapped with Tooltip? If this is the case, our anchor is the tooltip's trigger (ActionMenu.Button's grandchild)
5858
if (child.type === Tooltip) {
5959
// tooltip trigger
6060
const anchorChildren = child.props.children
6161
if (anchorChildren.type === MenuButton) {
6262
renderAnchor = anchorProps => {
63+
// We need to attach the anchor props to the tooltip trigger (ActionMenu.Button's grandchild) not the tooltip itself.
6364
const triggerButton = React.cloneElement(anchorChildren, {...anchorProps})
6465
return React.cloneElement(child, {children: triggerButton, ref: anchorRef})
6566
}

src/__tests__/ActionMenu.test.tsx

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import userEvent from '@testing-library/user-event'
33
import {axe, toHaveNoViolations} from 'jest-axe'
44
import React from 'react'
55
import theme from '../theme'
6-
import {ActionMenu, ActionList, BaseStyles, ThemeProvider, SSRProvider} from '..'
6+
import {ActionMenu, ActionList, BaseStyles, ThemeProvider, SSRProvider, Tooltip, Button} from '..'
7+
import {Tooltip as TooltipV2} from '../drafts/Tooltip/Tooltip'
78
import {behavesAsComponent, checkExports} from '../utils/testing'
89
import {SingleSelect} from '../ActionMenu/ActionMenu.features.stories'
910
import {MixedSelection} from '../ActionMenu/ActionMenu.examples.stories'
@@ -37,6 +38,46 @@ function Example(): JSX.Element {
3738
)
3839
}
3940

41+
function ExampleWithTooltip(): JSX.Element {
42+
return (
43+
<ThemeProvider theme={theme}>
44+
<SSRProvider>
45+
<BaseStyles>
46+
<Tooltip aria-label="Additional context about the menu button" direction="s">
47+
<ActionMenu>
48+
<ActionMenu.Button>Toggle Menu</ActionMenu.Button>
49+
<ActionMenu.Overlay>
50+
<ActionList>
51+
<ActionList.Item>New file</ActionList.Item>
52+
</ActionList>
53+
</ActionMenu.Overlay>
54+
</ActionMenu>
55+
</Tooltip>
56+
</BaseStyles>
57+
</SSRProvider>
58+
</ThemeProvider>
59+
)
60+
}
61+
62+
function ExampleWithTooltipV2(actionMenuTrigger: React.ReactElement): JSX.Element {
63+
return (
64+
<ThemeProvider theme={theme}>
65+
<SSRProvider>
66+
<BaseStyles>
67+
<ActionMenu>
68+
{actionMenuTrigger}
69+
<ActionMenu.Overlay>
70+
<ActionList>
71+
<ActionList.Item>New file</ActionList.Item>
72+
</ActionList>
73+
</ActionMenu.Overlay>
74+
</ActionMenu>
75+
</BaseStyles>
76+
</SSRProvider>
77+
</ThemeProvider>
78+
)
79+
}
80+
4081
describe('ActionMenu', () => {
4182
behavesAsComponent({
4283
Component: ActionList,
@@ -244,4 +285,83 @@ describe('ActionMenu', () => {
244285
const results = await axe(container)
245286
expect(results).toHaveNoViolations()
246287
})
288+
289+
it('should open menu on menu button click and it is wrapped with tooltip', async () => {
290+
const component = HTMLRender(<ExampleWithTooltip />)
291+
const button = component.getByRole('button')
292+
293+
const user = userEvent.setup()
294+
await user.click(button)
295+
296+
expect(component.getByRole('menu')).toBeInTheDocument()
297+
})
298+
299+
it('should open menu on menu button click and it is wrapped with tooltip v2', async () => {
300+
const component = HTMLRender(
301+
ExampleWithTooltipV2(
302+
<TooltipV2 text="Additional context about the menu button" direction="s">
303+
<ActionMenu.Button>Toggle Menu</ActionMenu.Button>
304+
</TooltipV2>,
305+
),
306+
)
307+
const button = component.getByRole('button')
308+
309+
const user = userEvent.setup()
310+
await user.click(button)
311+
312+
expect(component.getByRole('menu')).toBeInTheDocument()
313+
})
314+
315+
it('should display tooltip when menu button is focused', async () => {
316+
const component = HTMLRender(<ExampleWithTooltip />)
317+
const button = component.getByRole('button')
318+
button.focus()
319+
expect(component.getByRole('tooltip')).toBeInTheDocument()
320+
})
321+
322+
it('should display tooltip v2 when menu button is focused', async () => {
323+
const component = HTMLRender(
324+
ExampleWithTooltipV2(
325+
<TooltipV2 text="Additional context about the menu button" direction="s">
326+
<ActionMenu.Button>Toggle Menu</ActionMenu.Button>
327+
</TooltipV2>,
328+
),
329+
)
330+
const button = component.getByRole('button')
331+
button.focus()
332+
expect(component.getByRole('tooltip')).toBeInTheDocument()
333+
})
334+
335+
it('should open menu on menu anchor click and it is wrapped with tooltip v2', async () => {
336+
const component = HTMLRender(
337+
ExampleWithTooltipV2(
338+
<ActionMenu.Anchor>
339+
<TooltipV2 text="Additional context about the menu button" direction="n">
340+
<Button>Toggle Menu</Button>
341+
</TooltipV2>
342+
</ActionMenu.Anchor>,
343+
),
344+
)
345+
const button = component.getByRole('button')
346+
347+
const user = userEvent.setup()
348+
await user.click(button)
349+
350+
expect(component.getByRole('menu')).toBeInTheDocument()
351+
})
352+
353+
it('should display tooltip v2 and menu anchor is focused', async () => {
354+
const component = HTMLRender(
355+
ExampleWithTooltipV2(
356+
<ActionMenu.Anchor>
357+
<TooltipV2 text="Additional context about the menu button" direction="n">
358+
<Button>Toggle Menu</Button>
359+
</TooltipV2>
360+
</ActionMenu.Anchor>,
361+
),
362+
)
363+
const button = component.getByRole('button')
364+
button.focus()
365+
expect(component.getByRole('tooltip')).toBeInTheDocument()
366+
})
247367
})

src/drafts/Tooltip/Tooltip.examples.stories.tsx

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import React from 'react'
2-
import {Button, IconButton, Breadcrumbs, ActionMenu, ActionList} from '../..'
2+
import {Button, IconButton, Breadcrumbs, ActionMenu, ActionList, NavList} from '../..'
33
import {PageHeader} from '../../PageHeader'
44
import {Tooltip} from './Tooltip'
5-
import {GitBranchIcon, KebabHorizontalIcon, TriangleDownIcon, CheckIcon} from '@primer/octicons-react'
5+
import {
6+
GitBranchIcon,
7+
KebabHorizontalIcon,
8+
TriangleDownIcon,
9+
CheckIcon,
10+
PeopleIcon,
11+
SmileyIcon,
12+
EyeIcon,
13+
CommentIcon,
14+
} from '@primer/octicons-react'
615
import {default as VisuallyHidden} from '../../_VisuallyHidden'
716

817
export default {
@@ -137,3 +146,34 @@ FilesPage.parameters = {
137146
defaultViewport: 'small',
138147
},
139148
}
149+
150+
export const Hyperlist = () => (
151+
<NavList>
152+
<NavList.Item href="/assigned" aria-current="page">
153+
<NavList.LeadingVisual>
154+
<PeopleIcon />
155+
</NavList.LeadingVisual>
156+
Assigned to me
157+
</NavList.Item>
158+
<Tooltip text="Created by me ⌥ ⇧ 2" direction="n">
159+
<NavList.Item href="/created">
160+
<NavList.LeadingVisual>
161+
<SmileyIcon />
162+
</NavList.LeadingVisual>
163+
Created by me
164+
</NavList.Item>
165+
</Tooltip>
166+
<NavList.Item href="/mentioned">
167+
<NavList.LeadingVisual>
168+
<CommentIcon />
169+
</NavList.LeadingVisual>
170+
Mentioned
171+
</NavList.Item>
172+
<NavList.Item href="/recent-activity">
173+
<NavList.LeadingVisual>
174+
<EyeIcon />
175+
</NavList.LeadingVisual>
176+
Recent activity
177+
</NavList.Item>
178+
</NavList>
179+
)

src/drafts/Tooltip/Tooltip.features.stories.tsx

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import React from 'react'
22
import {IconButton, Button, Box, Link, StyledOcticon, ActionMenu, ActionList} from '../..'
33
import {Tooltip} from './Tooltip'
4-
import {default as TooltipV1} from '../../Tooltip'
54
import {SearchIcon, BookIcon, CheckIcon, TriangleDownIcon, GitBranchIcon} from '@primer/octicons-react'
65

76
export default {
@@ -174,18 +173,3 @@ export const OnActionMenuAnchor = () => (
174173
</ActionMenu>
175174
</Box>
176175
)
177-
178-
export const TestOldTooltipWithActionMenu = () => (
179-
<TooltipV1 aria-label="Tooltip informatuon" direction="s">
180-
<ActionMenu>
181-
<ActionMenu.Button variant={'primary'} size="small" disabled aria-disabled>
182-
ActionMenu
183-
</ActionMenu.Button>
184-
<ActionMenu.Overlay>
185-
<ActionList>
186-
<ActionList.Item>Item</ActionList.Item>
187-
</ActionList>
188-
</ActionMenu.Overlay>
189-
</ActionMenu>
190-
</TooltipV1>
191-
)

src/drafts/Tooltip/__tests__/Tooltip.test.tsx

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,34 @@ import React from 'react'
22
import {Tooltip, TooltipProps} from '../Tooltip'
33
import {checkStoriesForAxeViolations} from '../../../utils/testing'
44
import {render as HTMLRender} from '@testing-library/react'
5-
import {Button} from '../../../Button'
5+
import theme from '../../../theme'
6+
import {Button, ActionMenu, ActionList, ThemeProvider, SSRProvider, BaseStyles} from '../../../'
67

78
const TooltipComponent = (props: TooltipProps) => (
89
<Tooltip text="Tooltip text" {...props}>
910
<Button>Button Text</Button>
1011
</Tooltip>
1112
)
1213

14+
function ExampleWithActionMenu(actionMenuTrigger: React.ReactElement): JSX.Element {
15+
return (
16+
<ThemeProvider theme={theme}>
17+
<SSRProvider>
18+
<BaseStyles>
19+
<ActionMenu>
20+
{actionMenuTrigger}
21+
<ActionMenu.Overlay>
22+
<ActionList>
23+
<ActionList.Item>New file</ActionList.Item>
24+
</ActionList>
25+
</ActionMenu.Overlay>
26+
</ActionMenu>
27+
</BaseStyles>
28+
</SSRProvider>
29+
</ThemeProvider>
30+
)
31+
}
32+
1333
describe('Tooltip', () => {
1434
checkStoriesForAxeViolations('Tooltip.features', '../drafts/Tooltip/')
1535

@@ -41,4 +61,34 @@ describe('Tooltip', () => {
4161
const {getByText} = HTMLRender(<TooltipComponent />)
4262
expect(getByText('Tooltip text')).toHaveAttribute('role', 'tooltip')
4363
})
64+
65+
it('should spread the accessibility attributes correctly on the trigger (ActionMenu.Button) when tooltip is used in an action menu', () => {
66+
const {getByRole, getByText} = HTMLRender(
67+
ExampleWithActionMenu(
68+
<Tooltip text="Additional context about the menu button">
69+
<ActionMenu.Button>Toggle Menu</ActionMenu.Button>
70+
</Tooltip>,
71+
),
72+
)
73+
const menuButton = getByRole('button')
74+
const tooltip = getByText('Additional context about the menu button')
75+
expect(menuButton).toHaveAttribute('aria-describedby', tooltip.id)
76+
expect(menuButton).toHaveAttribute('aria-haspopup', 'true')
77+
})
78+
79+
it('should spread the accessibility attributes correctly on the trigger (Button) when tooltip is used in an action menu', () => {
80+
const {getByRole, getByText} = HTMLRender(
81+
ExampleWithActionMenu(
82+
<ActionMenu.Anchor>
83+
<Tooltip text="Additional context about the menu button">
84+
<Button>Toggle Menu</Button>
85+
</Tooltip>
86+
</ActionMenu.Anchor>,
87+
),
88+
)
89+
const menuButton = getByRole('button')
90+
const tooltip = getByText('Additional context about the menu button')
91+
expect(menuButton).toHaveAttribute('aria-describedby', tooltip.id)
92+
expect(menuButton).toHaveAttribute('aria-haspopup', 'true')
93+
})
4494
})

0 commit comments

Comments
 (0)