Skip to content

Commit

Permalink
[Link] Address issues identified during the verification process (#217)
Browse files Browse the repository at this point in the history
* fix: [Links] Add intro paragraph and source link

* fix: [Links] Align story content with DS

* fix: [Links] Switch jump link stories to rely on   between elements instead of DSR-only CSS

* test: [Link] Add unit tests
  • Loading branch information
meissadia authored Oct 31, 2023
1 parent 41261f2 commit a88bf52
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 50 deletions.
9 changes: 0 additions & 9 deletions src/components/Link/Link.less

This file was deleted.

109 changes: 69 additions & 40 deletions src/components/Link/Link.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,106 +5,135 @@ import {
Link,
LinkText,
List,
ListLink as ListLinkComponent
ListLink
} from '~/src/index';

const meta: Meta<typeof Link> = {
title: 'Components (Draft)/Links',
component: Link,
parameters: {
docs: {
description: {
component: `
### CFPB DS Link component
https://cfpb.github.io/design-system/components/links
`
}
}
}
component: Link
};

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
const DefaultArguments = {
args: {
href: '#',
children: <LinkText>Link Text</LinkText>
children: 'Link Text'
}
};

export const ListLink: Story = {
export const Inline: Story = {
name: 'Inline links',
render: () => (
<p>
Here&apos;s the <Link href='/#'>default link style</Link>. For reference,
here&apos;s the{' '}
<Link href='/#' className='hover'>
hover link style
</Link>
. Train your eyes on the{' '}
<Link href='/#' className='focus'>
focused link style
</Link>
. Jump to the{' '}
<Link href='/#' className='active'>
active link style
</Link>
. We&apos;ve all been to the{' '}
<Link href='/#' className='visited'>
visited link style
</Link>
.
</p>
)
};

export const CallToAction: Story = {
name: 'Call-to-action links',
args: {
...Default.args
...DefaultArguments.args
},
render: arguments_ => (
<List isLinks>
<ListLinkComponent {...arguments_} />
<ListLink {...arguments_}>Sample call-to-action link</ListLink>
<ListLink {...arguments_}>Another sample call-to-action link</ListLink>
</List>
)
};

export const Destructive: Story = {
name: 'Destructive links',
args: {
...Default.args
...DefaultArguments.args,
children: 'Sample destructive link'
},
render: arguments_ => <DestructiveLink {...arguments_} />
};

export const WithIcon: Story = {
export const StandardLinkWithIcon: Story = {
name: 'Standard link with icon',
args: {
...Default.args,
hasIcon: true,
type: 'default'
...DefaultArguments.args,
hasIcon: true
},
render: arguments_ => (
<Link {...arguments_}>
<LinkText>Download file</LinkText> <Icon name='download' />
</Link>
<>
The document icon should emphasize a link that contains a{' '}
<Link {...arguments_}>
<LinkText>file or document</LinkText> <Icon name='download' />
</Link>
. Use the external link icon to emphasize{' '}
<Link {...arguments_}>
<LinkText>a non-CFPB webpage</LinkText> <Icon name='external-link' />
</Link>{' '}
for further details.
</>
)
};

export const WithIconNoWrapping: Story = {
export const StandardLinkWithIconNoWrapping: Story = {
name: 'Non-wrapping icon links',
args: {
...WithIcon.args,
...StandardLinkWithIcon.args,
noWrap: true
},
render: arguments_ => (
<Link {...arguments_}>
<LinkText>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Obcaecati
incidunt explicabo, odio delectus quia magnam non . teeeeext
</LinkText>

<Icon name='document' />
</Link>
<>
The document icon should emphasize a link that contains a{' '}
<Link {...arguments_}>
<LinkText>file or document</LinkText> <Icon name='download' />
</Link>
.
</>
)
};

export const JumpLink: Story = {
name: 'Jump link',
args: {
...Default.args,
...DefaultArguments.args,
isJump: true
},
render: arguments_ => (
<Link {...arguments_}>
<LinkText>Jump link</LinkText>
<LinkText>Default jump link</LinkText>&nbsp;
<Icon name='right' />
</Link>
)
};

export const JumpLinkIconLeft: Story = {
name: 'Jump link with icon on left',
args: {
...Default.args,
...DefaultArguments.args,
isJumpLeft: true
},
render: arguments_ => (
<Link {...arguments_}>
<Icon name='left' />
<LinkText>Jump link</LinkText>
&nbsp;<LinkText>Jump link with icon on left</LinkText>
</Link>
)
};
101 changes: 101 additions & 0 deletions src/components/Link/Link.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import { DestructiveLink, Link, LinkText, ListLink } from '~/src/index';

describe('<Link />', () => {
const linkBaseProperties = {
href: '/#',
'data-testid': 'link-test-id'
};

const testId = linkBaseProperties['data-testid'];

it('Type: "default"', () => {
render(<Link {...linkBaseProperties} />);
const link = screen.getByTestId(testId);
expect(link).toHaveClass('a-link');
expect(link).toHaveAttribute('href', '/#');
});

it('Type: "destructive"', () => {
render(<Link {...linkBaseProperties} type='destructive' />);
const link = screen.getByTestId(testId);
expect(link).toHaveClass('a-link a-btn a-btn__link a-btn__warning');
});

it('Type: "list"', () => {
render(<Link {...linkBaseProperties} type='list' />);
const link = screen.getByTestId(testId);
expect(link).toHaveClass('m-list_link');
expect(link).not.toHaveClass('a-link');
});

it('Option: noWrap - it adds classnames', () => {
render(<Link {...linkBaseProperties} noWrap />);
const link = screen.getByTestId(testId);
expect(link).toHaveClass('a-link__no-wrap');
});

it('Option: isJump - it adds classnames', () => {
render(<Link {...linkBaseProperties} isJump />);
const link = screen.getByTestId(testId);
expect(link).toHaveClass('a-link__jump a-link__icon-after-text');
});

it('Option: isJumpLeft - it adds classnames', () => {
render(<Link {...linkBaseProperties} isJumpLeft />);
const link = screen.getByTestId(testId);
expect(link).toHaveClass('a-link__jump a-link__icon-before-text');
});

it('Option: hasIcon - it adds classnames', () => {
render(<Link {...linkBaseProperties} hasIcon />);
const link = screen.getByTestId(testId);
expect(link).toHaveClass('a-link a-link__icon');
});

it('Other: propagates other attributes', () => {
render(<Link {...linkBaseProperties} target='_blank' />);
const link = screen.getByTestId(testId);
expect(link).toHaveAttribute('target', '_blank');
});
});

describe('<LinkText>', () => {
it('includes appropriate classnames', () => {
render(<LinkText data-testid='link-text'>Test text</LinkText>);
const linkText = screen.getByTestId('link-text');
expect(linkText).toHaveClass('a-link_text');
expect(linkText).toHaveTextContent('Test text');
});
});

describe('<ListLink>', () => {
const testId = 'list-link';

it('includes all expected elements', () => {
render(<ListLink data-testid={testId}>Test text</ListLink>);
// ListItem
const listItem = screen.getByRole('listitem');
expect(listItem).toBeInTheDocument();
expect(listItem).toHaveTextContent('Test text');

// Link
const linkText = screen.getByTestId(testId);
expect(linkText).toHaveClass('m-list_link');
expect(linkText).toHaveTextContent('Test text');
});
});

describe('<DestructiveLink>', () => {
const testId = 'destructive-link';

it('includes all expected elements', () => {
render(<DestructiveLink data-testid={testId}>Test text</DestructiveLink>);
const linkDestructive = screen.getByTestId(testId);
expect(linkDestructive).toHaveClass(
'a-link a-btn a-btn__link a-btn__warning'
);
expect(linkDestructive).toHaveTextContent('Test text');
});
});
6 changes: 5 additions & 1 deletion src/components/Link/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import classnames from 'classnames';
import type { JSXElement } from '../../types/jsxElement';
import ListItem from '../List/ListItem';
import './Link.less';

interface LinkProperties extends React.HTMLProps<HTMLAnchorElement> {
type?: 'default' | 'destructive' | 'list';
Expand All @@ -11,6 +10,11 @@ interface LinkProperties extends React.HTMLProps<HTMLAnchorElement> {
isJumpLeft?: boolean;
}

/**
* Links lead users to a different page or further information. In contrast, buttons are used to signal actions. Users should be able to identify links without relying on color or styling alone.
*
* Source: https://cfpb.github.io/design-system/components/links
*/
export default function Link({
children,
type = 'default',
Expand Down

0 comments on commit a88bf52

Please sign in to comment.