mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-07 03:22:21 +03:00
Merge pull request #6145 from ErisDS/view-refactor
Unify code for picking a template to render with
This commit is contained in:
commit
bc83dbce09
@ -3,9 +3,9 @@ var config = require('../../config'),
|
|||||||
|
|
||||||
defaults = {
|
defaults = {
|
||||||
index: {
|
index: {
|
||||||
name: 'home',
|
name: 'index',
|
||||||
route: '/',
|
route: '/',
|
||||||
firstPageTemplate: 'home'
|
frontPageTemplate: 'home'
|
||||||
},
|
},
|
||||||
tag: {
|
tag: {
|
||||||
name: 'tag',
|
name: 'tag',
|
||||||
|
@ -82,10 +82,9 @@ function processQuery(query, slugParam) {
|
|||||||
* Does a first round of formatting on the response, and returns
|
* Does a first round of formatting on the response, and returns
|
||||||
*
|
*
|
||||||
* @param {Object} channelOptions
|
* @param {Object} channelOptions
|
||||||
* @param {String} slugParam
|
|
||||||
* @returns {Promise} response
|
* @returns {Promise} response
|
||||||
*/
|
*/
|
||||||
function fetchData(channelOptions, slugParam) {
|
function fetchData(channelOptions) {
|
||||||
// Temporary workaround to make RSS work, moving towards dynamic channels will provide opportunities to
|
// Temporary workaround to make RSS work, moving towards dynamic channels will provide opportunities to
|
||||||
// improve this, I hope :)
|
// improve this, I hope :)
|
||||||
function handlePostsPerPage(channelOptions) {
|
function handlePostsPerPage(channelOptions) {
|
||||||
@ -102,10 +101,10 @@ function fetchData(channelOptions, slugParam) {
|
|||||||
|
|
||||||
// All channels must have a posts query, use the default if not provided
|
// All channels must have a posts query, use the default if not provided
|
||||||
postQuery = _.defaultsDeep({}, pageOptions, defaultPostQuery);
|
postQuery = _.defaultsDeep({}, pageOptions, defaultPostQuery);
|
||||||
props.posts = processQuery(postQuery, slugParam);
|
props.posts = processQuery(postQuery, channelOptions.slugParam);
|
||||||
|
|
||||||
_.each(channelOptions.data, function (query, name) {
|
_.each(channelOptions.data, function (query, name) {
|
||||||
props[name] = processQuery(query, slugParam);
|
props[name] = processQuery(query, channelOptions.slugParam);
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.props(props).then(function formatResponse(results) {
|
return Promise.props(props).then(function formatResponse(results) {
|
||||||
|
@ -12,7 +12,7 @@ var _ = require('lodash'),
|
|||||||
errors = require('../../errors'),
|
errors = require('../../errors'),
|
||||||
filters = require('../../filters'),
|
filters = require('../../filters'),
|
||||||
Promise = require('bluebird'),
|
Promise = require('bluebird'),
|
||||||
template = require('../../helpers/template'),
|
templates = require('./templates'),
|
||||||
routeMatch = require('path-match')(),
|
routeMatch = require('path-match')(),
|
||||||
safeString = require('../../utils/index').safeString,
|
safeString = require('../../utils/index').safeString,
|
||||||
handleError = require('./error'),
|
handleError = require('./error'),
|
||||||
@ -21,7 +21,6 @@ var _ = require('lodash'),
|
|||||||
channelConfig = require('./channel-config'),
|
channelConfig = require('./channel-config'),
|
||||||
setResponseContext = require('./context'),
|
setResponseContext = require('./context'),
|
||||||
setRequestIsSecure = require('./secure'),
|
setRequestIsSecure = require('./secure'),
|
||||||
getActiveThemePaths = require('./theme-paths'),
|
|
||||||
|
|
||||||
frontendControllers,
|
frontendControllers,
|
||||||
staticPostPermalink = routeMatch('/:slug/:edit?');
|
staticPostPermalink = routeMatch('/:slug/:edit?');
|
||||||
@ -34,8 +33,7 @@ var _ = require('lodash'),
|
|||||||
*/
|
*/
|
||||||
function renderPost(req, res) {
|
function renderPost(req, res) {
|
||||||
return function renderPost(post) {
|
return function renderPost(post) {
|
||||||
var paths = getActiveThemePaths(req),
|
var view = templates.single(req.app.get('activeTheme'), post),
|
||||||
view = template.getThemeViewForPost(paths, post),
|
|
||||||
response = formatResponse.single(post);
|
response = formatResponse.single(post);
|
||||||
|
|
||||||
setResponseContext(req, res, response);
|
setResponseContext(req, res, response);
|
||||||
@ -53,6 +51,7 @@ function renderChannel(channelOpts) {
|
|||||||
channelOpts.postOptions = channelOpts.postOptions || {};
|
channelOpts.postOptions = channelOpts.postOptions || {};
|
||||||
// Set page on postOptions for the query made later
|
// Set page on postOptions for the query made later
|
||||||
channelOpts.postOptions.page = pageParam;
|
channelOpts.postOptions.page = pageParam;
|
||||||
|
channelOpts.slugParam = slugParam;
|
||||||
|
|
||||||
// @TODO this should really use the url building code in config.url
|
// @TODO this should really use the url building code in config.url
|
||||||
function createUrl(page) {
|
function createUrl(page) {
|
||||||
@ -75,7 +74,7 @@ function renderChannel(channelOpts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call fetchData to get everything we need from the API
|
// Call fetchData to get everything we need from the API
|
||||||
return fetchData(channelOpts, slugParam).then(function handleResult(result) {
|
return fetchData(channelOpts).then(function handleResult(result) {
|
||||||
// If page is greater than number of pages we have, redirect to last page
|
// If page is greater than number of pages we have, redirect to last page
|
||||||
if (pageParam > result.meta.pagination.pages) {
|
if (pageParam > result.meta.pagination.pages) {
|
||||||
return res.redirect(createUrl(result.meta.pagination.pages));
|
return res.redirect(createUrl(result.meta.pagination.pages));
|
||||||
@ -90,17 +89,7 @@ function renderChannel(channelOpts) {
|
|||||||
|
|
||||||
// @TODO: properly design these filters
|
// @TODO: properly design these filters
|
||||||
filters.doFilter('prePostsRender', result.posts, res.locals).then(function then(posts) {
|
filters.doFilter('prePostsRender', result.posts, res.locals).then(function then(posts) {
|
||||||
var paths = getActiveThemePaths(req),
|
var view = templates.channel(req.app.get('activeTheme'), channelOpts);
|
||||||
view = 'index';
|
|
||||||
|
|
||||||
// Calculate which template to use to render the data
|
|
||||||
if (channelOpts.firstPageTemplate && paths.hasOwnProperty(channelOpts.firstPageTemplate + '.hbs')) {
|
|
||||||
view = (pageParam > 1) ? 'index' : channelOpts.firstPageTemplate;
|
|
||||||
} else if (channelOpts.slugTemplate) {
|
|
||||||
view = template.getThemeViewForChannel(paths, channelOpts.name, slugParam);
|
|
||||||
} else if (paths.hasOwnProperty(channelOpts.name + '.hbs')) {
|
|
||||||
view = channelOpts.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do final data formatting and then render
|
// Do final data formatting and then render
|
||||||
result.posts = posts;
|
result.posts = posts;
|
||||||
@ -248,7 +237,7 @@ frontendControllers = {
|
|||||||
},
|
},
|
||||||
private: function private(req, res) {
|
private: function private(req, res) {
|
||||||
var defaultPage = path.resolve(config.paths.adminViews, 'private.hbs'),
|
var defaultPage = path.resolve(config.paths.adminViews, 'private.hbs'),
|
||||||
paths = getActiveThemePaths(req),
|
paths = templates.getActiveThemePaths(req.app.get('activeTheme')),
|
||||||
data = {};
|
data = {};
|
||||||
|
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
|
100
core/server/controllers/frontend/templates.js
Normal file
100
core/server/controllers/frontend/templates.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// # Templates
|
||||||
|
//
|
||||||
|
// Figure out which template should be used to render a request
|
||||||
|
// based on the templates which are allowed, and what is available in the theme
|
||||||
|
var _ = require('lodash'),
|
||||||
|
config = require('../../config');
|
||||||
|
|
||||||
|
function getActiveThemePaths(activeTheme) {
|
||||||
|
return config.paths.availableThemes[activeTheme];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ## Get Channel Template Hierarchy
|
||||||
|
*
|
||||||
|
* Fetch the ordered list of templates that can be used to render this request.
|
||||||
|
* 'index' is the default / fallback
|
||||||
|
* For channels with slugs: [:channelName-:slug, :channelName, index]
|
||||||
|
* For channels without slugs: [:channelName, index]
|
||||||
|
* Channels can also have a front page template which is used if this is the first page of the channel, e.g. 'home.hbs'
|
||||||
|
*
|
||||||
|
* @param {Object} channelOpts
|
||||||
|
* @returns {String[]}
|
||||||
|
*/
|
||||||
|
function getChannelTemplateHierarchy(channelOpts) {
|
||||||
|
var templateList = ['index'];
|
||||||
|
|
||||||
|
if (channelOpts.name && channelOpts.name !== 'index') {
|
||||||
|
templateList.unshift(channelOpts.name);
|
||||||
|
|
||||||
|
if (channelOpts.slugTemplate && channelOpts.slugParam) {
|
||||||
|
templateList.unshift(channelOpts.name + '-' + channelOpts.slugParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channelOpts.frontPageTemplate && channelOpts.postOptions.page === 1) {
|
||||||
|
templateList.unshift(channelOpts.frontPageTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return templateList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ## Get Single Template Hierarchy
|
||||||
|
*
|
||||||
|
* Fetch the ordered list of templates that can be used to render this request.
|
||||||
|
* 'post' is the default / fallback
|
||||||
|
* For posts: [post-:slug, post]
|
||||||
|
* For pages: [page-:slug, page, post]
|
||||||
|
*
|
||||||
|
* @param {Object} single
|
||||||
|
* @returns {String[]}
|
||||||
|
*/
|
||||||
|
function getSingleTemplateHierarchy(single) {
|
||||||
|
var templateList = ['post'],
|
||||||
|
type = 'post';
|
||||||
|
|
||||||
|
if (single.page) {
|
||||||
|
templateList.unshift('page');
|
||||||
|
type = 'page';
|
||||||
|
}
|
||||||
|
|
||||||
|
templateList.unshift(type + '-' + single.slug);
|
||||||
|
|
||||||
|
return templateList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ## Pick Template
|
||||||
|
*
|
||||||
|
* Taking the ordered list of allowed templates for this request
|
||||||
|
* Cycle through and find the first one which has a match in the theme
|
||||||
|
*
|
||||||
|
* @param {Object} themePaths
|
||||||
|
* @param {Array} templateList
|
||||||
|
*/
|
||||||
|
function pickTemplate(themePaths, templateList) {
|
||||||
|
var template = _.find(templateList, function (template) {
|
||||||
|
return themePaths.hasOwnProperty(template + '.hbs');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!template) {
|
||||||
|
template = templateList[templateList.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTemplateForSingle(activeTheme, single) {
|
||||||
|
return pickTemplate(getActiveThemePaths(activeTheme), getSingleTemplateHierarchy(single));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTemplateForChannel(activeTheme, channelOpts) {
|
||||||
|
return pickTemplate(getActiveThemePaths(activeTheme), getChannelTemplateHierarchy(channelOpts));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getActiveThemePaths: getActiveThemePaths,
|
||||||
|
channel: getTemplateForChannel,
|
||||||
|
single: getTemplateForSingle
|
||||||
|
};
|
@ -1,14 +0,0 @@
|
|||||||
var config = require('../../config');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the paths object of the active theme via way of a promise.
|
|
||||||
* @return {Promise} The promise resolves with the value of the paths.
|
|
||||||
*/
|
|
||||||
function getActiveThemePaths(req) {
|
|
||||||
var activeTheme = req.app.get('activeTheme'),
|
|
||||||
paths = config.paths.availableThemes[activeTheme];
|
|
||||||
|
|
||||||
return paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = getActiveThemePaths;
|
|
@ -199,12 +199,14 @@ generate = function generate(req, res, next) {
|
|||||||
// Set page on postOptions for the query made later
|
// Set page on postOptions for the query made later
|
||||||
req.channelConfig.postOptions.page = pageParam;
|
req.channelConfig.postOptions.page = pageParam;
|
||||||
|
|
||||||
|
req.channelConfig.slugParam = slugParam;
|
||||||
|
|
||||||
// No negative pages, or page 1
|
// No negative pages, or page 1
|
||||||
if (isNaN(pageParam) || pageParam < 1 || (req.params.page !== undefined && pageParam === 1)) {
|
if (isNaN(pageParam) || pageParam < 1 || (req.params.page !== undefined && pageParam === 1)) {
|
||||||
return res.redirect(baseUrl);
|
return res.redirect(baseUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
return getData(req.channelConfig, slugParam).then(function then(data) {
|
return getData(req.channelConfig).then(function then(data) {
|
||||||
var maxPage = data.results.meta.pagination.pages;
|
var maxPage = data.results.meta.pagination.pages;
|
||||||
|
|
||||||
// If page is greater than number of pages we have, redirect to last page
|
// If page is greater than number of pages we have, redirect to last page
|
||||||
|
@ -8,10 +8,9 @@
|
|||||||
|
|
||||||
var hbs = require('express-hbs'),
|
var hbs = require('express-hbs'),
|
||||||
_ = require('lodash'),
|
_ = require('lodash'),
|
||||||
api = require('../api'),
|
|
||||||
config = require('../config'),
|
|
||||||
filters = require('../filters'),
|
filters = require('../filters'),
|
||||||
template = require('./template'),
|
// @TODO Fix this
|
||||||
|
template = require('../controllers/frontend/templates'),
|
||||||
body_class;
|
body_class;
|
||||||
|
|
||||||
body_class = function (options) {
|
body_class = function (options) {
|
||||||
@ -19,7 +18,9 @@ body_class = function (options) {
|
|||||||
context = options.data.root.context,
|
context = options.data.root.context,
|
||||||
post = this.post,
|
post = this.post,
|
||||||
tags = this.post && this.post.tags ? this.post.tags : this.tags || [],
|
tags = this.post && this.post.tags ? this.post.tags : this.tags || [],
|
||||||
page = this.post && this.post.page ? this.post.page : this.page || false;
|
page = this.post && this.post.page ? this.post.page : this.page || false,
|
||||||
|
activeTheme = options.data.root.settings.activeTheme,
|
||||||
|
view;
|
||||||
|
|
||||||
if (post) {
|
if (post) {
|
||||||
// To be removed from pages by #2597 when we're ready to deprecate this
|
// To be removed from pages by #2597 when we're ready to deprecate this
|
||||||
@ -53,26 +54,20 @@ body_class = function (options) {
|
|||||||
classes.push('archive-template');
|
classes.push('archive-template');
|
||||||
}
|
}
|
||||||
|
|
||||||
return api.settings.read({context: {internal: true}, key: 'activeTheme'}).then(function (response) {
|
if (post && page) {
|
||||||
var activeTheme = response.settings[0],
|
view = template.single(activeTheme, post).split('-');
|
||||||
paths = config.paths.availableThemes[activeTheme.value],
|
|
||||||
view;
|
|
||||||
|
|
||||||
if (post && page) {
|
if (view[0] === 'page' && view.length > 1) {
|
||||||
view = template.getThemeViewForPost(paths, post).split('-');
|
classes.push(view.join('-'));
|
||||||
|
// To be removed by #2597 when we're ready to deprecate this
|
||||||
if (view[0] === 'page' && view.length > 1) {
|
view.splice(1, 0, 'template');
|
||||||
classes.push(view.join('-'));
|
classes.push(view.join('-'));
|
||||||
// To be removed by #2597 when we're ready to deprecate this
|
|
||||||
view.splice(1, 0, 'template');
|
|
||||||
classes.push(view.join('-'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return filters.doFilter('body_class', classes).then(function (classes) {
|
return filters.doFilter('body_class', classes).then(function (classes) {
|
||||||
var classString = _.reduce(classes, function (memo, item) { return memo + ' ' + item; }, '');
|
var classString = _.reduce(classes, function (memo, item) { return memo + ' ' + item; }, '');
|
||||||
return new hbs.handlebars.SafeString(classString.trim());
|
return new hbs.handlebars.SafeString(classString.trim());
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -22,47 +22,4 @@ templates.execute = function (name, context, options) {
|
|||||||
return new hbs.handlebars.SafeString(partial(context, options));
|
return new hbs.handlebars.SafeString(partial(context, options));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Given a theme object and a post object this will return
|
|
||||||
// which theme template page should be used.
|
|
||||||
// If given a post object that is a regular post
|
|
||||||
// it will return 'post'.
|
|
||||||
// If given a static post object it will return 'page'.
|
|
||||||
// If given a static post object and a custom page template
|
|
||||||
// exits it will return that page.
|
|
||||||
templates.getThemeViewForPost = function (themePaths, post) {
|
|
||||||
var customPageView = 'page-' + post.slug,
|
|
||||||
view = 'post';
|
|
||||||
|
|
||||||
if (post.page) {
|
|
||||||
if (themePaths.hasOwnProperty(customPageView + '.hbs')) {
|
|
||||||
view = customPageView;
|
|
||||||
} else if (themePaths.hasOwnProperty('page.hbs')) {
|
|
||||||
view = 'page';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return view;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Given a theme object and a slug this will return
|
|
||||||
// which theme template page should be used.
|
|
||||||
// If no default or custom tag template exists then 'index'
|
|
||||||
// will be returned
|
|
||||||
// If no custom template exists but a default does then
|
|
||||||
// the default will be returned
|
|
||||||
// If given a slug and a custom template
|
|
||||||
// exits it will return that view.
|
|
||||||
templates.getThemeViewForChannel = function (themePaths, channelName, slug) {
|
|
||||||
var customChannelView = channelName + '-' + slug,
|
|
||||||
view = channelName;
|
|
||||||
|
|
||||||
if (themePaths.hasOwnProperty(customChannelView + '.hbs')) {
|
|
||||||
view = customChannelView;
|
|
||||||
} else if (!themePaths.hasOwnProperty(channelName + '.hbs')) {
|
|
||||||
view = 'index';
|
|
||||||
}
|
|
||||||
|
|
||||||
return view;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = templates;
|
module.exports = templates;
|
||||||
|
@ -133,6 +133,7 @@ describe('fetchData', function () {
|
|||||||
postOptions: {
|
postOptions: {
|
||||||
filter: 'tags:%s'
|
filter: 'tags:%s'
|
||||||
},
|
},
|
||||||
|
slugParam: 'testing',
|
||||||
data: {
|
data: {
|
||||||
tag: {
|
tag: {
|
||||||
type: 'read',
|
type: 'read',
|
||||||
@ -140,10 +141,9 @@ describe('fetchData', function () {
|
|||||||
options: {slug: '%s'}
|
options: {slug: '%s'}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
slugParam = 'testing';
|
|
||||||
|
|
||||||
fetchData(channelOpts, slugParam).then(function (result) {
|
fetchData(channelOpts).then(function (result) {
|
||||||
should.exist(result);
|
should.exist(result);
|
||||||
result.should.be.an.Object.with.properties('posts', 'meta', 'data');
|
result.should.be.an.Object.with.properties('posts', 'meta', 'data');
|
||||||
result.data.should.be.an.Object.with.properties('tag');
|
result.data.should.be.an.Object.with.properties('tag');
|
||||||
|
@ -987,6 +987,8 @@ describe('Frontend Controller', function () {
|
|||||||
}]
|
}]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
config.set({paths: {availableThemes: {casper: casper}}});
|
||||||
|
|
||||||
var date = moment(mockPosts[1].posts[0].published_at).format('YYYY');
|
var date = moment(mockPosts[1].posts[0].published_at).format('YYYY');
|
||||||
mockPosts[1].posts[0].url = '/' + date + '/' + mockPosts[1].posts[0].slug + '/';
|
mockPosts[1].posts[0].url = '/' + date + '/' + mockPosts[1].posts[0].slug + '/';
|
||||||
});
|
});
|
||||||
@ -1375,6 +1377,8 @@ describe('Frontend Controller', function () {
|
|||||||
render: sinon.spy(),
|
render: sinon.spy(),
|
||||||
redirect: sinon.spy()
|
redirect: sinon.spy()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
config.set({paths: {availableThemes: {casper: {}}}});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render draft post', function (done) {
|
it('should render draft post', function (done) {
|
||||||
|
176
core/test/unit/controllers/frontend/templates_spec.js
Normal file
176
core/test/unit/controllers/frontend/templates_spec.js
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
/*globals describe, it, afterEach, beforeEach*/
|
||||||
|
/*jshint expr:true*/
|
||||||
|
var should = require('should'),
|
||||||
|
rewire = require('rewire'),
|
||||||
|
_ = require('lodash'),
|
||||||
|
|
||||||
|
// Stuff we are testing
|
||||||
|
templates = rewire('../../../../server/controllers/frontend/templates'),
|
||||||
|
|
||||||
|
config = require('../../../../server/config'),
|
||||||
|
origConfig = _.cloneDeep(config);
|
||||||
|
|
||||||
|
// To stop jshint complaining
|
||||||
|
should.equal(true, true);
|
||||||
|
|
||||||
|
describe('templates', function () {
|
||||||
|
afterEach(function () {
|
||||||
|
config.set(origConfig);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('utils', function () {
|
||||||
|
var channelTemplateList = templates.__get__('getChannelTemplateHierarchy');
|
||||||
|
|
||||||
|
it('should return just index for empty channelOpts', function () {
|
||||||
|
channelTemplateList({}).should.eql(['index']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return just index if channel name is index', function () {
|
||||||
|
channelTemplateList({name: 'index'}).should.eql(['index']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return just index if channel name is index even if slug is set', function () {
|
||||||
|
channelTemplateList({name: 'index', slugTemplate: true, slugParam: 'test'}).should.eql(['index']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return channel, index if channel has name', function () {
|
||||||
|
channelTemplateList({name: 'tag'}).should.eql(['tag', 'index']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return channel-slug, channel, index if channel has name & slug + slugTemplate set', function () {
|
||||||
|
channelTemplateList({name: 'tag', slugTemplate: true, slugParam: 'test'}).should.eql(['tag-test', 'tag', 'index']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return front, channel-slug, channel, index if name, slugParam+slugTemplate & frontPageTemplate+pageParam=1 is set', function () {
|
||||||
|
channelTemplateList({
|
||||||
|
name: 'tag', slugTemplate: true, slugParam: 'test', frontPageTemplate: 'front-tag', postOptions: {page: 1}
|
||||||
|
}).should.eql(['front-tag', 'tag-test', 'tag', 'index']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return home, index for index channel if front is set and pageParam = 1', function () {
|
||||||
|
channelTemplateList({name: 'index', frontPageTemplate: 'home', postOptions: {page: 1}}).should.eql(['home', 'index']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('single', function () {
|
||||||
|
describe('with many templates', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
config.set({
|
||||||
|
paths: {
|
||||||
|
availableThemes: {
|
||||||
|
casper: {
|
||||||
|
assets: null,
|
||||||
|
'default.hbs': '/content/themes/casper/default.hbs',
|
||||||
|
'index.hbs': '/content/themes/casper/index.hbs',
|
||||||
|
'page.hbs': '/content/themes/casper/page.hbs',
|
||||||
|
'page-about.hbs': '/content/themes/casper/page-about.hbs',
|
||||||
|
'post.hbs': '/content/themes/casper/post.hbs',
|
||||||
|
'post-welcome-to-ghost.hbs': '/content/themes/casper/post-welcome-to-ghost.hbs'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('will return correct template for a post WITHOUT custom template', function () {
|
||||||
|
var view = templates.single('casper', {
|
||||||
|
page: 0,
|
||||||
|
slug: 'test-post'
|
||||||
|
});
|
||||||
|
view.should.exist;
|
||||||
|
view.should.eql('post');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('will return correct template for a post WITH custom template', function () {
|
||||||
|
var view = templates.single('casper', {
|
||||||
|
page: 0,
|
||||||
|
slug: 'welcome-to-ghost'
|
||||||
|
});
|
||||||
|
view.should.exist;
|
||||||
|
view.should.eql('post-welcome-to-ghost', 'post');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('will return correct template for a page WITHOUT custom template', function () {
|
||||||
|
var view = templates.single('casper', {
|
||||||
|
page: 1,
|
||||||
|
slug: 'contact'
|
||||||
|
});
|
||||||
|
view.should.exist;
|
||||||
|
view.should.eql('page');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('will return correct template for a page WITH custom template', function () {
|
||||||
|
var view = templates.single('casper', {
|
||||||
|
page: 1,
|
||||||
|
slug: 'about'
|
||||||
|
});
|
||||||
|
view.should.exist;
|
||||||
|
view.should.eql('page-about');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('will fall back to post even if no index.hbs', function () {
|
||||||
|
config.set({paths: {availableThemes: {casper: {
|
||||||
|
assets: null,
|
||||||
|
'default.hbs': '/content/themes/casper/default.hbs'
|
||||||
|
}}}});
|
||||||
|
|
||||||
|
var view = templates.single('casper', {page: 1});
|
||||||
|
view.should.exist;
|
||||||
|
view.should.eql('post');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('channel', function () {
|
||||||
|
describe('without tag templates', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
config.set({paths: {availableThemes: {casper: {
|
||||||
|
assets: null,
|
||||||
|
'default.hbs': '/content/themes/casper/default.hbs',
|
||||||
|
'index.hbs': '/content/themes/casper/index.hbs'
|
||||||
|
}}}});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('will return correct view for a tag', function () {
|
||||||
|
var view = templates.channel('casper', {name: 'tag', slugParam: 'development', slugTemplate: true});
|
||||||
|
view.should.exist;
|
||||||
|
view.should.eql('index');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with tag templates', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
config.set({paths: {availableThemes: {casper: {
|
||||||
|
assets: null,
|
||||||
|
'default.hbs': '/content/themes/casper/default.hbs',
|
||||||
|
'index.hbs': '/content/themes/casper/index.hbs',
|
||||||
|
'tag.hbs': '/content/themes/casper/tag.hbs',
|
||||||
|
'tag-design.hbs': '/content/themes/casper/tag-about.hbs'
|
||||||
|
}}}});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('will return correct view for a tag', function () {
|
||||||
|
var view = templates.channel('casper', {name: 'tag', slugParam: 'design', slugTemplate: true});
|
||||||
|
view.should.exist;
|
||||||
|
view.should.eql('tag-design');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('will return correct view for a tag', function () {
|
||||||
|
var view = templates.channel('casper', {name: 'tag', slugParam: 'development', slugTemplate: true});
|
||||||
|
view.should.exist;
|
||||||
|
view.should.eql('tag');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('will fall back to index even if no index.hbs', function () {
|
||||||
|
config.set({paths: {availableThemes: {casper: {
|
||||||
|
assets: null,
|
||||||
|
'default.hbs': '/content/themes/casper/default.hbs'
|
||||||
|
}}}});
|
||||||
|
|
||||||
|
var view = templates.channel('casper', {name: 'tag', slugParam: 'development', slugTemplate: true});
|
||||||
|
view.should.exist;
|
||||||
|
view.should.eql('index');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,26 +1,16 @@
|
|||||||
/*globals describe, before, after, it*/
|
/*globals describe, before, beforeEach, after, it*/
|
||||||
/*jshint expr:true*/
|
/*jshint expr:true*/
|
||||||
var should = require('should'),
|
var should = require('should'),
|
||||||
sinon = require('sinon'),
|
|
||||||
Promise = require('bluebird'),
|
|
||||||
hbs = require('express-hbs'),
|
hbs = require('express-hbs'),
|
||||||
utils = require('./utils'),
|
utils = require('./utils'),
|
||||||
|
|
||||||
// Stuff we are testing
|
// Stuff we are testing
|
||||||
handlebars = hbs.handlebars,
|
handlebars = hbs.handlebars,
|
||||||
helpers = require('../../../server/helpers'),
|
helpers = require('../../../server/helpers');
|
||||||
api = require('../../../server/api');
|
|
||||||
|
|
||||||
describe('{{body_class}} helper', function () {
|
describe('{{body_class}} helper', function () {
|
||||||
var sandbox;
|
var options = {};
|
||||||
|
|
||||||
before(function () {
|
before(function () {
|
||||||
sandbox = sinon.sandbox.create();
|
|
||||||
sandbox.stub(api.settings, 'read', function () {
|
|
||||||
return Promise.resolve({
|
|
||||||
settings: [{value: 'casper'}]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
utils.loadHelpers();
|
utils.loadHelpers();
|
||||||
utils.overrideConfig({paths: {
|
utils.overrideConfig({paths: {
|
||||||
availableThemes: {
|
availableThemes: {
|
||||||
@ -36,9 +26,19 @@ describe('{{body_class}} helper', function () {
|
|||||||
}});
|
}});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
options = {
|
||||||
|
data: {
|
||||||
|
root: {
|
||||||
|
context: [],
|
||||||
|
settings: {activeTheme: 'casper'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
after(function () {
|
after(function () {
|
||||||
utils.restoreConfig();
|
utils.restoreConfig();
|
||||||
sandbox.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has loaded body_class helper', function () {
|
it('has loaded body_class helper', function () {
|
||||||
@ -46,7 +46,9 @@ describe('{{body_class}} helper', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('can render class string', function (done) {
|
it('can render class string', function (done) {
|
||||||
helpers.body_class.call({}, {data: {root: {context: ['home']}}}).then(function (rendered) {
|
options.data.root.context = ['home'];
|
||||||
|
|
||||||
|
helpers.body_class.call({}, options).then(function (rendered) {
|
||||||
should.exist(rendered);
|
should.exist(rendered);
|
||||||
|
|
||||||
rendered.string.should.equal('home-template');
|
rendered.string.should.equal('home-template');
|
||||||
@ -55,111 +57,90 @@ describe('{{body_class}} helper', function () {
|
|||||||
}).catch(done);
|
}).catch(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can render class string for context', function (done) {
|
describe('can render class string for context', function () {
|
||||||
Promise.all([
|
function callBodyClassWithContext(context, self) {
|
||||||
// Standard home page
|
options.data.root.context = context;
|
||||||
helpers.body_class.call(
|
return helpers.body_class.call(
|
||||||
{relativeUrl: '/'},
|
self,
|
||||||
{data: {root: {context: ['home', 'index']}}}
|
options
|
||||||
),
|
);
|
||||||
// A post
|
}
|
||||||
helpers.body_class.call(
|
|
||||||
{relativeUrl: '/a-post-title', post: {}},
|
|
||||||
{data: {root: {context: ['post']}}}
|
|
||||||
),
|
|
||||||
// Paginated index
|
|
||||||
helpers.body_class.call(
|
|
||||||
{relativeUrl: '/page/4'},
|
|
||||||
{data: {root: {context: ['index', 'paged']}}}
|
|
||||||
),
|
|
||||||
// Tag page
|
|
||||||
helpers.body_class.call(
|
|
||||||
{relativeUrl: '/tag/foo', tag: {slug: 'foo'}},
|
|
||||||
{data: {root: {context: ['tag']}}}
|
|
||||||
),
|
|
||||||
// Paginated tag page
|
|
||||||
helpers.body_class.call(
|
|
||||||
{relativeUrl: '/tag/foo/page/2', tag: {slug: 'foo'}},
|
|
||||||
{data: {root: {context: ['tag', 'paged']}}}
|
|
||||||
),
|
|
||||||
// Author page
|
|
||||||
helpers.body_class.call(
|
|
||||||
{relativeUrl: '/author/bar', author: {slug: 'bar'}},
|
|
||||||
{data: {root: {context: ['author']}}}
|
|
||||||
),
|
|
||||||
// Paginated author page
|
|
||||||
helpers.body_class.call(
|
|
||||||
{relativeUrl: '/author/bar/page/2', author: {slug: 'bar'}},
|
|
||||||
{data: {root: {context: ['author', 'paged']}}}
|
|
||||||
),
|
|
||||||
// Private route for password protection
|
|
||||||
helpers.body_class.call(
|
|
||||||
{relativeUrl: '/private/'},
|
|
||||||
{data: {root: {context: ['private']}}}
|
|
||||||
),
|
|
||||||
// Post with tags
|
|
||||||
helpers.body_class.call(
|
|
||||||
{relativeUrl: '/my-awesome-post/', post: {tags: [{slug: 'foo'}, {slug: 'bar'}]}},
|
|
||||||
{data: {root: {context: ['post']}}}
|
|
||||||
)
|
|
||||||
]).then(function (rendered) {
|
|
||||||
rendered.length.should.equal(9);
|
|
||||||
|
|
||||||
should.exist(rendered[0]);
|
it('Standard home page', function (done) {
|
||||||
should.exist(rendered[1]);
|
callBodyClassWithContext(['home', 'index'], {relativeUrl: '/'}).then(function (rendered) {
|
||||||
should.exist(rendered[2]);
|
rendered.string.should.equal('home-template');
|
||||||
should.exist(rendered[3]);
|
done();
|
||||||
should.exist(rendered[4]);
|
}).catch(done);
|
||||||
should.exist(rendered[5]);
|
});
|
||||||
should.exist(rendered[6]);
|
|
||||||
should.exist(rendered[7]);
|
|
||||||
should.exist(rendered[8]);
|
|
||||||
|
|
||||||
rendered[0].string.should.equal('home-template');
|
it('a post', function (done) {
|
||||||
rendered[1].string.should.equal('post-template');
|
callBodyClassWithContext(['post'], {relativeUrl: '/a-post-title', post: {}}).then(function (rendered) {
|
||||||
rendered[2].string.should.equal('paged archive-template');
|
rendered.string.should.equal('post-template');
|
||||||
rendered[3].string.should.equal('tag-template tag-foo');
|
done();
|
||||||
rendered[4].string.should.equal('tag-template tag-foo paged archive-template');
|
}).catch(done);
|
||||||
rendered[5].string.should.equal('author-template author-bar');
|
});
|
||||||
rendered[6].string.should.equal('author-template author-bar paged archive-template');
|
|
||||||
rendered[7].string.should.equal('private-template');
|
|
||||||
rendered[8].string.should.equal('post-template tag-foo tag-bar');
|
|
||||||
|
|
||||||
done();
|
it('paginated index', function (done) {
|
||||||
}).catch(done);
|
callBodyClassWithContext(['index', 'paged'], {relativeUrl: '/page/4'}).then(function (rendered) {
|
||||||
});
|
rendered.string.should.equal('paged archive-template');
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
it('can render class for static page', function (done) {
|
it('tag page', function (done) {
|
||||||
helpers.body_class.call(
|
callBodyClassWithContext(['tag'], {relativeUrl: '/tag/foo', tag: {slug: 'foo'}}).then(function (rendered) {
|
||||||
{
|
rendered.string.should.equal('tag-template tag-foo');
|
||||||
relativeUrl: '/about',
|
done();
|
||||||
post: {
|
}).catch(done);
|
||||||
page: true
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
{data: {root: {context: ['page']}}}
|
|
||||||
).then(function (rendered) {
|
|
||||||
should.exist(rendered);
|
|
||||||
rendered.string.should.equal('post-template page-template page');
|
|
||||||
|
|
||||||
done();
|
it('paginated tag page', function (done) {
|
||||||
}).catch(done);
|
callBodyClassWithContext(['tag', 'paged'], {relativeUrl: '/tag/foo/page/2', tag: {slug: 'foo'}}).then(function (rendered) {
|
||||||
});
|
rendered.string.should.equal('tag-template tag-foo paged archive-template');
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
it('can render class for static page with custom template', function (done) {
|
it('author page', function (done) {
|
||||||
helpers.body_class.call(
|
callBodyClassWithContext(['author'], {relativeUrl: '/author/bar', author: {slug: 'bar'}}).then(function (rendered) {
|
||||||
{
|
rendered.string.should.equal('author-template author-bar');
|
||||||
relativeUrl: '/about',
|
done();
|
||||||
post: {
|
}).catch(done);
|
||||||
page: true,
|
});
|
||||||
slug: 'about'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{data: {root: {context: ['page']}}}).then(function (rendered) {
|
|
||||||
should.exist(rendered);
|
|
||||||
rendered.string.should.equal('post-template page-template page page-about page-template-about');
|
|
||||||
|
|
||||||
done();
|
it('paginated author page', function (done) {
|
||||||
}).catch(done);
|
callBodyClassWithContext(['author', 'paged'], {relativeUrl: '/author/bar/page/2', author: {slug: 'bar'}}).then(function (rendered) {
|
||||||
|
rendered.string.should.equal('author-template author-bar paged archive-template');
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('private route for password protection', function (done) {
|
||||||
|
callBodyClassWithContext(['private'], {relativeUrl: '/private/'}).then(function (rendered) {
|
||||||
|
rendered.string.should.equal('private-template');
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('post with tags', function (done) {
|
||||||
|
callBodyClassWithContext(['post'], {relativeUrl: '/my-awesome-post/', post: {tags: [{slug: 'foo'}, {slug: 'bar'}]}}).then(function (rendered) {
|
||||||
|
rendered.string.should.equal('post-template tag-foo tag-bar');
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('a static page', function (done) {
|
||||||
|
callBodyClassWithContext(['page'], {relativeUrl: '/about', post: {page: true}}).then(function (rendered) {
|
||||||
|
rendered.string.should.equal('post-template page-template page');
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('a static page with custom template', function (done) {
|
||||||
|
callBodyClassWithContext(['page'], {relativeUrl: '/about', post: {page: true, slug: 'about'}}).then(function (rendered) {
|
||||||
|
rendered.string.should.equal('post-template page-template page page-about page-template-about');
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -15,71 +15,4 @@ describe('Helpers Template', function () {
|
|||||||
should.exist(safeString);
|
should.exist(safeString);
|
||||||
safeString.should.have.property('string').and.equal('<h1>Hello world</h1>');
|
safeString.should.have.property('string').and.equal('<h1>Hello world</h1>');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getThemeViewForPost', function () {
|
|
||||||
var themePaths = {
|
|
||||||
assets: null,
|
|
||||||
'default.hbs': '/content/themes/casper/default.hbs',
|
|
||||||
'index.hbs': '/content/themes/casper/index.hbs',
|
|
||||||
'page.hbs': '/content/themes/casper/page.hbs',
|
|
||||||
'page-about.hbs': '/content/themes/casper/page-about.hbs',
|
|
||||||
'post.hbs': '/content/themes/casper/post.hbs'
|
|
||||||
},
|
|
||||||
posts = [{
|
|
||||||
page: 1,
|
|
||||||
slug: 'about'
|
|
||||||
}, {
|
|
||||||
page: 1,
|
|
||||||
slug: 'contact'
|
|
||||||
}, {
|
|
||||||
page: 0,
|
|
||||||
slug: 'test-post'
|
|
||||||
}];
|
|
||||||
|
|
||||||
it('will return correct view for a post', function () {
|
|
||||||
var view = template.getThemeViewForPost(themePaths, posts[0]);
|
|
||||||
view.should.exist;
|
|
||||||
view.should.eql('page-about');
|
|
||||||
|
|
||||||
view = template.getThemeViewForPost(themePaths, posts[1]);
|
|
||||||
view.should.exist;
|
|
||||||
view.should.eql('page');
|
|
||||||
|
|
||||||
view = template.getThemeViewForPost(themePaths, posts[2]);
|
|
||||||
view.should.exist;
|
|
||||||
view.should.eql('post');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getThemeViewForChannel', function () {
|
|
||||||
var themePathsWithTagViews = {
|
|
||||||
assets: null,
|
|
||||||
'default.hbs': '/content/themes/casper/default.hbs',
|
|
||||||
'index.hbs': '/content/themes/casper/index.hbs',
|
|
||||||
'tag.hbs': '/content/themes/casper/tag.hbs',
|
|
||||||
'tag-design.hbs': '/content/themes/casper/tag-about.hbs'
|
|
||||||
},
|
|
||||||
themePaths = {
|
|
||||||
assets: null,
|
|
||||||
'default.hbs': '/content/themes/casper/default.hbs',
|
|
||||||
'index.hbs': '/content/themes/casper/index.hbs'
|
|
||||||
},
|
|
||||||
CHANNEL = 'tag',
|
|
||||||
CUSTOM_EXISTS = 'design',
|
|
||||||
DEFAULT = 'development';
|
|
||||||
|
|
||||||
it('will return correct view for a tag', function () {
|
|
||||||
var view = template.getThemeViewForChannel(themePathsWithTagViews, CHANNEL, CUSTOM_EXISTS);
|
|
||||||
view.should.exist;
|
|
||||||
view.should.eql('tag-design');
|
|
||||||
|
|
||||||
view = template.getThemeViewForChannel(themePathsWithTagViews, CHANNEL, DEFAULT);
|
|
||||||
view.should.exist;
|
|
||||||
view.should.eql('tag');
|
|
||||||
|
|
||||||
view = template.getThemeViewForChannel(themePaths, CHANNEL, DEFAULT);
|
|
||||||
view.should.exist;
|
|
||||||
view.should.eql('index');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user