mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
✨ Added number & index matching to {{#has}} helper (#8902)
refs #8901 - Adds support for: ``` {{#has number="3"}} // A single number {{#has number="3, 6, 9"}} // list the numbers you want to match against {{#has number="nth:3"}} // special syntax for nth item ``` And ``` {{#has index="3"}} // A single number {{#has index="3, 6, 9"}} // list the numbers you want to match against {{#has index="nth:3"}} // special syntax for nth item ```
This commit is contained in:
parent
d064eda229
commit
6ee9bb491c
2 changed files with 336 additions and 152 deletions
|
@ -8,17 +8,6 @@ var proxy = require('./proxy'),
|
|||
logging = proxy.logging,
|
||||
i18n = proxy.i18n;
|
||||
|
||||
module.exports = function has(options) {
|
||||
options = options || {};
|
||||
options.hash = options.hash || {};
|
||||
|
||||
var tags = _.map(this.tags, 'name'),
|
||||
author = this.author ? this.author.name : null,
|
||||
tagList = options.hash.tag || false,
|
||||
authorList = options.hash.author || false,
|
||||
tagsOk,
|
||||
authorOk;
|
||||
|
||||
function evaluateTagList(expr, tags) {
|
||||
return expr.split(',').map(function (v) {
|
||||
return v.trim();
|
||||
|
@ -40,15 +29,45 @@ module.exports = function has(options) {
|
|||
return _.includes(authorList, author.toLocaleLowerCase());
|
||||
}
|
||||
|
||||
if (!tagList && !authorList) {
|
||||
function evaluateIntegerMatch(expr, integer) {
|
||||
var nthMatch = expr.match(/^nth:(\d+)/);
|
||||
if (nthMatch) {
|
||||
return integer % parseInt(nthMatch[1], 10) === 0;
|
||||
}
|
||||
|
||||
return expr.split(',').reduce(function (bool, _integer) {
|
||||
return bool || parseInt(_integer, 10) === integer;
|
||||
}, false);
|
||||
}
|
||||
|
||||
module.exports = function has(options) {
|
||||
options = options || {};
|
||||
options.hash = options.hash || {};
|
||||
|
||||
var tags = _.map(this.tags, 'name'),
|
||||
author = this.author ? this.author.name : null,
|
||||
number = options.data.number,
|
||||
index = options.data.index,
|
||||
tagList = options.hash.tag || false,
|
||||
authorList = options.hash.author || false,
|
||||
numberList = options.hash.number || false,
|
||||
indexList = options.hash.index || false,
|
||||
tagsOk,
|
||||
authorOk,
|
||||
numberOk,
|
||||
indexOk;
|
||||
|
||||
if (!tagList && !authorList && !numberList && !indexList) {
|
||||
logging.warn(i18n.t('warnings.helpers.has.invalidAttribute'));
|
||||
return;
|
||||
}
|
||||
|
||||
tagsOk = tagList && evaluateTagList(tagList, tags) || false;
|
||||
authorOk = authorList && evaluateAuthorList(authorList, author) || false;
|
||||
numberOk = numberList && evaluateIntegerMatch(numberList, number) || false;
|
||||
indexOk = indexList && evaluateIntegerMatch(indexList, index) || false;
|
||||
|
||||
if (tagsOk || authorOk) {
|
||||
if (tagsOk || authorOk || numberOk || indexOk) {
|
||||
return options.fn(this);
|
||||
}
|
||||
return options.inverse(this);
|
||||
|
|
|
@ -7,176 +7,341 @@ var should = require('should'), // jshint ignore:line
|
|||
sandbox = sinon.sandbox.create();
|
||||
|
||||
describe('{{#has}} helper', function () {
|
||||
var fn, inverse, thisCtx, handlebarsOptions;
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('should handle tag list that validates true', function () {
|
||||
var fn = sandbox.spy(),
|
||||
beforeEach(function () {
|
||||
fn = sandbox.spy();
|
||||
inverse = sandbox.spy();
|
||||
|
||||
helpers.has.call(
|
||||
{tags: [{name: 'foo'}, {name: 'bar'}, {name: 'baz'}]},
|
||||
{hash: {tag: 'invalid, bar, wat'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
thisCtx = {};
|
||||
|
||||
// This object mocks out the object that handlebars helpers get passed
|
||||
handlebarsOptions = {
|
||||
hash: {},
|
||||
data: {},
|
||||
fn: fn,
|
||||
inverse: inverse
|
||||
};
|
||||
});
|
||||
|
||||
function callHasHelper(thisCtx, hash) {
|
||||
// Hash is the options passed in
|
||||
handlebarsOptions.hash = hash;
|
||||
return helpers.has.call(thisCtx, handlebarsOptions);
|
||||
}
|
||||
|
||||
describe('tag match', function () {
|
||||
it('should handle tag list that validates true', function () {
|
||||
thisCtx = {tags: [{name: 'foo'}, {name: 'bar'}, {name: 'baz'}]};
|
||||
|
||||
// {{#has tag="invalid, bar, wat"}}
|
||||
callHasHelper(thisCtx, {tag: 'invalid, bar, wat'});
|
||||
|
||||
fn.called.should.be.true();
|
||||
inverse.called.should.be.false();
|
||||
});
|
||||
|
||||
it('should handle tags with case-insensitivity', function () {
|
||||
var fn = sandbox.spy(),
|
||||
inverse = sandbox.spy();
|
||||
thisCtx = {tags: [{name: 'ghost'}]};
|
||||
|
||||
helpers.has.call(
|
||||
{tags: [{name: 'ghost'}]},
|
||||
{hash: {tag: 'GhoSt'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
// {{#has tag="GhoSt"}}
|
||||
callHasHelper(thisCtx, {tag: 'GhoSt'});
|
||||
|
||||
fn.called.should.be.true();
|
||||
inverse.called.should.be.false();
|
||||
});
|
||||
|
||||
it('should match exact tags, not superstrings', function () {
|
||||
var fn = sandbox.spy(),
|
||||
inverse = sandbox.spy();
|
||||
thisCtx = {tags: [{name: 'magical'}]};
|
||||
|
||||
helpers.has.call(
|
||||
{tags: [{name: 'magical'}]},
|
||||
{hash: {tag: 'magic'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
callHasHelper(thisCtx, {tag: 'magic'});
|
||||
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
});
|
||||
|
||||
it('should match exact tags, not substrings', function () {
|
||||
var fn = sandbox.spy(),
|
||||
inverse = sandbox.spy();
|
||||
thisCtx = {tags: [{name: 'magic'}]};
|
||||
|
||||
helpers.has.call(
|
||||
{tags: [{name: 'magic'}]},
|
||||
{hash: {tag: 'magical'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
callHasHelper(thisCtx, {tag: 'magical'});
|
||||
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
});
|
||||
|
||||
it('should handle tag list that validates false', function () {
|
||||
var fn = sandbox.spy(),
|
||||
inverse = sandbox.spy();
|
||||
thisCtx = {tags: [{name: 'foo'}, {name: 'bar'}, {name: 'baz'}]};
|
||||
|
||||
helpers.has.call(
|
||||
{tags: [{name: 'foo'}, {name: 'bar'}, {name: 'baz'}]},
|
||||
{hash: {tag: 'much, such, wow'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
callHasHelper(thisCtx, {tag: 'much, such, wow'});
|
||||
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
});
|
||||
|
||||
it('should not do anything if there are no attributes', function () {
|
||||
var fn = sandbox.spy(),
|
||||
inverse = sandbox.spy();
|
||||
thisCtx = {tags: [{name: 'foo'}, {name: 'bar'}, {name: 'baz'}]};
|
||||
|
||||
helpers.has.call(
|
||||
{tags: [{name: 'foo'}, {name: 'bar'}, {name: 'baz'}]},
|
||||
{fn: fn, inverse: inverse}
|
||||
);
|
||||
callHasHelper(thisCtx);
|
||||
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.false();
|
||||
});
|
||||
|
||||
it('should not do anything when an invalid attribute is given', function () {
|
||||
var fn = sandbox.spy(),
|
||||
inverse = sandbox.spy();
|
||||
thisCtx = {tags: [{name: 'foo'}, {name: 'bar'}, {name: 'baz'}]};
|
||||
|
||||
helpers.has.call(
|
||||
{tags: [{name: 'foo'}, {name: 'bar'}, {name: 'baz'}]},
|
||||
{hash: {invalid: 'nonsense'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
callHasHelper(thisCtx, {invalid: 'nonsense'});
|
||||
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.false();
|
||||
});
|
||||
});
|
||||
|
||||
describe('author match', function () {
|
||||
it('should handle author list that evaluates to true', function () {
|
||||
var fn = sandbox.spy(),
|
||||
inverse = sandbox.spy();
|
||||
thisCtx = {author: {name: 'sam'}};
|
||||
|
||||
helpers.has.call(
|
||||
{author: {name: 'sam'}},
|
||||
{hash: {author: 'joe, sam, pat'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
callHasHelper(thisCtx, {author: 'joe, sam, pat'});
|
||||
|
||||
fn.called.should.be.true();
|
||||
inverse.called.should.be.false();
|
||||
});
|
||||
|
||||
it('should handle author list that evaluates to false', function () {
|
||||
var fn = sandbox.spy(),
|
||||
inverse = sandbox.spy();
|
||||
thisCtx = {author: {name: 'jamie'}};
|
||||
|
||||
helpers.has.call(
|
||||
{author: {name: 'jamie'}},
|
||||
{hash: {author: 'joe, sam, pat'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
callHasHelper(thisCtx, {author: 'joe, sam, pat'});
|
||||
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
});
|
||||
|
||||
it('should handle authors with case-insensitivity', function () {
|
||||
var fn = sandbox.spy(),
|
||||
inverse = sandbox.spy();
|
||||
thisCtx = {author: {name: 'Sam'}};
|
||||
|
||||
helpers.has.call(
|
||||
{author: {name: 'Sam'}},
|
||||
{hash: {author: 'joe, sAm, pat'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
callHasHelper(thisCtx, {author: 'joe, sAm, pat'});
|
||||
|
||||
fn.called.should.be.true();
|
||||
inverse.called.should.be.false();
|
||||
});
|
||||
|
||||
it('should handle tags and authors like an OR query (pass)', function () {
|
||||
var fn = sandbox.spy(),
|
||||
inverse = sandbox.spy();
|
||||
thisCtx = {author: {name: 'sam'}, tags: [{name: 'foo'}, {name: 'bar'}, {name: 'baz'}]};
|
||||
|
||||
helpers.has.call(
|
||||
{author: {name: 'sam'}, tags: [{name: 'foo'}, {name: 'bar'}, {name: 'baz'}]},
|
||||
{hash: {author: 'joe, sam, pat', tag: 'much, such, wow'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
callHasHelper(thisCtx, {author: 'joe, sam, pat', tag: 'much, such, wow'});
|
||||
|
||||
fn.called.should.be.true();
|
||||
inverse.called.should.be.false();
|
||||
});
|
||||
|
||||
it('should handle tags and authors like an OR query (pass)', function () {
|
||||
var fn = sandbox.spy(),
|
||||
inverse = sandbox.spy();
|
||||
thisCtx = {author: {name: 'sam'}, tags: [{name: 'much'}, {name: 'bar'}, {name: 'baz'}]};
|
||||
|
||||
helpers.has.call(
|
||||
{author: {name: 'sam'}, tags: [{name: 'much'}, {name: 'bar'}, {name: 'baz'}]},
|
||||
{hash: {author: 'joe, sam, pat', tag: 'much, such, wow'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
callHasHelper(thisCtx, {author: 'joe, sam, pat', tag: 'much, such, wow'});
|
||||
|
||||
fn.called.should.be.true();
|
||||
inverse.called.should.be.false();
|
||||
});
|
||||
|
||||
it('should handle tags and authors like an OR query (fail)', function () {
|
||||
var fn = sandbox.spy(),
|
||||
inverse = sandbox.spy();
|
||||
thisCtx = {author: {name: 'fred'}, tags: [{name: 'foo'}, {name: 'bar'}, {name: 'baz'}]};
|
||||
|
||||
helpers.has.call(
|
||||
{author: {name: 'fred'}, tags: [{name: 'foo'}, {name: 'bar'}, {name: 'baz'}]},
|
||||
{hash: {author: 'joe, sam, pat', tag: 'much, such, wow'}, fn: fn, inverse: inverse}
|
||||
);
|
||||
callHasHelper(thisCtx, {author: 'joe, sam, pat', tag: 'much, such, wow'});
|
||||
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
describe('number match (1-based index)', function () {
|
||||
it('will match on an exact number (pass)', function () {
|
||||
handlebarsOptions.data = {number: 6};
|
||||
|
||||
callHasHelper(thisCtx, {number: '6'});
|
||||
|
||||
fn.called.should.be.true();
|
||||
inverse.called.should.be.false();
|
||||
});
|
||||
|
||||
it('will match on an exact number (fail)', function () {
|
||||
handlebarsOptions.data = {number: 5};
|
||||
|
||||
callHasHelper(thisCtx, {number: '6'});
|
||||
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
});
|
||||
|
||||
it('will match on an exact number (loop)', function () {
|
||||
for (var number = 1; number < 9; number += 1) {
|
||||
handlebarsOptions.data = {number: number};
|
||||
// Will match 6
|
||||
callHasHelper(thisCtx, {number: '6'});
|
||||
}
|
||||
|
||||
fn.calledOnce.should.be.true();
|
||||
inverse.called.should.be.true();
|
||||
inverse.callCount.should.eql(7);
|
||||
});
|
||||
|
||||
it('will match on a number list (pass)', function () {
|
||||
handlebarsOptions.data = {number: 6};
|
||||
|
||||
callHasHelper(thisCtx, {number: '1, 3, 6,12'});
|
||||
|
||||
fn.called.should.be.true();
|
||||
inverse.called.should.be.false();
|
||||
});
|
||||
|
||||
it('will match on a number list (fail)', function () {
|
||||
handlebarsOptions.data = {number: 5};
|
||||
|
||||
callHasHelper(thisCtx, {number: '1, 3, 6,12'});
|
||||
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
});
|
||||
|
||||
it('will match on a number list (loop)', function () {
|
||||
for (var number = 1; number < 9; number += 1) {
|
||||
handlebarsOptions.data = {number: number};
|
||||
// Will match 1, 3, 6
|
||||
callHasHelper(thisCtx, {number: '1, 3, 6,12'});
|
||||
}
|
||||
|
||||
fn.called.should.be.true();
|
||||
fn.callCount.should.eql(3);
|
||||
inverse.called.should.be.true();
|
||||
inverse.callCount.should.eql(5);
|
||||
});
|
||||
|
||||
it('will match on a nth pattern (pass)', function () {
|
||||
handlebarsOptions.data = {number: 6};
|
||||
|
||||
callHasHelper(thisCtx, {number: 'nth:3'});
|
||||
|
||||
fn.called.should.be.true();
|
||||
inverse.called.should.be.false();
|
||||
});
|
||||
|
||||
it('will match on a nth pattern (fail)', function () {
|
||||
handlebarsOptions.data = {number: 5};
|
||||
|
||||
callHasHelper(thisCtx, {number: 'nth:3'});
|
||||
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
});
|
||||
|
||||
it('will match on a nth pattern (loop)', function () {
|
||||
for (var number = 1; number < 9; number += 1) {
|
||||
handlebarsOptions.data = {number: number};
|
||||
// Will match 3 & 6
|
||||
callHasHelper(thisCtx, {number: 'nth:3'});
|
||||
}
|
||||
|
||||
fn.called.should.be.true();
|
||||
fn.callCount.should.eql(2);
|
||||
inverse.called.should.be.true();
|
||||
inverse.callCount.should.eql(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe('index match (0-based index)', function () {
|
||||
it('will match on an exact index (pass)', function () {
|
||||
handlebarsOptions.data = {index: 6};
|
||||
|
||||
callHasHelper(thisCtx, {index: '6'});
|
||||
|
||||
fn.called.should.be.true();
|
||||
inverse.called.should.be.false();
|
||||
});
|
||||
|
||||
it('will match on an exact index (fail)', function () {
|
||||
handlebarsOptions.data = {index: 5};
|
||||
|
||||
callHasHelper(thisCtx, {index: '6'});
|
||||
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
});
|
||||
|
||||
it('will match on an exact index (loop)', function () {
|
||||
for (var index = 0; index < 8; index += 1) {
|
||||
handlebarsOptions.data = {index: index};
|
||||
// Will match 6
|
||||
callHasHelper(thisCtx, {index: '6'});
|
||||
}
|
||||
|
||||
fn.calledOnce.should.be.true();
|
||||
inverse.called.should.be.true();
|
||||
inverse.callCount.should.eql(7);
|
||||
});
|
||||
|
||||
it('will match on an index list (pass)', function () {
|
||||
handlebarsOptions.data = {index: 6};
|
||||
|
||||
callHasHelper(thisCtx, {index: '1, 3, 6,12'});
|
||||
|
||||
fn.called.should.be.true();
|
||||
inverse.called.should.be.false();
|
||||
});
|
||||
|
||||
it('will match on an index list (fail)', function () {
|
||||
handlebarsOptions.data = {index: 5};
|
||||
|
||||
callHasHelper(thisCtx, {index: '1, 3, 6,12'});
|
||||
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
});
|
||||
|
||||
it('will match on an index list (loop)', function () {
|
||||
for (var index = 0; index < 8; index += 1) {
|
||||
handlebarsOptions.data = {index: index};
|
||||
// Will match 1, 3, 6
|
||||
callHasHelper(thisCtx, {index: '1, 3, 6,12'});
|
||||
}
|
||||
|
||||
fn.called.should.be.true();
|
||||
fn.callCount.should.eql(3);
|
||||
inverse.called.should.be.true();
|
||||
inverse.callCount.should.eql(5);
|
||||
});
|
||||
|
||||
it('will match on a nth pattern (pass)', function () {
|
||||
handlebarsOptions.data = {index: 6};
|
||||
|
||||
callHasHelper(thisCtx, {index: 'nth:3'});
|
||||
|
||||
fn.called.should.be.true();
|
||||
inverse.called.should.be.false();
|
||||
});
|
||||
|
||||
it('will match on a nth pattern (fail)', function () {
|
||||
handlebarsOptions.data = {index: 5};
|
||||
|
||||
callHasHelper(thisCtx, {index: 'nth:3'});
|
||||
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
});
|
||||
|
||||
it('will match on a nth pattern (loop)', function () {
|
||||
for (var index = 0; index < 8; index += 1) {
|
||||
handlebarsOptions.data = {index: index};
|
||||
// Will match 0, 3, 6
|
||||
callHasHelper(thisCtx, {index: 'nth:3'});
|
||||
}
|
||||
|
||||
fn.called.should.be.true();
|
||||
fn.callCount.should.eql(3);
|
||||
inverse.called.should.be.true();
|
||||
inverse.callCount.should.eql(5);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue