diff --git a/core/server/controllers/frontend/channel-config.js b/core/server/controllers/frontend/channel-config.js index 6966fd4e63..5b885cce3d 100644 --- a/core/server/controllers/frontend/channel-config.js +++ b/core/server/controllers/frontend/channel-config.js @@ -3,9 +3,9 @@ var config = require('../../config'), defaults = { index: { - name: 'home', + name: 'index', route: '/', - firstPageTemplate: 'home' + frontPageTemplate: 'home' }, tag: { name: 'tag', diff --git a/core/server/controllers/frontend/fetch-data.js b/core/server/controllers/frontend/fetch-data.js index b63176b262..f3d2f7b758 100644 --- a/core/server/controllers/frontend/fetch-data.js +++ b/core/server/controllers/frontend/fetch-data.js @@ -82,10 +82,9 @@ function processQuery(query, slugParam) { * Does a first round of formatting on the response, and returns * * @param {Object} channelOptions - * @param {String} slugParam * @returns {Promise} response */ -function fetchData(channelOptions, slugParam) { +function fetchData(channelOptions) { // Temporary workaround to make RSS work, moving towards dynamic channels will provide opportunities to // improve this, I hope :) function handlePostsPerPage(channelOptions) { @@ -102,10 +101,10 @@ function fetchData(channelOptions, slugParam) { // All channels must have a posts query, use the default if not provided postQuery = _.defaultsDeep({}, pageOptions, defaultPostQuery); - props.posts = processQuery(postQuery, slugParam); + props.posts = processQuery(postQuery, channelOptions.slugParam); _.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) { diff --git a/core/server/controllers/frontend/index.js b/core/server/controllers/frontend/index.js index fbf7a2a9d6..7ac87bed2b 100644 --- a/core/server/controllers/frontend/index.js +++ b/core/server/controllers/frontend/index.js @@ -12,7 +12,7 @@ var _ = require('lodash'), errors = require('../../errors'), filters = require('../../filters'), Promise = require('bluebird'), - template = require('../../helpers/template'), + templates = require('./templates'), routeMatch = require('path-match')(), safeString = require('../../utils/index').safeString, handleError = require('./error'), @@ -21,7 +21,6 @@ var _ = require('lodash'), channelConfig = require('./channel-config'), setResponseContext = require('./context'), setRequestIsSecure = require('./secure'), - getActiveThemePaths = require('./theme-paths'), frontendControllers, staticPostPermalink = routeMatch('/:slug/:edit?'); @@ -34,8 +33,7 @@ var _ = require('lodash'), */ function renderPost(req, res) { return function renderPost(post) { - var paths = getActiveThemePaths(req), - view = template.getThemeViewForPost(paths, post), + var view = templates.single(req.app.get('activeTheme'), post), response = formatResponse.single(post); setResponseContext(req, res, response); @@ -53,6 +51,7 @@ function renderChannel(channelOpts) { channelOpts.postOptions = channelOpts.postOptions || {}; // Set page on postOptions for the query made later channelOpts.postOptions.page = pageParam; + channelOpts.slugParam = slugParam; // @TODO this should really use the url building code in config.url function createUrl(page) { @@ -75,7 +74,7 @@ function renderChannel(channelOpts) { } // 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 (pageParam > result.meta.pagination.pages) { return res.redirect(createUrl(result.meta.pagination.pages)); @@ -90,17 +89,7 @@ function renderChannel(channelOpts) { // @TODO: properly design these filters filters.doFilter('prePostsRender', result.posts, res.locals).then(function then(posts) { - var paths = getActiveThemePaths(req), - 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; - } + var view = templates.channel(req.app.get('activeTheme'), channelOpts); // Do final data formatting and then render result.posts = posts; @@ -248,7 +237,7 @@ frontendControllers = { }, private: function private(req, res) { var defaultPage = path.resolve(config.paths.adminViews, 'private.hbs'), - paths = getActiveThemePaths(req), + paths = templates.getActiveThemePaths(req.app.get('activeTheme')), data = {}; if (res.error) { diff --git a/core/server/controllers/frontend/templates.js b/core/server/controllers/frontend/templates.js new file mode 100644 index 0000000000..d69f74d37a --- /dev/null +++ b/core/server/controllers/frontend/templates.js @@ -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 +}; diff --git a/core/server/controllers/frontend/theme-paths.js b/core/server/controllers/frontend/theme-paths.js deleted file mode 100644 index 1464d37631..0000000000 --- a/core/server/controllers/frontend/theme-paths.js +++ /dev/null @@ -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; diff --git a/core/server/data/xml/rss/index.js b/core/server/data/xml/rss/index.js index 7bdcc3347f..6e5cda4ec1 100644 --- a/core/server/data/xml/rss/index.js +++ b/core/server/data/xml/rss/index.js @@ -199,12 +199,14 @@ generate = function generate(req, res, next) { // Set page on postOptions for the query made later req.channelConfig.postOptions.page = pageParam; + req.channelConfig.slugParam = slugParam; + // No negative pages, or page 1 if (isNaN(pageParam) || pageParam < 1 || (req.params.page !== undefined && pageParam === 1)) { 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; // If page is greater than number of pages we have, redirect to last page diff --git a/core/server/helpers/body_class.js b/core/server/helpers/body_class.js index 546b45f3e9..b782138531 100644 --- a/core/server/helpers/body_class.js +++ b/core/server/helpers/body_class.js @@ -8,10 +8,9 @@ var hbs = require('express-hbs'), _ = require('lodash'), - api = require('../api'), - config = require('../config'), filters = require('../filters'), - template = require('./template'), + // @TODO Fix this + template = require('../controllers/frontend/templates'), body_class; body_class = function (options) { @@ -19,7 +18,9 @@ body_class = function (options) { context = options.data.root.context, post = this.post, 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) { // 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'); } - return api.settings.read({context: {internal: true}, key: 'activeTheme'}).then(function (response) { - var activeTheme = response.settings[0], - paths = config.paths.availableThemes[activeTheme.value], - view; + if (post && page) { + view = template.single(activeTheme, post).split('-'); - if (post && page) { - view = template.getThemeViewForPost(paths, post).split('-'); - - if (view[0] === 'page' && view.length > 1) { - 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('-')); - } + if (view[0] === 'page' && view.length > 1) { + 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) { - var classString = _.reduce(classes, function (memo, item) { return memo + ' ' + item; }, ''); - return new hbs.handlebars.SafeString(classString.trim()); - }); + return filters.doFilter('body_class', classes).then(function (classes) { + var classString = _.reduce(classes, function (memo, item) { return memo + ' ' + item; }, ''); + return new hbs.handlebars.SafeString(classString.trim()); }); }; diff --git a/core/server/helpers/template.js b/core/server/helpers/template.js index e3ac69bc08..cb4da7f3e0 100644 --- a/core/server/helpers/template.js +++ b/core/server/helpers/template.js @@ -22,47 +22,4 @@ templates.execute = function (name, 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; diff --git a/core/test/unit/controllers/frontend/fetch-data_spec.js b/core/test/unit/controllers/frontend/fetch-data_spec.js index 7e61016950..a293a27f50 100644 --- a/core/test/unit/controllers/frontend/fetch-data_spec.js +++ b/core/test/unit/controllers/frontend/fetch-data_spec.js @@ -133,6 +133,7 @@ describe('fetchData', function () { postOptions: { filter: 'tags:%s' }, + slugParam: 'testing', data: { tag: { type: 'read', @@ -140,10 +141,9 @@ describe('fetchData', function () { options: {slug: '%s'} } } - }, - slugParam = 'testing'; + }; - fetchData(channelOpts, slugParam).then(function (result) { + fetchData(channelOpts).then(function (result) { should.exist(result); result.should.be.an.Object.with.properties('posts', 'meta', 'data'); result.data.should.be.an.Object.with.properties('tag'); diff --git a/core/test/unit/controllers/frontend/index_spec.js b/core/test/unit/controllers/frontend/index_spec.js index 2352fe3e6c..b52fa39667 100644 --- a/core/test/unit/controllers/frontend/index_spec.js +++ b/core/test/unit/controllers/frontend/index_spec.js @@ -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'); mockPosts[1].posts[0].url = '/' + date + '/' + mockPosts[1].posts[0].slug + '/'; }); @@ -1375,6 +1377,8 @@ describe('Frontend Controller', function () { render: sinon.spy(), redirect: sinon.spy() }; + + config.set({paths: {availableThemes: {casper: {}}}}); }); it('should render draft post', function (done) { diff --git a/core/test/unit/controllers/frontend/templates_spec.js b/core/test/unit/controllers/frontend/templates_spec.js new file mode 100644 index 0000000000..30bc27c43b --- /dev/null +++ b/core/test/unit/controllers/frontend/templates_spec.js @@ -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'); + }); + }); +}); diff --git a/core/test/unit/server_helpers/body_class_spec.js b/core/test/unit/server_helpers/body_class_spec.js index 0156eb7193..716a270cf9 100644 --- a/core/test/unit/server_helpers/body_class_spec.js +++ b/core/test/unit/server_helpers/body_class_spec.js @@ -1,26 +1,16 @@ -/*globals describe, before, after, it*/ +/*globals describe, before, beforeEach, after, it*/ /*jshint expr:true*/ var should = require('should'), - sinon = require('sinon'), - Promise = require('bluebird'), hbs = require('express-hbs'), utils = require('./utils'), // Stuff we are testing handlebars = hbs.handlebars, - helpers = require('../../../server/helpers'), - api = require('../../../server/api'); + helpers = require('../../../server/helpers'); describe('{{body_class}} helper', function () { - var sandbox; - + var options = {}; before(function () { - sandbox = sinon.sandbox.create(); - sandbox.stub(api.settings, 'read', function () { - return Promise.resolve({ - settings: [{value: 'casper'}] - }); - }); utils.loadHelpers(); utils.overrideConfig({paths: { availableThemes: { @@ -36,9 +26,19 @@ describe('{{body_class}} helper', function () { }}); }); + beforeEach(function () { + options = { + data: { + root: { + context: [], + settings: {activeTheme: 'casper'} + } + } + }; + }); + after(function () { utils.restoreConfig(); - sandbox.restore(); }); it('has loaded body_class helper', function () { @@ -46,7 +46,9 @@ describe('{{body_class}} helper', function () { }); 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); rendered.string.should.equal('home-template'); @@ -55,111 +57,90 @@ describe('{{body_class}} helper', function () { }).catch(done); }); - it('can render class string for context', function (done) { - Promise.all([ - // Standard home page - helpers.body_class.call( - {relativeUrl: '/'}, - {data: {root: {context: ['home', 'index']}}} - ), - // 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); + describe('can render class string for context', function () { + function callBodyClassWithContext(context, self) { + options.data.root.context = context; + return helpers.body_class.call( + self, + options + ); + } - should.exist(rendered[0]); - should.exist(rendered[1]); - should.exist(rendered[2]); - should.exist(rendered[3]); - should.exist(rendered[4]); - should.exist(rendered[5]); - should.exist(rendered[6]); - should.exist(rendered[7]); - should.exist(rendered[8]); + it('Standard home page', function (done) { + callBodyClassWithContext(['home', 'index'], {relativeUrl: '/'}).then(function (rendered) { + rendered.string.should.equal('home-template'); + done(); + }).catch(done); + }); - rendered[0].string.should.equal('home-template'); - rendered[1].string.should.equal('post-template'); - rendered[2].string.should.equal('paged archive-template'); - rendered[3].string.should.equal('tag-template tag-foo'); - rendered[4].string.should.equal('tag-template tag-foo paged archive-template'); - 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'); + it('a post', function (done) { + callBodyClassWithContext(['post'], {relativeUrl: '/a-post-title', post: {}}).then(function (rendered) { + rendered.string.should.equal('post-template'); + done(); + }).catch(done); + }); - done(); - }).catch(done); - }); + it('paginated index', function (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) { - helpers.body_class.call( - { - relativeUrl: '/about', - post: { - page: true - } - }, - {data: {root: {context: ['page']}}} - ).then(function (rendered) { - should.exist(rendered); - rendered.string.should.equal('post-template page-template page'); + it('tag page', function (done) { + callBodyClassWithContext(['tag'], {relativeUrl: '/tag/foo', tag: {slug: 'foo'}}).then(function (rendered) { + rendered.string.should.equal('tag-template tag-foo'); + done(); + }).catch(done); + }); - done(); - }).catch(done); - }); + it('paginated tag page', function (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) { - helpers.body_class.call( - { - relativeUrl: '/about', - post: { - 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'); + it('author page', function (done) { + callBodyClassWithContext(['author'], {relativeUrl: '/author/bar', author: {slug: 'bar'}}).then(function (rendered) { + rendered.string.should.equal('author-template author-bar'); + done(); + }).catch(done); + }); - done(); - }).catch(done); + it('paginated author page', function (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); + }); }); }); diff --git a/core/test/unit/server_helpers_template_spec.js b/core/test/unit/server_helpers_template_spec.js index d4b1fa0bef..df65c81750 100644 --- a/core/test/unit/server_helpers_template_spec.js +++ b/core/test/unit/server_helpers_template_spec.js @@ -15,71 +15,4 @@ describe('Helpers Template', function () { should.exist(safeString); safeString.should.have.property('string').and.equal('

Hello world

'); }); - - 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'); - }); - }); });