Favicon URI (#7700)

closes #7688

- Use `/favicon.ico` and `/favicon.png` in blog app. Depending on type of storage (custom upload = local file storage), serves either from storage adapter with `read()` method or reads the bytes via `fs`.
- Redirects requests for `favicon.ico` to `favicon.png` if custom `png` icon is uploaded and vice versa.
- Redirect requests for `favicon.png` to `favicon.ico` if default icon is used (in `core/shared`).
- Changes the `{{asset}}` helper for favicon to not serve from theme assets anymore. It will either be served the custom blog-icon or the default one.
- The `{{@blog.icon}}` helper renders the url of the **uploaded** blog icon. It won't render the default icon.
This commit is contained in:
Aileen Nowak 2017-01-27 01:01:19 +07:00 committed by Kevin Ansfield
parent 584bd15b76
commit d2f2888da0
12 changed files with 377 additions and 66 deletions

View File

@ -67,9 +67,7 @@ updateConfigCache = function () {
config.set('theme:timezone', (settingsCache.activeTimezone && settingsCache.activeTimezone.value) || config.get('theme').timezone);
config.set('theme:url', globalUtils.url.urlFor('home', true));
config.set('theme:amp', (settingsCache.amp && settingsCache.amp.value === 'true'));
config.set('theme:icon', (settingsCache.icon && settingsCache.icon.value) ?
{type: 'upload', url: (settingsCache.icon && settingsCache.icon.value)} :
{type: 'default', url: config.get('theme:icon')});
config.set('theme:icon', settingsCache.icon && settingsCache.icon.value);
_.each(labsValue, function (value, key) {
config.set('labs:' + key, value);

View File

@ -20,7 +20,8 @@ var debug = require('debug')('ghost:blog'),
prettyURLs = require('../middleware/pretty-urls'),
serveSharedFile = require('../middleware/serve-shared-file'),
staticTheme = require('../middleware/static-theme'),
themeHandler = require('../middleware/theme-handler');
themeHandler = require('../middleware/theme-handler'),
serveFavicon = require('../middleware/serve-favicon');
module.exports = function setupBlogApp() {
debug('Blog setup start');
@ -41,7 +42,7 @@ module.exports = function setupBlogApp() {
// Static content/assets
// @TODO make sure all of these have a local 404 error handler
// Favicon
blogApp.use(serveSharedFile('favicon.ico', 'image/x-icon', utils.ONE_DAY_S));
blogApp.use(serveFavicon());
// Ghost-Url
blogApp.use(serveSharedFile('shared/ghost-url.js', 'application/javascript', utils.ONE_HOUR_S));
blogApp.use(serveSharedFile('shared/ghost-url.min.js', 'application/javascript', utils.ONE_HOUR_S));

View File

@ -64,11 +64,7 @@
"publishAPostBySchedulerToleranceInMinutes": 2
},
"theme": {
"timezone": "Etc/UTC",
"icon": {
"type": "default",
"url": "core/shared/favicon.ico"
}
"timezone": "Etc/UTC"
},
"maintenance": {
"enabled": false

View File

@ -6,14 +6,22 @@ function getAssetUrl(path, isAdmin, minify) {
output += utils.url.urlJoin(utils.url.getSubdir(), '/');
if (!path.match(/^favicon\.ico$/) && !path.match(/^shared/) && !path.match(/^asset/)) {
if (!path.match(/^favicon\.(ico|png)$/) && !path.match(/^shared/) && !path.match(/^asset/)) {
if (isAdmin) {
output = utils.url.urlJoin(output, 'ghost/');
}
output = utils.url.urlJoin(output, 'assets/');
}
// Serve either uploaded favicon or default
// for favicon, we don't care anymore about the `/` leading slash, as don't support theme favicons
if (path.match(/\/?favicon\.(ico|png)$/)) {
if (isAdmin) {
output = utils.url.urlJoin(utils.url.getSubdir(), '/favicon.ico');
} else {
output = config.get('theme:icon') ? utils.url.urlJoin(utils.url.getSubdir(), utils.url.urlFor('image', {image: config.get('theme:icon')})) : utils.url.urlJoin(utils.url.getSubdir(), '/favicon.ico');
}
}
// Get rid of any leading slash on the path
path = path.replace(/^\//, '');
@ -22,9 +30,10 @@ function getAssetUrl(path, isAdmin, minify) {
path = path.replace(/\.([^\.]*)$/, '.min.$1');
}
output += path;
if (!path.match(/^favicon\.(ico|png)$/)) {
// we don't want to concat the path with our favicon url
output += path;
if (!path.match(/^favicon\.ico$/)) {
if (!config.get('assetHash')) {
config.set('assetHash', utils.generateAssetHash());
}

View File

@ -16,6 +16,7 @@ var getMetaData = require('../data/meta'),
config = require('../config'),
Promise = require('bluebird'),
labs = require('../utils/labs'),
utils = require('../utils'),
api = require('../api');
function getClient() {
@ -86,7 +87,10 @@ function ghost_head(options) {
fetch = {
metaData: getMetaData(this, options.data.root),
client: getClient()
};
},
// CASE: blog icon is not set in config, we serve the default
iconType = !config.get('theme:icon') ? 'x-icon' : config.get('theme:icon').match(/\/favicon\.ico$/i) ? 'x-icon' : 'png',
favicon = !config.get('theme:icon') ? '/favicon.ico' : utils.url.urlFor('image', {image: config.get('theme:icon')}, true);
return Promise.props(fetch).then(function (response) {
client = response.client;
@ -94,6 +98,7 @@ function ghost_head(options) {
if (context) {
// head is our main array that holds our meta data
head.push('<link rel="shortcut icon" href="' + favicon + '" type="' + iconType + '" />');
head.push('<link rel="canonical" href="' +
escapeExpression(metaData.canonicalUrl) + '" />');
head.push('<meta name="referrer" content="' + referrerPolicy + '" />');

View File

@ -0,0 +1,88 @@
var fs = require('fs'),
path = require('path'),
storage = require('../storage'),
config = require('../config'),
utils = require('../utils'),
crypto = require('crypto'),
buildContentResponse,
content;
buildContentResponse = function buildContentResponse(ext, buf) {
content = {
headers: {
'Content-Type': 'image/' + ext,
'Content-Length': buf.length,
ETag: '"' + crypto.createHash('md5').update(buf, 'utf8').digest('hex') + '"',
'Cache-Control': 'public, max-age=' + utils.ONE_DAY_S
},
body: buf
};
return content;
};
// ### serveFavicon Middleware
// Handles requests to favicon.png and favicon.ico
function serveFavicon() {
var iconType,
filePath;
return function serveFavicon(req, res, next) {
if (req.path.match(/^\/favicon\.(ico|png)/i)) {
// CASE: favicon is default
// confusing: if you upload an icon, it's same logic as storing images
// we store as /content/images, because this is the url path images get requested via the browser
// we are using an express route to skip /content/images and the result is a image path
// based on config.getContentPath('images') + req.path
// in this case we don't use path rewrite, that's why we have to make it manually
filePath = config.get('theme:icon').replace(/\/content\/images\//, '');
var originalExtension = path.extname(filePath).toLowerCase(),
requestedExtension = path.extname(req.path).toLowerCase();
// CASE: custom favicon exists, load it from local file storage
if (config.get('theme:icon')) {
// depends on the uploaded icon extension
if (originalExtension !== requestedExtension) {
return res.redirect(302, '/favicon' + originalExtension);
}
storage.getStorage().read({path: filePath}).then(function readFile(buf, err) {
if (err) {
return next(err);
}
iconType = config.get('theme:icon').match(/\/favicon\.ico$/i) ? 'x-icon' : 'png';
content = buildContentResponse(iconType, buf);
res.writeHead(200, content.headers);
res.end(content.body);
});
} else {
filePath = 'core/shared/favicon.ico';
originalExtension = path.extname(filePath).toLowerCase();
// CASE: always redirect to .ico for default icon
if (originalExtension !== requestedExtension) {
return res.redirect(302, '/favicon.ico');
}
fs.readFile(filePath, function readFile(err, buf) {
if (err) {
return next(err);
}
content = buildContentResponse('x-icon', buf);
res.writeHead(200, content.headers);
res.end(content.body);
});
}
} else {
next();
}
};
}
module.exports = serveFavicon;

View File

@ -37,10 +37,7 @@ describe('Config', function () {
logo: 'casper',
cover: 'casper',
timezone: 'Etc/UTC',
icon: {
type: 'default',
url: 'core/shared/favicon.ico'
}
icon: 'core/shared/favicon.ico'
}
});
});
@ -61,10 +58,7 @@ describe('Config', function () {
themeConfig.should.have.property('logo', 'casper');
themeConfig.should.have.property('cover', 'casper');
themeConfig.should.have.property('timezone', 'Etc/UTC');
themeConfig.should.have.property('icon', {
type: 'default',
url: 'core/shared/favicon.ico'
});
themeConfig.should.have.property('icon', 'core/shared/favicon.ico');
});
});
@ -93,43 +87,6 @@ describe('Config', function () {
});
});
describe('Favicon default', function () {
it('should use uploaded blog icon', function () {
var themeConfig = config.get('theme');
// Check values are as we expect
themeConfig.should.have.property('icon', {
type: 'default',
url: 'core/shared/favicon.ico'
});
configUtils.set({
theme: {
icon: {
type: 'upload',
url: 'content/images/favicon.ico'
}
}
});
config.get('theme').should.have.property('icon', {
type: 'upload',
url: 'content/images/favicon.ico'
});
});
it('should set theme object with default favicon', function () {
var themeConfig = configUtils.defaultConfig;
// Check values are as we expect
themeConfig.should.have.property('theme');
themeConfig.theme.should.have.property('icon', {
type: 'default',
url: 'core/shared/favicon.ico'
});
});
});
describe('Index', function () {
it('should have exactly the right keys', function () {
var pathConfig = config.get('paths');

View File

@ -78,7 +78,6 @@ describe('getImageDimensions', function () {
getImageDimensions.__set__('getCachedImageSizeFromUrl', sizeOfStub);
getImageDimensions(metaData).then(function (result) {
console.log('result:', result);
should.exist(result);
sizeOfStub.calledWith(metaData.coverImage.url).should.be.true();
sizeOfStub.calledWith(metaData.authorImage.url).should.be.true();

View File

@ -0,0 +1,175 @@
var sinon = require('sinon'),
should = require('should'),
express = require('express'),
serveFavicon = require('../../../server/middleware/serve-favicon'),
configUtils = require('../../utils/configUtils'),
path = require('path'),
sandbox = sinon.sandbox.create();
should.equal(true, true);
describe('Serve Favicon', function () {
var req, res, next, blogApp;
beforeEach(function () {
req = sinon.spy();
res = sinon.spy();
next = sinon.spy();
blogApp = express();
req.app = blogApp;
});
afterEach(function () {
sandbox.restore();
configUtils.restore();
});
describe('serveFavicon', function () {
it('should return a middleware', function () {
var middleware = serveFavicon();
middleware.should.be.a.Function();
});
it('should skip if the request does NOT match the file', function () {
var middleware = serveFavicon();
req.path = '/robots.txt';
middleware(req, res, next);
next.called.should.be.true();
});
describe('serves', function () {
it('custom uploaded favicon.png', function (done) {
var middleware = serveFavicon();
req.path = '/favicon.png';
configUtils.set('paths:contentPath', path.join(__dirname, '../../../test/utils/fixtures/'));
configUtils.set({
theme: {
icon: 'favicon.png'
}
});
res = {
writeHead: function (statusCode) {
statusCode.should.eql(200);
},
end: function (body) {
body.length.should.eql(6792);
done();
}
};
middleware(req, res, next);
});
it('custom uploaded favicon.ico', function (done) {
var middleware = serveFavicon();
req.path = '/favicon.ico';
configUtils.set('paths:contentPath', path.join(__dirname, '../../../test/utils/fixtures/'));
configUtils.set({
theme: {
icon: 'favicon.ico'
}
});
res = {
writeHead: function (statusCode) {
statusCode.should.eql(200);
},
end: function (body) {
body.length.should.eql(15086);
done();
}
};
middleware(req, res, next);
});
it('default favicon.ico', function (done) {
var middleware = serveFavicon();
req.path = '/favicon.ico';
configUtils.set('paths:corePath', path.join(__dirname, '../../../test/utils/fixtures/'));
configUtils.set({
theme: {
icon: ''
}
});
res = {
writeHead: function (statusCode) {
statusCode.should.eql(200);
},
end: function (body) {
body.length.should.eql(15086);
done();
}
};
middleware(req, res, next);
});
});
describe('redirects', function () {
it('to custom favicon.ico when favicon.png is requested', function (done) {
var middleware = serveFavicon();
req.path = '/favicon.png';
configUtils.set('paths:contentPath', path.join(__dirname, '../../../test/utils/fixtures/'));
configUtils.set({
theme: {
icon: 'favicon.ico'
}
});
res = {
redirect: function (statusCode) {
statusCode.should.eql(302);
done();
}
};
middleware(req, res, next);
});
it('to custom favicon.png when favicon.ico is requested', function (done) {
var middleware = serveFavicon();
req.path = '/favicon.ico';
configUtils.set('paths:contentPath', path.join(__dirname, '../../../test/utils/fixtures/'));
configUtils.set({
theme: {
icon: 'favicon.png'
}
});
res = {
redirect: function (statusCode) {
statusCode.should.eql(302);
done();
}
};
middleware(req, res, next);
});
it('to favicon.ico when favicon.png is requested', function (done) {
var middleware = serveFavicon();
req.path = '/favicon.png';
configUtils.set('paths:corePath', path.join(__dirname, '../../../test/utils/fixtures/'));
configUtils.set({
theme: {
icon: ''
}
});
res = {
redirect: function (statusCode) {
statusCode.should.eql(302);
done();
}
};
middleware(req, res, next);
});
});
});
});

View File

@ -36,7 +36,33 @@ describe('{{asset}} helper', function () {
String(rendered).should.equal('/favicon.ico');
});
it('handles custom favicon correctly', function () {
configUtils.set('theme:icon', '/content/images/favicon.png');
// with ghost set and png
rendered = helpers.asset('favicon.png', {hash: {ghost: 'true'}});
should.exist(rendered);
String(rendered).should.equal('/favicon.ico');
// without ghost set and png
rendered = helpers.asset('favicon.png');
should.exist(rendered);
String(rendered).should.equal('/content/images/favicon.png');
configUtils.set('theme:icon', '/content/images/favicon.ico');
// with ghost set and ico
rendered = helpers.asset('favicon.ico', {hash: {ghost: 'true'}});
should.exist(rendered);
String(rendered).should.equal('/favicon.ico');
// without ghost set and ico
rendered = helpers.asset('favicon.ico');
should.exist(rendered);
String(rendered).should.equal('/content/images/favicon.ico');
});
it('handles shared assets correctly', function () {
configUtils.set('theme:icon', '');
// with ghost set
rendered = helpers.asset('shared/asset.js', {hash: {ghost: 'true'}});
should.exist(rendered);
@ -80,6 +106,31 @@ describe('{{asset}} helper', function () {
String(rendered).should.equal('/blog/favicon.ico');
});
it('handles custom favicon correctly', function () {
configUtils.set('theme:icon', '/content/images/favicon.png');
// with ghost set and png
rendered = helpers.asset('favicon.png', {hash: {ghost: 'true'}});
should.exist(rendered);
String(rendered).should.equal('/blog/favicon.ico');
// without ghost set and png
rendered = helpers.asset('favicon.png');
should.exist(rendered);
String(rendered).should.equal('/blog/content/images/favicon.png');
configUtils.set('theme:icon', '/content/images/favicon.ico');
// with ghost set and ico
rendered = helpers.asset('favicon.ico', {hash: {ghost: 'true'}});
should.exist(rendered);
String(rendered).should.equal('/blog/favicon.ico');
// without ghost set and ico
rendered = helpers.asset('favicon.ico');
should.exist(rendered);
String(rendered).should.equal('/blog/content/images/favicon.ico');
});
it('handles shared assets correctly', function () {
// with ghost set
rendered = helpers.asset('shared/asset.js', {hash: {ghost: 'true'}});
@ -105,5 +156,7 @@ describe('{{asset}} helper', function () {
should.exist(rendered);
String(rendered).should.equal('/blog/assets/js/asset.js?v=abc');
});
configUtils.restore();
});
});

View File

@ -53,7 +53,8 @@ describe('{{ghost_head}} helper', function () {
title: 'Ghost',
description: 'blog description',
cover: '/content/images/blog-cover.png',
amp: true
amp: true,
icon: 'core/shared/favicon.ico'
}
});
});
@ -68,6 +69,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: ['paged', 'index']}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/page\/2\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="alternate" type="application\/rss\+xml" title="Ghost" href="http:\/\/testurl.com\/rss\/" \/>/);
@ -84,6 +86,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: ['home', 'index']}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/" \/>/);
rendered.string.should.match(/<meta name="referrer" content="no-referrer-when-downgrade" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
@ -136,6 +139,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: ['page']}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/about\/" \/>/);
rendered.string.should.match(/<meta name="referrer" content="no-referrer-when-downgrade" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
@ -182,6 +186,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: ['tag']}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/tag\/tagtitle\/" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
rendered.string.should.match(/<meta property="og:type" content="website" \/>/);
@ -223,6 +228,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: ['tag']}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/tag\/tagtitle\/" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
rendered.string.should.match(/<meta property="og:type" content="website" \/>/);
@ -263,6 +269,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: ['tag']}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.not.match(/<meta property="og:description" \/>/);
rendered.string.should.not.match(/<meta name="twitter:description"\/>/);
rendered.string.should.not.match(/"description":/);
@ -284,6 +291,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: ['tag']}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/tag\/tagtitle\/page\/2\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="alternate" type="application\/rss\+xml" title="Ghost" href="http:\/\/testurl.com\/rss\/" \/>/);
@ -311,6 +319,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: ['author']}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/author\/AuthorName\/" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
rendered.string.should.match(/<meta property="og:type" content="profile" \/>/);
@ -356,6 +365,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: ['paged', 'author']}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/author\/AuthorName\/page\/2\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="alternate" type="application\/rss\+xml" title="Ghost" href="http:\/\/testurl.com\/rss\/" \/>/);
@ -372,6 +382,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: []}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.9" \/>/);
rendered.string.should.match(/<link rel="alternate" type="application\/rss\+xml" title="Ghost" href="http:\/\/testurl.com\/rss\/" \/>/);
@ -409,6 +420,7 @@ describe('{{ghost_head}} helper', function () {
re4 = new RegExp('"dateModified": "' + post.updated_at);
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/post\/" \/>/);
rendered.string.should.match(/<link rel="amphtml" href="http:\/\/testurl.com\/post\/amp\/" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
@ -487,6 +499,7 @@ describe('{{ghost_head}} helper', function () {
re4 = new RegExp('"dateModified": "' + post.updated_at);
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/post\/" \/>/);
rendered.string.should.not.match(/<link rel="amphtml" href="http:\/\/testurl.com\/post\/amp\/" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
@ -565,6 +578,7 @@ describe('{{ghost_head}} helper', function () {
re4 = new RegExp('"dateModified": "' + post.updated_at);
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/post\/" \/>/);
rendered.string.should.match(/<link rel="amphtml" href="http:\/\/testurl.com\/post\/amp\/" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
@ -641,6 +655,7 @@ describe('{{ghost_head}} helper', function () {
re4 = new RegExp('"dateModified": "' + post.updated_at);
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/post\/" \/>/);
rendered.string.should.match(/<link rel="amphtml" href="http:\/\/testurl.com\/post\/amp\/" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
@ -715,6 +730,7 @@ describe('{{ghost_head}} helper', function () {
re4 = new RegExp('"dateModified": "' + post.updated_at);
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/post\/" \/>/);
rendered.string.should.match(/<link rel="amphtml" href="http:\/\/testurl.com\/post\/amp\/" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
@ -766,6 +782,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: ['featured']}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/featured\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="alternate" type="application\/rss\+xml" title="Ghost" href="http:\/\/testurl.com\/rss\/" \/>/);
@ -794,6 +811,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: ['post']}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="amphtml" href="http:\/\/testurl.com\/post\/amp\/" \/>/);
rendered.string.should.match(/<meta property="og:description" content="This is a short post" \/>/);
rendered.string.should.match(/<meta name="twitter:description" content="This is a short post" \/>/);
@ -816,6 +834,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: ['page']}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/about\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="alternate" type="application\/rss\+xml" title="Ghost" href="http:\/\/testurl.com\/rss\/" \/>/);
@ -830,6 +849,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: ['index', 'paged'], pagination: {total: 4, page: 3, next: 4, prev: 2}}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/page\/3\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="prev" href="http:\/\/testurl.com\/page\/2\/" \/>/);
@ -848,6 +868,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: ['index', 'paged'], pagination: {total: 3, page: 2, next: 3, prev: 1}}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="core\/shared\/favicon.ico" type="x-icon" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/page\/2\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="prev" href="http:\/\/testurl.com\/" \/>/);
@ -868,7 +889,8 @@ describe('{{ghost_head}} helper', function () {
title: 'Ghost',
description: 'blog description',
cover: '/content/images/blog-cover.png',
amp: true
amp: true,
icon: '/content/images/favicon.png'
}
});
});
@ -879,6 +901,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: []}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="\/content\/images\/favicon.png" type="png" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/blog\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="alternate" type="application\/rss\+xml" title="Ghost" href="http:\/\/testurl.com\/blog\/rss\/" \/>/);
@ -897,7 +920,8 @@ describe('{{ghost_head}} helper', function () {
title: 'Ghost',
description: 'blog description',
cover: '/content/images/blog-cover.png',
amp: true
amp: true,
icon: '/content/images/favicon.png'
},
referrerPolicy: 'origin'
});
@ -909,6 +933,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: []}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="\/content\/images\/favicon.png" type="png" \/>/);
rendered.string.should.match(/<meta name="referrer" content="origin" \/>/);
done();
@ -924,7 +949,8 @@ describe('{{ghost_head}} helper', function () {
title: 'Ghost',
description: 'blog description',
cover: '/content/images/blog-cover.png',
amp: true
amp: true,
icon: '/content/images/favicon.png'
},
privacy: {
useStructuredData: false
@ -956,6 +982,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: ['post']}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="http:\/\/testurl.com\/content\/images\/favicon.png" type="png" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/post\/" \/>/);
rendered.string.should.match(/<link rel="amphtml" href="http:\/\/testurl.com\/post\/amp\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
@ -979,7 +1006,8 @@ describe('{{ghost_head}} helper', function () {
theme: {
title: 'Ghost',
description: 'blog description',
cover: '/content/images/blog-cover.png'
cover: '/content/images/blog-cover.png',
icon: '/content/images/favicon.png'
}
});
});
@ -990,6 +1018,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: []}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="http:\/\/testurl.com\/content\/images\/favicon.png" type="png" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="alternate" type="application\/rss\+xml" title="Ghost" href="http:\/\/testurl.com\/rss\/" \/>/);
@ -1022,6 +1051,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: []}}}
).then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<link rel="shortcut icon" href="http:\/\/testurl.com\/content\/images\/favicon.png" type="png" \/>/);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="alternate" type="application\/rss\+xml" title="Ghost" href="http:\/\/testurl.com\/rss\/" \/>/);

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB