Skip to content

Commit

Permalink
feat(v2): initial development
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathan Golden committed May 19, 2020
1 parent 8503653 commit a9c50c9
Show file tree
Hide file tree
Showing 18 changed files with 1,450 additions and 361 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ __diff_output__

# build
build/
*.tgz
170 changes: 154 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,87 @@ Want to get paid for your contributions to `react-seo`?
```bash
npm install @americanexpress/react-seo
```

<br />

Let's start with a minimal example of basic usage:

```javascript
import React from 'react';
import SEO from '@americanexpress/react-seo';

const MyModule = () => (
<div>
<SEO
author="John Doe"
title="Lorem Ipsum"
description="Lorem ipsum sat delor."
keywords={['foo', 'bar']}
siteUrl="https://example.com"
siteUrl="http://example.com"
image={{
src: 'http://example.com/foo.jpg'
}}
/>
</div>
);

export default MyModule;
```

This will result in the following tags being added to the `head` element:

```html
<head>
<title>Lorem Ipsum</title>
<link rel="canonical" href="http://example.com">
<meta property="og:url" content="http://example.com">
<meta property="og:title" content="Lorem Ipsum">
<meta property="og:description" content="Lorem ispum sat delor.">
<meta property="og:image" content="http://example.com/foo.jpg">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Lorem Ipsum">
<meta name="twitter:description" content="Lorem ispum sat delor.">
<meta name="twitter:image" content="http://example.com/foo.jpg">
<meta name="description" content="Lorem ispum sat delor.">
<meta name="keywords" content="foo, bar">
</head>
```

Notice in the example above that the Open Graph and Twitter Card metadata is constructed from the `title`, `description`, and `image` props. To override these values or add additional tags not provided by default, you may use the `openGraph` and `twitterCard` props.

```javascript
import React from 'react';
import SEO from '@americanexpress/react-seo';

const MyModule = () => (
<div>
<SEO
title="Lorem Ipsum"
image="https://example.com/foo.png"
meta=[{ charset: 'utf-8' }]
description="Lorem ipsum sat delor."
keywords={['foo', 'bar']}
siteUrl="http://example.com"
openGraph={{
title: 'Facebook Lorem Ipsum',
description: 'Facebook Lorem ipsum sat delor.',
image: {
src: 'http://example.com/facebook-foo.jpg',
alt: 'Lorem ipsum',
}
}}
twitterCard={{
title: 'Twitter Lorem Ipsum',
description: 'Twitter Lorem ipsum sat delor.',
image: {
src: 'http://example.com/twitter-foo.jpg',
alt: 'Lorem ipsum',
}
}}
/>
</div>
);

export default MyModule;
```

<br />

## 🎛️ API
Expand All @@ -53,19 +114,96 @@ The interface for `react-seo` is denoted below:

