0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00
ghost/test/unit/helpers/foreach_spec.js
Rish 886f564dc4 Updated default visibility in foreach for posts to all
closes https://github.com/TryGhost/Team/issues/485

In order to loop over all posts, we currently need to pass a visibility="all" flag to a foreach as default for all items in current `visibility` helper is set to `public`. For a post, this behaviour is unintuitive, and inconsistent with the API. Instead, the default visibility should be "all" for the posts. The update allows themes to get all posts directly without passing in visibility -

```
{{#get "posts"}}
    {{#foreach posts}}
        //Loops over all posts, not just `public`
    {{/foreach}}
{{/get}}
```
2021-03-02 15:44:19 +05:30

615 lines
28 KiB
JavaScript

const should = require('should');
const sinon = require('sinon');
const _ = require('lodash');
const helpers = require('../../../core/frontend/helpers');
const handlebars = require('../../../core/frontend/services/themes/engine').handlebars;
describe('{{#foreach}} helper', function () {
let options;
let context;
let _this;
let resultData;
afterEach(function () {
sinon.restore();
});
describe('(function call)', function () {
beforeEach(function () {
context = [];
_this = {};
resultData = [];
function fn(input, data) {
resultData.push(_.cloneDeep(data));
}
options = {
fn: sinon.spy(fn),
inverse: sinon.spy(),
data: {}
};
});
function runTest(self, _context, _options) {
helpers.foreach.call(self, _context, _options);
}
it('should not populate data if no private data is supplied (array)', function () {
delete options.data;
options.hash = {
columns: 0
};
// test with context as an array
context = 'hello world this is ghost'.split(' ');
runTest(_this, context, options);
options.fn.called.should.be.true();
options.fn.getCalls().length.should.eql(_.size(context));
_.each(context, function (value, index) {
options.fn.getCall(index).args[0].should.eql(value);
should(options.fn.getCall(index).args[1].data).be.undefined();
});
});
it('should not populate data if no private data is supplied (object)', function () {
delete options.data;
options.hash = {
columns: 0
};
context = {
one: 'hello',
two: 'world',
three: 'this',
four: 'is',
five: 'ghost'
};
runTest(_this, context, options);
options.fn.called.should.be.true();
options.fn.getCalls().length.should.eql(_.size(context));
_.each(_.keys(context), function (value, index) {
options.fn.getCall(index).args[0].should.eql(context[value]);
should(options.fn.getCall(index).args[1].data).be.undefined();
});
});
it('should populate data when private data is supplied (array)', function () {
const expected = [
{first: true, last: false, even: false, odd: true, rowStart: false, rowEnd: false},
{first: false, last: false, even: true, odd: false, rowStart: false, rowEnd: false},
{first: false, last: false, even: false, odd: true, rowStart: false, rowEnd: false},
{first: false, last: false, even: true, odd: false, rowStart: false, rowEnd: false},
{first: false, last: true, even: false, odd: true, rowStart: false, rowEnd: false}
];
options.hash = {
columns: 0
};
context = 'hello world this is ghost'.split(' ');
runTest(_this, context, options);
options.fn.called.should.be.true();
options.fn.getCalls().length.should.eql(_.size(context));
_.each(context, function (value, index) {
options.fn.getCall(index).args[0].should.eql(value);
should(options.fn.getCall(index).args[1].data).not.be.undefined();
// Expected properties
resultData[index].data.should.containEql(expected[index]);
// Incrementing properties
resultData[index].data.should.have.property('key', index);
resultData[index].data.should.have.property('index', index);
resultData[index].data.should.have.property('number', index + 1);
});
resultData[_.size(context) - 1].data.should.eql(options.fn.lastCall.args[1].data);
});
it('should populate data when private data is supplied (object)', function () {
const expected = [
{first: true, last: false, even: false, odd: true, rowStart: false, rowEnd: false},
{first: false, last: false, even: true, odd: false, rowStart: false, rowEnd: false},
{first: false, last: false, even: false, odd: true, rowStart: false, rowEnd: false},
{first: false, last: false, even: true, odd: false, rowStart: false, rowEnd: false},
{first: false, last: true, even: false, odd: true, rowStart: false, rowEnd: false}
];
options.hash = {
columns: 0
};
context = {
one: 'hello',
two: 'world',
three: 'this',
four: 'is',
five: 'ghost'
};
runTest(_this, context, options);
options.fn.called.should.be.true();
options.fn.getCalls().length.should.eql(_.size(context));
_.each(_.keys(context), function (value, index) {
options.fn.getCall(index).args[0].should.eql(context[value]);
should(options.fn.getCall(index).args[1].data).not.be.undefined();
// Expected properties
resultData[index].data.should.containEql(expected[index]);
// Incrementing properties
resultData[index].data.should.have.property('key', value);
resultData[index].data.should.have.property('index', index);
resultData[index].data.should.have.property('number', index + 1);
});
resultData[_.size(context) - 1].data.should.eql(options.fn.lastCall.args[1].data);
});
it('should handle rowStart and rowEnd for multiple columns (array)', function () {
const expected = [
{first: true, last: false, even: false, odd: true, rowStart: true, rowEnd: false},
{first: false, last: false, even: true, odd: false, rowStart: false, rowEnd: true},
{first: false, last: false, even: false, odd: true, rowStart: true, rowEnd: false},
{first: false, last: false, even: true, odd: false, rowStart: false, rowEnd: true},
{first: false, last: true, even: false, odd: true, rowStart: true, rowEnd: false}
];
options.hash = {
columns: 2
};
// test with context as an array
context = 'hello world this is ghost'.split(' ');
runTest(_this, context, options);
options.fn.called.should.be.true();
options.fn.getCalls().length.should.eql(_.size(context));
_.each(context, function (value, index) {
options.fn.getCall(index).args[0].should.eql(value);
should(options.fn.getCall(index).args[1].data).not.be.undefined();
// Expected properties
resultData[index].data.should.containEql(expected[index]);
// Incrementing properties
resultData[index].data.should.have.property('key', index);
resultData[index].data.should.have.property('index', index);
resultData[index].data.should.have.property('number', index + 1);
});
resultData[_.size(context) - 1].data.should.eql(options.fn.lastCall.args[1].data);
});
it('should handle rowStart and rowEnd for multiple columns (array)', function () {
const expected = [
{first: true, last: false, even: false, odd: true, rowStart: true, rowEnd: false},
{first: false, last: false, even: true, odd: false, rowStart: false, rowEnd: true},
{first: false, last: false, even: false, odd: true, rowStart: true, rowEnd: false},
{first: false, last: false, even: true, odd: false, rowStart: false, rowEnd: true},
{first: false, last: true, even: false, odd: true, rowStart: true, rowEnd: false}
];
options.hash = {
columns: 2
};
// test with context as an object
context = {
one: 'hello',
two: 'world',
three: 'this',
four: 'is',
five: 'ghost'
};
runTest(_this, context, options);
options.fn.called.should.be.true();
options.fn.getCalls().length.should.eql(_.size(context));
_.each(_.keys(context), function (value, index) {
options.fn.getCall(index).args[0].should.eql(context[value]);
should(options.fn.getCall(index).args[1].data).not.be.undefined();
// Expected properties
resultData[index].data.should.containEql(expected[index]);
// Incrementing properties
resultData[index].data.should.have.property('key', value);
resultData[index].data.should.have.property('index', index);
resultData[index].data.should.have.property('number', index + 1);
});
resultData[_.size(context) - 1].data.should.eql(options.fn.lastCall.args[1].data);
});
it('should return the correct inverse result if no context is provided', function () {
_this = 'the inverse data';
options.hash = {
columns: 0
};
runTest(_this, context, options);
options.fn.called.should.be.false();
options.inverse.called.should.be.true();
options.inverse.calledOnce.should.be.true();
});
});
describe('(compile)', function () {
const objectHash = {
posts: {
first: {title: 'first'},
second: {title: 'second'},
third: {title: 'third'},
fourth: {title: 'fourth'},
fifth: {title: 'fifth'}
}
};
const objectHashWithVis = {
posts: {
first: {title: 'first', visibility: 'members', slug: 'first', html: ''},
second: {title: 'second'},
third: {title: 'third'},
fourth: {title: 'fourth'},
fifth: {title: 'fifth'}
}
};
const arrayHash = {
posts: [
{title: 'first'}, {title: 'second'}, {title: 'third'}, {title: 'fourth'}, {title: 'fifth'}
]
};
const arrayHashWithVis = {
posts: [
{title: 'first', visibility: 'members', slug: 'first', html: ''}, {title: 'second'}, {title: 'third'}, {title: 'fourth'}, {title: 'fifth'}
]
};
const arrayHash2 = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
const objectHash2 = {
goodbyes: {foo: {text: 'goodbye'}, bar: {text: 'Goodbye'}, baz: {text: 'GOODBYE'}},
world: 'world'
};
function shouldCompileToExpected(templateString, hash, expected) {
const template = handlebars.compile(templateString);
const result = template(hash);
result.should.eql(expected);
}
before(function () {
handlebars.registerHelper('foreach', helpers.foreach);
});
/** Many of these are copied direct from the handlebars spec */
it('object and @key', function () {
const templateString = '<ul>{{#foreach posts}}<li>{{@key}} {{title}}</li>{{/foreach}}</ul>';
const expected = '<ul><li>first first</li><li>second second</li><li>third third</li><li>fourth fourth</li><li>fifth fifth</li></ul>';
shouldCompileToExpected(templateString, objectHash, expected);
});
it('@index', function () {
const templateString = '<ul>{{#foreach posts}}<li>{{@index}} {{title}}</li>{{/foreach}}</ul>';
const expected = '<ul><li>0 first</li><li>1 second</li><li>2 third</li><li>3 fourth</li><li>4 fifth</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('@number', function () {
const templateString = '<ul>{{#foreach posts}}<li>{{@number}} {{title}}</li>{{/foreach}}</ul>';
const expected = '<ul><li>1 first</li><li>2 second</li><li>3 third</li><li>4 fourth</li><li>5 fifth</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('nested @index', function () {
const templateString = '{{#foreach goodbyes}}{{@index}}. {{text}}! {{#foreach ../goodbyes}}{{@index}} {{/foreach}}After {{@index}} {{/foreach}}{{@index}}cruel {{world}}!';
const expected = '0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!';
shouldCompileToExpected(templateString, arrayHash2, expected);
shouldCompileToExpected(templateString, objectHash2, expected);
});
it('array block params', function () {
const templateString = '{{#foreach goodbyes as |value index|}}{{index}}. {{value.text}}! {{#foreach ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/foreach}} After {{index}} {{/foreach}}{{index}}cruel {{world}}!';
const expected = '0. goodbye! 0 0 0 1 0 2 After 0 1. Goodbye! 1 0 1 1 1 2 After 1 2. GOODBYE! 2 0 2 1 2 2 After 2 cruel world!';
shouldCompileToExpected(templateString, arrayHash2, expected);
});
it('object block params', function () {
const templateString = '{{#foreach goodbyes as |value index|}}{{index}}. {{value.text}}! {{#foreach ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/foreach}} After {{index}} {{/foreach}}{{index}}cruel {{world}}!';
const expected = 'foo. goodbye! foo foo foo bar foo baz After foo bar. Goodbye! bar foo bar bar bar baz After bar baz. GOODBYE! baz foo baz bar baz baz After baz cruel world!';
shouldCompileToExpected(templateString, objectHash2, expected);
});
it('@first', function () {
const templateString = '{{#foreach goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/foreach}}cruel {{world}}!';
const expected = 'goodbye! cruel world!';
shouldCompileToExpected(templateString, arrayHash2, expected);
shouldCompileToExpected(templateString, objectHash2, expected);
});
it('nested @first', function () {
const templateString = '{{#foreach goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#foreach ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/foreach}}{{#if @first}} {{text}}!{{/if}}) {{/foreach}}cruel {{world}}!';
const expected = '(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!';
shouldCompileToExpected(templateString, arrayHash2, expected);
shouldCompileToExpected(templateString, objectHash2, expected);
});
it('@last', function () {
const templateString = '{{#foreach goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/foreach}}cruel {{world}}!';
const expected = 'GOODBYE! cruel world!';
shouldCompileToExpected(templateString, arrayHash2, expected);
shouldCompileToExpected(templateString, objectHash2, expected);
});
it('nested @last', function () {
const templateString = '{{#foreach goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#foreach ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/foreach}}{{#if @last}} {{text}}!{{/if}}) {{/foreach}}cruel {{world}}!';
const expected = '(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!';
shouldCompileToExpected(templateString, arrayHash2, expected);
shouldCompileToExpected(templateString, objectHash2, expected);
});
it('@last in foreach with limit', function () {
const templateString = '{{#foreach goodbyes limit="2"}}{{#if @last}}{{text}}! {{/if}}{{/foreach}}cruel {{world}}!';
const expected = 'Goodbye! cruel world!';
shouldCompileToExpected(templateString, arrayHash2, expected);
shouldCompileToExpected(templateString, objectHash2, expected);
});
it('foreach with limit 1', function () {
const templateString = '<ul>{{#foreach posts limit="1"}}<li>{{title}}</li>{{else}}not this{{/foreach}}</ul>';
const expected = '<ul><li>first</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('foreach with limit 3', function () {
const templateString = '<ul>{{#foreach posts limit="3"}}<li>{{title}}</li>{{else}}not this{{/foreach}}</ul>';
const expected = '<ul><li>first</li><li>second</li><li>third</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('foreach with limit 3 and post with members visibility', function () {
const templateString = '<ul>{{#foreach posts limit="3"}}<li>{{title}}</li>{{else}}not this{{/foreach}}</ul>';
const expected = '<ul><li>first</li><li>second</li><li>third</li></ul>';
shouldCompileToExpected(templateString, arrayHashWithVis, expected);
shouldCompileToExpected(templateString, objectHashWithVis, expected);
});
it('foreach with from 2', function () {
const templateString = '<ul>{{#foreach posts from="2"}}<li>{{title}}</li>{{else}}not this{{/foreach}}</ul>';
const expected = '<ul><li>second</li><li>third</li><li>fourth</li><li>fifth</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('foreach with to 4', function () {
const templateString = '<ul>{{#foreach posts to="4"}}<li>{{title}}</li>{{else}}not this{{/foreach}}</ul>';
const expected = '<ul><li>first</li><li>second</li><li>third</li><li>fourth</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('foreach with from 2 and to 3', function () {
const templateString = '<ul>{{#foreach posts from="2" to="3"}}<li>{{title}}</li>{{else}}not this{{/foreach}}</ul>';
const expected = '<ul><li>second</li><li>third</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('foreach with from 3 and limit 2', function () {
const templateString = '<ul>{{#foreach posts from="3" limit="2"}}<li>{{title}}</li>{{else}}not this{{/foreach}}</ul>';
const expected = '<ul><li>third</li><li>fourth</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('foreach with from 2, to 5 and limit 3', function () {
const templateString = '<ul>{{#foreach posts from="2" to="5" limit="3"}}<li>{{title}}</li>{{else}}not this{{/foreach}}</ul>';
const expected = '<ul><li>second</li><li>third</li><li>fourth</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('@first in foreach with from 2 and to 4', function () {
const templateString = '<ul>{{#foreach posts from="2" to="4"}}{{#if @first}}<li>{{title}}</li>{{/if}}{{/foreach}}</ul>';
const expected = '<ul><li>second</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('@last in foreach with from 2 and to 4', function () {
const templateString = '<ul>{{#foreach posts from="2" to="4"}}{{#if @last}}<li>{{title}}</li>{{/if}}{{/foreach}}</ul>';
const expected = '<ul><li>fourth</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('@last in foreach with from 4', function () {
const templateString = '<ul>{{#foreach posts from="4"}}{{#if @last}}<li>{{title}}</li>{{/if}}{{/foreach}}</ul>';
const expected = '<ul><li>fifth</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('@first in foreach with from 4', function () {
const templateString = '<ul>{{#foreach posts from="4"}}{{#if @first}}<li>{{title}}</li>{{/if}}{{/foreach}}</ul>';
const expected = '<ul><li>fourth</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('@last in foreach with to 4', function () {
const templateString = '<ul>{{#foreach posts to="4"}}{{#if @last}}<li>{{title}}</li>{{/if}}{{/foreach}}</ul>';
const expected = '<ul><li>fourth</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('@first in foreach with to 4', function () {
const templateString = '<ul>{{#foreach posts to="4"}}{{#if @first}}<li>{{title}}</li>{{/if}}{{/foreach}}</ul>';
const expected = '<ul><li>first</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('@last in foreach with from 4 and limit 3', function () {
const templateString = '<ul>{{#foreach posts from="4" limit="3"}}{{#if @last}}<li>{{title}}</li>{{/if}}{{/foreach}}</ul>';
const expected = '<ul><li>fifth</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('@first in foreach with from 4 and limit 3', function () {
const templateString = '<ul>{{#foreach posts from="4" limit="3"}}{{#if @first}}<li>{{title}}</li>{{/if}}{{/foreach}}</ul>';
const expected = '<ul><li>fourth</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('@last in foreach with to 4 and limit 3', function () {
const templateString = '<ul>{{#foreach posts to="4" limit="3"}}{{#if @last}}<li>{{title}}</li>{{/if}}{{/foreach}}</ul>';
const expected = '<ul><li>third</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
it('@first in foreach with to 4 and limit 3', function () {
const templateString = '<ul>{{#foreach posts to="4" limit="3"}}{{#if @first}}<li>{{title}}</li>{{/if}}{{/foreach}}</ul>';
const expected = '<ul><li>first</li></ul>';
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
describe('Internal Tags', function () {
const tagArrayHash = {
tags: [
{name: 'first', visibility: 'public'},
{name: 'second', visibility: 'public'},
{name: 'third', visibility: 'internal'},
{name: 'fourth', visibility: 'public'},
{name: 'fifth', visibility: 'public'}
]
};
const tagObjectHash = {
tags: {
first: {name: 'first', visibility: 'public'},
second: {name: 'second', visibility: 'public'},
third: {name: 'third', visibility: 'internal'},
fourth: {name: 'fourth', visibility: 'public'},
fifth: {name: 'fifth', visibility: 'public'}
}
};
it('will not output internal tags', function () {
const templateString = '<ul>{{#foreach tags}}<li>{{@index}} {{name}}</li>{{/foreach}}</ul>';
const expected = '<ul><li>0 first</li><li>1 second</li><li>2 fourth</li><li>3 fifth</li></ul>';
shouldCompileToExpected(templateString, tagObjectHash, expected);
shouldCompileToExpected(templateString, tagArrayHash, expected);
});
it('should still correctly apply from & limit tags', function () {
const templateString = '<ul>{{#foreach tags from="2" limit="2"}}<li>{{@index}} {{name}}</li>{{/foreach}}</ul>';
const expected = '<ul><li>1 second</li><li>2 fourth</li></ul>';
shouldCompileToExpected(templateString, tagObjectHash, expected);
shouldCompileToExpected(templateString, tagArrayHash, expected);
});
it('should output all tags with visibility="all"', function () {
const templateString = '<ul>{{#foreach tags visibility="all"}}<li>{{@index}} {{name}}</li>{{/foreach}}</ul>';
const expected = '<ul><li>0 first</li><li>1 second</li><li>2 third</li><li>3 fourth</li><li>4 fifth</li></ul>';
shouldCompileToExpected(templateString, tagObjectHash, expected);
shouldCompileToExpected(templateString, tagArrayHash, expected);
});
it('should output all tags with visibility property set with visibility="public,internal"', function () {
const templateString = '<ul>{{#foreach tags visibility="public,internal"}}<li>{{@index}} {{name}}</li>{{/foreach}}</ul>';
const expected = '<ul><li>0 first</li><li>1 second</li><li>2 third</li><li>3 fourth</li><li>4 fifth</li></ul>';
shouldCompileToExpected(templateString, tagObjectHash, expected);
shouldCompileToExpected(templateString, tagArrayHash, expected);
});
it('should output all tags with visibility="internal"', function () {
const templateString = '<ul>{{#foreach tags visibility="internal"}}<li>{{@index}} {{name}}</li>{{/foreach}}</ul>';
const expected = '<ul><li>0 third</li></ul>';
shouldCompileToExpected(templateString, tagObjectHash, expected);
shouldCompileToExpected(templateString, tagArrayHash, expected);
});
it('should output nothing if all tags are internal', function () {
const internalTagArrayHash = {
tags: [
{name: 'first', visibility: 'internal'},
{name: 'second', visibility: 'internal'}
]
};
const internalTagObjectHash = {
tags: {
first: {name: 'first', visibility: 'internal'},
second: {name: 'second', visibility: 'internal'}
}
};
const templateString = '<ul>{{#foreach tags}}<li>{{@index}} {{name}}</li>{{/foreach}}</ul>';
const expected = '<ul></ul>';
shouldCompileToExpected(templateString, internalTagObjectHash, expected);
shouldCompileToExpected(templateString, internalTagArrayHash, expected);
});
});
});
});