🐛 Fixed async helpers nested in template helpers

closes #10643

The async resolver in express-hbs relies on storing the state of the
promises on the `this` value inside of a helper, which is always set to
the `context`. This patch updates our helpers which render templates, to
use `this` as the context when rendering their templates.
This commit is contained in:
Fabien O'Carroll 2019-05-20 12:31:56 +02:00
parent 0e2ce29468
commit 15585971e2
3 changed files with 35 additions and 31 deletions

View File

@ -75,8 +75,11 @@ module.exports = function navigation(options) {
return out;
});
const context = _.merge({}, this, options.hash, {navigation: output});
// CASE: The navigation helper should have access to the navigation items at the top level.
this.navigation = output;
// CASE: The navigation helper will forward attributes passed to it.
_.merge(this, options.hash);
const data = createFrame(options.data);
return templates.execute('navigation', context, {data});
return templates.execute('navigation', this, {data});
};

View File

@ -41,10 +41,14 @@ pagination = function (options) {
!_.isNumber(this.pagination.total) || !_.isNumber(this.pagination.limit)) {
throw new errors.IncorrectUsageError({message: i18n.t('warnings.helpers.pagination.valuesMustBeNumeric')});
}
const context = _.merge({}, this, options.hash, this.pagination);
// CASE: The pagination helper should have access to the pagination properties at the top level.
_.merge(this, this.pagination);
// CASE: The pagination helper will forward attributes passed to it.
_.merge(this, options.hash);
const data = createFrame(options.data);
return templates.execute('pagination', context, {data});
return templates.execute('pagination', this, {data});
};
module.exports = pagination;

View File

@ -1,18 +1,14 @@
var should = require('should'),
hbs = require('../../../frontend/services/themes/engine'),
const should = require('should');
const hbs = require('../../../frontend/services/themes/engine');
const configUtils = require('../../utils/configUtils');
const path = require('path');
const helpers = require('../../../frontend/helpers');
configUtils = require('../../utils/configUtils'),
path = require('path'),
helpers = require('../../../frontend/helpers');
const runHelper = data => helpers.navigation.call({}, data);
const runHelperThunk = data => () => runHelper(data);
describe('{{navigation}} helper', function () {
var runHelper = function (data) {
return function () {
helpers.navigation(data);
};
},
optionsData;
let optionsData;
before(function (done) {
hbs.express4({
@ -26,6 +22,7 @@ describe('{{navigation}} helper', function () {
// The navigation partial expects this helper
// @TODO: change to register with Ghost's own registration tools
hbs.registerHelper('url', helpers.url);
hbs.registerHelper('foreach', helpers.foreach);
});
beforeEach(function () {
@ -44,24 +41,24 @@ describe('{{navigation}} helper', function () {
it('should throw errors on invalid data', function () {
// Test 1: navigation = string
optionsData.data.blog.navigation = 'not an object';
runHelper(optionsData).should.throwError('navigation data is not an object or is a function');
runHelperThunk(optionsData).should.throwError('navigation data is not an object or is a function');
// Test 2: navigation = function
optionsData.data.blog.navigation = function () {
};
runHelper(optionsData).should.throwError('navigation data is not an object or is a function');
runHelperThunk(optionsData).should.throwError('navigation data is not an object or is a function');
// Test 3: invalid label
optionsData.data.blog.navigation = [{label: 1, url: 'bar'}];
runHelper(optionsData).should.throwError('Invalid value, Url and Label must be strings');
runHelperThunk(optionsData).should.throwError('Invalid value, Url and Label must be strings');
// Test 4: invalid url
optionsData.data.blog.navigation = [{label: 'foo', url: 1}];
runHelper(optionsData).should.throwError('Invalid value, Url and Label must be strings');
runHelperThunk(optionsData).should.throwError('Invalid value, Url and Label must be strings');
});
it('can render empty nav', function () {
var rendered = helpers.navigation(optionsData);
var rendered = runHelper(optionsData);
should.exist(rendered);
rendered.string.should.be.equal('');
@ -73,7 +70,7 @@ describe('{{navigation}} helper', function () {
delete optionsData.data.root.relativeUrl;
optionsData.data.blog.navigation = [singleItem];
rendered = helpers.navigation(optionsData);
rendered = runHelper(optionsData);
rendered.string.should.containEql('li');
rendered.string.should.containEql('nav-foo');
rendered.string.should.containEql('/foo');
@ -85,7 +82,7 @@ describe('{{navigation}} helper', function () {
rendered;
optionsData.data.blog.navigation = [singleItem];
rendered = helpers.navigation(optionsData);
rendered = runHelper(optionsData);
should.exist(rendered);
rendered.string.should.containEql('li');
@ -101,7 +98,7 @@ describe('{{navigation}} helper', function () {
rendered;
optionsData.data.blog.navigation = [firstItem, secondItem];
rendered = helpers.navigation(optionsData);
rendered = runHelper(optionsData);
should.exist(rendered);
rendered.string.should.containEql('nav-foo');
@ -117,7 +114,7 @@ describe('{{navigation}} helper', function () {
optionsData.data.blog.navigation = [firstItem, secondItem];
optionsData.data.root.relativeUrl = '/foo';
rendered = helpers.navigation(optionsData);
rendered = runHelper(optionsData);
should.exist(rendered);
rendered.string.should.containEql('nav-foo');
@ -133,7 +130,7 @@ describe('{{navigation}} helper', function () {
optionsData.data.blog.navigation = [firstItem, secondItem];
optionsData.data.root.relativeUrl = '/foo/';
rendered = helpers.navigation(optionsData);
rendered = runHelper(optionsData);
should.exist(rendered);
rendered.string.should.containEql('nav-foo');
@ -147,7 +144,7 @@ describe('{{navigation}} helper', function () {
rendered;
optionsData.data.blog.navigation = [firstItem];
rendered = helpers.navigation(optionsData);
rendered = runHelper(optionsData);
should.exist(rendered);
rendered.string.should.not.containEql('=');
@ -160,7 +157,7 @@ describe('{{navigation}} helper', function () {
rendered;
optionsData.data.blog.navigation = [firstItem];
rendered = helpers.navigation(optionsData);
rendered = runHelper(optionsData);
should.exist(rendered);
rendered.string.should.containEql('foo=space%20bar');
@ -173,7 +170,7 @@ describe('{{navigation}} helper', function () {
rendered;
optionsData.data.blog.navigation = [firstItem];
rendered = helpers.navigation(optionsData);
rendered = runHelper(optionsData);
should.exist(rendered);
rendered.string.should.not.containEql('foo=space%2520bar');
@ -213,7 +210,7 @@ describe('{{navigation}} helper with custom template', function () {
// Set @blog.title
optionsData.data.blog.title = 'Chaos is a ladder.';
rendered = helpers.navigation(optionsData);
rendered = runHelper(optionsData);
should.exist(rendered);
rendered.string.should.containEql('Chaos is a ladder');
@ -229,7 +226,7 @@ describe('{{navigation}} helper with custom template', function () {
// Simulate {{navigation isHeader=true}}
optionsData.hash = {isHeader: true};
rendered = helpers.navigation(optionsData);
rendered = runHelper(optionsData);
should.exist(rendered);
rendered.string.should.not.containEql('Chaos is a ladder');