Skip to content

Commit

Permalink
Merge pull request #67 from edx/hyperlink-component
Browse files Browse the repository at this point in the history
Hyperlink Component
  • Loading branch information
jaebradley committed Nov 13, 2017
2 parents 2461018 + 4c9f71c commit c408b0d
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 1 deletion.
48 changes: 48 additions & 0 deletions .storybook/__snapshots__/Storyshots.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,54 @@ exports[`Storyshots Dropdown basic usage 1`] = `
</div>
`;

exports[`Storyshots HyperLink minimal usage 1`] = `
<a
href="https://en.wikipedia.org/wiki/Hyperlink"
onClick={[Function]}
target="_self"
>
edX.org
</a>
`;

exports[`Storyshots HyperLink with blank target 1`] = `
<a
href="https://www.edx.org"
onClick={[Function]}
target="_blank"
>
edX.org
<span>
<span
aria-hidden={false}
aria-label="Opens in a new window"
className="fa fa-external-link"
title="Opens in a new window"
/>
</span>
</a>
`;

exports[`Storyshots HyperLink with onClick 1`] = `
<a
href="https://www.edx.org"
onClick={[Function]}
target="_blank"
>
edX.org
<span>
<span
aria-hidden={false}
aria-label="Opens in a new window"
className="fa fa-external-link"
title="Opens in a new window"
/>
</span>
</a>
`;

exports[`Storyshots InputSelect basic usage 1`] = `
<div
className="form-group"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@edx/paragon",
"version": "1.1.6",
"version": "1.1.7",
"description": "Accessible, responsive UI component library based on Bootstrap.",
"main": "src/index.js",
"author": "arizzitano",
Expand Down
40 changes: 40 additions & 0 deletions src/Hyperlink/Hyperlink.stories.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* eslint-disable import/no-extraneous-dependencies, no-console */
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { setConsoleOptions } from '@storybook/addon-console';

import Hyperlink from './index';

setConsoleOptions({
panelExclude: ['warn', 'error'],
});

const onClick = (event) => {
console.log(`onClick fired for ${event.target}`);

action('HyperLink Click');
};

storiesOf('HyperLink', module)
.add('minimal usage', () => (
<Hyperlink
destination={'https://en.wikipedia.org/wiki/Hyperlink'}
content={'edX.org'}
/>
))
.add('with blank target', () => (
<Hyperlink
destination={'https://www.edx.org'}
content={'edX.org'}
target={'_blank'}
/>
))
.add('with onClick', () => (
<Hyperlink
destination={'https://www.edx.org'}
content={'edX.org'}
target={'_blank'}
onClick={onClick}
/>
));
67 changes: 67 additions & 0 deletions src/Hyperlink/Hyperlink.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { shallow, mount } from 'enzyme';
import classNames from 'classnames';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';

import Hyperlink from './index';

const content = 'content';
const destination = 'destination';
const onClick = () => {};
const props = {
content,
destination,
onClick,
};
const externalLinkAlternativeText = 'externalLinkAlternativeText';
const externalLinkTitle = 'externalLinktTitle';
const externalLinkProps = {
target: '_blank',
externalLinkAlternativeText,
externalLinkTitle,
...props,
};

describe('correct rendering', () => {
it('renders Hyperlink', () => {
const wrapper = shallow(<Hyperlink {...props} />);
expect(wrapper.type()).toEqual('a');
expect(wrapper).toHaveLength(1);

expect(wrapper.prop('children')).toEqual([content, undefined]);
expect(wrapper.prop('href')).toEqual(destination);
expect(wrapper.prop('target')).toEqual('_self');
expect(wrapper.prop('onClick')).toEqual(onClick);

expect(wrapper.find('span')).toHaveLength(0);
expect(wrapper.find('i')).toHaveLength(0);
});

it('renders external Hyperlink', () => {
const wrapper = mount(<Hyperlink {...externalLinkProps} />);

expect(wrapper.find('span')).toHaveLength(2);

const icon = wrapper.find('span').at(1);

expect(icon.prop('aria-hidden')).toEqual(false);
expect(icon.prop('className'))
.toEqual(classNames(FontAwesomeStyles.fa, FontAwesomeStyles['fa-external-link']));
expect(icon.prop('aria-label')).toEqual(externalLinkAlternativeText);
expect(icon.prop('title')).toEqual(externalLinkTitle);
});
});

describe('event handlers are triggered correctly', () => {
let spy;

beforeEach(() => { spy = jest.fn(); });

it('should fire onClick', () => {
const wrapper = mount(<Hyperlink {...props} onClick={spy} />);
expect(spy).toHaveBeenCalledTimes(0);
wrapper.simulate('click');
expect(spy).toHaveBeenCalledTimes(1);
});
});
61 changes: 61 additions & 0 deletions src/Hyperlink/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import classNames from 'classnames';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import PropTypes from 'prop-types';
import isRequiredIf from 'react-proptype-conditional-require';

function Hyperlink(props) {
const {
destination,
content,
target,
onClick,
externalLinkAlternativeText,
externalLinkTitle,
...other
} = props;

let externalLinkIcon;

if (target === '_blank') {
externalLinkIcon = (
// Space between content and icon
<span>{' '}
<span
className={classNames(FontAwesomeStyles.fa, FontAwesomeStyles['fa-external-link'])}
aria-hidden={false}
aria-label={externalLinkAlternativeText}
title={externalLinkTitle}
/>
</span>
);
}

return (
<a
href={destination}
target={target}
onClick={onClick}
{...other}
>{content}{externalLinkIcon}
</a>
);
}

Hyperlink.defaultProps = {
target: '_self',
onClick: () => {},
externalLinkAlternativeText: 'Opens in a new window',
externalLinkTitle: 'Opens in a new window',
};

Hyperlink.propTypes = {
destination: PropTypes.string.isRequired,
content: PropTypes.string.isRequired,
target: PropTypes.string,
onClick: PropTypes.func,
externalLinkAlternativeText: isRequiredIf(PropTypes.string, props => props.target === '_blank'),
externalLinkTitle: isRequiredIf(PropTypes.string, props => props.target === '_blank'),
};

export default Hyperlink;

0 comments on commit c408b0d

Please sign in to comment.