0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Added any & all matching to {{#has}} helper

closes #8901

- Adds support for

```
{{#has any="twitter, facebook, website"}}
{{#has any="author.facebook, author.twitter,author.website"}}
{{#has any="@blog.facebook, @blog.twitter, @labs.subscribers"}}
{{#has all="@labs.subscribers,@labs.publicAPI"}}
```
This commit is contained in:
Hannah Wolfe 2017-08-17 12:22:29 +01:00 committed by Kevin Ansfield
parent 746ac2db4d
commit ff15dc1667
2 changed files with 299 additions and 2 deletions

View file

@ -7,7 +7,7 @@ var proxy = require('./proxy'),
_ = require('lodash'), _ = require('lodash'),
logging = proxy.logging, logging = proxy.logging,
i18n = proxy.i18n, i18n = proxy.i18n,
validAttrs = ['tag', 'author', 'slug', 'id', 'number', 'index']; validAttrs = ['tag', 'author', 'slug', 'id', 'number', 'index', 'any', 'all'];
function evaluateTagList(expr, tags) { function evaluateTagList(expr, tags) {
return expr.split(',').map(function (v) { return expr.split(',').map(function (v) {
@ -49,6 +49,25 @@ function evaluateStringMatch(expr, str, ci) {
return expr === str; return expr === str;
} }
/**
*
* @param {String} type - either some or every - the lodash function to use
* @param {String} expr - the attribute value passed into {{#has}}
* @param {Object} obj - "this" context from the helper
* @param {Object} data - global params
*/
function evaluateList(type, expr, obj, data) {
return expr.split(',').map(function (prop) {
return prop.trim().toLocaleLowerCase();
})[type](function (prop) {
if (prop.match(/^@/)) {
return _.has(data, prop.replace(/@/, '')) && !_.isEmpty(_.get(data, prop.replace(/@/, '')));
} else {
return _.has(obj, prop) && !_.isEmpty(_.get(obj, prop));
}
});
}
module.exports = function has(options) { module.exports = function has(options) {
options = options || {}; options = options || {};
options.hash = options.hash || {}; options.hash = options.hash || {};
@ -56,13 +75,16 @@ module.exports = function has(options) {
var self = this, var self = this,
attrs = _.pick(options.hash, validAttrs), attrs = _.pick(options.hash, validAttrs),
data = _.pick(options.data, ['blog', 'config', 'labs']),
checks = { checks = {
tag: function () { return attrs.tag && evaluateTagList(attrs.tag, _.map(self.tags, 'name')) || false; }, tag: function () { return attrs.tag && evaluateTagList(attrs.tag, _.map(self.tags, 'name')) || false; },
author: function () { return attrs.author && evaluateAuthorList(attrs.author, _.get(self, 'author.name')) || false; }, author: function () { return attrs.author && evaluateAuthorList(attrs.author, _.get(self, 'author.name')) || false; },
number: function () { return attrs.number && evaluateIntegerMatch(attrs.number, options.data.number) || false; }, number: function () { return attrs.number && evaluateIntegerMatch(attrs.number, options.data.number) || false; },
index: function () { return attrs.index && evaluateIntegerMatch(attrs.index, options.data.index) || false; }, index: function () { return attrs.index && evaluateIntegerMatch(attrs.index, options.data.index) || false; },
slug: function () { return attrs.slug && evaluateStringMatch(attrs.slug, self.slug, true) || false; }, slug: function () { return attrs.slug && evaluateStringMatch(attrs.slug, self.slug, true) || false; },
id: function () { return attrs.id && evaluateStringMatch(attrs.id, self.id, true) || false; } id: function () { return attrs.id && evaluateStringMatch(attrs.id, self.id, true) || false; },
any: function () { return attrs.any && evaluateList('some', attrs.any, self, data) || false; },
all: function () { return attrs.all && evaluateList('every', attrs.all, self, data) || false; }
}, },
result; result;

View file

@ -249,6 +249,24 @@ describe('{{#has}} helper', function () {
inverse.called.should.be.true(); inverse.called.should.be.true();
inverse.callCount.should.eql(6); inverse.callCount.should.eql(6);
}); });
it('fails gracefully if there is no number property', function () {
handlebarsOptions.data = {};
callHasHelper(thisCtx, {number: 'nth:3'});
fn.called.should.be.false();
inverse.called.should.be.true();
});
it('fails gracefully if there is no data property', function () {
handlebarsOptions.data = null;
callHasHelper(thisCtx, {number: 'nth:3'});
fn.called.should.be.false();
inverse.called.should.be.true();
});
}); });
describe('index match (0-based index)', function () { describe('index match (0-based index)', function () {
@ -343,6 +361,15 @@ describe('{{#has}} helper', function () {
inverse.called.should.be.true(); inverse.called.should.be.true();
inverse.callCount.should.eql(5); inverse.callCount.should.eql(5);
}); });
it('fails gracefully if there is no index property', function () {
handlebarsOptions.data = {};
callHasHelper(thisCtx, {index: 'nth:3'});
fn.called.should.be.false();
inverse.called.should.be.true();
});
}); });
describe('slug match', function () { describe('slug match', function () {
@ -408,4 +435,252 @@ describe('{{#has}} helper', function () {
inverse.called.should.be.true(); inverse.called.should.be.true();
}); });
}); });
describe('any match', function () {
it('matches on a single property (pass)', function () {
thisCtx = {
twitter: 'foo',
facebook: '',
website: null
};
// {{#has any="twitter"}}
callHasHelper(thisCtx, {any: 'twitter'});
fn.called.should.be.true();
inverse.called.should.be.false();
});
it('matches on a single property (fail)', function () {
thisCtx = {
twitter: 'foo',
facebook: '',
website: null
};
// {{#has any="facebook"}}
callHasHelper(thisCtx, {any: 'facebook'});
fn.called.should.be.false();
inverse.called.should.be.true();
});
it('matches on multiple properties (pass)', function () {
thisCtx = {
twitter: 'foo',
facebook: '',
website: null
};
// {{#has any="twitter, facebook,website"}}
callHasHelper(thisCtx, {any: 'twitter, facebook,website'});
fn.called.should.be.true();
inverse.called.should.be.false();
});
it('matches on multiple properties (fail)', function () {
thisCtx = {
twitter: 'foo',
facebook: '',
website: null
};
// {{#has any="facebook,website, foo"}}
callHasHelper(thisCtx, {any: 'facebook,website, foo'});
fn.called.should.be.false();
inverse.called.should.be.true();
});
it('matches on global properties (pass)', function () {
thisCtx = {};
handlebarsOptions.data = {
blog: {
twitter: 'foo',
facebook: '',
website: null
}
};
// {{#has any="@blog.twitter, @blog.facebook,@blog.website"}}
callHasHelper(thisCtx, {any: '@blog.twitter, @blog.facebook,@blog.website'});
fn.called.should.be.true();
inverse.called.should.be.false();
});
it('matches on global properties (fail)', function () {
thisCtx = {};
handlebarsOptions.data = {
blog: {
twitter: 'foo',
facebook: '',
website: null
}
};
// {{#has any="@blog.facebook,@blog.website, @blog.foo"}}
callHasHelper(thisCtx, {any: '@blog.facebook,@blog.website, @not.foo'});
fn.called.should.be.false();
inverse.called.should.be.true();
});
it('matches on path expressions (pass)', function () {
thisCtx = {
author: {
twitter: 'foo',
facebook: '',
website: null
}
};
// {{#has any="author.twitter, author.facebook,author.website"}}
callHasHelper(thisCtx, {any: 'author.twitter, author.facebook,author.website'});
fn.called.should.be.true();
inverse.called.should.be.false();
});
it('matches on path expressions (fail)', function () {
thisCtx = {
author: {
twitter: 'foo',
facebook: '',
website: null
}
};
// {{#has any="author.facebook,author.website, author.foo"}}
callHasHelper(thisCtx, {any: 'author.facebook,author.website, fred.foo'});
fn.called.should.be.false();
inverse.called.should.be.true();
});
});
describe('all match', function () {
it('matches on a single property (pass)', function () {
thisCtx = {
twitter: 'foo',
facebook: 'bar',
website: null
};
// {{#has all="twitter"}}
callHasHelper(thisCtx, {all: 'twitter'});
fn.called.should.be.true();
inverse.called.should.be.false();
});
it('matches on a single property (fail)', function () {
thisCtx = {
twitter: 'foo',
facebook: 'bar',
website: null
};
// {{#has all="website"}}
callHasHelper(thisCtx, {all: 'website'});
fn.called.should.be.false();
inverse.called.should.be.true();
});
it('matches on multiple properties (pass)', function () {
thisCtx = {
twitter: 'foo',
facebook: 'bar',
website: null
};
// {{#has all="twitter, facebook"}}
callHasHelper(thisCtx, {all: 'twitter, facebook'});
fn.called.should.be.true();
inverse.called.should.be.false();
});
it('matches on multiple properties (fail)', function () {
thisCtx = {
twitter: 'foo',
facebook: 'bar',
website: null
};
// {{#has all="facebook,website, foo"}}
callHasHelper(thisCtx, {all: 'facebook,website, foo'});
fn.called.should.be.false();
inverse.called.should.be.true();
});
it('matches on global properties (pass)', function () {
thisCtx = {};
handlebarsOptions.data = {
blog: {
twitter: 'foo',
facebook: 'bar',
website: null
}
};
// {{#has all="@blog.twitter, @blog.facebook"}}
callHasHelper(thisCtx, {all: '@blog.twitter, @blog.facebook'});
fn.called.should.be.true();
inverse.called.should.be.false();
});
it('matches on global properties (fail)', function () {
thisCtx = {};
handlebarsOptions.data = {
blog: {
twitter: 'foo',
facebook: 'bar',
website: null
}
};
// {{#has all="@blog.facebook,@blog.website, @blog.foo"}}
callHasHelper(thisCtx, {all: '@blog.facebook,@blog.website, @not.foo'});
fn.called.should.be.false();
inverse.called.should.be.true();
});
it('matches on path expressions (pass)', function () {
thisCtx = {
author: {
twitter: 'foo',
facebook: 'bar',
website: null
}
};
// {{#has all="author.twitter, author.facebook"}}
callHasHelper(thisCtx, {all: 'author.twitter, author.facebook'});
fn.called.should.be.true();
inverse.called.should.be.false();
});
it('matches on path expressions (fail)', function () {
thisCtx = {
author: {
twitter: 'foo',
facebook: 'bar',
website: null
}
};
// {{#has all="author.facebook,author.website, author.foo"}}
callHasHelper(thisCtx, {all: 'author.facebook,author.website, fred.foo'});
fn.called.should.be.false();
inverse.called.should.be.true();
});
});
}); });