Skip to content

Commit

Permalink
Add JSON-LD Local Business (garmeeh#26)
Browse files Browse the repository at this point in the history
This adds support for[ basic local business](https://developers.google.com/search/docs/data-types/local-business) properties as outlined in the readme. 

By default the type will be LocalBusiness but users can provide any valid sub-type as per the documentation. 

Images code was taken from product but not turned into a generic helper. This felt like it could be reused but leaving them separate allowed for better isolation. Happy to change this and move buildImages to a common builders file with tests. 

Additional changes to reorder imports/exports alphabetically and add the missing product link in the readme are included in the same commit.   

Fixes garmeeh#23
  • Loading branch information
timReynolds authored and garmeeh committed Dec 24, 2018
1 parent eafaf8b commit 6475a95
Show file tree
Hide file tree
Showing 8 changed files with 386 additions and 21 deletions.
74 changes: 63 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,8 @@ Below you will find a very basic page implementing each of the available JSON-LD
- [Article](#article)
- [Blog](#blog)
- [Course](#course)
- [Local Business](#local-business)
- [Product](#product)
- [Social Profile](#social-profile)

More to follow very, very soon!
Expand Down Expand Up @@ -571,6 +573,63 @@ export default () => (
);
```

### Local Business

Local business is supported with a sub-set of properties.

```jsx
<LocalBusinessJsonLd
type="Store"
id="http://davesdeptstore.example.com"
name="Dave's Department Store"
description="Dave's latest department store in San Jose, now open"
url="http://www.example.com/store-locator/sl/San-Jose-Westgate-Store/1427"
telephone="+14088717984"
address={{
streetAddress: '1600 Saratoga Ave',
addressLocality: 'San Jose',
addressRegion: 'CA',
postalCode: '95129',
addressCountry: 'US',
}}
geo={{
latitude: '37.293058',
longitude: '-121.988331',
}}
images={[
'https://example.com/photos/1x1/photo.jpg',
'https://example.com/photos/4x3/photo.jpg',
'https://example.com/photos/16x9/photo.jpg',
]}
/>
```

**Required properties**

| Property | Info |
| ------------------------- | -------------------------------------------------------------------------- |
| `@id` | Globally unique ID of the specific business location in the form of a URL. |
| `type` | LocalBusiness or any sub-type |
| `address` | Address of the specific business location |
| `address.addressCountry` | The 2-letter ISO 3166-1 alpha-2 country code |
| `address.addressLocality` | City |
| `address.addressRegion` | State or province, if applicable. |
| `address.postalCode` | Postal or zip code. |
| `address.streetAddress` | Street number, street name, and unit number. |
| `name` | Business name. |

**Supported properties**

| Property | Info |
| --------------- | ----------------------------------------------------------------------------------- |
| `description` | Description of the business location |
| `geo` | Geographic coordinates of the business. |
| `geo.latitude` | The latitude of the business location |
| `geo.longitude` | The longitude of the business location |
| `images` | An image or images of the business. Required for valid markup depending on the type |
| `telephone` | A business phone number meant to be the primary contact method for customers. |
| `url` | The fully-qualified URL of the specific business location. |

### Logo

```jsx
Expand All @@ -588,17 +647,10 @@ export default () => (
);
```

**Required properties**

| Property | Info |
| -------- | ---- |


|
| `url` | The URL of the website associated with the logo. [Logo guidelines](https://developers.google.com/search/docs/data-types/logo#definitions)
|
| `logo` | URL of a logo that is representative of the organization.
|
| Property | Info |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `url` | The URL of the website associated with the logo. [Logo guidelines](https://developers.google.com/search/docs/data-types/logo#definitions) |
| `logo` | URL of a logo that is representative of the organization. |

### Product

Expand Down
61 changes: 54 additions & 7 deletions e2e/cypress/e2e/jsonld.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { assertSchema } from '@cypress/schema-tools';
import schemas from '../schemas';

const expectedJSONResults = 6;
const expectedJSONResults = 7;

describe('Validates JSON-LD For:', () => {
it('Article', () => {
Expand Down Expand Up @@ -122,12 +122,59 @@ describe('Validates JSON-LD For:', () => {
});
});

it('Logo', () => {
it('Local Business', () => {
cy.visit('http://localhost:3000/jsonld');
cy.get('head script[type="application/ld+json"]')
.should('have.length', expectedJSONResults)
.then(tags => {
const jsonLD = JSON.parse(tags[3].innerHTML);
assertSchema(schemas)('Local Business', '1.0.0')(jsonLD);
});
});

it('Local Business', () => {
cy.visit('http://localhost:3000/jsonld');
cy.get('head script[type="application/ld+json"]')
.should('have.length', expectedJSONResults)
.then(tags => {
const jsonLD = JSON.parse(tags[3].innerHTML);
expect(jsonLD).to.deep.equal({
'@context': 'http://schema.org',
'@type': 'Store',
'@id': 'http://davesdeptstore.example.com',
name: "Dave's Department Store",
description: "Dave's latest department store in San Jose, now open",
url:
'http://www.example.com/store-locator/sl/San-Jose-Westgate-Store/1427',
telephone: '+14088717984',
address: {
'@type': 'PostalAddress',
streetAddress: '1600 Saratoga Ave',
addressLocality: 'San Jose',
addressRegion: 'CA',
postalCode: '95129',
addressCountry: 'US',
},
geo: {
'@type': 'GeoCoordinates',
latitude: '37.293058',
longitude: '-121.988331',
},
image: [
'https://example.com/photos/1x1/photo.jpg',
'https://example.com/photos/4x3/photo.jpg',
'https://example.com/photos/16x9/photo.jpg',
],
});
});
});

it('Logo', () => {
cy.visit('http://localhost:3000/jsonld');
cy.get('head script[type="application/ld+json"]')
.should('have.length', expectedJSONResults)
.then(tags => {
const jsonLD = JSON.parse(tags[4].innerHTML);
assertSchema(schemas)('Logo', '1.0.0')(jsonLD);
});
});
Expand All @@ -137,7 +184,7 @@ describe('Validates JSON-LD For:', () => {
cy.get('head script[type="application/ld+json"]')
.should('have.length', expectedJSONResults)
.then(tags => {
const jsonLD = JSON.parse(tags[3].innerHTML);
const jsonLD = JSON.parse(tags[4].innerHTML);
expect(jsonLD).to.deep.equal({
'@context': 'http://schema.org',
'@type': 'Organization',
Expand All @@ -152,7 +199,7 @@ describe('Validates JSON-LD For:', () => {
cy.get('head script[type="application/ld+json"]')
.should('have.length', expectedJSONResults)
.then(tags => {
const jsonLD = JSON.parse(tags[4].innerHTML);
const jsonLD = JSON.parse(tags[5].innerHTML);
assertSchema(schemas)('Product', '1.0.0')(jsonLD);
});
});
Expand All @@ -162,7 +209,7 @@ describe('Validates JSON-LD For:', () => {
cy.get('head script[type="application/ld+json"]')
.should('have.length', expectedJSONResults)
.then(tags => {
const jsonLD = JSON.parse(tags[4].innerHTML);
const jsonLD = JSON.parse(tags[5].innerHTML);
expect(jsonLD).to.deep.equal({
'@context': 'http://schema.org/',
'@type': 'Product',
Expand Down Expand Up @@ -221,7 +268,7 @@ describe('Validates JSON-LD For:', () => {
cy.get('head script[type="application/ld+json"]')
.should('have.length', expectedJSONResults)
.then(tags => {
const jsonLD = JSON.parse(tags[5].innerHTML);
const jsonLD = JSON.parse(tags[6].innerHTML);
assertSchema(schemas)('Social Profile', '1.0.0')(jsonLD);
});
});
Expand All @@ -231,7 +278,7 @@ describe('Validates JSON-LD For:', () => {
cy.get('head script[type="application/ld+json"]')
.should('have.length', expectedJSONResults)
.then(tags => {
const jsonLD = JSON.parse(tags[5].innerHTML);
const jsonLD = JSON.parse(tags[6].innerHTML);
expect(jsonLD).to.deep.equal({
'@context': 'http://schema.org',
'@type': 'Person',
Expand Down
7 changes: 5 additions & 2 deletions e2e/cypress/schemas/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { combineSchemas } from '@cypress/schema-tools';
import courseVersions from './course-schema';

import articleVersions from './article-schema';
import blogVersions from './blog-schema';
import courseVersions from './course-schema';
import localBusiness from './local-business-schema';
import logoVersions from './logo-schema';
import productVersions from './product-schema';
import socialProfileVersions from './social-profile-schema';

const schemas = combineSchemas(
courseVersions,
articleVersions,
blogVersions,
courseVersions,
localBusiness,
logoVersions,
productVersions,
socialProfileVersions,
Expand Down
136 changes: 136 additions & 0 deletions e2e/cypress/schemas/local-business-schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { versionSchemas } from '@cypress/schema-tools';

const socialProfile100 = {
version: {
major: 1,
minor: 0,
patch: 0,
},
schema: {
type: 'object',
title: 'Local Business',
description:
'An example schema describing JSON-LD for type: Local Business',
properties: {
'@context': {
type: 'string',
description: 'Schema.org context',
},
'@type': {
type: 'string',
description:
'Any more specific type supported by Local Business https://schema.org/LocalBusiness',
},
'@id': {
type: 'string',
description:
'Globally unique ID of the specific business location in the form of a URL. The ID should be stable and unchanging over time. Google Search treats the URL as an opaque string and it does not have to be a working link. If the business has multiple locations, make sure the @id is unique for each location.',
},
name: {
type: 'string',
description: 'The name of the person or organisation.',
},
description: {
type: 'string',
description: 'Description for the local business.',
},
url: {
type: 'string',
description:
'The fully-qualified URL of the specific business location. Unlike the @id property, this URL property should be a working link.',
},
telephone: {
type: 'string',
description:
'A business phone number meant to be the primary contact method for customers. Be sure to include the country code and area code in the phone number.',
},
image: {
type: 'array',
items: {
type: 'string',
},
description: "Array of image URL's",
},
address: {
type: 'object',
description: "Array of social profile URL's",
properties: {
'@type': {
type: 'string',
description: 'JSON-LD type: PostalAddress',
},
streetAddress: {
type: 'string',
description: 'Street number, street name, and unit number',
},
addressLocality: {
type: 'string',
description: 'City',
},
addressRegion: {
type: 'string',
description: 'State or province, if applicable.',
},
postalCode: {
type: 'string',
description: 'Postal or zip code.',
},
addressCountry: {
type: 'string',
description: 'The 2-letter ISO 3166-1 alpha-2 country code',
},
},
},
geo: {
type: 'object',
description: "Array of social profile URL's",
properties: {
'@type': {
type: 'string',
description: 'JSON-LD type: GeoCoordinates',
},
latitude: {
type: 'string',
description: 'The latitude of the business location.',
},
longitude: {
type: 'string',
description: 'The longitude of the business location.',
},
},
},
},
required: true,
additionalProperties: false,
},
example: {
'@context': 'http://schema.org',
'@type': 'Store',
image: [
'https://example.com/photos/1x1/photo.jpg',
'https://example.com/photos/4x3/photo.jpg',
'https://example.com/photos/16x9/photo.jpg',
],
'@id': 'http://davesdeptstore.example.com',
name: "Dave's Department Store",
description: 'Some form of description',
address: {
'@type': 'PostalAddress',
streetAddress: '1600 Saratoga Ave',
addressLocality: 'San Jose',
addressRegion: 'CA',
postalCode: '95129',
addressCountry: 'US',
},
geo: {
'@type': 'GeoCoordinates',
latitude: '37.293058',
longitude: '-121.988331',
},
url: 'http://www.example.com/store-locator/sl/San-Jose-Westgate-Store/1427',
telephone: '+14088717984',
},
};

const socialProfile = versionSchemas(socialProfile100);
export default socialProfile;
Loading

0 comments on commit 6475a95

Please sign in to comment.