```javascript
SEO.propTypes = {
article: PropTypes.bool,
author: PropTypes.string,
description: PropTypes.string,
image: PropTypes.shape({
src: PropTypes.string,
title: string,
description: string,
canonical: string,
image: shape({
src: string,
secureUrl: string,
type: string,
width: number,
height: number,
alt: string,
}),
video: shape({
src: string,
secureUrl: string,
type: string,
width: number,
height: number,
alt: string,
}),
openGraph: shape({
type: string,
url: string,
title: string,
description: string,
determiner: string,
locale: string,
localeAlternate: string,
siteName: string,
image: shape({
src: string,
secureUrl: string,
type: string,
width: number,
height: number,
alt: string,
}),
video: shape({
src: string,
secureUrl: string,
type: string,
width: number,
height: number,
alt: string,
}),
audio: shape({
src: string,
secureUrl: string,
type: string,
}),
}),
twitterCard: shape({
card: string,
title: string,
description: string,
image: shape({
src: string,
alt: string,
}),
site: string,
siteId: string,
creator: string,
creatorId: string,
app: shape({
country: string,
iphone: shape({
id: string,
url: string,
name: string,
}),
ipad: shape({
id: string,
url: string,
name: string,
}),
googlePlay: shape({
id: string,
url: string,
name: string,
}),
}),
player: shape({
src: string,
width: number,
height: number,
}),
}),
keywords: PropTypes.arrayOf(PropTypes.string),
locale: PropTypes.string,
meta: PropTypes.arrayOf(PropTypes.object),
pathname: PropTypes.string,
siteUrl: PropTypes.string,
title: PropTypes.string,
canonical: PropTypes.string,
keywords: arrayOf(string),
locale: string,
meta: arrayOf(object),
siteUrl: string,
};

SEO.defaultProps = {
Expand Down
65 changes: 44 additions & 21 deletions __tests__/index.spec.jsx → __tests__/components/SEO.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,81 +14,104 @@

import React from 'react';
import { shallow } from 'enzyme';
import SEO from '../src';
import SEO from '../../src';

jest.mock('react-helmet', () => ({ Helmet: 'Helmet' }));

describe('SEO', () => {
it('should render the snapshot correctly', () => {
it('should render correctly with the minimal tags', () => {
const component = shallow(
<SEO
title="Lorem Ipsum"
siteUrl="https://example.com"
/>
);
expect(component).toMatchSnapshot();
});

it('should provide the canonical URL', () => {
const component = shallow(
<SEO
author="John Doe"
description="Lorem ipsum sat delor."
keywords={['foo', 'bar']}
siteUrl="https://example.com"
title="Lorem Ipsum"
canonical="https://example.com/foo/bar"
canonical="https://example.com/index.html"
/>
);
expect(component).toMatchSnapshot();

const helmet = component.find('Helmet');
const { link } = helmet.props();

expect(link).toEqual([{
rel: 'canonical', href: 'https://example.com/index.html',
}]);
});

it('should render articles correctly', () => {
it('should render image tags correctly', () => {
const component = shallow(
<SEO
author="John Doe"
description="Lorem ipsum sat delor."
keywords={['foo', 'bar']}
siteUrl="https://example.com"
title="Lorem Ipsum"
article={true}
image={{
src: 'http://example.com/ogp.jpg',
type: 'image/jpeg',
width: 500,
height: 300,
alt: 'A shiny red apple with a bite taken out',
}}
/>
);
expect(component).toMatchSnapshot();
});

it('should render images correctly', () => {
it('should render video tags correctly', () => {
const component = shallow(
<SEO
author="John Doe"
description="Lorem ipsum sat delor."
keywords={['foo', 'bar']}
siteUrl="https://example.com"
title="Lorem Ipsum"
image={{
src: '/foo.png',
video={{
src: 'http://example.com/movie.swf',
type: 'application/x-shockwave-flash',
width: 500,
height: 300,
alt: 'A shiny red apple with a bite taken out',
}}
/>
);
expect(component).toMatchSnapshot();
});

it('should render images pathnames correctly', () => {
it('should render Open Graph tags correctly', () => {
const component = shallow(
<SEO
author="John Doe"
description="Lorem ipsum sat delor."
keywords={['foo', 'bar']}
siteUrl="https://example.com"
title="Lorem Ipsum"
pathname="/foo"
openGraph={{
title: 'Open Graph Title',
}}
/>
);
expect(component).toMatchSnapshot();
});

it('should render children correctly', () => {
it('should render Twitter Card tags correctly', () => {
const component = shallow(
<SEO
author="John Doe"
description="Lorem ipsum sat delor."
keywords={['foo', 'bar']}
siteUrl="https://example.com"
title="Lorem Ipsum"
pathname="/foo"
>
<script src="https://example.com/foo.js" />
</SEO>
twitterCard={{
title: 'Twitter Card Title',
}}
/>
);
expect(component).toMatchSnapshot();
});
Expand Down
Loading

0 comments on commit a9c50c9

Please sign in to comment.