Skip to content

Commit 9ea374e

Browse files
katiekleinkatiegoines
andauthored
a11y - add aria-label to external link instances (#7874)
* remove import * adding externallink aria-labels --------- Co-authored-by: katiegoines <[email protected]>
1 parent 5ac6f98 commit 9ea374e

File tree

14 files changed

+95
-65
lines changed

14 files changed

+95
-65
lines changed

src/components/ExternalLink/__tests__/ExternalLink.test.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,27 @@ describe('ExternalLink', () => {
1414
it('should render the ExternalLink component', async () => {
1515
render(component);
1616
const externalLink = await screen.getByRole('link', {
17-
name: 'Click Here!'
17+
name: '(opens in new tab)'
1818
});
19+
const externalLinkText = await screen.findByText('Click Here!');
1920

20-
expect(externalLink).toBeInTheDocument();
21+
await waitFor(() => {
22+
expect(externalLink).toBeInTheDocument();
23+
expect(externalLinkText).toBeInTheDocument();
24+
});
2125
});
2226

2327
it('should open external links in a new window', async () => {
2428
render(component);
2529
const externalLink = await screen.getByRole('link', {
26-
name: 'Click Here!'
30+
name: '(opens in new tab)'
2731
});
32+
const externalLinkText = await screen.findByText('Click Here!');
2833

2934
expect(externalLink).toHaveAttribute('rel', 'noopener noreferrer');
3035
expect(externalLink).toHaveAttribute('target', '_blank');
36+
expect(externalLinkText).toHaveAttribute('rel', 'noopener noreferrer');
37+
expect(externalLinkText).toHaveAttribute('target', '_blank');
3138
});
3239

3340
it('should trackExternalLink on click', async () => {

src/components/ExternalLink/index.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useEffect, useRef } from 'react';
22
import { trackExternalLink } from '../../utils/track';
33

44
type ExternalLinkProps = {
@@ -15,15 +15,27 @@ const ExternalLink: React.FC<ExternalLinkProps> = ({
1515
href,
1616
className
1717
}) => {
18+
const [label, setLabel] = React.useState('');
19+
const linkRef = useRef<HTMLAnchorElement>(null);
20+
21+
useEffect(() => {
22+
if (linkRef.current) {
23+
const text = linkRef.current.innerText;
24+
setLabel(text ? text : '');
25+
}
26+
}, []);
27+
1828
return (
1929
<a
2030
href={href}
2131
className={className}
32+
aria-label={label + ' (opens in new tab)'}
2233
rel="noopener noreferrer"
2334
target="_blank"
2435
onClick={() => {
2536
trackLink(href);
2637
}}
38+
ref={linkRef}
2739
>
2840
{children}
2941
</a>

src/components/ExternalLinkButton/ExternalLinkButton.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Button, ButtonProps } from '@aws-amplify/ui-react';
22
import { IconExternalLink } from '../Icons';
33
import { trackExternalLink } from '../../utils/track';
4+
import { useEffect, useRef } from 'react';
5+
import React from 'react';
46

57
interface ExternalLinkButtonProps {
68
variation?: ButtonProps['variation'];
@@ -21,6 +23,16 @@ export const ExternalLinkButton = ({
2123
children,
2224
className
2325
}: ExternalLinkButtonProps) => {
26+
const [label, setLabel] = React.useState('');
27+
const buttonRef = useRef<HTMLAnchorElement>(null);
28+
29+
useEffect(() => {
30+
if (buttonRef.current) {
31+
const text = buttonRef.current.innerText;
32+
setLabel(text ? text : '');
33+
}
34+
}, []);
35+
2436
return (
2537
<Button
2638
href={href}
@@ -32,9 +44,11 @@ export const ExternalLinkButton = ({
3244
as="a"
3345
align-items="center"
3446
className={className}
47+
aria-label={label + ' (opens in new tab)'}
3548
onClick={() => {
3649
trackLink(href);
3750
}}
51+
ref={buttonRef}
3852
>
3953
{children} <IconExternalLink />
4054
</Button>

src/components/ExternalLinkButton/__tests__/ExternalLinkButton.test.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,15 @@ describe('ExternalLinkButton', () => {
1818
it('should render the ExternalLinkButton component', async () => {
1919
render(component);
2020

21-
const externalLinkButtonNode = await screen.findByRole('link', {
22-
name: 'Click Here!'
21+
const externalLinkButtonNode = await screen.getByRole('link', {
22+
name: '(opens in new tab)'
23+
});
24+
const externalLinkButtonNodeText = await screen.findByText('Click Here!');
25+
26+
await waitFor(() => {
27+
expect(externalLinkButtonNode).toBeInTheDocument();
28+
expect(externalLinkButtonNodeText).toBeInTheDocument();
2329
});
24-
expect(externalLinkButtonNode).toBeInTheDocument();
2530
});
2631

2732
it('should render the ExternalLink icon', async () => {

src/components/Footer/__tests__/__snapshots__/Footer.test.tsx.snap

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/components/GlobalNav/__tests__/GlobalNav.test.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ describe('GlobalNav', () => {
2929

3030
it('should render the GlobalNav component', async () => {
3131
render(component);
32-
const link = await screen.findByRole('link', { name: 'About AWS Amplify' });
32+
const link = await screen.findByRole('link', {
33+
name: 'About AWS Amplify (opens in new tab)'
34+
});
3335
expect(link).toBeInTheDocument();
3436
});
3537
});

src/components/GlobalNav/components/NavMenuLink.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export function NavMenuLink({
4040
return (
4141
<Link
4242
isExternal={true}
43+
aria-label={label + ' (opens in new tab)'}
4344
className="navbar-menu-item"
4445
href={navMenuItem.url}
4546
>

src/components/LinkCard/LinkCard.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ const LinkCard: React.FC<LinkCardProps> = ({
1616
}) => {
1717
return (
1818
href && (
19-
<Link href={href} isExternal={isExternal} className="link-card">
19+
<Link
20+
href={href}
21+
isExternal={isExternal}
22+
className="link-card"
23+
aria-label={children + ' (opens in new tab)'}
24+
>
2025
<Flex direction="column" justifyContent="space-between" height="100%">
2126
<View>{icon()}</View>
2227
<View className="link-card-children">{children}</View>

src/components/LinkCards/__tests__/LinkCards.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@ describe('LinkCards', () => {
77
it('should render the LinkCards component', async () => {
88
render(component);
99
const linkCardNode = await screen.findByRole('link', {
10-
name: 'React Libraries on GitHub'
10+
name: 'React Libraries on GitHub (opens in new tab)'
1111
});
1212
expect(linkCardNode).toBeInTheDocument();
1313
});
1414

1515
it('should link each card to external href', async () => {
1616
render(component);
1717
const githubCard = await screen.findByRole('link', {
18-
name: 'React Libraries on GitHub'
18+
name: 'React Libraries on GitHub (opens in new tab)'
1919
});
2020
const discordCard = await screen.findByRole('link', {
21-
name: 'Amplify Discord'
21+
name: 'Amplify Discord (opens in new tab)'
2222
});
2323
const learnCard = await screen.findByRole('link', {
24-
name: 'Amplify Learn'
24+
name: 'Amplify Learn (opens in new tab)'
2525
});
2626

2727
expect(githubCard.href).toBe('https://github.com/aws-amplify/amplify-ui');

src/components/MDXComponents/__tests__/MDXLink.test.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const routerMock = {
1616
jest.mock('next/router', () => routerMock);
1717

1818
describe('MDXLink', () => {
19-
it('should render external link', () => {
19+
it('should render external link', async () => {
2020
const externalUrl = 'https://amazon.com';
2121
const linkText = 'External Site';
2222

@@ -26,10 +26,16 @@ describe('MDXLink', () => {
2626
</MDXLink>
2727
);
2828

29-
const linkElement = screen.getByRole('link', { name: linkText });
29+
const linkElement = await screen.getByRole('link', {
30+
name: '(opens in new tab)'
31+
});
32+
const linkElementText = await screen.findByText('External Site');
3033
expect(linkElement).toBeInTheDocument();
3134
expect(linkElement).toHaveAttribute('href', externalUrl);
3235
expect(linkElement).toHaveAttribute('rel', 'noopener noreferrer');
36+
expect(linkElementText).toBeInTheDocument();
37+
expect(linkElementText).toHaveAttribute('href', externalUrl);
38+
expect(linkElementText).toHaveAttribute('rel', 'noopener noreferrer');
3339
});
3440

3541
it('should render internal link', () => {

0 commit comments

Comments
 (0)