Introduced renderer to DRY up controllers (#9235)

refs #5091, #9192

- Renderer figures out templates, contexts, and does a render call
- Templating is now handled with a single function
- Context call is made in the renderer

Note:  to make this work, all controllers now define a little bit of config, currently stored in res._route. (That's a totally temporary location, as is res._template... when a sensible naming convention reveals itself I'll get rid of the weird _). This exposes a type and for custom routes a template name & default.
This commit is contained in:
Hannah Wolfe 2017-11-10 12:44:29 +00:00 committed by GitHub
parent e41d0c76fb
commit 98f5ae00fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 359 additions and 154 deletions

View File

@ -5,14 +5,20 @@ var path = require('path'),
// Dirty requires
errors = require('../../../errors'),
templates = require('../../../controllers/frontend/templates'),
postLookup = require('../../../controllers/frontend/post-lookup'),
setResponseContext = require('../../../controllers/frontend/context'),
renderer = require('../../../controllers/frontend/renderer'),
templateName = 'amp',
defaultTemplate = path.resolve(__dirname, 'views', templateName + '.hbs');
templateName = 'amp';
function _renderer(req, res, next) {
// Note: this is super similar to the config middleware used in channels
// @TODO refactor into to something explicit & DRY this up
res._route = {
type: 'custom',
templateName: templateName,
defaultTemplate: path.resolve(__dirname, 'views', templateName + '.hbs')
};
// Renderer begin
// Format data
var data = req.body || {};
@ -22,14 +28,8 @@ function _renderer(req, res, next) {
return next(new errors.NotFoundError({message: i18n.t('errors.errors.pageNotFound')}));
}
// Context
setResponseContext(req, res, data);
// Template
res.template = templates.pickTemplate(templateName, defaultTemplate);
// Render Call
return res.render(res.template, data);
return renderer(req, res, data);
}
// This here is a controller.
@ -49,7 +49,8 @@ function getPostData(req, res, next) {
}
// AMP frontend route
ampRouter.route('/')
ampRouter
.route('/')
.get(
getPostData,
_renderer

View File

@ -2,16 +2,22 @@ var path = require('path'),
express = require('express'),
middleware = require('./middleware'),
bodyParser = require('body-parser'),
templates = require('../../../controllers/frontend/templates'),
setResponseContext = require('../../../controllers/frontend/context'),
renderer = require('../../../controllers/frontend/renderer'),
brute = require('../../../middleware/brute'),
templateName = 'private',
defaultTemplate = path.resolve(__dirname, 'views', templateName + '.hbs'),
privateRouter = express.Router();
function _renderer(req, res) {
// Note: this is super similar to the config middleware used in channels
// @TODO refactor into to something explicit & DRY this up
res._route = {
type: 'custom',
templateName: templateName,
defaultTemplate: path.resolve(__dirname, 'views', templateName + '.hbs')
};
// Renderer begin
// Format data
var data = {};
@ -20,18 +26,13 @@ function _renderer(req, res) {
data.error = res.error;
}
// Context
setResponseContext(req, res);
// Template
res.template = templates.pickTemplate(templateName, defaultTemplate);
// Render Call
return res.render(res.template, data);
return renderer(req, res, data);
}
// password-protected frontend route
privateRouter.route('/')
privateRouter
.route('/')
.get(
middleware.isPrivateSessionAuth,
_renderer

View File

@ -8,27 +8,26 @@ var path = require('path'),
api = require('../../../api'),
errors = require('../../../errors'),
validator = require('../../../data/validation').validator,
templates = require('../../../controllers/frontend/templates'),
postLookup = require('../../../controllers/frontend/post-lookup'),
setResponseContext = require('../../../controllers/frontend/context'),
renderer = require('../../../controllers/frontend/renderer'),
templateName = 'subscribe',
defaultTemplate = path.resolve(__dirname, 'views', templateName + '.hbs');
templateName = 'subscribe';
// In future we'd have a more complex controller here - showing if someone already subscribed?!
function _renderer(req, res) {
// Note: this is super similar to the config middleware used in channels
// @TODO refactor into to something explicit & DRY this up
res._route = {
type: 'custom',
templateName: templateName,
defaultTemplate: path.resolve(__dirname, 'views', templateName + '.hbs')
};
// Renderer begin
// Format data
var data = req.body;
// Context
setResponseContext(req, res);
// Template
res.template = templates.pickTemplate(templateName, defaultTemplate);
// Render Call
return res.render(res.template, data);
return renderer(req, res, data);
}
/**
@ -106,7 +105,8 @@ function storeSubscriber(req, res, next) {
}
// subscribe frontend route
subscribeRouter.route('/')
subscribeRouter
.route('/')
.get(
_renderer
)

View File

@ -9,6 +9,12 @@ var utils = require('../utils'),
// It renders entries = individual posts or pages
// The "route" is handled in site/routes.js
module.exports = function entryController(req, res, next) {
// Note: this is super similar to the config middleware used in channels
// @TODO refactor into to something explicit
res._route = {
type: 'entry'
};
// Query database to find post
return postLookup(req.path).then(function then(lookup) {
// Format data 1

View File

@ -1,7 +1,6 @@
var debug = require('ghost-ignition').debug('channels:render'),
formatResponse = require('./format-response'),
setResponseContext = require('./context'),
templates = require('./templates');
renderer = require('./renderer');
module.exports = function renderChannel(req, res) {
debug('renderChannel called');
@ -9,16 +8,9 @@ module.exports = function renderChannel(req, res) {
// Renderer begin
// Format data 2
// Do final data formatting and then render
result = formatResponse.channel(result);
// Context
setResponseContext(req, res);
// Template
res.template = templates.channel(res.locals.channel);
var data = formatResponse.channel(result);
// Render Call
debug('Rendering view: ' + res.template);
res.render(res.template, result);
return renderer(req, res, data);
};
};

View File

@ -1,7 +1,6 @@
var debug = require('ghost-ignition').debug('channels:render-post'),
templates = require('./templates'),
formatResponse = require('./format-response'),
setResponseContext = require('./context');
renderer = require('./renderer');
/*
* Sets the response context around an entry (post or page)
* and renders it with the correct template.
@ -13,16 +12,9 @@ module.exports = function renderEntry(req, res) {
return function renderEntry(entry) {
// Renderer begin
// Format data 2 - 1 is in preview/entry
var response = formatResponse.entry(entry);
// Context
setResponseContext(req, res, response);
// Template
res.template = templates.entry(entry);
var data = formatResponse.entry(entry);
// Render Call
debug('Rendering view: ' + res.template);
res.render(res.template, response);
return renderer(req, res, data);
};
};

View File

@ -0,0 +1,16 @@
var debug = require('ghost-ignition').debug('renderer'),
setContext = require('./context'),
templates = require('./templates');
module.exports = function renderer(req, res, data) {
// Context
setContext(req, res, data);
// Template
templates.setTemplate(req, res, data);
// Render Call
debug('Rendering template: ' + res._template + ' for: ' + req.originalUrl);
debug('res.locals', res.locals);
res.render(res._template, data);
};

View File

@ -6,7 +6,8 @@
var _ = require('lodash'),
path = require('path'),
config = require('../../config'),
themes = require('../../themes');
themes = require('../../themes'),
_private = {};
/**
* ## Get Error Template Hierarchy
@ -18,7 +19,7 @@ var _ = require('lodash'),
* @param {integer} statusCode
* @returns {String[]}
*/
function getErrorTemplateHierarchy(statusCode) {
_private.getErrorTemplateHierarchy = function getErrorTemplateHierarchy(statusCode) {
var errorCode = _.toString(statusCode),
templateList = ['error'];
@ -29,7 +30,7 @@ function getErrorTemplateHierarchy(statusCode) {
templateList.unshift('error-' + errorCode);
return templateList;
}
};
/**
* ## Get Channel Template Hierarchy
@ -43,7 +44,7 @@ function getErrorTemplateHierarchy(statusCode) {
* @param {Object} channelOpts
* @returns {String[]}
*/
function getChannelTemplateHierarchy(channelOpts) {
_private.getChannelTemplateHierarchy = function getChannelTemplateHierarchy(channelOpts) {
var templateList = ['index'];
if (channelOpts.name && channelOpts.name !== 'index') {
@ -59,7 +60,7 @@ function getChannelTemplateHierarchy(channelOpts) {
}
return templateList;
}
};
/**
* ## Get Entry Template Hierarchy
@ -72,7 +73,7 @@ function getChannelTemplateHierarchy(channelOpts) {
* @param {Object} postObject
* @returns {String[]}
*/
function getEntryTemplateHierarchy(postObject) {
_private.getEntryTemplateHierarchy = function getEntryTemplateHierarchy(postObject) {
var templateList = ['post'],
slugTemplate = 'post-' + postObject.slug;
@ -88,7 +89,7 @@ function getEntryTemplateHierarchy(postObject) {
templateList.unshift(slugTemplate);
return templateList;
}
};
/**
* ## Pick Template
@ -99,7 +100,7 @@ function getEntryTemplateHierarchy(postObject) {
* @param {Array|String} templateList
* @param {String} fallback - a fallback template
*/
function pickTemplate(templateList, fallback) {
_private.pickTemplate = function pickTemplate(templateList, fallback) {
var template;
if (!_.isArray(templateList)) {
@ -119,29 +120,49 @@ function pickTemplate(templateList, fallback) {
}
return template;
}
function getTemplateForEntry(postObject) {
var templateList = getEntryTemplateHierarchy(postObject),
fallback = templateList[templateList.length - 1];
return pickTemplate(templateList, fallback);
}
function getTemplateForChannel(channelOpts) {
var templateList = getChannelTemplateHierarchy(channelOpts),
fallback = templateList[templateList.length - 1];
return pickTemplate(templateList, fallback);
}
function getTemplateForError(statusCode) {
var templateList = getErrorTemplateHierarchy(statusCode),
fallback = path.resolve(config.get('paths').defaultViews, 'error.hbs');
return pickTemplate(templateList, fallback);
}
module.exports = {
channel: getTemplateForChannel,
entry: getTemplateForEntry,
error: getTemplateForError,
pickTemplate: pickTemplate
};
_private.getTemplateForEntry = function getTemplateForEntry(postObject) {
var templateList = _private.getEntryTemplateHierarchy(postObject),
fallback = templateList[templateList.length - 1];
return _private.pickTemplate(templateList, fallback);
};
_private.getTemplateForChannel = function getTemplateForChannel(channelOpts) {
var templateList = _private.getChannelTemplateHierarchy(channelOpts),
fallback = templateList[templateList.length - 1];
return _private.pickTemplate(templateList, fallback);
};
_private.getTemplateForError = function getTemplateForError(statusCode) {
var templateList = _private.getErrorTemplateHierarchy(statusCode),
fallback = path.resolve(config.get('paths').defaultViews, 'error.hbs');
return _private.pickTemplate(templateList, fallback);
};
module.exports.setTemplate = function setTemplate(req, res, data) {
var routeConfig = res._route || {};
if (res._template) {
return;
}
if (req.err) {
res._template = _private.getTemplateForError(res.statusCode);
return;
}
switch (routeConfig.type) {
case 'custom':
res._template = _private.pickTemplate(routeConfig.templateName, routeConfig.defaultTemplate);
break;
case 'channel':
res._template = _private.getTemplateForChannel(res.locals.channel);
break;
case 'entry':
res._template = _private.getTemplateForEntry(data.post);
break;
default:
res._template = 'index';
}
};

View File

@ -14,6 +14,12 @@ module.exports = function previewController(req, res, next) {
include: 'author,tags'
};
// Note: this is super similar to the config middleware used in channels
// @TODO refactor into to something explicit
res._route = {
type: 'entry'
};
api.posts.read(params).then(function then(result) {
// Format data 1
var post = result.posts[0];

View File

@ -71,7 +71,7 @@ _private.JSONErrorRenderer = function JSONErrorRenderer(err, req, res, next) { /
});
};
// @TODO: differenciate properly between rendering errors for theme templates, and other situations
// @TODO: differentiate properly between rendering errors for theme templates, and other situations
_private.HTMLErrorRenderer = function HTMLErrorRender(err, req, res, next) {
// If the error code is explicitly set to STATIC_FILE_NOT_FOUND,
// Skip trying to render an HTML error, and move on to the basic error renderer
@ -83,7 +83,7 @@ _private.HTMLErrorRenderer = function HTMLErrorRender(err, req, res, next) {
// Renderer begin
// Format Data
var templateData = {
var data = {
message: err.message,
// @deprecated
code: err.statusCode,
@ -95,20 +95,21 @@ _private.HTMLErrorRenderer = function HTMLErrorRender(err, req, res, next) {
// We don't do context for errors?!
// Template
res.template = templates.error(err.statusCode);
templates.setTemplate(req, res);
// It can be that something went wrong with the theme or otherwise loading handlebars
// This ensures that no matter what res.render will work here
// @TODO: split the error handler for assets, admin & theme to refactor this away
if (_.isEmpty(req.app.engines)) {
res.template = 'error';
res._template = 'error';
req.app.engine('hbs', _private.createHbsEngine());
req.app.set('view engine', 'hbs');
req.app.set('views', config.get('paths').defaultViews);
}
// @TODO use renderer here?!
// Render Call - featuring an error handler for what happens if rendering fails
res.render(res.template, templateData, function renderResponse(err, html) {
res.render(res._template, data, function renderResponse(err, html) {
if (!err) {
return res.send(html);
}

View File

@ -40,6 +40,8 @@ function rssConfigMiddleware(req, res, next) {
function channelConfigMiddleware(channel) {
return function doChannelConfig(req, res, next) {
res.locals.channel = _.cloneDeep(channel);
// @TODO refactor into to something explicit
res._route = {type: 'channel'};
next();
};
}

View File

@ -9,14 +9,15 @@ var should = require('should'),
sandbox = sinon.sandbox.create();
describe('templates', function () {
var getActiveThemeStub, hasTemplateStub;
var getActiveThemeStub, hasTemplateStub,
_private = templates.__get__('_private');
afterEach(function () {
sandbox.restore();
});
describe('[private] getChannelTemplateHierarchy', function () {
var channelTemplateList = templates.__get__('getChannelTemplateHierarchy');
var channelTemplateList = _private.getChannelTemplateHierarchy;
it('should return just index for empty channelOpts', function () {
channelTemplateList({}).should.eql(['index']);
@ -61,7 +62,7 @@ describe('templates', function () {
});
});
describe('pickTemplate', function () {
describe('[private] pickTemplate', function () {
beforeEach(function () {
hasTemplateStub = sandbox.stub().returns(false);
@ -73,13 +74,13 @@ describe('templates', function () {
it('returns fallback if there is no active_theme', function () {
getActiveThemeStub.returns(undefined);
templates.pickTemplate(['tag-test', 'tag', 'index'], 'fallback').should.eql('fallback');
templates.pickTemplate(['page-my-post', 'page', 'post'], 'fallback').should.eql('fallback');
_private.pickTemplate(['tag-test', 'tag', 'index'], 'fallback').should.eql('fallback');
_private.pickTemplate(['page-my-post', 'page', 'post'], 'fallback').should.eql('fallback');
});
it('returns fallback if active_theme has no templates', function () {
templates.pickTemplate(['tag-test', 'tag', 'index'], 'fallback').should.eql('fallback');
templates.pickTemplate(['page-about', 'page', 'post'], 'fallback').should.eql('fallback');
_private.pickTemplate(['tag-test', 'tag', 'index'], 'fallback').should.eql('fallback');
_private.pickTemplate(['page-about', 'page', 'post'], 'fallback').should.eql('fallback');
});
describe('with many templates', function () {
@ -94,20 +95,20 @@ describe('templates', function () {
});
it('returns first matching template', function () {
templates.pickTemplate(['page-about', 'page', 'post'], 'fallback').should.eql('page-about');
templates.pickTemplate(['page-magic', 'page', 'post'], 'fallback').should.eql('page');
templates.pickTemplate(['page', 'post'], 'fallback').should.eql('page');
_private.pickTemplate(['page-about', 'page', 'post'], 'fallback').should.eql('page-about');
_private.pickTemplate(['page-magic', 'page', 'post'], 'fallback').should.eql('page');
_private.pickTemplate(['page', 'post'], 'fallback').should.eql('page');
});
it('returns correctly if template list is a string', function () {
templates.pickTemplate('amp', 'fallback').should.eql('amp');
templates.pickTemplate('subscribe', 'fallback').should.eql('fallback');
templates.pickTemplate('post', 'fallback').should.eql('post');
_private.pickTemplate('amp', 'fallback').should.eql('amp');
_private.pickTemplate('subscribe', 'fallback').should.eql('fallback');
_private.pickTemplate('post', 'fallback').should.eql('post');
});
});
});
describe('entry', function () {
describe('[private] getTemplateForEntry', function () {
beforeEach(function () {
hasTemplateStub = sandbox.stub().returns(false);
@ -127,7 +128,7 @@ describe('templates', function () {
});
it('post without custom slug template', function () {
var view = templates.entry({
var view = _private.getTemplateForEntry({
page: 0,
slug: 'test-post'
});
@ -137,7 +138,7 @@ describe('templates', function () {
it('post with custom slug template', function () {
hasTemplateStub.withArgs('post-welcome-to-ghost').returns(true);
var view = templates.entry({
var view = _private.getTemplateForEntry({
page: 0,
slug: 'welcome-to-ghost'
});
@ -146,7 +147,7 @@ describe('templates', function () {
});
it('page without custom slug template', function () {
var view = templates.entry({
var view = _private.getTemplateForEntry({
page: 1,
slug: 'contact'
});
@ -155,7 +156,7 @@ describe('templates', function () {
});
it('page with custom slug template', function () {
var view = templates.entry({
var view = _private.getTemplateForEntry({
page: 1,
slug: 'about'
});
@ -166,7 +167,7 @@ describe('templates', function () {
it('post with custom template', function () {
hasTemplateStub.withArgs('custom-about').returns(true);
var view = templates.entry({
var view = _private.getTemplateForEntry({
page: 0,
custom_template: 'custom-about'
});
@ -177,7 +178,7 @@ describe('templates', function () {
it('page with custom template', function () {
hasTemplateStub.withArgs('custom-about').returns(true);
var view = templates.entry({
var view = _private.getTemplateForEntry({
page: 1,
custom_template: 'custom-about'
});
@ -188,7 +189,7 @@ describe('templates', function () {
it('post with custom template configured, but the template is missing', function () {
hasTemplateStub.withArgs('custom-about').returns(false);
var view = templates.entry({
var view = _private.getTemplateForEntry({
page: 0,
custom_template: 'custom-about'
});
@ -199,7 +200,7 @@ describe('templates', function () {
it('page with custom template configured, but the template is missing', function () {
hasTemplateStub.withArgs('custom-about').returns(false);
var view = templates.entry({
var view = _private.getTemplateForEntry({
page: 1,
custom_template: 'custom-about'
});
@ -211,7 +212,7 @@ describe('templates', function () {
hasTemplateStub.withArgs('custom-about').returns(true);
hasTemplateStub.withArgs('post-about').returns(true);
var view = templates.entry({
var view = _private.getTemplateForEntry({
page: 0,
slug: 'about',
custom_template: 'custom-about'
@ -224,7 +225,7 @@ describe('templates', function () {
hasTemplateStub.withArgs('custom-about').returns(false);
hasTemplateStub.withArgs('post-about').returns(false);
var view = templates.entry({
var view = _private.getTemplateForEntry({
page: 0,
slug: 'about',
custom_template: 'custom-about'
@ -237,13 +238,13 @@ describe('templates', function () {
it('will fall back to post even if no index.hbs', function () {
hasTemplateStub.returns(false);
var view = templates.entry({page: 1});
var view = _private.getTemplateForEntry({page: 1});
should.exist(view);
view.should.eql('post');
});
});
describe('channel', function () {
describe('[private] getTemplateForChannel', function () {
beforeEach(function () {
hasTemplateStub = sandbox.stub().returns(false);
@ -259,7 +260,7 @@ describe('templates', function () {
});
it('will return correct view for a tag', function () {
var view = templates.channel({name: 'tag', slugParam: 'development', slugTemplate: true});
var view = _private.getTemplateForChannel({name: 'tag', slugParam: 'development', slugTemplate: true});
should.exist(view);
view.should.eql('index');
});
@ -274,26 +275,26 @@ describe('templates', function () {
});
it('will return correct view for a tag', function () {
var view = templates.channel({name: 'tag', slugParam: 'design', slugTemplate: true});
var view = _private.getTemplateForChannel({name: 'tag', slugParam: 'design', slugTemplate: true});
should.exist(view);
view.should.eql('tag-design');
});
it('will return correct view for a tag', function () {
var view = templates.channel({name: 'tag', slugParam: 'development', slugTemplate: true});
var view = _private.getTemplateForChannel({name: 'tag', slugParam: 'development', slugTemplate: true});
should.exist(view);
view.should.eql('tag');
});
});
it('will fall back to index even if no index.hbs', function () {
var view = templates.channel({name: 'tag', slugParam: 'development', slugTemplate: true});
var view = _private.getTemplateForChannel({name: 'tag', slugParam: 'development', slugTemplate: true});
should.exist(view);
view.should.eql('index');
});
});
describe('error', function () {
describe('[private] getTemplateForError', function () {
beforeEach(function () {
hasTemplateStub = sandbox.stub().returns(false);
@ -305,33 +306,33 @@ describe('templates', function () {
it('will fall back to default if there is no active_theme', function () {
getActiveThemeStub.returns(undefined);
templates.error(500).should.match(/core\/server\/views\/error.hbs$/);
_private.getTemplateForError(500).should.match(/core\/server\/views\/error.hbs$/);
});
it('will fall back to default for all statusCodes with no custom error templates', function () {
templates.error(500).should.match(/core\/server\/views\/error.hbs$/);
templates.error(503).should.match(/core\/server\/views\/error.hbs$/);
templates.error(422).should.match(/core\/server\/views\/error.hbs$/);
templates.error(404).should.match(/core\/server\/views\/error.hbs$/);
_private.getTemplateForError(500).should.match(/core\/server\/views\/error.hbs$/);
_private.getTemplateForError(503).should.match(/core\/server\/views\/error.hbs$/);
_private.getTemplateForError(422).should.match(/core\/server\/views\/error.hbs$/);
_private.getTemplateForError(404).should.match(/core\/server\/views\/error.hbs$/);
});
it('will use custom error.hbs for all statusCodes if there are no other templates', function () {
hasTemplateStub.withArgs('error').returns(true);
templates.error(500).should.eql('error');
templates.error(503).should.eql('error');
templates.error(422).should.eql('error');
templates.error(404).should.eql('error');
_private.getTemplateForError(500).should.eql('error');
_private.getTemplateForError(503).should.eql('error');
_private.getTemplateForError(422).should.eql('error');
_private.getTemplateForError(404).should.eql('error');
});
it('will use more specific error-4xx.hbs for all 4xx statusCodes if available', function () {
hasTemplateStub.withArgs('error').returns(true);
hasTemplateStub.withArgs('error-4xx').returns(true);
templates.error(500).should.eql('error');
templates.error(503).should.eql('error');
templates.error(422).should.eql('error-4xx');
templates.error(404).should.eql('error-4xx');
_private.getTemplateForError(500).should.eql('error');
_private.getTemplateForError(503).should.eql('error');
_private.getTemplateForError(422).should.eql('error-4xx');
_private.getTemplateForError(404).should.eql('error-4xx');
});
it('will use explicit error-404.hbs for 404 statusCode if available', function () {
@ -339,10 +340,10 @@ describe('templates', function () {
hasTemplateStub.withArgs('error-4xx').returns(true);
hasTemplateStub.withArgs('error-404').returns(true);
templates.error(500).should.eql('error');
templates.error(503).should.eql('error');
templates.error(422).should.eql('error-4xx');
templates.error(404).should.eql('error-404');
_private.getTemplateForError(500).should.eql('error');
_private.getTemplateForError(503).should.eql('error');
_private.getTemplateForError(422).should.eql('error-4xx');
_private.getTemplateForError(404).should.eql('error-404');
});
it('cascade works the same for 500 errors', function () {
@ -350,10 +351,10 @@ describe('templates', function () {
hasTemplateStub.withArgs('error-5xx').returns(true);
hasTemplateStub.withArgs('error-503').returns(true);
templates.error(500).should.eql('error-5xx');
templates.error(503).should.eql('error-503');
templates.error(422).should.eql('error');
templates.error(404).should.eql('error');
_private.getTemplateForError(500).should.eql('error-5xx');
_private.getTemplateForError(503).should.eql('error-503');
_private.getTemplateForError(422).should.eql('error');
_private.getTemplateForError(404).should.eql('error');
});
it('cascade works with many specific templates', function () {
@ -363,12 +364,178 @@ describe('templates', function () {
hasTemplateStub.withArgs('error-4xx').returns(true);
hasTemplateStub.withArgs('error-404').returns(true);
templates.error(500).should.eql('error-5xx');
templates.error(503).should.eql('error-503');
templates.error(422).should.eql('error-4xx');
templates.error(404).should.eql('error-404');
templates.error(401).should.eql('error-4xx');
templates.error(501).should.eql('error-5xx');
_private.getTemplateForError(500).should.eql('error-5xx');
_private.getTemplateForError(503).should.eql('error-503');
_private.getTemplateForError(422).should.eql('error-4xx');
_private.getTemplateForError(404).should.eql('error-404');
_private.getTemplateForError(401).should.eql('error-4xx');
_private.getTemplateForError(501).should.eql('error-5xx');
});
});
describe('setTemplate', function () {
var stubs = {}, req, res, data;
beforeEach(function () {
req = {};
res = {
locals: {}
};
data = {};
stubs.pickTemplate = sandbox.stub(_private, 'pickTemplate').returns('testFromPickTemplate');
stubs.getTemplateForEntry = sandbox.stub(_private, 'getTemplateForEntry').returns('testFromEntry');
stubs.getTemplateForChannel = sandbox.stub(_private, 'getTemplateForChannel').returns('testFromChannel');
stubs.getTemplateForError = sandbox.stub(_private, 'getTemplateForError').returns('testFromError');
});
it('does nothing if template is already set', function () {
// Pre-set template
res._template = 'thing';
// Call setTemplate
templates.setTemplate(req, res, data);
// It hasn't changed
res._template.should.eql('thing');
// And nothing got called
stubs.pickTemplate.called.should.be.false();
stubs.getTemplateForEntry.called.should.be.false();
stubs.getTemplateForChannel.called.should.be.false();
stubs.getTemplateForError.called.should.be.false();
});
it('defaults to index', function () {
// No route or template config here!!!
// Call setTemplate
templates.setTemplate(req, res, data);
// It should be index
res._template.should.eql('index');
// And nothing got called
stubs.pickTemplate.called.should.be.false();
stubs.getTemplateForEntry.called.should.be.false();
stubs.getTemplateForChannel.called.should.be.false();
stubs.getTemplateForError.called.should.be.false();
});
it('calls pickTemplate for custom routes', function () {
res._route = {
type: 'custom',
templateName: 'test',
defaultTemplate: 'path/to/local/test.hbs'
};
// Call setTemplate
templates.setTemplate(req, res, data);
// should be testFromPickTemplate
res._template.should.eql('testFromPickTemplate');
// Only pickTemplate got called
stubs.pickTemplate.called.should.be.true();
stubs.getTemplateForEntry.called.should.be.false();
stubs.getTemplateForChannel.called.should.be.false();
stubs.getTemplateForError.called.should.be.false();
stubs.pickTemplate.calledWith('test', 'path/to/local/test.hbs').should.be.true();
});
it('calls pickTemplate for custom routes', function () {
res._route = {
type: 'custom',
templateName: 'test',
defaultTemplate: 'path/to/local/test.hbs'
};
// Call setTemplate
templates.setTemplate(req, res, data);
// should be testFromPickTemplate
res._template.should.eql('testFromPickTemplate');
// Only pickTemplate got called
stubs.pickTemplate.called.should.be.true();
stubs.getTemplateForEntry.called.should.be.false();
stubs.getTemplateForChannel.called.should.be.false();
stubs.getTemplateForError.called.should.be.false();
stubs.pickTemplate.calledWith('test', 'path/to/local/test.hbs').should.be.true();
});
it('calls getTemplateForEntry for entry routes', function () {
res._route = {
type: 'entry'
};
// Requires a post to be set
data = {post: {slug: 'test'}};
// Call setTemplate
templates.setTemplate(req, res, data);
// should be getTemplateForEntry
res._template.should.eql('testFromEntry');
// Only pickTemplate got called
stubs.pickTemplate.called.should.be.false();
stubs.getTemplateForEntry.called.should.be.true();
stubs.getTemplateForChannel.called.should.be.false();
stubs.getTemplateForError.called.should.be.false();
stubs.getTemplateForEntry.calledWith({slug: 'test'}).should.be.true();
});
it('calls getTemplateForChannel for channel routes', function () {
res._route = {
type: 'channel'
};
res.locals.channel = {testChannel: 'test'};
// Call setTemplate
templates.setTemplate(req, res, data);
// should be testFromChannel
res._template.should.eql('testFromChannel');
// Only pickTemplate got called
stubs.pickTemplate.called.should.be.false();
stubs.getTemplateForEntry.called.should.be.false();
stubs.getTemplateForChannel.called.should.be.true();
stubs.getTemplateForError.called.should.be.false();
stubs.getTemplateForChannel.calledWith({testChannel: 'test'}).should.be.true();
});
it('calls getTemplateForError if there is an error', function () {
// Make the config look like a custom route
res._route = {
type: 'custom',
templateName: 'test',
defaultTemplate: 'path/to/local/test.hbs'
};
// Setup an error
res.statusCode = 404;
req.err = new Error();
// Call setTemplate
templates.setTemplate(req, res, data);
// should be testFromError
res._template.should.eql('testFromError');
// Only pickTemplate got called
stubs.pickTemplate.called.should.be.false();
stubs.getTemplateForEntry.called.should.be.false();
stubs.getTemplateForChannel.called.should.be.false();
stubs.getTemplateForError.called.should.be.true();
stubs.getTemplateForError.calledWith(404).should.be.true();
});
});
});