🐛 Added error handling to prev/next post helpers

refs #8703

- On API error, call inverse with a data error, the same as the get helper
This commit is contained in:
Hannah Wolfe 2017-08-27 18:00:32 +02:00 committed by Katharina Irrgang
parent b6b2930d15
commit c49dba12a0
4 changed files with 160 additions and 71 deletions

View File

@ -103,7 +103,7 @@ get = function get(resource, options) {
apiMethod; apiMethod;
if (!options.fn) { if (!options.fn) {
data.error = i18n.t('warnings.helpers.get.mustBeCalledAsBlock'); data.error = i18n.t('warnings.helpers.get.mustBeCalledAsBlock', {helperName: 'get'});
logging.warn(data.error); logging.warn(data.error);
return Promise.resolve(); return Promise.resolve();
} }
@ -136,6 +136,7 @@ get = function get(resource, options) {
blockParams: blockParams blockParams: blockParams
}); });
}).catch(function error(err) { }).catch(function error(err) {
logging.error(err);
data.error = err.message; data.error = err.message;
return options.inverse(self, {data: data}); return options.inverse(self, {data: data});
}); });

View File

@ -6,23 +6,36 @@
var proxy = require('./proxy'), var proxy = require('./proxy'),
Promise = require('bluebird'), Promise = require('bluebird'),
logging = proxy.logging,
i18n = proxy.i18n,
createFrame = proxy.hbs.handlebars.createFrame,
api = proxy.api, api = proxy.api,
isPost = proxy.checks.isPost, isPost = proxy.checks.isPost,
fetch; fetch;
fetch = function fetch(apiOptions, options) { fetch = function fetch(apiOptions, options, data) {
return api.posts.read(apiOptions).then(function (result) { var self = this;
var related = result.posts[0];
if (related.previous) { return api.posts
return options.fn(related.previous); .read(apiOptions)
} else if (related.next) { .then(function handleSuccess(result) {
return options.fn(related.next); var related = result.posts[0];
} else {
return options.inverse(this); if (related.previous) {
} return options.fn(related.previous, {data: data});
}); } else if (related.next) {
return options.fn(related.next, {data: data});
} else {
return options.inverse(self, {data: data});
}
})
.catch(function handleError(err) {
logging.error(err);
data.error = err.message;
return options.inverse(self, {data: data});
});
}; };
// If prevNext method is called without valid post data then we must return a promise, if there is valid post data // If prevNext method is called without valid post data then we must return a promise, if there is valid post data
@ -31,14 +44,21 @@ fetch = function fetch(apiOptions, options) {
module.exports = function prevNext(options) { module.exports = function prevNext(options) {
options = options || {}; options = options || {};
var apiOptions = { var data = createFrame(options.data),
include: options.name === 'prev_post' ? 'previous,previous.author,previous.tags' : 'next,next.author,next.tags' apiOptions = {
}; include: options.name === 'prev_post' ? 'previous,previous.author,previous.tags' : 'next,next.author,next.tags'
};
if (!options.fn) {
data.error = i18n.t('warnings.helpers.mustBeCalledAsBlock', {helperName: options.name});
logging.warn(data.error);
return Promise.resolve();
}
if (isPost(this) && this.status === 'published') { if (isPost(this) && this.status === 'published') {
apiOptions.slug = this.slug; apiOptions.slug = this.slug;
return fetch(apiOptions, options); return fetch(apiOptions, options, data);
} else { } else {
return Promise.resolve(options.inverse(this)); return Promise.resolve(options.inverse(this, {data: data}));
} }
}; };

View File

@ -487,11 +487,11 @@
"helperNotAvailable": "The \\{\\{{helperName}\\}\\} helper is not available.", "helperNotAvailable": "The \\{\\{{helperName}\\}\\} helper is not available.",
"flagMustBeEnabled": "The {flagName} flag must be enabled in labs if you wish to use the \\{\\{{helperName}\\}\\} helper.", "flagMustBeEnabled": "The {flagName} flag must be enabled in labs if you wish to use the \\{\\{{helperName}\\}\\} helper.",
"seeLink": "See {url}", "seeLink": "See {url}",
"mustBeCalledAsBlock": "The \\{\\{{helperName}\\}\\} helper must be called as a block. E.g. \\{\\{#{helperName}\\}\\} \\{\\{/{helperName}\\}\\}",
"foreach": { "foreach": {
"iteratorNeeded": "Need to pass an iterator to #foreach" "iteratorNeeded": "Need to pass an iterator to #foreach"
}, },
"get": { "get": {
"mustBeCalledAsBlock": "Get helper must be called as a block",
"invalidResource": "Invalid resource given to get helper" "invalidResource": "Invalid resource given to get helper"
}, },
"has": { "has": {

View File

@ -5,6 +5,7 @@ var should = require('should'), // jshint ignore:line
// Stuff we are testing // Stuff we are testing
helpers = require('../../../server/helpers'), helpers = require('../../../server/helpers'),
api = require('../../../server/api'), api = require('../../../server/api'),
errors = require('../../../server/errors'),
sandbox = sinon.sandbox.create(); sandbox = sinon.sandbox.create();
@ -31,25 +32,29 @@ describe('{{next_post}} helper', function () {
inverse = sinon.spy(), inverse = sinon.spy(),
optionsData = {name: 'next_post', fn: fn, inverse: inverse}; optionsData = {name: 'next_post', fn: fn, inverse: inverse};
helpers.prev_post.call({ helpers.prev_post
html: 'content', .call({
status: 'published', html: 'content',
mobiledoc: markdownToMobiledoc('ff'), status: 'published',
title: 'post2', mobiledoc: markdownToMobiledoc('ff'),
slug: 'current', title: 'post2',
created_at: new Date(0), slug: 'current',
url: '/current/' created_at: new Date(0),
}, optionsData).then(function () { url: '/current/'
fn.calledOnce.should.be.true(); }, optionsData)
inverse.calledOnce.should.be.false(); .then(function () {
fn.calledOnce.should.be.true();
inverse.calledOnce.should.be.false();
readPostStub.calledOnce.should.be.true(); console.log(fn.firstCall.args);
readPostStub.firstCall.args[0].include.should.eql('next,next.author,next.tags'); fn.firstCall.args.should.have.lengthOf(2);
done(); fn.firstCall.args[0].should.have.properties('slug', 'title');
}).catch(function (err) { fn.firstCall.args[1].should.be.an.Object().and.have.property('data');
console.log('err ', err); readPostStub.calledOnce.should.be.true();
done(err); readPostStub.firstCall.args[0].include.should.eql('next,next.author,next.tags');
}); done();
})
.catch(done);
}); });
}); });
@ -67,20 +72,27 @@ describe('{{next_post}} helper', function () {
inverse = sinon.spy(), inverse = sinon.spy(),
optionsData = {name: 'next_post', fn: fn, inverse: inverse}; optionsData = {name: 'next_post', fn: fn, inverse: inverse};
helpers.prev_post.call({ helpers.prev_post
html: 'content', .call({
mobiledoc: markdownToMobiledoc('ff'), html: 'content',
title: 'post2', mobiledoc: markdownToMobiledoc('ff'),
slug: 'current', title: 'post2',
created_at: new Date(0), slug: 'current',
url: '/current/' created_at: new Date(0),
}, optionsData).then(function () { url: '/current/'
fn.called.should.be.false(); }, optionsData)
inverse.called.should.be.true(); .then(function () {
done(); fn.called.should.be.false();
}).catch(function (err) { inverse.called.should.be.true();
done(err);
}); console.log(inverse.firstCall.args);
inverse.firstCall.args.should.have.lengthOf(2);
inverse.firstCall.args[0].should.have.properties('slug', 'title');
inverse.firstCall.args[1].should.be.an.Object().and.have.property('data');
done();
})
.catch(done);
}); });
}); });
@ -98,14 +110,15 @@ describe('{{next_post}} helper', function () {
inverse = sinon.spy(), inverse = sinon.spy(),
optionsData = {name: 'next_post', fn: fn, inverse: inverse}; optionsData = {name: 'next_post', fn: fn, inverse: inverse};
helpers.prev_post.call({}, optionsData).then(function () { helpers.prev_post
fn.called.should.be.false(); .call({}, optionsData)
inverse.called.should.be.true(); .then(function () {
readPostStub.called.should.be.false(); fn.called.should.be.false();
done(); inverse.called.should.be.true();
}).catch(function (err) { readPostStub.called.should.be.false();
done(err); done();
}); })
.catch(done);
}); });
}); });
@ -125,22 +138,77 @@ describe('{{next_post}} helper', function () {
inverse = sinon.spy(), inverse = sinon.spy(),
optionsData = {name: 'next_post', fn: fn, inverse: inverse}; optionsData = {name: 'next_post', fn: fn, inverse: inverse};
helpers.prev_post.call({ helpers.prev_post
html: 'content', .call({
status: 'published', html: 'content',
mobiledoc: markdownToMobiledoc('ff'), status: 'draft',
title: 'post2', mobiledoc: markdownToMobiledoc('ff'),
slug: 'current', title: 'post2',
created_at: new Date(0), slug: 'current',
url: '/current/' created_at: new Date(0),
}, optionsData) url: '/current/'
}, optionsData)
.then(function () { .then(function () {
fn.called.should.be.true(); fn.called.should.be.false();
inverse.called.should.be.false(); inverse.called.should.be.true();
done(); done();
}).catch(function (err) { })
done(err); .catch(done);
});
});
describe('general error handling', function () {
beforeEach(function () {
readPostStub = sandbox.stub(api.posts, 'read', function () {
return Promise.reject(new errors.NotFoundError({message: 'Something wasn\'t found'}));
}); });
}); });
it('should handle error from the API', function (done) {
var fn = sinon.spy(),
inverse = sinon.spy(),
optionsData = {name: 'next_post', fn: fn, inverse: inverse};
helpers.prev_post
.call({
html: 'content',
status: 'published',
mobiledoc: markdownToMobiledoc('ff'),
title: 'post2',
slug: 'current',
created_at: new Date(0),
url: '/current/'
}, optionsData)
.then(function () {
fn.called.should.be.false();
inverse.calledOnce.should.be.true();
inverse.firstCall.args[1].should.be.an.Object().and.have.property('data');
inverse.firstCall.args[1].data.should.be.an.Object().and.have.property('error');
inverse.firstCall.args[1].data.error.should.match(/^Something wasn't found/);
done();
})
.catch(done);
});
it('should show warning for call without any options', function (done) {
var fn = sinon.spy(),
inverse = sinon.spy(),
optionsData = {name: 'next_post'};
helpers.prev_post
.call(
{},
optionsData
)
.then(function () {
fn.called.should.be.false();
inverse.called.should.be.false();
done();
})
.catch(done);
});
}); });
}); });