diff --git a/.storybook/__snapshots__/Storyshots.test.js.snap b/.storybook/__snapshots__/Storyshots.test.js.snap index d9d58e9bcb..19cb5ac69b 100644 --- a/.storybook/__snapshots__/Storyshots.test.js.snap +++ b/.storybook/__snapshots__/Storyshots.test.js.snap @@ -133,6 +133,54 @@ exports[`Storyshots Dropdown basic usage 1`] = ` `; +exports[`Storyshots HyperLink minimal usage 1`] = ` + + edX.org + +`; + +exports[`Storyshots HyperLink with blank target 1`] = ` + + edX.org + + + + + +`; + +exports[`Storyshots HyperLink with onClick 1`] = ` + + edX.org + + + + + +`; + exports[`Storyshots InputSelect basic usage 1`] = `
{ + console.log(`onClick fired for ${event.target}`); + + action('HyperLink Click'); +}; + +storiesOf('HyperLink', module) + .add('minimal usage', () => ( + + )) + .add('with blank target', () => ( + + )) + .add('with onClick', () => ( + + )); diff --git a/src/Hyperlink/Hyperlink.test.jsx b/src/Hyperlink/Hyperlink.test.jsx new file mode 100644 index 0000000000..09c416ddbb --- /dev/null +++ b/src/Hyperlink/Hyperlink.test.jsx @@ -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(); + 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(); + + 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(); + expect(spy).toHaveBeenCalledTimes(0); + wrapper.simulate('click'); + expect(spy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/Hyperlink/index.jsx b/src/Hyperlink/index.jsx new file mode 100644 index 0000000000..9da42a4286 --- /dev/null +++ b/src/Hyperlink/index.jsx @@ -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 + {' '} + + + ); + } + + return ( + {content}{externalLinkIcon} + + ); +} + +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;