mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-28 22:43:30 +03:00
Add path resolution to get helper
refs #5993 - deps: jsonpath@0.2.0 - adds `resolvePaths` method - supports handlebars style arrays with `.[]` - supports shorthand post.tags and post.author for common usecases - adds more tests & improves existing ones
This commit is contained in:
parent
64d9ce44cf
commit
994a20cf8b
@ -6,12 +6,20 @@ var _ = require('lodash'),
|
||||
Promise = require('bluebird'),
|
||||
errors = require('../errors'),
|
||||
api = require('../api'),
|
||||
jsonpath = require('jsonpath'),
|
||||
resources,
|
||||
pathAliases,
|
||||
get;
|
||||
|
||||
// Endpoints that the helper is able to access
|
||||
resources = ['posts', 'tags', 'users'];
|
||||
|
||||
// Short forms of paths which we should understand
|
||||
pathAliases = {
|
||||
'post.tags': 'post.tags[*].slug',
|
||||
'post.author': 'post.author.slug'
|
||||
};
|
||||
|
||||
/**
|
||||
* ## Is Browse
|
||||
* Is this a Browse request or a Read request?
|
||||
@ -29,6 +37,34 @@ function isBrowse(context, options) {
|
||||
return browse;
|
||||
}
|
||||
|
||||
/**
|
||||
* ## Resolve Paths
|
||||
* Find and resolve path strings
|
||||
*
|
||||
* @param {Object} data
|
||||
* @param {String} value
|
||||
* @returns {String}
|
||||
*/
|
||||
function resolvePaths(data, value) {
|
||||
var regex = /\{\{(.*?)\}\}/g;
|
||||
|
||||
value = value.replace(regex, function (match, path) {
|
||||
var result;
|
||||
|
||||
// Handle aliases
|
||||
path = pathAliases[path] ? pathAliases[path] : path;
|
||||
// Handle Handlebars .[] style arrays
|
||||
path = path.replace(/\.\[/g, '[');
|
||||
|
||||
// Do the query, and convert from array to string
|
||||
result = jsonpath.query(data, path).join(',');
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* ## Parse Options
|
||||
* Ensure options passed in make sense
|
||||
@ -46,6 +82,10 @@ function parseOptions(data, options) {
|
||||
options.author = options.author.slug;
|
||||
}
|
||||
|
||||
if (_.isString(options.filter)) {
|
||||
options.filter = resolvePaths(data, options.filter);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
|
@ -9,17 +9,20 @@ var should = require('should'),
|
||||
// Stuff we are testing
|
||||
handlebars = hbs.handlebars,
|
||||
helpers = require('../../../server/helpers'),
|
||||
api = require('../../../server/api');
|
||||
api = require('../../../server/api'),
|
||||
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
describe('{{#get}} helper', function () {
|
||||
var sandbox;
|
||||
var fn, inverse;
|
||||
|
||||
before(function () {
|
||||
utils.loadHelpers();
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox = sinon.sandbox.create();
|
||||
fn = sandbox.spy();
|
||||
inverse = sandbox.spy();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@ -31,18 +34,19 @@ describe('{{#get}} helper', function () {
|
||||
});
|
||||
|
||||
describe('posts', function () {
|
||||
var testPostsArr = [
|
||||
{id: 1, title: 'Test Post 1', author: 'cameron'},
|
||||
{id: 2, title: 'Test Post 2', author: 'cameron', featured: true},
|
||||
var browseStub, readStub, testPostsArr = [
|
||||
{id: 1, title: 'Test Post 1', author: {slug: 'cameron'}},
|
||||
{id: 2, title: 'Test Post 2', author: {slug: 'cameron'}, featured: true},
|
||||
{id: 3, title: 'Test Post 3', tags: [{slug: 'test'}]},
|
||||
{id: 4, title: 'Test Post 4'}
|
||||
];
|
||||
],
|
||||
meta = {pagination: {}};
|
||||
beforeEach(function () {
|
||||
var browseStub = sandbox.stub(api.posts, 'browse'),
|
||||
readStub = sandbox.stub(api.posts, 'read');
|
||||
browseStub = sandbox.stub(api.posts, 'browse');
|
||||
readStub = sandbox.stub(api.posts, 'read');
|
||||
|
||||
browseStub.returns(new Promise.resolve({posts: testPostsArr}));
|
||||
browseStub.withArgs({limit: '3'}).returns(new Promise.resolve({posts: testPostsArr.slice(0, 3)}));
|
||||
browseStub.withArgs({limit: '3'}).returns(new Promise.resolve({posts: testPostsArr.slice(0, 3), meta: meta}));
|
||||
browseStub.withArgs({limit: '1'}).returns(new Promise.resolve({posts: testPostsArr.slice(0, 1)}));
|
||||
browseStub.withArgs({tag: 'test'}).returns(new Promise.resolve({posts: testPostsArr.slice(2, 3)}));
|
||||
browseStub.withArgs({tag: 'none'}).returns(new Promise.resolve({posts: []}));
|
||||
@ -52,9 +56,6 @@ describe('{{#get}} helper', function () {
|
||||
});
|
||||
|
||||
it('should handle default browse posts call', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.get.call(
|
||||
{},
|
||||
'posts',
|
||||
@ -71,9 +72,6 @@ describe('{{#get}} helper', function () {
|
||||
});
|
||||
|
||||
it('should handle browse posts call with limit 3', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.get.call(
|
||||
{},
|
||||
'posts',
|
||||
@ -90,9 +88,6 @@ describe('{{#get}} helper', function () {
|
||||
});
|
||||
|
||||
it('should handle browse posts call with limit 1', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.get.call(
|
||||
{},
|
||||
'posts',
|
||||
@ -109,9 +104,6 @@ describe('{{#get}} helper', function () {
|
||||
});
|
||||
|
||||
it('should handle browse posts call with limit 1', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.get.call(
|
||||
{},
|
||||
'posts',
|
||||
@ -128,9 +120,6 @@ describe('{{#get}} helper', function () {
|
||||
});
|
||||
|
||||
it('should handle browse post call with explicit tag', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.get.call(
|
||||
{},
|
||||
'posts',
|
||||
@ -146,9 +135,6 @@ describe('{{#get}} helper', function () {
|
||||
});
|
||||
|
||||
it('should handle browse post call with relative tag', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.get.call(
|
||||
{},
|
||||
'posts',
|
||||
@ -164,9 +150,6 @@ describe('{{#get}} helper', function () {
|
||||
});
|
||||
|
||||
it('should handle browse post call with explicit author', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.get.call(
|
||||
{},
|
||||
'posts',
|
||||
@ -182,9 +165,6 @@ describe('{{#get}} helper', function () {
|
||||
});
|
||||
|
||||
it('should handle browse post call with relative author', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.get.call(
|
||||
{},
|
||||
'posts',
|
||||
@ -200,9 +180,6 @@ describe('{{#get}} helper', function () {
|
||||
});
|
||||
|
||||
it('should handle browse post call with featured:true', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.get.call(
|
||||
{},
|
||||
'posts',
|
||||
@ -218,9 +195,6 @@ describe('{{#get}} helper', function () {
|
||||
});
|
||||
|
||||
it('should handle read post by id call', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.get.call(
|
||||
{},
|
||||
'posts',
|
||||
@ -237,9 +211,6 @@ describe('{{#get}} helper', function () {
|
||||
});
|
||||
|
||||
it('should handle empty result set', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.get.call(
|
||||
{},
|
||||
'posts',
|
||||
@ -257,9 +228,6 @@ describe('{{#get}} helper', function () {
|
||||
|
||||
describe('general error handling', function () {
|
||||
it('should return an error for an unknown resource', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.get.call(
|
||||
{},
|
||||
'magic',
|
||||
@ -276,9 +244,6 @@ describe('{{#get}} helper', function () {
|
||||
});
|
||||
|
||||
it('should handle error from the API', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.get.call(
|
||||
{},
|
||||
'posts',
|
||||
@ -295,9 +260,6 @@ describe('{{#get}} helper', function () {
|
||||
});
|
||||
|
||||
it('should show warning for call without any options', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy();
|
||||
|
||||
helpers.get.call(
|
||||
{},
|
||||
'posts'
|
||||
@ -309,4 +271,89 @@ describe('{{#get}} helper', function () {
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('path resolution', function () {
|
||||
var browseStub, readStub, data = {
|
||||
post: {id: 3, title: 'Test 3', author: {slug: 'cameron'}, tags: [{slug: 'test'}, {slug: 'magic'}]}
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
browseStub = sandbox.stub(api.posts, 'browse').returns(new Promise.resolve());
|
||||
readStub = sandbox.stub(api.posts, 'read').returns(new Promise.resolve());
|
||||
});
|
||||
|
||||
it('should resolve post.tags alias', function (done) {
|
||||
helpers.get.call(
|
||||
data,
|
||||
'posts',
|
||||
{hash: {filter: 'tags:[{{post.tags}}]'}, fn: fn, inverse: inverse}
|
||||
).then(function () {
|
||||
browseStub.firstCall.args.should.be.an.Array.with.lengthOf(1);
|
||||
browseStub.firstCall.args[0].should.be.an.Object.with.property('filter');
|
||||
browseStub.firstCall.args[0].filter.should.eql('tags:[test,magic]');
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('should resolve post.author alias', function (done) {
|
||||
helpers.get.call(
|
||||
data,
|
||||
'posts',
|
||||
{hash: {filter: 'author:{{post.author}}'}, fn: fn, inverse: inverse}
|
||||
).then(function () {
|
||||
browseStub.firstCall.args.should.be.an.Array.with.lengthOf(1);
|
||||
browseStub.firstCall.args[0].should.be.an.Object.with.property('filter');
|
||||
browseStub.firstCall.args[0].filter.should.eql('author:cameron');
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('should resolve basic path', function (done) {
|
||||
helpers.get.call(
|
||||
data,
|
||||
'posts',
|
||||
{hash: {filter: 'id:-{{post.id}}'}, fn: fn, inverse: inverse}
|
||||
).then(function () {
|
||||
browseStub.firstCall.args.should.be.an.Array.with.lengthOf(1);
|
||||
browseStub.firstCall.args[0].should.be.an.Object.with.property('filter');
|
||||
browseStub.firstCall.args[0].filter.should.eql('id:-3');
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('should handle arrays the same as handlebars', function (done) {
|
||||
var tpl = handlebars.compile('{{post.tags.[0].slug}}'),
|
||||
output = tpl(data);
|
||||
|
||||
helpers.get.call(
|
||||
data,
|
||||
'posts',
|
||||
{hash: {filter: 'tags:{{post.tags.[0].slug}}'}, fn: fn, inverse: inverse}
|
||||
).then(function () {
|
||||
browseStub.firstCall.args.should.be.an.Array.with.lengthOf(1);
|
||||
browseStub.firstCall.args[0].should.be.an.Object.with.property('filter');
|
||||
browseStub.firstCall.args[0].filter.should.eql('tags:' + output);
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('should output nothing if path does not resolve', function (done) {
|
||||
helpers.get.call(
|
||||
data,
|
||||
'posts',
|
||||
{hash: {filter: 'id:{{post.thing}}'}, fn: fn, inverse: inverse}
|
||||
).then(function () {
|
||||
browseStub.firstCall.args.should.be.an.Array.with.lengthOf(1);
|
||||
browseStub.firstCall.args[0].should.be.an.Object.with.property('filter');
|
||||
browseStub.firstCall.args[0].filter.should.eql('id:');
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -46,6 +46,7 @@
|
||||
"html-to-text": "1.3.2",
|
||||
"intl": "1.0.0",
|
||||
"intl-messageformat": "1.1.0",
|
||||
"jsonpath": "0.2.0",
|
||||
"knex": "0.7.3",
|
||||
"lodash": "3.10.1",
|
||||
"moment": "2.10.6",
|
||||
|
Loading…
Reference in New Issue
Block a user