Skip to content

Commit 81b09de

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

File tree

5 files changed

+219
-21
lines changed

5 files changed

+219
-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: 124 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,49 @@ 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+
81+
// tooltip in the document and maybe check if it's visible
82+
// action button is still active and can actionMenuTrigger the menu.
83+
4084
describe('ActionMenu', () => {
4185
behavesAsComponent({
4286
Component: ActionList,
@@ -244,4 +288,83 @@ describe('ActionMenu', () => {
244288
const results = await axe(container)
245289
expect(results).toHaveNoViolations()
246290
})
291+
292+
it('should open menu on menu button click and it is wrapped with tooltip', async () => {
293+
const component = HTMLRender(<ExampleWithTooltip />)
294+
const button = component.getByRole('button')
295+
296+
const user = userEvent.setup()
297+
await user.click(button)
298+
299+
expect(component.getByRole('menu')).toBeInTheDocument()
300+
})
301+
302+
it('should open menu on menu button click and it is wrapped with tooltip v2', async () => {
303+
const component = HTMLRender(
304+
ExampleWithTooltipV2(
305+
<TooltipV2 text="Additional context about the menu button" direction="s">
306+
<ActionMenu.Button>Toggle Menu</ActionMenu.Button>
307+
</TooltipV2>,
308+
),
309+
)
310+
const button = component.getByRole('button')
311+
312+
const user = userEvent.setup()
313+
await user.click(button)
314+
315+
expect(component.getByRole('menu')).toBeInTheDocument()
316+
})
317+
318+
it('should display tooltip when menu button is focused', async () => {
319+
const component = HTMLRender(<ExampleWithTooltip />)
320+
const button = component.getByRole('button')
321+
button.focus()
322+
expect(component.getByRole('tooltip')).toBeInTheDocument()
323+
})
324+
325+
it('should display tooltip v2 when menu button is focused', async () => {
326+
const component = HTMLRender(
327+
ExampleWithTooltipV2(
328+
<TooltipV2 text="Additional context about the menu button" direction="s">
329+
<ActionMenu.Button>Toggle Menu</ActionMenu.Button>
330+
</TooltipV2>,
331+
),
332+
)
333+
const button = component.getByRole('button')
334+
button.focus()
335+
expect(component.getByRole('tooltip')).toBeInTheDocument()
336+
})
337+
338+
it('should open menu on menu anchor click and it is wrapped with tooltip v2', async () => {
339+
const component = HTMLRender(
340+
ExampleWithTooltipV2(
341+
<ActionMenu.Anchor>
342+
<TooltipV2 text="Additional context about the menu button" direction="n">
343+
<Button>Toggle Menu</Button>
344+
</TooltipV2>
345+
</ActionMenu.Anchor>,
346+
),
347+
)
348+
const button = component.getByRole('button')
349+
350+
const user = userEvent.setup()
351+
await user.click(button)
352+
353+
expect(component.getByRole('menu')).toBeInTheDocument()
354+
})
355+
356+
it('should display tooltip v2 and menu anchor is focused', async () => {
357+
const component = HTMLRender(
358+
ExampleWithTooltipV2(
359+
<ActionMenu.Anchor>
360+
<TooltipV2 text="Additional context about the menu button" direction="n">
361+
<Button>Toggle Menu</Button>
362+
</TooltipV2>
363+
</ActionMenu.Anchor>,
364+
),
365+
)
366+
const button = component.getByRole('button')
367+
button.focus()
368+
expect(component.getByRole('tooltip')).toBeInTheDocument()
369+
})
247370
})

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)