Updated meta/structured data sources & fallbacks (#11068)

refs https://github.com/TryGhost/Ghost/issues/10921, closes https://github.com/TryGhost/Ghost/issues/11357, closes https://github.com/TryGhost/Ghost/issues/11403

- updates the sources and fallbacks for the output of `{{ghost_head}}` meta/structured data
- re-works related tests to better show the fallback chains for different scenarios
- fixes `{{ghost_head}}` tests to use `before/afterEach` so that tests are not interdependent
This commit is contained in:
Kevin Ansfield 2019-11-21 13:08:00 +00:00 committed by GitHub
parent 193c179110
commit be4a5a84d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1412 additions and 697 deletions

View File

@ -22,6 +22,10 @@ function getContextObject(data, context) {
chosenContext = data.post;
} else if (_.includes(context, 'page') && data.page) {
chosenContext = data.page;
} else if (_.includes(context, 'tag') && data.tag) {
chosenContext = data.tag;
} else if (_.includes(context, 'author') && data.author) {
chosenContext = data.author;
} else if (data[context]) {
// @NOTE: This is confusing as hell. It tries to get data[['author']], which works, but coincidence?
chosenContext = data[context];

View File

@ -1,59 +1,83 @@
const _ = require('lodash');
const settingsCache = require('../../server/services/settings/cache');
const getExcerpt = require('./excerpt');
function getDescription(data, root, options) {
function getDescription(data, root, options = {}) {
const context = root ? root.context : null;
const siteDescription = settingsCache.get('meta_description') || settingsCache.get('description');
let description = '';
let postSdDescription;
options = options ? options : {};
// We only return meta_description if provided
if (data.meta_description) {
description = data.meta_description;
} else if (_.includes(context, 'paged')) {
description = '';
} else if (_.includes(context, 'home')) {
if (options && options.property) {
const siteSdDescription = options.property + '_description';
description = settingsCache.get(siteSdDescription) || '';
const siteDescription = settingsCache.get('meta_description') || settingsCache.get('description');
if (options.property) {
// options.property = null/'og'/'twitter'
const optionsPropertyName = `${options.property || 'meta'}_description`;
description = settingsCache.get(optionsPropertyName) || siteDescription || '';
} else {
description = siteDescription;
}
} else if (_.includes(context, 'author') && data.author) {
// The usage of meta data fields for author is currently not implemented.
// We do have meta_description and meta_title fields
// in the users table, but there's no UI to populate those.
description = data.author.meta_description || '';
if (!options.property && _.includes(context, 'paged')) {
description = '';
} else {
// The usage of meta data fields for author is currently not implemented.
// We do have meta_description and meta_title fields
// in the users table, but there's no UI to populate those.
description = data.author.meta_description
|| data.author.bio
|| (options.property ? settingsCache.get('meta_description') : '')
|| '';
}
} else if (_.includes(context, 'tag') && data.tag) {
description = data.tag.meta_description || '';
if (!options.property && _.includes(context, 'paged')) {
description = '';
} else {
description = data.tag.meta_description
|| data.tag.description
|| (options.property ? settingsCache.get('meta_description') : '')
|| '';
}
} else if (_.includes(context, 'post') && data.post) {
if (options && options.property) {
postSdDescription = options.property + '_description';
description = data.post[postSdDescription] || '';
if (options.property) {
description = data.post[`${options.property}_description`]
|| data.post.custom_excerpt
|| data.post.meta_description
|| getExcerpt(data.post.html || '', {words: 50})
|| settingsCache.get('description')
|| '';
} else {
description = data.post.meta_description || '';
}
} else if (_.includes(context, 'page') && data.post) {
// Page title dependent on legacy object formatting (https://github.com/TryGhost/Ghost/issues/10042)
if (options && options.property) {
postSdDescription = options.property + '_description';
description = data.post[postSdDescription] || '';
// Page description dependent on legacy object formatting (https://github.com/TryGhost/Ghost/issues/10042)
if (options.property) {
description = data.post[`${options.property}_description`]
|| data.post.custom_excerpt
|| data.post.meta_description
|| getExcerpt(data.post.html || '', {words: 50})
|| settingsCache.get('description')
|| '';
} else {
description = data.post.meta_description || '';
}
} else if (_.includes(context, 'page') && data.page) {
if (options && options.property) {
postSdDescription = options.property + '_description';
description = data.page[postSdDescription] || '';
if (options.property) {
description = data.page[`${options.property}_description`]
|| data.page.custom_excerpt
|| data.page.meta_description
|| getExcerpt(data.page.html || '', {words: 50})
|| settingsCache.get('description')
|| '';
} else {
description = data.page.meta_description || '';
}
}
return (description || '').trim();
return (description || '').trim() || null;
}
module.exports = getDescription;

View File

@ -44,7 +44,7 @@ function getMetaData(data, root) {
url: getAuthorImage(data, true)
},
ogImage: {
url: getOgImage(data, true)
url: getOgImage(data)
},
ogTitle: getTitle(data, root, {property: 'og'}),
ogDescription: getDescription(data, root, {property: 'og'}),

View File

@ -5,19 +5,31 @@ const settingsCache = require('../../server/services/settings/cache');
function getOgImage(data) {
const context = data.context ? data.context : null;
const contextObject = getContextObject(data, context);
const siteOgImage = settingsCache.get('og_image');
const contextObject = getContextObject(data, context, false);
if (_.includes(context, 'home')) {
const imgUrl = settingsCache.get('og_image') || settingsCache.get('cover_image');
return (imgUrl && urlUtils.relativeToAbsolute(imgUrl)) || null;
}
if (_.includes(context, 'post') || _.includes(context, 'page') || _.includes(context, 'amp')) {
if (contextObject.og_image) {
return urlUtils.urlFor('image', {image: contextObject.og_image}, true);
return urlUtils.relativeToAbsolute(contextObject.og_image);
} else if (contextObject.feature_image) {
return urlUtils.urlFor('image', {image: contextObject.feature_image}, true);
return urlUtils.relativeToAbsolute(contextObject.feature_image);
}
}
if (_.includes(context, 'home') && siteOgImage) {
return urlUtils.urlFor('image', {image: siteOgImage}, true);
if (_.includes(context, 'author') && contextObject.cover_image) {
return urlUtils.relativeToAbsolute(contextObject.cover_image);
}
if (_.includes(context, 'tag')) {
if (contextObject.feature_image) {
return urlUtils.relativeToAbsolute(contextObject.feature_image);
} else if (settingsCache.get('cover_image')) {
return urlUtils.relativeToAbsolute(settingsCache.get('cover_image'));
}
}
return null;

View File

@ -11,11 +11,11 @@ function getStructuredData(metaData) {
structuredData = {
'og:site_name': metaData.site.title,
'og:type': metaData.ogType,
'og:title': metaData.ogTitle || metaData.metaTitle,
'og:title': metaData.ogTitle,
// CASE: metaData.excerpt for post context is populated by either the custom excerpt,
// the meta description, or the automated excerpt of 50 words. It is empty for any
// other context and *always* uses the provided meta description fields.
'og:description': metaData.ogDescription || metaData.excerpt || metaData.metaDescription,
'og:description': metaData.ogDescription,
'og:url': metaData.canonicalUrl,
'og:image': metaData.ogImage.url || metaData.coverImage.url,
'article:published_time': metaData.publishedDate,
@ -24,8 +24,8 @@ function getStructuredData(metaData) {
'article:publisher': metaData.site.facebook ? socialUrls.facebook(metaData.site.facebook) : undefined,
'article:author': metaData.authorFacebook ? socialUrls.facebook(metaData.authorFacebook) : undefined,
'twitter:card': card,
'twitter:title': metaData.twitterTitle || metaData.metaTitle,
'twitter:description': metaData.twitterDescription || metaData.excerpt || metaData.metaDescription,
'twitter:title': metaData.twitterTitle,
'twitter:description': metaData.twitterDescription,
'twitter:url': metaData.canonicalUrl,
'twitter:image': metaData.twitterImage || metaData.coverImage.url,
'twitter:label1': metaData.authorName ? 'Written by' : undefined,

View File

@ -1,16 +1,16 @@
const _ = require('lodash');
const settingsCache = require('../../server/services/settings/cache');
function getTitle(data, root, options) {
function getTitle(data, root, options = {}) {
const context = root ? root.context : null;
const siteTitle = settingsCache.get('meta_title') || settingsCache.get('title');
const siteTitle = settingsCache.get('title');
const pagination = root ? root.pagination : null;
let title = '';
let postSdTitle;
let pageString = '';
// options.property = null/'og'/'twitter'
const optionsPropertyName = `${options.property || 'meta'}_title`;
options = options ? options : {};
let title = '';
let pageString = '';
if (pagination && pagination.total > 1) {
pageString = _.has(options.hash, 'page') ? options.hash.page.replace('%', pagination.page) : ' (Page ' + pagination.page + ')';
@ -21,11 +21,10 @@ function getTitle(data, root, options) {
title = data.meta_title;
// Home title
} else if (_.includes(context, 'home')) {
if (options && options.property) {
const siteSdTitle = options.property + '_title';
title = settingsCache.get(siteSdTitle) || '';
if (options.property) {
title = settingsCache.get(optionsPropertyName) || siteTitle;
} else {
title = siteTitle;
title = settingsCache.get('meta_title') || siteTitle;
}
// Author title, paged
} else if (_.includes(context, 'author') && data.author && _.includes(context, 'paged')) {
@ -41,28 +40,13 @@ function getTitle(data, root, options) {
title = data.tag.meta_title || data.tag.name + ' - ' + siteTitle;
// Post title
} else if (_.includes(context, 'post') && data.post) {
if (options && options.property) {
postSdTitle = options.property + '_title';
title = data.post[postSdTitle] || '';
} else {
title = data.post.meta_title || data.post.title;
}
title = data.post[optionsPropertyName] || data.post.meta_title || data.post.title;
// Page title dependent on legacy object formatting (https://github.com/TryGhost/Ghost/issues/10042)
} else if (_.includes(context, 'page') && data.post) {
if (options && options.property) {
postSdTitle = options.property + '_title';
title = data.post[postSdTitle] || '';
} else {
title = data.post.meta_title || data.post.title;
}
title = data.post[optionsPropertyName] || data.post.meta_title || data.post.title;
// Page title v2
} else if (_.includes(context, 'page') && data.page) {
if (options && options.property) {
postSdTitle = options.property + '_title';
title = data.page[postSdTitle] || '';
} else {
title = data.page.meta_title || data.page.title;
}
title = data.page[optionsPropertyName] || data.page.meta_title || data.page.title;
// Fallback
} else {
title = siteTitle + pageString;

View File

@ -5,19 +5,31 @@ const settingsCache = require('../../server/services/settings/cache');
function getTwitterImage(data) {
const context = data.context ? data.context : null;
const contextObject = getContextObject(data, context);
const siteTwitterImage = settingsCache.get('twitter_image');
const contextObject = getContextObject(data, context, false);
if (_.includes(context, 'home')) {
const imgUrl = settingsCache.get('twitter_image') || settingsCache.get('cover_image');
return (imgUrl && urlUtils.relativeToAbsolute(imgUrl)) || null;
}
if (_.includes(context, 'post') || _.includes(context, 'page') || _.includes(context, 'amp')) {
if (contextObject.twitter_image) {
return urlUtils.urlFor('image', {image: contextObject.twitter_image}, true);
return urlUtils.relativeToAbsolute(contextObject.twitter_image);
} else if (contextObject.feature_image) {
return urlUtils.urlFor('image', {image: contextObject.feature_image}, true);
return urlUtils.relativeToAbsolute(contextObject.feature_image);
}
}
if (_.includes(context, 'home') && siteTwitterImage) {
return urlUtils.urlFor('image', {image: siteTwitterImage}, true);
if (_.includes(context, 'author') && contextObject.cover_image) {
return urlUtils.relativeToAbsolute(contextObject.cover_image);
}
if (_.includes(context, 'tag')) {
if (contextObject.feature_image) {
return urlUtils.relativeToAbsolute(contextObject.feature_image);
} else if (settingsCache.get('cover_image')) {
return urlUtils.relativeToAbsolute(settingsCache.get('cover_image'));
}
}
return null;

View File

@ -16,211 +16,614 @@ describe('getMetaDescription', function () {
sinon.restore();
});
beforeEach(function () {
localSettingsCache.description = 'Site description';
localSettingsCache.meta_description = 'Site meta description';
localSettingsCache.og_description = 'Site og description';
localSettingsCache.twitter_description = 'Site twitter description';
});
afterEach(function () {
localSettingsCache = {};
});
it('should return site description when in home context', function () {
localSettingsCache.description = 'Site description';
var description = getMetaDescription({
}, {
context: ['home']
});
description.should.equal('Site description');
});
it('should return site OG description when in home context', function () {
localSettingsCache.og_description = 'My site Facebook description';
var description = getMetaDescription({
}, {
context: ['home']
}, {
property: 'og'
});
description.should.equal('My site Facebook description');
});
it('should return site twitter description when in home context', function () {
localSettingsCache.twitter_description = 'My site Twitter description';
var description = getMetaDescription({
}, {
context: ['home']
}, {
property: 'twitter'
});
description.should.equal('My site Twitter description');
});
it('should return site meta_description when it is defined and in home context', function () {
localSettingsCache.description = 'Site description';
localSettingsCache.meta_description = 'Site meta description';
var description = getMetaDescription({
}, {
context: ['home']
});
description.should.equal('Site meta description');
});
it('should return meta_description if on data root', function () {
var description = getMetaDescription({
meta_description: 'My test description.'
const description = getMetaDescription({
meta_description: 'My data meta description'
}, {
context: 'home'
});
description.should.equal('My test description.');
description.should.equal('My data meta description');
});
it('should return empty string if on root context contains paged', function () {
var description = getMetaDescription({}, {
context: ['paged']
// <meta name="description">
describe('property: null', function () {
it('has correct fallbacks for context: home', function () {
getMetaDescription({}, {context: 'home'})
.should.equal('Site meta description');
localSettingsCache.meta_description = '';
getMetaDescription({}, {context: 'home'})
.should.equal('Site description');
localSettingsCache.description = '';
should(
getMetaDescription({}, {context: 'home'})
).equal(null);
});
it('has correct fallbacks for context: post', function () {
const post = {
meta_description: 'Post meta description'
};
getMetaDescription({post}, {context: 'post'})
.should.equal('Post meta description');
post.meta_description = '';
should(
getMetaDescription({post}, {context: 'post'})
).equal(null);
});
it('has correct fallbacks for context: page', function () {
const page = {
meta_description: 'Page meta description'
};
getMetaDescription({page}, {context: 'page'})
.should.equal('Page meta description');
page.meta_description = '';
should(
getMetaDescription({page}, {context: 'page'})
).equal(null);
});
// NOTE: this is a legacy format and should be resolved with https://github.com/TryGhost/Ghost/issues/10042
it('has correct fallbacks for context: page (legacy format)', function () {
const post = {
meta_description: 'Page meta description'
};
getMetaDescription({post}, {context: 'page'})
.should.equal('Page meta description');
post.meta_description = '';
should(
getMetaDescription({post}, {context: 'page'})
).equal(null);
});
it('has correct fallbacks for context: author', function () {
const author = {
meta_description: 'Author meta description',
bio: 'Author bio'
};
getMetaDescription({author}, {context: 'author'})
.should.equal('Author meta description');
author.meta_description = '';
getMetaDescription({author}, {context: 'author'})
.should.equal('Author bio');
author.bio = '';
should(
getMetaDescription({author}, {context: 'author'})
).equal(null);
});
it('has correct fallbacks for context: author_paged', function () {
const author = {
meta_description: 'Author meta description',
bio: 'Author bio'
};
should(
getMetaDescription({author}, {context: ['author', 'paged']})
).equal(null);
});
it('has correct fallbacks for context: tag', function () {
const tag = {
meta_description: 'Tag meta description',
description: 'Tag description'
};
getMetaDescription({tag}, {context: 'tag'})
.should.equal('Tag meta description');
tag.meta_description = '';
getMetaDescription({tag}, {context: 'tag'})
.should.equal('Tag description');
tag.description = '';
should(
getMetaDescription({tag}, {context: 'tag'})
).equal(null);
});
it('has correct fallbacks for context: tag_paged', function () {
const tag = {
meta_description: 'Tag meta description',
description: 'Tag description'
};
should(
getMetaDescription({tag}, {context: ['tag', 'paged']})
).equal(null);
});
description.should.equal('');
});
it('should not return meta description for author if on root context contains author and no meta description provided', function () {
var description = getMetaDescription({
author: {
bio: 'Just some hack building code to make the world better.'
}
}, {
context: ['author']
describe('property: og', function () {
let options;
beforeEach(function () {
options = {property: 'og'};
});
it('has correct fallbacks for context: home', function () {
getMetaDescription({}, {context: 'home'}, options)
.should.equal('Site og description');
localSettingsCache.og_description = '';
getMetaDescription({}, {context: 'home'}, options)
.should.equal('Site meta description');
localSettingsCache.meta_description = '';
getMetaDescription({}, {context: 'home'}, options)
.should.equal('Site description');
localSettingsCache.description = '';
should(
getMetaDescription({}, {context: 'home'}, options)
).equal(null);
});
it('has correct fallbacks for context: post', function () {
const post = {
html: '<p>Post html</p>',
custom_excerpt: 'Post custom excerpt',
meta_description: 'Post meta description',
og_description: 'Post og description'
};
getMetaDescription({post}, {context: 'post'}, options)
.should.equal('Post og description');
post.og_description = '';
getMetaDescription({post}, {context: 'post'}, options)
.should.equal('Post custom excerpt');
post.custom_excerpt = '';
getMetaDescription({post}, {context: 'post'}, options)
.should.equal('Post meta description');
post.meta_description = '';
getMetaDescription({post}, {context: 'post'}, options)
.should.equal('Post html');
post.html = '';
getMetaDescription({post}, {context: 'post'}, options)
.should.equal('Site description');
});
it('has correct fallbacks for context: page', function () {
const page = {
html: '<p>Page html</p>',
custom_excerpt: 'Page custom excerpt',
meta_description: 'Page meta description',
og_description: 'Page og description'
};
getMetaDescription({page}, {context: 'page'}, options)
.should.equal('Page og description');
page.og_description = '';
getMetaDescription({page}, {context: 'page'}, options)
.should.equal('Page custom excerpt');
page.custom_excerpt = '';
getMetaDescription({page}, {context: 'page'}, options)
.should.equal('Page meta description');
page.meta_description = '';
getMetaDescription({page}, {context: 'page'}, options)
.should.equal('Page html');
page.html = '';
getMetaDescription({page}, {context: 'page'}, options)
.should.equal('Site description');
});
// NOTE: this is a legacy format and should be resolved with https://github.com/TryGhost/Ghost/issues/10042
it('has correct fallbacks for context: page (legacy format)', function () {
const post = {
html: '<p>Page html</p>',
custom_excerpt: 'Page custom excerpt',
meta_description: 'Page meta description',
og_description: 'Page og description'
};
getMetaDescription({post}, {context: 'page'}, options)
.should.equal('Page og description');
post.og_description = '';
getMetaDescription({post}, {context: 'page'}, options)
.should.equal('Page custom excerpt');
post.custom_excerpt = '';
getMetaDescription({post}, {context: 'page'}, options)
.should.equal('Page meta description');
post.meta_description = '';
getMetaDescription({post}, {context: 'page'}, options)
.should.equal('Page html');
post.html = '';
getMetaDescription({post}, {context: 'page'}, options)
.should.equal('Site description');
});
it('has correct fallbacks for context: author', function () {
const author = {
meta_description: 'Author meta description',
bio: 'Author bio'
};
getMetaDescription({author}, {context: 'author'}, options)
.should.equal('Author meta description');
author.meta_description = '';
getMetaDescription({author}, {context: 'author'}, options)
.should.equal('Author bio');
author.bio = '';
getMetaDescription({author}, {context: 'author'}, options)
.should.equal('Site meta description');
localSettingsCache.meta_description = '';
should(
getMetaDescription({author}, {context: 'author'}, options)
).equal(null);
});
it('has correct fallbacks for context: author_paged', function () {
const author = {
meta_description: 'Author meta description',
bio: 'Author bio'
};
getMetaDescription({author}, {context: ['author', 'paged']}, options)
.should.equal('Author meta description');
author.meta_description = '';
getMetaDescription({author}, {context: ['author', 'paged']}, options)
.should.equal('Author bio');
author.bio = '';
getMetaDescription({author}, {context: ['author', 'paged']}, options)
.should.equal('Site meta description');
localSettingsCache.meta_description = '';
should(
getMetaDescription({author}, {context: ['author', 'paged']}, options)
).equal(null);
});
it('has correct fallbacks for context: tag', function () {
const tag = {
meta_description: 'Tag meta description',
description: 'Tag description'
};
getMetaDescription({tag}, {context: 'tag'}, options)
.should.equal('Tag meta description');
tag.meta_description = '';
getMetaDescription({tag}, {context: 'tag'}, options)
.should.equal('Tag description');
tag.description = '';
getMetaDescription({tag}, {context: 'tag'}, options)
.should.equal('Site meta description');
localSettingsCache.meta_description = '';
should(
getMetaDescription({tag}, {context: 'tag'}, options)
).equal(null);
});
it('has correct fallbacks for context: tag_paged', function () {
const tag = {
meta_description: 'Tag meta description',
description: 'Tag description'
};
getMetaDescription({tag}, {context: ['tag', 'paged']}, options)
.should.equal('Tag meta description');
tag.meta_description = '';
getMetaDescription({tag}, {context: ['tag', 'paged']}, options)
.should.equal('Tag description');
tag.description = '';
getMetaDescription({tag}, {context: ['tag', 'paged']}, options)
.should.equal('Site meta description');
localSettingsCache.meta_description = '';
should(
getMetaDescription({tag}, {context: ['tag', 'paged']}, options)
).equal(null);
});
description.should.equal('');
});
it('should return meta description for author if on root context contains author and meta description provided', function () {
var description = getMetaDescription({
author: {
bio: 'Just some hack building code to make the world better.',
meta_description: 'Author meta description.'
}
}, {
context: ['author']
});
description.should.equal('Author meta description.');
});
describe('property: twitter', function () {
let options;
it('should return data tag meta description if on root context contains tag', function () {
var description = getMetaDescription({
tag: {
meta_description: 'Best tag ever!'
}
}, {
context: ['tag']
beforeEach(function () {
options = {property: 'twitter'};
});
description.should.equal('Best tag ever!');
});
it('should not return data tag description if no meta description for tag', function () {
var description = getMetaDescription({
tag: {
meta_description: '',
description: 'The normal description'
}
}, {
context: ['tag']
});
description.should.equal('');
});
it('has correct fallbacks for context: home', function () {
getMetaDescription({}, {context: 'home'}, options)
.should.equal('Site twitter description');
it('should return data post meta description if on root context contains post', function () {
var description = getMetaDescription({
post: {
meta_description: 'Best post ever!'
}
}, {
context: ['post']
});
description.should.equal('Best post ever!');
});
localSettingsCache.twitter_description = '';
it('should return OG data post meta description if on root context contains post', function () {
var description = getMetaDescription({
post: {
meta_description: 'Best post ever!',
og_description: 'My custom Facebook description!'
}
}, {
context: ['post']
}, {
property: 'og'
});
description.should.equal('My custom Facebook description!');
});
getMetaDescription({}, {context: 'home'}, options)
.should.equal('Site meta description');
it('should not return data post meta description if on root context contains post and called with OG property', function () {
var description = getMetaDescription({
post: {
meta_description: 'Best post ever!',
og_description: ''
}
}, {
context: ['post']
}, {
property: 'og'
});
description.should.equal('');
});
localSettingsCache.meta_description = '';
it('should return Twitter data post meta description if on root context contains post', function () {
var description = getMetaDescription({
post: {
meta_description: 'Best post ever!',
twitter_description: 'My custom Twitter description!'
}
}, {
context: ['post']
}, {
property: 'twitter'
});
description.should.equal('My custom Twitter description!');
});
getMetaDescription({}, {context: 'home'}, options)
.should.equal('Site description');
it('should return data post meta description if on root context contains post for an AMP post', function () {
var description = getMetaDescription({
post: {
meta_description: 'Best AMP post ever!'
}
}, {
context: ['amp', 'post']
});
description.should.equal('Best AMP post ever!');
});
localSettingsCache.description = '';
// NOTE: this is a legacy format and should be resolved with https://github.com/TryGhost/Ghost/issues/10042
it('legacy: should return data post meta description if on root context contains page', function () {
var description = getMetaDescription({
post: {
meta_description: 'Best page ever!'
}
}, {
context: ['page']
should(
getMetaDescription({}, {context: 'home'}, options)
).equal(null);
});
description.should.equal('Best page ever!');
});
it('v2: should return data page meta description if on root context contains page', function () {
var description = getMetaDescription({
page: {
meta_description: 'Best page ever!'
}
}, {
context: ['page']
});
description.should.equal('Best page ever!');
});
it('has correct fallbacks for context: post', function () {
const post = {
html: '<p>Post html</p>',
custom_excerpt: 'Post custom excerpt',
meta_description: 'Post meta description',
twitter_description: 'Post twitter description'
};
it('canary: should return data page meta description if on root context contains page', function () {
var description = getMetaDescription({
page: {
meta_description: 'Best page ever!'
}
}, {
context: ['page']
getMetaDescription({post}, {context: 'post'}, options)
.should.equal('Post twitter description');
post.twitter_description = '';
getMetaDescription({post}, {context: 'post'}, options)
.should.equal('Post custom excerpt');
post.custom_excerpt = '';
getMetaDescription({post}, {context: 'post'}, options)
.should.equal('Post meta description');
post.meta_description = '';
getMetaDescription({post}, {context: 'post'}, options)
.should.equal('Post html');
post.html = '';
getMetaDescription({post}, {context: 'post'}, options)
.should.equal('Site description');
});
it('has correct fallbacks for context: page', function () {
const page = {
html: '<p>Page html</p>',
custom_excerpt: 'Page custom excerpt',
meta_description: 'Page meta description',
twitter_description: 'Page twitter description'
};
getMetaDescription({page}, {context: 'page'}, options)
.should.equal('Page twitter description');
page.twitter_description = '';
getMetaDescription({page}, {context: 'page'}, options)
.should.equal('Page custom excerpt');
page.custom_excerpt = '';
getMetaDescription({page}, {context: 'page'}, options)
.should.equal('Page meta description');
page.meta_description = '';
getMetaDescription({page}, {context: 'page'}, options)
.should.equal('Page html');
page.html = '';
getMetaDescription({page}, {context: 'page'}, options)
.should.equal('Site description');
});
// NOTE: this is a legacy format and should be resolved with https://github.com/TryGhost/Ghost/issues/10042
it('has correct fallbacks for context: page (legacy format)', function () {
const post = {
html: '<p>Page html</p>',
custom_excerpt: 'Page custom excerpt',
meta_description: 'Page meta description',
twitter_description: 'Page twitter description'
};
getMetaDescription({post}, {context: 'page'}, options)
.should.equal('Page twitter description');
post.twitter_description = '';
getMetaDescription({post}, {context: 'page'}, options)
.should.equal('Page custom excerpt');
post.custom_excerpt = '';
getMetaDescription({post}, {context: 'page'}, options)
.should.equal('Page meta description');
post.meta_description = '';
getMetaDescription({post}, {context: 'page'}, options)
.should.equal('Page html');
post.html = '';
getMetaDescription({post}, {context: 'page'}, options)
.should.equal('Site description');
});
it('has correct fallbacks for context: author', function () {
const author = {
meta_description: 'Author meta description',
bio: 'Author bio'
};
getMetaDescription({author}, {context: 'author'}, options)
.should.equal('Author meta description');
author.meta_description = '';
getMetaDescription({author}, {context: 'author'}, options)
.should.equal('Author bio');
author.bio = '';
getMetaDescription({author}, {context: 'author'}, options)
.should.equal('Site meta description');
localSettingsCache.meta_description = '';
should(
getMetaDescription({author}, {context: 'author'}, options)
).equal(null);
});
it('has correct fallbacks for context: author_paged', function () {
const author = {
meta_description: 'Author meta description',
bio: 'Author bio'
};
getMetaDescription({author}, {context: ['author', 'paged']}, options)
.should.equal('Author meta description');
author.meta_description = '';
getMetaDescription({author}, {context: ['author', 'paged']}, options)
.should.equal('Author bio');
author.bio = '';
getMetaDescription({author}, {context: ['author', 'paged']}, options)
.should.equal('Site meta description');
localSettingsCache.meta_description = '';
should(
getMetaDescription({author}, {context: ['author', 'paged']}, options)
).equal(null);
});
it('has correct fallbacks for context: tag', function () {
const tag = {
meta_description: 'Tag meta description',
description: 'Tag description'
};
getMetaDescription({tag}, {context: 'tag'}, options)
.should.equal('Tag meta description');
tag.meta_description = '';
getMetaDescription({tag}, {context: 'tag'}, options)
.should.equal('Tag description');
tag.description = '';
getMetaDescription({tag}, {context: 'tag'}, options)
.should.equal('Site meta description');
localSettingsCache.meta_description = '';
should(
getMetaDescription({tag}, {context: 'tag'}, options)
).equal(null);
});
it('has correct fallbacks for context: tag_paged', function () {
const tag = {
meta_description: 'Tag meta description',
description: 'Tag description'
};
getMetaDescription({tag}, {context: ['tag', 'paged']}, options)
.should.equal('Tag meta description');
tag.meta_description = '';
getMetaDescription({tag}, {context: ['tag', 'paged']}, options)
.should.equal('Tag description');
tag.description = '';
getMetaDescription({tag}, {context: ['tag', 'paged']}, options)
.should.equal('Site meta description');
localSettingsCache.meta_description = '';
should(
getMetaDescription({tag}, {context: ['tag', 'paged']}, options)
).equal(null);
});
description.should.equal('Best page ever!');
});
it('v3: should return data page meta description if on root context contains page', function () {

View File

@ -4,118 +4,189 @@ const getOgImage = require('../../../../frontend/meta/og_image');
const settingsCache = require('../../../../server/services/settings/cache');
describe('getOgImage', function () {
describe('[home]', function () {
it('should return null if [home] context and no og_image set', function () {
sinon.stub(settingsCache, 'get').callsFake(function (key) {
return {
og_image: null
}[key];
});
let localSettingsCache = {};
var ogImageUrl = getOgImage({
context: ['home'],
home: {}
});
should(ogImageUrl).equal(null);
sinon.restore();
});
it('should return image URL if [home] context and og_image set', function () {
sinon.stub(settingsCache, 'get').callsFake(function (key) {
return {
og_image: '/content/images/home-og.jpg'
}[key];
});
var ogImageUrl = getOgImage({
context: ['home'],
home: {}
});
ogImageUrl.should.not.equal('/content/images/home-og.jpg');
ogImageUrl.should.match(/\/content\/images\/home-og\.jpg$/);
sinon.restore();
beforeEach(function () {
sinon.stub(settingsCache, 'get').callsFake(function (key) {
return localSettingsCache[key];
});
});
it('[home] should return null if not post context [home] and no og_image set', function () {
var ogImageUrl = getOgImage({
context: ['home'],
home: {}
});
should(ogImageUrl).equal(null);
afterEach(function () {
sinon.restore();
localSettingsCache = {};
});
it('should return null if not post context [author]', function () {
var ogImageUrl = getOgImage({
context: ['author'],
author: {}
});
should(ogImageUrl).equal(null);
it('has correct fallbacks for context: home', function () {
localSettingsCache.og_image = '/content/images/settings-og.jpg';
localSettingsCache.cover_image = '/content/images/settings-cover.jpg';
getOgImage({context: ['home'], home: {}})
.should.endWith('/content/images/settings-og.jpg');
localSettingsCache.og_image = '';
getOgImage({context: ['home'], home: {}})
.should.endWith('/content/images/settings-cover.jpg');
localSettingsCache.cover_image = '';
should(
getOgImage({context: ['home'], home: {}})
).equal(null);
});
it('should return null if not post context [tag]', function () {
var ogImageUrl = getOgImage({
context: ['tag'],
author: {}
});
should(ogImageUrl).equal(null);
it('has correct fallbacks for context: post', function () {
localSettingsCache.og_image = '/content/images/settings-og.jpg';
localSettingsCache.cover_image = '/content/images/settings-cover.jpg';
const post = {
og_image: '/content/images/post-og.jpg',
feature_image: '/content/images/post-feature.jpg'
};
getOgImage({context: ['post'], post})
.should.endWith('post-og.jpg');
post.og_image = '';
getOgImage({context: ['post'], post})
.should.endWith('post-feature.jpg');
post.feature_image = '';
should(
getOgImage({context: ['post'], post})
).equal(null);
});
it('should return absolute url for OG image in post context', function () {
var ogImageUrl = getOgImage({
context: ['post'],
post: {
feature_image: '/content/images/my-test-image.jpg',
og_image: '/content/images/my-special-og-image.jpg'
}
});
ogImageUrl.should.not.equal('/content/images/my-special-og-image.jpg');
ogImageUrl.should.match(/\/content\/images\/my-special-og-image\.jpg$/);
it('has correct fallbacks for context: page', function () {
localSettingsCache.og_image = '/content/images/settings-og.jpg';
localSettingsCache.cover_image = '/content/images/settings-cover.jpg';
const page = {
og_image: '/content/images/page-og.jpg',
feature_image: '/content/images/page-feature.jpg'
};
getOgImage({context: ['page'], page})
.should.endWith('page-og.jpg');
page.og_image = '';
getOgImage({context: ['page'], page})
.should.endWith('page-feature.jpg');
page.feature_image = '';
should(
getOgImage({context: ['page'], page})
).equal(null);
});
it('should return absolute url for feature image in post context', function () {
var ogImageUrl = getOgImage({
context: ['post'],
post: {
feature_image: '/content/images/my-test-image.jpg',
og_image: ''
}
});
ogImageUrl.should.not.equal('/content/images/my-test-image.jpg');
ogImageUrl.should.match(/\/content\/images\/my-test-image\.jpg$/);
it('has correct fallbacks for context: page (legacy format)', function () {
localSettingsCache.og_image = '/content/images/settings-og.jpg';
localSettingsCache.cover_image = '/content/images/settings-cover.jpg';
const post = {
og_image: '/content/images/page-og.jpg',
feature_image: '/content/images/page-feature.jpg'
};
getOgImage({context: ['page'], post})
.should.endWith('page-og.jpg');
post.og_image = '';
getOgImage({context: ['page'], post})
.should.endWith('page-feature.jpg');
post.feature_image = '';
should(
getOgImage({context: ['page'], post})
).equal(null);
});
it('should return absolute url for OG image in AMP context', function () {
var ogImageUrl = getOgImage({
context: ['amp', 'post'],
post: {
feature_image: '/content/images/my-test-image.jpg',
og_image: '/content/images/my-special-og-image.jpg'
}
});
ogImageUrl.should.not.equal('/content/images/my-special-og-image.jpg');
ogImageUrl.should.match(/\/content\/images\/my-special-og-image\.jpg$/);
it('has correct fallbacks for context: author', function () {
localSettingsCache.og_image = '/content/images/settings-og.jpg';
localSettingsCache.cover_image = '/content/images/settings-cover.jpg';
const author = {
cover_image: '/content/images/author-cover.jpg'
};
getOgImage({context: ['author'], author})
.should.endWith('author-cover.jpg');
author.cover_image = '';
should(
getOgImage({context: ['author'], author})
).equal(null);
});
it('should return absolute url for feature image in AMP context', function () {
var ogImageUrl = getOgImage({
context: ['amp', 'post'],
post: {
feature_image: '/content/images/my-test-image.jpg',
og_image: ''
}
});
ogImageUrl.should.not.equal('/content/images/my-test-image.jpg');
ogImageUrl.should.match(/\/content\/images\/my-test-image\.jpg$/);
it('has correct fallbacks for context: author_paged', function () {
localSettingsCache.og_image = '/content/images/settings-og.jpg';
localSettingsCache.cover_image = '/content/images/settings-cover.jpg';
const author = {
cover_image: '/content/images/author-cover.jpg'
};
getOgImage({context: ['author', 'paged'], author})
.should.endWith('author-cover.jpg');
author.cover_image = '';
should(
getOgImage({context: ['author', 'paged'], author})
).equal(null);
});
it('should return null if missing image', function () {
var ogImageUrl = getOgImage({
context: ['post'],
post: {}
});
should(ogImageUrl).equal(null);
it('has correct fallbacks for context: tag', function () {
localSettingsCache.og_image = '/content/images/settings-og.jpg';
localSettingsCache.cover_image = '/content/images/settings-cover.jpg';
const tag = {
feature_image: '/content/images/tag-feature.jpg'
};
getOgImage({context: ['tag'], tag})
.should.endWith('tag-feature.jpg');
tag.feature_image = '';
getOgImage({context: ['tag'], tag})
.should.endWith('settings-cover.jpg');
localSettingsCache.cover_image = '';
should(
getOgImage({context: ['tag'], tag})
).equal(null);
});
it('has correct fallbacks for context: tag_paged', function () {
localSettingsCache.og_image = '/content/images/settings-og.jpg';
localSettingsCache.cover_image = '/content/images/settings-cover.jpg';
const tag = {
feature_image: '/content/images/tag-feature.jpg'
};
getOgImage({context: ['tag', 'paged'], tag})
.should.endWith('tag-feature.jpg');
tag.feature_image = '';
getOgImage({context: ['tag', 'paged'], tag})
.should.endWith('settings-cover.jpg');
localSettingsCache.cover_image = '';
should(
getOgImage({context: ['tag', 'paged'], tag})
).equal(null);
});
});

View File

@ -26,10 +26,10 @@ describe('getStructuredData', function () {
url: null
},
twitterImage: null,
ogTitle: '',
ogDescription: '',
twitterTitle: '',
twitterDescription: '',
ogTitle: 'Post Title',
ogDescription: 'Post meta description',
twitterTitle: 'Post Title',
twitterDescription: 'Post meta description',
authorFacebook: 'testpage',
creatorTwitter: '@twitterpage',
keywords: ['one', 'two', 'tag'],
@ -146,9 +146,9 @@ describe('getStructuredData', function () {
url: null
},
twitterImage: null,
ogTitle: null,
ogTitle: 'Post Title',
ogDescription: null,
twitterTitle: null,
twitterTitle: 'Post Title',
twitterDescription: null,
keywords: null,
metaDescription: null

View File

@ -25,279 +25,415 @@ describe('getTitle', function () {
title.should.equal('My test title');
});
it('should return site title if on home', function () {
localSettingsCache.title = 'My site title';
describe('property: null', function () {
it('has correct fallbacks for context: home', function () {
localSettingsCache.title = 'My site title';
localSettingsCache.meta_title = 'My site meta title';
var title = getTitle({}, {context: 'home'});
title.should.equal('My site title');
});
getTitle({}, {context: 'home'})
.should.equal('My site meta title');
it('should return site meta_title if on home and mata_title present', function () {
localSettingsCache.title = 'My site title';
localSettingsCache.meta_title = 'My site meta title';
localSettingsCache.meta_title = '';
var title = getTitle({}, {context: 'home'});
title.should.equal('My site meta title');
});
it('should return facebook site title if in home context', function () {
localSettingsCache.title = 'My site title';
localSettingsCache.og_title = 'My site facebook meta title';
var title = getTitle({
}, {
context: ['home']
}, {
property: 'og'
getTitle({}, {context: 'home'})
.should.equal('My site title');
});
title.should.equal('My site facebook meta title');
});
it('has correct fallbacks for context: post', function () {
localSettingsCache.title = 'My site title';
const post = {
title: 'Post title',
meta_title: 'Post meta title'
};
it('should return twitter site title if in home context', function () {
localSettingsCache.title = 'My site title';
localSettingsCache.twitter_title = 'My site twitter meta title';
getTitle({post}, {context: 'post'})
.should.equal('Post meta title');
var title = getTitle({
}, {
context: ['home']
}, {
property: 'twitter'
post.meta_title = '';
getTitle({post}, {context: 'post'})
.should.equal('Post title');
post.title = '';
getTitle({post}, {context: 'post'})
.should.equal('');
});
title.should.equal('My site twitter meta title');
});
it('has correct fallbacks for context: page', function () {
localSettingsCache.title = 'My site title';
const page = {
title: 'Page title',
meta_title: 'Page meta title'
};
it('should return author name - site title if on data author page', function () {
localSettingsCache.title = 'My site title 2';
getTitle({page}, {context: 'page'})
.should.equal('Page meta title');
var title = getTitle({
author: {
name: 'Author Name'
}
}, {context: ['author']});
page.meta_title = '';
title.should.equal('Author Name - My site title 2');
});
getTitle({page}, {context: 'page'})
.should.equal('Page title');
it('should return author page title if on data author page with more then one page', function () {
localSettingsCache.title = 'My site title 2';
page.title = '';
var title = getTitle({
author: {
name: 'Author Name'
}
}, {
context: ['author', 'paged'],
pagination: {
total: 40,
page: 3
}
getTitle({page}, {context: 'page'})
.should.equal('');
});
title.should.equal('Author Name - My site title 2 (Page 3)');
});
// NOTE: this is a legacy format and should be resolved with https://github.com/TryGhost/Ghost/issues/10042
it('has correct fallbacks for context: page (legacy format)', function () {
localSettingsCache.title = 'My site title';
const post = {
title: 'Page title',
meta_title: 'Page meta title'
};
it('should return tag name - site title if on data tag page no meta_title', function () {
localSettingsCache.title = 'My site title 3';
getTitle({post}, {context: 'page'})
.should.equal('Page meta title');
var title = getTitle({
tag: {
name: 'Tag Name'
}
}, {context: ['tag']});
post.meta_title = '';
title.should.equal('Tag Name - My site title 3');
});
getTitle({post}, {context: 'page'})
.should.equal('Page title');
it('should return tag name - site title if on data tag page no meta_title (Page #)', function () {
localSettingsCache.title = 'My site title 3';
post.title = '';
var title = getTitle({
tag: {
name: 'Tag Name'
}
}, {
context: ['tag', 'paged'],
pagination: {
total: 40,
page: 39
}
getTitle({post}, {context: 'page'})
.should.equal('');
});
title.should.equal('Tag Name - My site title 3 (Page 39)');
});
it('has correct fallbacks for context: author', function () {
localSettingsCache.title = 'Site title';
localSettingsCache.meta_title = 'Site meta title';
const author = {
name: 'Author name'
};
it('should return post title if in page context', function () {
var title = getTitle({
// 'post' property is dependent on legacy object formatting (https://github.com/TryGhost/Ghost/issues/10042
post: {
title: 'My awesome page!'
}
}, {context: ['page']});
title.should.equal('My awesome page!');
});
it('should return translated pagination-string if passed in options object', function () {
localSettingsCache.title = 'This is my site title';
var title = getTitle({
tag: {
name: 'Tag Name'
}
}, {
context: ['tag', 'paged'],
pagination: {
total: 40,
page: 23
}
}, {
hash: {
page: ' p.%'
}
getTitle({author}, {context: 'author'})
.should.equal('Author name - Site title');
});
title.should.equal('Tag Name - This is my site title p.23');
});
it('has correct fallbacks for context: author_paged', function () {
localSettingsCache.title = 'Site title';
localSettingsCache.meta_title = 'Site meta title';
const author = {
name: 'Author name'
};
it('should return tag meta_title if in tag data', function () {
var title = getTitle({
tag: {
name: 'Tag Name',
meta_title: 'My Tag Meta Title!'
}
}, {context: ['tag']});
title.should.equal('My Tag Meta Title!');
});
it('should return post title if in post context', function () {
var title = getTitle({
post: {
title: 'My awesome post!'
}
}, {context: ['post']});
title.should.equal('My awesome post!');
});
it('should return OG post title if in post context', function () {
var title = getTitle({
post: {
title: 'My awesome post!',
og_title: 'My Custom Facebook Title'
}
}, {
context: ['post']
}, {
property: 'og'
getTitle({author}, {context: ['author', 'paged'], pagination: {total: 40, page: 3}})
.should.equal('Author name - Site title (Page 3)');
});
title.should.equal('My Custom Facebook Title');
});
it('has correct fallbacks for context: tag', function () {
localSettingsCache.title = 'Site title';
localSettingsCache.meta_title = 'Site meta title';
const tag = {
name: 'Tag name',
meta_title: 'Tag meta title'
};
it('should return twitter post title if in post context', function () {
var title = getTitle({
post: {
title: 'My awesome post!',
twitter_title: 'My Custom Twitter Title'
}
}, {
context: ['post']
}, {
property: 'twitter'
getTitle({tag}, {context: 'tag'})
.should.equal('Tag meta title');
tag.meta_title = '';
getTitle({tag}, {context: 'tag'})
.should.equal('Tag name - Site title');
});
title.should.equal('My Custom Twitter Title');
it('has correct fallbacks for context: tag_paged', function () {
localSettingsCache.title = 'Site title';
localSettingsCache.meta_title = 'Site meta title';
const tag = {
name: 'Tag name',
meta_title: 'Tag meta title'
};
getTitle({tag}, {context: ['tag', 'paged'], pagination: {total: 40, page: 3}})
.should.equal('Tag meta title');
tag.meta_title = '';
getTitle({tag}, {context: ['tag', 'paged'], pagination: {total: 40, page: 3}})
.should.equal('Tag name - Site title (Page 3)');
});
});
it('should not return default post title if in amp context and called with twitter property', function () {
var title = getTitle({
post: {
title: 'My awesome post!',
twitter_title: ''
}
}, {
context: ['amp', 'post']
}, {
property: 'twitter'
describe('property: og', function () {
it('has correct fallbacks for context: home', function () {
localSettingsCache.title = 'My site title';
localSettingsCache.meta_title = 'My site meta title';
localSettingsCache.og_title = 'My site og title';
getTitle({}, {context: 'home'}, {property: 'og'})
.should.equal('My site og title');
localSettingsCache.og_title = '';
getTitle({}, {context: 'home'}, {property: 'og'})
.should.equal('My site title');
});
title.should.equal('');
it('has correct fallbacks for context: post', function () {
const post = {
title: 'Post title',
meta_title: 'Post meta title',
og_title: 'Post og title'
};
getTitle({post}, {context: 'post'}, {property: 'og'})
.should.equal('Post og title');
post.og_title = '';
getTitle({post}, {context: 'post'}, {property: 'og'})
.should.equal('Post meta title');
post.meta_title = '';
getTitle({post}, {context: 'post'}, {property: 'og'})
.should.equal('Post title');
});
it('has correct fallbacks for context: page', function () {
localSettingsCache.title = 'My site title';
const page = {
title: 'Page title',
meta_title: 'Page meta title',
og_title: 'Page og title'
};
getTitle({page}, {context: 'page'}, {property: 'og'})
.should.equal('Page og title');
page.og_title = '';
getTitle({page}, {context: 'page'}, {property: 'og'})
.should.equal('Page meta title');
page.meta_title = '';
getTitle({page}, {context: 'page'}, {property: 'og'})
.should.equal('Page title');
});
// NOTE: this is a legacy format and should be resolved with https://github.com/TryGhost/Ghost/issues/10042
it('has correct fallbacks for context: page (legacy format)', function () {
localSettingsCache.title = 'My site title';
const post = {
title: 'Page title',
meta_title: 'Page meta title',
og_title: 'Page og title'
};
getTitle({post}, {context: 'page'}, {property: 'og'})
.should.equal('Page og title');
post.og_title = '';
getTitle({post}, {context: 'page'}, {property: 'og'})
.should.equal('Page meta title');
post.meta_title = '';
getTitle({post}, {context: 'page'}, {property: 'og'})
.should.equal('Page title');
});
it('has correct fallbacks for context: author', function () {
localSettingsCache.title = 'Site title';
localSettingsCache.meta_title = 'Site meta title';
const author = {
name: 'Author name'
};
getTitle({author}, {context: 'author'}, {property: 'og'})
.should.equal('Author name - Site title');
});
it('has correct fallbacks for context: author_paged', function () {
localSettingsCache.title = 'Site title';
localSettingsCache.meta_title = 'Site meta title';
const author = {
name: 'Author name'
};
getTitle({author}, {context: ['author', 'paged'], pagination: {total: 40, page: 3}}, {property: 'og'})
.should.equal('Author name - Site title (Page 3)');
});
it('has correct fallbacks for context: tag', function () {
localSettingsCache.title = 'Site title';
localSettingsCache.meta_title = 'Site meta title';
const tag = {
name: 'Tag name',
meta_title: 'Tag meta title'
};
getTitle({tag}, {context: 'tag'}, {property: 'og'})
.should.equal('Tag meta title');
tag.meta_title = '';
getTitle({tag}, {context: 'tag'}, {property: 'og'})
.should.equal('Tag name - Site title');
});
it('has correct fallbacks for context: tag_paged', function () {
localSettingsCache.title = 'Site title';
localSettingsCache.meta_title = 'Site meta title';
const tag = {
name: 'Tag name',
meta_title: 'Tag meta title'
};
getTitle({tag}, {context: ['tag', 'paged'], pagination: {total: 40, page: 3}}, {property: 'og'})
.should.equal('Tag meta title');
tag.meta_title = '';
getTitle({tag}, {context: ['tag', 'paged'], pagination: {total: 40, page: 3}}, {property: 'og'})
.should.equal('Tag name - Site title (Page 3)');
});
});
it('should return post title if in amp context', function () {
var title = getTitle({
post: {
title: 'My awesome post!'
}
}, {context: ['amp', 'post']});
describe('property: twitter', function () {
it('has correct fallbacks for context: home', function () {
localSettingsCache.title = 'My site title';
localSettingsCache.meta_title = 'My site meta title';
localSettingsCache.twitter_title = 'My site twitter title';
title.should.equal('My awesome post!');
});
getTitle({}, {context: 'home'}, {property: 'twitter'})
.should.equal('My site twitter title');
it('v2: should return page title if in page context', function () {
var title = getTitle({
page: {
title: 'My awesome page!'
}
}, {context: ['page']});
localSettingsCache.twitter_title = '';
title.should.equal('My awesome page!');
});
getTitle({}, {context: 'home'}, {property: 'twitter'})
.should.equal('My site title');
});
it('canary: should return page title if in page context', function () {
var title = getTitle({
page: {
title: 'My awesome page!'
}
}, {context: ['page']});
it('has correct fallbacks for context: post', function () {
const post = {
title: 'Post title',
meta_title: 'Post meta title',
twitter_title: 'Post twitter title'
};
title.should.equal('My awesome page!');
});
getTitle({post}, {context: 'post'}, {property: 'twitter'})
.should.equal('Post twitter title');
it('v3: should return page title if in page context', function () {
var title = getTitle({
page: {
title: 'My awesome page!'
}
}, {context: ['page']});
post.twitter_title = '';
title.should.equal('My awesome page!');
});
getTitle({post}, {context: 'post'}, {property: 'twitter'})
.should.equal('Post meta title');
// NOTE: this case is unlikely as Ghost doesn't support AMP for static pages
it('should return post title if in amp and page context', function () {
var title = getTitle({
post: {
title: 'My awesome page!'
}
}, {context: ['amp', 'page']});
post.meta_title = '';
title.should.equal('My awesome page!');
});
getTitle({post}, {context: 'post'}, {property: 'twitter'})
.should.equal('Post title');
});
it('should return post meta_title if in post data', function () {
var title = getTitle({
post: {
name: 'My awesome post!',
meta_title: 'My Tag Meta Title Post! '
}
}, {context: ['post']});
it('has correct fallbacks for context: page', function () {
localSettingsCache.title = 'My site title';
const page = {
title: 'Page title',
meta_title: 'Page meta title',
twitter_title: 'Page twitter title'
};
title.should.equal('My Tag Meta Title Post!');
});
getTitle({page}, {context: 'page'}, {property: 'twitter'})
.should.equal('Page twitter title');
it('should return post meta_title if amp context in post data', function () {
var title = getTitle({
post: {
name: 'My awesome post!',
meta_title: 'My Tag Meta Title Post! '
}
}, {context: ['amp', 'post']});
page.twitter_title = '';
title.should.equal('My Tag Meta Title Post!');
getTitle({page}, {context: 'page'}, {property: 'twitter'})
.should.equal('Page meta title');
page.meta_title = '';
getTitle({page}, {context: 'page'}, {property: 'twitter'})
.should.equal('Page title');
});
// NOTE: this is a legacy format and should be resolved with https://github.com/TryGhost/Ghost/issues/10042
it('has correct fallbacks for context: page (legacy format)', function () {
localSettingsCache.title = 'My site title';
const post = {
title: 'Page title',
meta_title: 'Page meta title',
twitter_title: 'Page twitter title'
};
getTitle({post}, {context: 'page'}, {property: 'twitter'})
.should.equal('Page twitter title');
post.twitter_title = '';
getTitle({post}, {context: 'page'}, {property: 'twitter'})
.should.equal('Page meta title');
post.meta_title = '';
getTitle({post}, {context: 'page'}, {property: 'twitter'})
.should.equal('Page title');
});
it('has correct fallbacks for context: author', function () {
localSettingsCache.title = 'Site title';
localSettingsCache.meta_title = 'Site meta title';
const author = {
name: 'Author name'
};
getTitle({author}, {context: 'author'}, {property: 'twitter'})
.should.equal('Author name - Site title');
});
it('has correct fallbacks for context: author_paged', function () {
localSettingsCache.title = 'Site title';
localSettingsCache.meta_title = 'Site meta title';
const author = {
name: 'Author name'
};
getTitle({author}, {context: ['author', 'paged'], pagination: {total: 40, page: 3}}, {property: 'twitter'})
.should.equal('Author name - Site title (Page 3)');
});
it('has correct fallbacks for context: tag', function () {
localSettingsCache.title = 'Site title';
localSettingsCache.meta_title = 'Site meta title';
const tag = {
name: 'Tag name',
meta_title: 'Tag meta title'
};
getTitle({tag}, {context: 'tag'}, {property: 'twitter'})
.should.equal('Tag meta title');
tag.meta_title = '';
getTitle({tag}, {context: 'tag'}, {property: 'twitter'})
.should.equal('Tag name - Site title');
});
it('has correct fallbacks for context: tag_paged', function () {
localSettingsCache.title = 'Site title';
localSettingsCache.meta_title = 'Site meta title';
const tag = {
name: 'Tag name',
meta_title: 'Tag meta title'
};
getTitle({tag}, {context: ['tag', 'paged'], pagination: {total: 40, page: 3}}, {property: 'twitter'})
.should.equal('Tag meta title');
tag.meta_title = '';
getTitle({tag}, {context: ['tag', 'paged'], pagination: {total: 40, page: 3}}, {property: 'twitter'})
.should.equal('Tag name - Site title (Page 3)');
});
});
it('should return site title with page if unknown type', function () {

View File

@ -4,122 +4,189 @@ const getTwitterImage = require('../../../../frontend/meta/twitter_image');
const settingsCache = require('../../../../server/services/settings/cache');
describe('getTwitterImage', function () {
describe('[home]', function () {
it('should return null if [home] context and no twitter_image set', function () {
sinon.stub(settingsCache, 'get').callsFake(function (key) {
return {
twitter_image: null
}[key];
});
let localSettingsCache = {};
var twitterImageUrl = getTwitterImage({
context: ['home'],
home: {}
});
should(twitterImageUrl).equal(null);
sinon.restore();
});
it('should return image URL if [home] context and twitter_image set', function () {
sinon.stub(settingsCache, 'get').callsFake(function (key) {
return {
twitter_image: '/content/images/home-twitter.jpg'
}[key];
});
var twitterImageUrl = getTwitterImage({
context: ['home'],
home: {}
});
twitterImageUrl.should.not.equal('/content/images/home-twitter.jpg');
twitterImageUrl.should.match(/\/content\/images\/home-twitter\.jpg$/);
sinon.restore();
beforeEach(function () {
sinon.stub(settingsCache, 'get').callsFake(function (key) {
return localSettingsCache[key];
});
});
it('should return null if not post context [author]', function () {
var twitterImageUrl = getTwitterImage({
context: ['author'],
author: {}
});
should(twitterImageUrl).equal(null);
afterEach(function () {
sinon.restore();
localSettingsCache = {};
});
it('should return null if not post context [tag]', function () {
var twitterImageUrl = getTwitterImage({
context: ['tag'],
author: {}
});
should(twitterImageUrl).equal(null);
it('has correct fallbacks for context: home', function () {
localSettingsCache.twitter_image = '/content/images/settings-twitter.jpg';
localSettingsCache.cover_image = '/content/images/settings-cover.jpg';
getTwitterImage({context: ['home'], home: {}})
.should.endWith('/content/images/settings-twitter.jpg');
localSettingsCache.twitter_image = '';
getTwitterImage({context: ['home'], home: {}})
.should.endWith('/content/images/settings-cover.jpg');
localSettingsCache.cover_image = '';
should(
getTwitterImage({context: ['home'], home: {}})
).equal(null);
});
it('should return absolute url for Twitter image in post context', function () {
var twitterImageUrl = getTwitterImage({
context: ['post'],
post: {
feature_image: '/content/images/my-test-image.jpg',
twitter_image: '/content/images/my-special-twitter-image.jpg'
}
});
twitterImageUrl.should.not.equal('/content/images/my-special-twitter-image.jpg');
twitterImageUrl.should.match(/\/content\/images\/my-special-twitter-image\.jpg$/);
it('has correct fallbacks for context: post', function () {
localSettingsCache.twitter_image = '/content/images/settings-twitter.jpg';
localSettingsCache.cover_image = '/content/images/settings-cover.jpg';
const post = {
twitter_image: '/content/images/post-twitter.jpg',
feature_image: '/content/images/post-feature.jpg'
};
getTwitterImage({context: ['post'], post})
.should.endWith('post-twitter.jpg');
post.twitter_image = '';
getTwitterImage({context: ['post'], post})
.should.endWith('post-feature.jpg');
post.feature_image = '';
should(
getTwitterImage({context: ['post'], post})
).equal(null);
});
it('should return absolute url for Twitter image in post context', function () {
var twitterImageUrl = getTwitterImage({
context: ['news', 'post'],
post: {
feature_image: '/content/images/my-test-image.jpg',
twitter_image: '/content/images/my-special-twitter-image.jpg'
}
});
twitterImageUrl.should.not.equal('/content/images/my-special-twitter-image.jpg');
twitterImageUrl.should.match(/\/content\/images\/my-special-twitter-image\.jpg$/);
it('has correct fallbacks for context: page', function () {
localSettingsCache.twitter_image = '/content/images/settings-twitter.jpg';
localSettingsCache.cover_image = '/content/images/settings-cover.jpg';
const page = {
twitter_image: '/content/images/page-twitter.jpg',
feature_image: '/content/images/page-feature.jpg'
};
getTwitterImage({context: ['page'], page})
.should.endWith('page-twitter.jpg');
page.twitter_image = '';
getTwitterImage({context: ['page'], page})
.should.endWith('page-feature.jpg');
page.feature_image = '';
should(
getTwitterImage({context: ['page'], page})
).equal(null);
});
it('should return absolute url for feature image in post context', function () {
var twitterImageUrl = getTwitterImage({
context: ['post'],
post: {
feature_image: '/content/images/my-test-image.jpg',
twitter_image: ''
}
});
twitterImageUrl.should.not.equal('/content/images/my-test-image.jpg');
twitterImageUrl.should.match(/\/content\/images\/my-test-image\.jpg$/);
it('has correct fallbacks for context: page (legacy format)', function () {
localSettingsCache.twitter_image = '/content/images/settings-twitter.jpg';
localSettingsCache.cover_image = '/content/images/settings-cover.jpg';
const post = {
twitter_image: '/content/images/page-twitter.jpg',
feature_image: '/content/images/page-feature.jpg'
};
getTwitterImage({context: ['page'], post})
.should.endWith('page-twitter.jpg');
post.twitter_image = '';
getTwitterImage({context: ['page'], post})
.should.endWith('page-feature.jpg');
post.feature_image = '';
should(
getTwitterImage({context: ['page'], post})
).equal(null);
});
it('should return absolute url for Twitter image in AMP context', function () {
var twitterImageUrl = getTwitterImage({
context: ['amp', 'post'],
post: {
feature_image: '/content/images/my-test-image.jpg',
twitter_image: '/content/images/my-special-twitter-image.jpg'
}
});
twitterImageUrl.should.not.equal('/content/images/my-special-twitter-image.jpg');
twitterImageUrl.should.match(/\/content\/images\/my-special-twitter-image\.jpg$/);
it('has correct fallbacks for context: author', function () {
localSettingsCache.twitter_image = '/content/images/settings-twitter.jpg';
localSettingsCache.cover_image = '/content/images/settings-cover.jpg';
const author = {
cover_image: '/content/images/author-cover.jpg'
};
getTwitterImage({context: ['author'], author})
.should.endWith('author-cover.jpg');
author.cover_image = '';
should(
getTwitterImage({context: ['author'], author})
).equal(null);
});
it('should return absolute url for feature image in AMP context', function () {
var twitterImageUrl = getTwitterImage({
context: ['amp', 'post'],
post: {
feature_image: '/content/images/my-test-image.jpg',
twitter_image: ''
}
});
twitterImageUrl.should.not.equal('/content/images/my-test-image.jpg');
twitterImageUrl.should.match(/\/content\/images\/my-test-image\.jpg$/);
it('has correct fallbacks for context: author_paged', function () {
localSettingsCache.twitter_image = '/content/images/settings-twitter.jpg';
localSettingsCache.cover_image = '/content/images/settings-cover.jpg';
const author = {
cover_image: '/content/images/author-cover.jpg'
};
getTwitterImage({context: ['author', 'paged'], author})
.should.endWith('author-cover.jpg');
author.cover_image = '';
should(
getTwitterImage({context: ['author', 'paged'], author})
).equal(null);
});
it('should return null if missing image', function () {
var twitterImageUrl = getTwitterImage({
context: ['post'],
post: {}
});
should(twitterImageUrl).equal(null);
it('has correct fallbacks for context: tag', function () {
localSettingsCache.twitter_image = '/content/images/settings-twitter.jpg';
localSettingsCache.cover_image = '/content/images/settings-cover.jpg';
const tag = {
feature_image: '/content/images/tag-feature.jpg'
};
getTwitterImage({context: ['tag'], tag})
.should.endWith('tag-feature.jpg');
tag.feature_image = '';
getTwitterImage({context: ['tag'], tag})
.should.endWith('settings-cover.jpg');
localSettingsCache.cover_image = '';
should(
getTwitterImage({context: ['tag'], tag})
).equal(null);
});
it('has correct fallbacks for context: tag_paged', function () {
localSettingsCache.twitter_image = '/content/images/settings-twitter.jpg';
localSettingsCache.cover_image = '/content/images/settings-cover.jpg';
const tag = {
feature_image: '/content/images/tag-feature.jpg'
};
getTwitterImage({context: ['tag', 'paged'], tag})
.should.endWith('tag-feature.jpg');
tag.feature_image = '';
getTwitterImage({context: ['tag', 'paged'], tag})
.should.endWith('settings-cover.jpg');
localSettingsCache.cover_image = '';
should(
getTwitterImage({context: ['tag', 'paged'], tag})
).equal(null);
});
});

View File

@ -37,6 +37,7 @@ describe('{{ghost_head}} helper', function () {
feature_image: '/content/images/tag-image.png'
}));
tags.push(createTag({
description: '',
meta_description: '',
name: 'tagtitle',
meta_title: '',
@ -274,6 +275,9 @@ describe('{{ghost_head}} helper', function () {
before(function () {
// @TODO: remove when visibility is refactored out of models
models.init();
});
beforeEach(function () {
sinon.stub(urlService, 'getUrlByResourceId').returns('https://mysite.com/fakeauthor/');
// @TODO: this is a LOT of mocking :/
@ -292,7 +296,7 @@ describe('{{ghost_head}} helper', function () {
makeFixtures();
});
after(function () {
afterEach(function () {
sinon.restore();
configUtils.restore();
});
@ -300,13 +304,13 @@ describe('{{ghost_head}} helper', function () {
describe('without Code Injection', function () {
let sandbox;
before(function () {
beforeEach(function () {
sandbox = sinon.createSandbox();
testUrlUtils.stubUrlUtils({url: 'http://localhost:65530/'}, sandbox);
});
after(function () {
afterEach(function () {
sandbox.restore();
});
@ -1085,16 +1089,16 @@ describe('{{ghost_head}} helper', function () {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="\/favicon.ico" type="image\/x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/localhost:65530\/tag\/tagtitle\/" \/>/);
rendered.string.should.not.match(/<meta name="description"/);
rendered.string.should.match(/<meta name="description" content="tag description"/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
rendered.string.should.match(/<meta property="og:type" content="website" \/>/);
rendered.string.should.match(/<meta property="og:title" content="tagtitle - Ghost" \/>/);
rendered.string.should.not.match(/<meta property="og:description"/);
rendered.string.should.match(/<meta property="og:description" content="tag description"/);
rendered.string.should.match(/<meta property="og:url" content="http:\/\/localhost:65530\/tag\/tagtitle\/" \/>/);
rendered.string.should.match(/<meta property="og:image" content="http:\/\/localhost:65530\/content\/images\/tag-image.png" \/>/);
rendered.string.should.match(/<meta name="twitter:card" content="summary_large_image" \/>/);
rendered.string.should.match(/<meta name="twitter:title" content="tagtitle - Ghost" \/>/);
rendered.string.should.not.match(/<meta name="twitter:description"/);
rendered.string.should.match(/<meta name="twitter:description" content="tag description"/);
rendered.string.should.match(/<meta name="twitter:url" content="http:\/\/localhost:65530\/tag\/tagtitle\/" \/>/);
rendered.string.should.match(/<meta name="twitter:image" content="http:\/\/localhost:65530\/content\/images\/tag-image.png" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
@ -1106,7 +1110,6 @@ describe('{{ghost_head}} helper', function () {
rendered.string.should.match(/"url": "http:\/\/localhost:65530\/tag\/tagtitle\/"/);
rendered.string.should.match(/"image": "http:\/\/localhost:65530\/content\/images\/tag-image.png"/);
rendered.string.should.match(/"name": "tagtitle"/);
rendered.string.should.not.match(/"description":/);
done();
}).catch(done);
@ -1175,16 +1178,16 @@ describe('{{ghost_head}} helper', function () {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="\/favicon.ico" type="image\/x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/localhost:65530\/author\/authorname\/" \/>/);
rendered.string.should.not.match(/<meta name="description"/);
rendered.string.should.match(/<meta name="description" content="Author bio"/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
rendered.string.should.match(/<meta property="og:type" content="profile" \/>/);
rendered.string.should.not.match(/<meta property="og:description"/);
rendered.string.should.match(/<meta property="og:description" content="Author bio"/);
rendered.string.should.match(/<meta property="og:url" content="http:\/\/localhost:65530\/author\/authorname\/" \/>/);
rendered.string.should.match(/<meta property="og:image" content="http:\/\/localhost:65530\/content\/images\/author-cover-image.png" \/>/);
rendered.string.should.match(/<meta property="article:author" content="https:\/\/www.facebook.com\/testuser\" \/>/);
rendered.string.should.match(/<meta name="twitter:card" content="summary_large_image" \/>/);
rendered.string.should.match(/<meta name="twitter:title" content="Author name - Ghost" \/>/);
rendered.string.should.not.match(/<meta name="twitter:description"/);
rendered.string.should.match(/<meta name="twitter:description" content="Author bio"/);
rendered.string.should.match(/<meta name="twitter:url" content="http:\/\/localhost:65530\/author\/authorname\/" \/>/);
rendered.string.should.match(/<meta name="twitter:creator" content="@testuser" \/>/);
rendered.string.should.match(/<meta name="twitter:image" content="http:\/\/localhost:65530\/content\/images\/author-cover-image.png" \/>/);
@ -1197,7 +1200,6 @@ describe('{{ghost_head}} helper', function () {
rendered.string.should.match(/"url": "https:\/\/mysite.com\/fakeauthor\/"/);
rendered.string.should.match(/"image": "http:\/\/localhost:65530\/content\/images\/author-cover-image.png"/);
rendered.string.should.match(/"name": "Author name"/);
rendered.string.should.not.match(/"description":/);
done();
}).catch(done);
@ -1297,7 +1299,7 @@ describe('{{ghost_head}} helper', function () {
describe('with /site subdirectory', function () {
let sandbox;
before(function () {
beforeEach(function () {
sandbox = sinon.createSandbox();
settingsCache.get.withArgs('icon').returns('/content/images/favicon.png');
@ -1307,7 +1309,7 @@ describe('{{ghost_head}} helper', function () {
routing.registry.getRssUrl.returns('http://localhost:65530/site/rss/');
});
after(function () {
afterEach(function () {
sandbox.restore();
routing.registry.getRssUrl.returns('http://localhost:65530/rss/');
});
@ -1334,7 +1336,7 @@ describe('{{ghost_head}} helper', function () {
describe('with changed origin in config file', function () {
let sandbox;
before(function () {
beforeEach(function () {
sandbox = sinon.createSandbox();
settingsCache.get.withArgs('icon').returns('/content/images/favicon.png');
@ -1346,7 +1348,7 @@ describe('{{ghost_head}} helper', function () {
testUrlUtils.stubUrlUtils({url: 'http://localhost:65530/site'}, sandbox);
});
after(function () {
afterEach(function () {
sandbox.restore();
});
@ -1370,7 +1372,7 @@ describe('{{ghost_head}} helper', function () {
describe('with useStructuredData is set to false in config file', function () {
let sandbox;
before(function () {
beforeEach(function () {
sandbox = sinon.createSandbox();
settingsCache.get.withArgs('icon').returns('/content/images/favicon.png');
@ -1383,7 +1385,7 @@ describe('{{ghost_head}} helper', function () {
testUrlUtils.stubUrlUtils({url: 'http://localhost:65530/'}, sandbox);
});
after(function () {
afterEach(function () {
sandbox.restore();
});
@ -1418,7 +1420,7 @@ describe('{{ghost_head}} helper', function () {
describe('with Code Injection', function () {
let sandbox;
before(function () {
beforeEach(function () {
sandbox = sinon.createSandbox();
settingsCache.get.withArgs('icon').returns('/content/images/favicon.png');
settingsCache.get.withArgs('ghost_head').returns('<style>body {background: red;}</style>');
@ -1426,7 +1428,7 @@ describe('{{ghost_head}} helper', function () {
testUrlUtils.stubUrlUtils({url: 'http://localhost:65530/'}, sandbox);
});
after(function () {
afterEach(function () {
sandbox.restore();
});
@ -1539,7 +1541,7 @@ describe('{{ghost_head}} helper', function () {
});
describe('amp is disabled', function () {
before(function () {
beforeEach(function () {
settingsCache.get.withArgs('amp').returns(false);
});

View File

@ -81,14 +81,14 @@ describe('{{meta_description}} helper', function () {
String(rendered).should.equal('');
});
it('returns empty description for an author page', function () {
it('returns author bio for an author page', function () {
var rendered = helpers.meta_description.call(
{author: {bio: 'I am a Duck.'}},
{data: {root: {context: ['author']}}}
);
should.exist(rendered);
String(rendered).should.equal('');
String(rendered).should.equal('I am a Duck.');
});
it('returns empty description for a paginated author page', function () {

View File

@ -176,7 +176,7 @@ describe('{{meta_title}} helper', function () {
);
should.exist(rendered);
String(rendered).should.equal('Meta Title Ghost (Page 2)');
String(rendered).should.equal('Ghost (Page 2)');
});
it('returns correct title for a tag page', function () {
@ -187,7 +187,7 @@ describe('{{meta_title}} helper', function () {
);
should.exist(rendered);
String(rendered).should.equal('Rasper Red - Meta Title Ghost');
String(rendered).should.equal('Rasper Red - Ghost');
});
it('returns correct title for an author page', function () {
@ -197,7 +197,7 @@ describe('{{meta_title}} helper', function () {
);
should.exist(rendered);
String(rendered).should.equal('Donald Duck - Meta Title Ghost');
String(rendered).should.equal('Donald Duck - Ghost');
});
it('returns correct title for a paginated author page', function () {
@ -207,7 +207,7 @@ describe('{{meta_title}} helper', function () {
);
should.exist(rendered);
String(rendered).should.equal('Donald Duck - Meta Title Ghost (Page 2)');
String(rendered).should.equal('Donald Duck - Ghost (Page 2)');
});
});
});