diff --git a/cypress/e2e/movie.spec.js b/cypress/e2e/movie.spec.js new file mode 100644 index 00000000..e1593404 --- /dev/null +++ b/cypress/e2e/movie.spec.js @@ -0,0 +1,69 @@ +import { assertSchema } from '@cypress/schema-tools'; +import schemas from '../schemas'; + +describe('Movie JSON-LD', () => { + it('matches schema', () => { + cy.visit('http://localhost:3000/jsonld/movie'); + cy.get('head script[type="application/ld+json"]').then(tags => { + const jsonLD = JSON.parse(tags[0].innerHTML); + assertSchema(schemas)('Movie', '1.0.0')(jsonLD); + }); + }); + + it('renders with all props', () => { + cy.visit('http://localhost:3000/jsonld/movie'); + cy.get('head script[type="application/ld+json"]').then(tags => { + const jsonLD = JSON.parse(tags[0].innerHTML); + + expect(jsonLD).to.deep.equal({ + '@context': 'https://schema.org', + '@type': 'Movie', + name: 'Movie Example', + contentRating: 5, + duration: 'PT2H30M', + dateCreated: '2022-01-01', + description: 'This is a movie description.', + image: 'https://example.com/movie.jpg', + author: { '@type': 'Person', name: 'Author Name' }, + director: { '@type': 'Person', name: 'Director Name' }, + actor: [ + { + '@type': 'Person', + name: 'John Doe', + characterName: 'John Doe', + }, + { + '@type': 'Person', + name: 'Jane Doe', + characterName: 'Jane Doe', + }, + ], + genre: ['Action', 'Adventure'], + offers: [ + { + '@type': 'Offer', + price: '12.99', + priceCurrency: 'USD', + availability: 'https://schema.org/InStock', + url: 'https://example.com/movie', + seller: { + '@type': 'Organization', + name: 'Seller Name', + }, + }, + ], + trailer: 'https://example.com/movie-trailer', + countryOfOrigin: 'USA', + aggregateRating: { + '@type': 'AggregateRating', + ratingValue: '44', + reviewCount: '89', + ratingCount: '684', + bestRating: '100', + worstRating: '1', + }, + isAccessibleForFree: true, + }); + }); + }); +}); diff --git a/src/index.tsx b/src/index.tsx index e5687e39..a8f58b21 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -17,6 +17,7 @@ export { default as LocalBusinessJsonLd, LocalBusinessJsonLdProps, } from './jsonld/localBusiness'; +export { default as MovieJsonLd, MovieJsonLdProps } from './jsonld/movie'; export { default as QAPageJsonLd, QAPageJsonLdProps } from './jsonld/qaPage'; export { default as ProfilePageJsonLd, diff --git a/src/jsonld/movie.tsx b/src/jsonld/movie.tsx new file mode 100644 index 00000000..27cdd0e4 --- /dev/null +++ b/src/jsonld/movie.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { Actor, AggregateRating, Offers, Video } from 'src/types'; +import { JsonLd, JsonLdProps } from './jsonld'; + +import { setAuthor } from 'src/utils/schema/setAuthor'; +import { setOffers } from 'src/utils/schema/setOffers'; +import { setAggregateRating } from 'src/utils/schema/setAggregateRating'; +import { setActors } from 'src/utils/schema/setActors'; +import { setDirector } from 'src/utils/schema/setDirector'; +import { setVideo } from 'src/utils/schema/setVideo'; + +export interface MovieJsonLdProps extends JsonLdProps { + keyOverride?: string; + name: string; + contentRating: number; + duration: string; + dateCreated: string; + description: string; + image: string; + authorName?: string; + directorName?: string; + actors?: Actor | Actor[]; + genreName?: string | string[]; + trailer?: Video; + offers?: Offers | Offers[]; + countryOfOrigin?: string; + aggregateRating?: AggregateRating; +} + +function MovieJsonLd({ + type = 'Movie', + keyOverride, + authorName, + directorName, + actors, + genreName, + trailer, + offers, + countryOfOrigin, + aggregateRating, + ...rest +}: MovieJsonLdProps) { + const data = { + ...rest, + author: setAuthor(authorName), + director: setDirector(directorName), + actors: setActors(actors), + genre: genreName, + trailer: setVideo(trailer), + offers: setOffers(offers), + countryOfOrigin: countryOfOrigin, + aggregateRating: setAggregateRating(aggregateRating), + }; + + return ( + + ); +} + +export default MovieJsonLd; diff --git a/src/types.ts b/src/types.ts index ac9ddd0a..fcaf3fd3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -385,6 +385,11 @@ export type Author = { name: string; }; +export type Actor = { + actor: string; + characterName?: string; +}; + export type ArticleAuthor = { name: string; url?: string; diff --git a/src/utils/schema/setActors.ts b/src/utils/schema/setActors.ts new file mode 100644 index 00000000..6138bb3f --- /dev/null +++ b/src/utils/schema/setActors.ts @@ -0,0 +1,24 @@ +import { Actor } from 'src/types'; + +export function setActors(actors?: Actor | Actor[]) { + function mapOffer({ actor, characterName }: Actor) { + return { + '@type': 'PerformanceRole', + ...(actors && { + actor: { + '@type': 'Person', + name: actor, + }, + ...(characterName && { characterName: characterName }), + }), + }; + } + + if (Array.isArray(actors)) { + return actors.map(mapOffer); + } else if (actors) { + return mapOffer(actors); + } + + return undefined; +} diff --git a/src/utils/schema/setDirector.ts b/src/utils/schema/setDirector.ts new file mode 100644 index 00000000..235b015c --- /dev/null +++ b/src/utils/schema/setDirector.ts @@ -0,0 +1,9 @@ +export function setDirector(director?: string) { + if (director) { + return { + '@type': 'Person', + name: director, + }; + } + return undefined; +}