mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
✨ Prev & next post filtering, with primary tag support (#9141)
closes #9140 * Rip out existing prev/next implementation * New implementation using filter * Support next/prev in primary_tag
This commit is contained in:
parent
8de691d575
commit
1c382792ef
8 changed files with 343 additions and 297 deletions
|
@ -9,8 +9,7 @@ var Promise = require('bluebird'),
|
|||
i18n = require('../i18n'),
|
||||
docName = 'posts',
|
||||
allowedIncludes = [
|
||||
'created_by', 'updated_by', 'published_by', 'author', 'tags', 'fields',
|
||||
'next', 'previous', 'next.author', 'next.tags', 'previous.author', 'previous.tags'
|
||||
'created_by', 'updated_by', 'published_by', 'author', 'tags', 'fields'
|
||||
],
|
||||
unsafeAttrs = ['author_id'],
|
||||
posts;
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
// `{{#next_post}}<a href ="{{url absolute="true">next post</a>{{/next_post}}'
|
||||
|
||||
var proxy = require('./proxy'),
|
||||
_ = require('lodash'),
|
||||
Promise = require('bluebird'),
|
||||
moment = require('moment'),
|
||||
|
||||
logging = proxy.logging,
|
||||
i18n = proxy.i18n,
|
||||
|
@ -13,20 +15,41 @@ var proxy = require('./proxy'),
|
|||
api = proxy.api,
|
||||
isPost = proxy.checks.isPost,
|
||||
|
||||
fetch;
|
||||
fetch,
|
||||
buildApiOptions;
|
||||
|
||||
fetch = function fetch(apiOptions, options, data) {
|
||||
var self = this;
|
||||
buildApiOptions = function buildApiOptions(options, post) {
|
||||
var publishedAt = moment(post.published_at).format('YYYY-MM-DD HH:mm:ss'),
|
||||
slug = post.slug,
|
||||
op = options.name === 'prev_post' ? '<=' : '>',
|
||||
order = options.name === 'prev_post' ? 'desc' : 'asc',
|
||||
apiOptions = {
|
||||
include: 'author,tags',
|
||||
order: 'published_at ' + order,
|
||||
limit: 1,
|
||||
// This line deliberately uses double quotes because GQL cannot handle either double quotes
|
||||
// or escaped singles, see TryGhost/GQL#34
|
||||
filter: "slug:-" + slug + "+published_at:" + op + "'" + publishedAt + "'", // jscs:ignore
|
||||
};
|
||||
|
||||
if (_.get(options, 'hash.in') && options.hash.in === 'primary_tag' && _.get(post, 'primary_tag.slug')) {
|
||||
apiOptions.filter += '+primary_tag:' + post.primary_tag.slug;
|
||||
}
|
||||
|
||||
return apiOptions;
|
||||
};
|
||||
|
||||
fetch = function fetch(options, data) {
|
||||
var self = this,
|
||||
apiOptions = buildApiOptions(options, this);
|
||||
|
||||
return api.posts
|
||||
.read(apiOptions)
|
||||
.browse(apiOptions)
|
||||
.then(function handleSuccess(result) {
|
||||
var related = result.posts[0];
|
||||
|
||||
if (related.previous) {
|
||||
return options.fn(related.previous, {data: data});
|
||||
} else if (related.next) {
|
||||
return options.fn(related.next, {data: data});
|
||||
if (related) {
|
||||
return options.fn(related, {data: data});
|
||||
} else {
|
||||
return options.inverse(self, {data: data});
|
||||
}
|
||||
|
@ -44,21 +67,20 @@ fetch = function fetch(apiOptions, options, data) {
|
|||
module.exports = function prevNext(options) {
|
||||
options = options || {};
|
||||
|
||||
var data = createFrame(options.data),
|
||||
apiOptions = {
|
||||
include: options.name === 'prev_post' ? 'previous,previous.author,previous.tags' : 'next,next.author,next.tags'
|
||||
};
|
||||
var data = createFrame(options.data);
|
||||
|
||||
if (!options.fn) {
|
||||
// Guard against incorrect usage of the helpers
|
||||
if (!options.fn || !options.inverse) {
|
||||
data.error = i18n.t('warnings.helpers.mustBeCalledAsBlock', {helperName: options.name});
|
||||
logging.warn(data.error);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (isPost(this) && this.status === 'published') {
|
||||
apiOptions.slug = this.slug;
|
||||
return fetch(apiOptions, options, data);
|
||||
} else {
|
||||
// Guard against trying to execute prev/next on previews, pages, or other resources
|
||||
if (!isPost(this) || this.status !== 'published' || this.page) {
|
||||
return Promise.resolve(options.inverse(this, {data: data}));
|
||||
}
|
||||
|
||||
// With the guards out of the way, attempt to build the apiOptions, and then fetch the data
|
||||
return fetch.call(this, options, data);
|
||||
};
|
||||
|
|
|
@ -664,23 +664,6 @@ Post = ghostBookshelf.Model.extend({
|
|||
findOne: function findOne(data, options) {
|
||||
options = options || {};
|
||||
|
||||
var withNext = _.includes(options.include, 'next'),
|
||||
withPrev = _.includes(options.include, 'previous'),
|
||||
nextRelations = _.transform(options.include, function (relations, include) {
|
||||
if (include === 'next.tags') {
|
||||
relations.push('tags');
|
||||
} else if (include === 'next.author') {
|
||||
relations.push('author');
|
||||
}
|
||||
}, []),
|
||||
prevRelations = _.transform(options.include, function (relations, include) {
|
||||
if (include === 'previous.tags') {
|
||||
relations.push('tags');
|
||||
} else if (include === 'previous.author') {
|
||||
relations.push('author');
|
||||
}
|
||||
}, []);
|
||||
|
||||
data = _.defaults(data || {}, {
|
||||
status: 'published'
|
||||
});
|
||||
|
@ -689,52 +672,9 @@ Post = ghostBookshelf.Model.extend({
|
|||
delete data.status;
|
||||
}
|
||||
|
||||
// Add related objects, excluding next and previous as they are not real db objects
|
||||
options.withRelated = _.union(options.withRelated, _.pull(
|
||||
[].concat(options.include),
|
||||
'next', 'next.author', 'next.tags', 'previous', 'previous.author', 'previous.tags')
|
||||
);
|
||||
options.withRelated = _.union(options.withRelated, options.include);
|
||||
|
||||
return ghostBookshelf.Model.findOne.call(this, data, options).then(function then(post) {
|
||||
if ((withNext || withPrev) && post && !post.page) {
|
||||
var publishedAt = moment(post.get('published_at')).format('YYYY-MM-DD HH:mm:ss'),
|
||||
prev,
|
||||
next;
|
||||
|
||||
if (withNext) {
|
||||
next = Post.forge().query(function queryBuilder(qb) {
|
||||
qb.where('status', '=', 'published')
|
||||
.andWhere('page', '=', 0)
|
||||
.andWhere('published_at', '>', publishedAt)
|
||||
.orderBy('published_at', 'asc')
|
||||
.limit(1);
|
||||
}).fetch({withRelated: nextRelations});
|
||||
}
|
||||
|
||||
if (withPrev) {
|
||||
prev = Post.forge().query(function queryBuilder(qb) {
|
||||
qb.where('status', '=', 'published')
|
||||
.andWhere('page', '=', 0)
|
||||
.andWhere('published_at', '<', publishedAt)
|
||||
.orderBy('published_at', 'desc')
|
||||
.limit(1);
|
||||
}).fetch({withRelated: prevRelations});
|
||||
}
|
||||
|
||||
return Promise.join(next, prev)
|
||||
.then(function then(nextAndPrev) {
|
||||
if (nextAndPrev[0]) {
|
||||
post.relations.next = nextAndPrev[0];
|
||||
}
|
||||
if (nextAndPrev[1]) {
|
||||
post.relations.previous = nextAndPrev[1];
|
||||
}
|
||||
return post;
|
||||
});
|
||||
}
|
||||
|
||||
return post;
|
||||
});
|
||||
return ghostBookshelf.Model.findOne.call(this, data, options);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -439,32 +439,6 @@ describe('Post API', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('can retrieve next and previous posts', function (done) {
|
||||
request.get(testUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[2].id + '/?include=next,previous'))
|
||||
.set('Authorization', 'Bearer ' + ownerAccessToken)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
var jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.posts);
|
||||
testUtils.API.checkResponse(jsonResponse.posts[0], 'post', ['next', 'previous']);
|
||||
jsonResponse.posts[0].page.should.not.be.ok();
|
||||
|
||||
jsonResponse.posts[0].next.should.be.an.Object();
|
||||
testUtils.API.checkResponse(jsonResponse.posts[0].next, 'post');
|
||||
jsonResponse.posts[0].previous.should.be.an.Object();
|
||||
testUtils.API.checkResponse(jsonResponse.posts[0].previous, 'post');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can retrieve a static page', function (done) {
|
||||
request.get(testUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[5].id + '/'))
|
||||
.set('Authorization', 'Bearer ' + ownerAccessToken)
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
var should = require('should'),
|
||||
supertest = require('supertest'),
|
||||
_ = require('lodash'),
|
||||
moment = require('moment'),
|
||||
testUtils = require('../../../utils'),
|
||||
configUtils = require('../../../utils/configUtils'),
|
||||
_ = require('lodash'),
|
||||
config = require('../../../../../core/server/config'),
|
||||
ghost = testUtils.startGhost,
|
||||
request;
|
||||
|
@ -504,4 +505,65 @@ describe('Public API', function () {
|
|||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fetch the most recent post, then the prev, then the next should match the first', function (done) {
|
||||
function createFilter(publishedAt, op) {
|
||||
// This line deliberately uses double quotes because GQL cannot handle either double quotes
|
||||
// or escaped singles, see TryGhost/GQL#34
|
||||
return encodeURIComponent("published_at:" + op + "'" + publishedAt + "'"); // jscs:ignore
|
||||
}
|
||||
|
||||
request
|
||||
.get(testUtils.API.getApiQuery(
|
||||
'posts/?client_id=ghost-admin&client_secret=not_available&limit=1'
|
||||
))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then(function (res) {
|
||||
should.exist(res.body.posts[0]);
|
||||
var post = res.body.posts[0],
|
||||
publishedAt = moment(post.published_at).format('YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
post.title.should.eql('Welcome to Ghost');
|
||||
|
||||
return request
|
||||
.get(testUtils.API.getApiQuery(
|
||||
'posts/?client_id=ghost-admin&client_secret=not_available&limit=1&filter='
|
||||
+ createFilter(publishedAt, '<')
|
||||
))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200);
|
||||
})
|
||||
.then(function (res) {
|
||||
should.exist(res.body.posts[0]);
|
||||
var post = res.body.posts[0],
|
||||
publishedAt = moment(post.published_at).format('YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
post.title.should.eql('Using the Ghost editor');
|
||||
|
||||
return request
|
||||
.get(testUtils.API.getApiQuery(
|
||||
'posts/?client_id=ghost-admin&client_secret=not_available&limit=1&filter='
|
||||
+ createFilter(publishedAt, '>')
|
||||
))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200);
|
||||
})
|
||||
.then(function (res) {
|
||||
should.exist(res.body.posts[0]);
|
||||
var post = res.body.posts[0];
|
||||
|
||||
post.title.should.eql('Welcome to Ghost');
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -627,91 +627,6 @@ describe('Post API', function () {
|
|||
}).catch(done);
|
||||
});
|
||||
|
||||
it('can include next post', function (done) {
|
||||
PostAPI.read({
|
||||
context: {user: testUtils.DataGenerator.Content.users[1].id},
|
||||
id: testUtils.DataGenerator.Content.posts[2].id,
|
||||
include: 'next'
|
||||
}).then(function (results) {
|
||||
should.exist(results.posts[0].next.slug);
|
||||
results.posts[0].next.slug.should.eql('not-so-short-bit-complex');
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('can include next post with author and tags', function (done) {
|
||||
PostAPI.read({
|
||||
context: {user: testUtils.DataGenerator.Content.users[1].id},
|
||||
id: testUtils.DataGenerator.Content.posts[2].id,
|
||||
include: 'next,next.tags,next.author'
|
||||
}).then(function (results) {
|
||||
should.exist(results.posts[0].next.slug);
|
||||
results.posts[0].next.slug.should.eql('not-so-short-bit-complex');
|
||||
results.posts[0].next.author.should.be.an.Object();
|
||||
results.posts[0].next.tags.should.be.an.Array();
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('can include next post with just tags', function (done) {
|
||||
PostAPI.read({
|
||||
context: {user: testUtils.DataGenerator.Content.users[1].id},
|
||||
id: testUtils.DataGenerator.Content.posts[1].id,
|
||||
include: 'next,next.tags'
|
||||
}).then(function (results) {
|
||||
should.exist(results.posts[0].next.slug);
|
||||
results.posts[0].next.slug.should.eql('short-and-sweet');
|
||||
results.posts[0].next.author.should.eql('1');
|
||||
results.posts[0].next.tags.should.be.an.Array();
|
||||
results.posts[0].next.tags[0].name.should.eql('chorizo');
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('can include previous post', function (done) {
|
||||
PostAPI.read({
|
||||
context: {user: testUtils.DataGenerator.Content.users[1].id},
|
||||
id: testUtils.DataGenerator.Content.posts[2].id,
|
||||
include: 'previous'
|
||||
}).then(function (results) {
|
||||
should.exist(results.posts[0].previous.slug);
|
||||
results.posts[0].previous.slug.should.eql('ghostly-kitchen-sink');
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('can include previous post with author and tags', function (done) {
|
||||
PostAPI.read({
|
||||
context: {user: testUtils.DataGenerator.Content.users[1].id},
|
||||
id: testUtils.DataGenerator.Content.posts[2].id,
|
||||
include: 'previous,previous.author,previous.tags'
|
||||
}).then(function (results) {
|
||||
should.exist(results.posts[0].previous.slug);
|
||||
results.posts[0].previous.slug.should.eql('ghostly-kitchen-sink');
|
||||
results.posts[0].previous.author.should.be.an.Object();
|
||||
results.posts[0].previous.author.name.should.eql('Joe Bloggs');
|
||||
results.posts[0].previous.tags.should.be.an.Array();
|
||||
results.posts[0].previous.tags.should.have.lengthOf(2);
|
||||
results.posts[0].previous.tags[0].slug.should.eql('kitchen-sink');
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('can include previous post with just author', function (done) {
|
||||
PostAPI.read({
|
||||
context: {user: testUtils.DataGenerator.Content.users[1].id},
|
||||
id: testUtils.DataGenerator.Content.posts[2].id,
|
||||
include: 'previous,previous.author'
|
||||
}).then(function (results) {
|
||||
should.exist(results.posts[0].previous.slug);
|
||||
should.not.exist(results.posts[0].previous.tags);
|
||||
results.posts[0].previous.slug.should.eql('ghostly-kitchen-sink');
|
||||
results.posts[0].previous.author.should.be.an.Object();
|
||||
results.posts[0].previous.author.name.should.eql('Joe Bloggs');
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
// TODO: this should be a 422?
|
||||
it('cannot fetch a post with an invalid slug', function (done) {
|
||||
PostAPI.read({slug: 'invalid!'}).then(function () {
|
||||
|
|
|
@ -2,6 +2,7 @@ var should = require('should'), // jshint ignore:line
|
|||
sinon = require('sinon'),
|
||||
Promise = require('bluebird'),
|
||||
markdownToMobiledoc = require('../../utils/fixtures/data-generator').markdownToMobiledoc,
|
||||
|
||||
// Stuff we are testing
|
||||
helpers = require('../../../server/helpers'),
|
||||
api = require('../../../server/api'),
|
||||
|
@ -10,7 +11,7 @@ var should = require('should'), // jshint ignore:line
|
|||
sandbox = sinon.sandbox.create();
|
||||
|
||||
describe('{{next_post}} helper', function () {
|
||||
var readPostStub;
|
||||
var browsePostStub;
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
|
@ -18,10 +19,10 @@ describe('{{next_post}} helper', function () {
|
|||
|
||||
describe('with valid post data - ', function () {
|
||||
beforeEach(function () {
|
||||
readPostStub = sandbox.stub(api.posts, 'read', function (options) {
|
||||
if (options.include.indexOf('next') === 0) {
|
||||
browsePostStub = sandbox.stub(api.posts, 'browse', function (options) {
|
||||
if (options.filter.indexOf('published_at:>') > -1) {
|
||||
return Promise.resolve({
|
||||
posts: [{slug: '/current/', title: 'post 2', next: {slug: '/next/', title: 'post 3'}}]
|
||||
posts: [{slug: '/next/', title: 'post 3'}]
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -32,26 +33,26 @@ describe('{{next_post}} helper', function () {
|
|||
inverse = sinon.spy(),
|
||||
optionsData = {name: 'next_post', fn: fn, inverse: inverse};
|
||||
|
||||
helpers.prev_post
|
||||
helpers.next_post
|
||||
.call({
|
||||
html: 'content',
|
||||
status: 'published',
|
||||
mobiledoc: markdownToMobiledoc('ff'),
|
||||
title: 'post2',
|
||||
slug: 'current',
|
||||
created_at: new Date(0),
|
||||
published_at: new Date(0),
|
||||
url: '/current/'
|
||||
}, optionsData)
|
||||
.then(function () {
|
||||
fn.calledOnce.should.be.true();
|
||||
inverse.calledOnce.should.be.false();
|
||||
|
||||
console.log(fn.firstCall.args);
|
||||
fn.firstCall.args.should.have.lengthOf(2);
|
||||
fn.firstCall.args[0].should.have.properties('slug', 'title');
|
||||
fn.firstCall.args[1].should.be.an.Object().and.have.property('data');
|
||||
readPostStub.calledOnce.should.be.true();
|
||||
readPostStub.firstCall.args[0].include.should.eql('next,next.author,next.tags');
|
||||
browsePostStub.calledOnce.should.be.true();
|
||||
browsePostStub.firstCall.args[0].include.should.eql('author,tags');
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
|
@ -60,9 +61,9 @@ describe('{{next_post}} helper', function () {
|
|||
|
||||
describe('for valid post with no next post', function () {
|
||||
beforeEach(function () {
|
||||
readPostStub = sandbox.stub(api.posts, 'read', function (options) {
|
||||
if (options.include.indexOf('next') === 0) {
|
||||
return Promise.resolve({posts: [{slug: '/current/', title: 'post 2'}]});
|
||||
browsePostStub = sandbox.stub(api.posts, 'browse', function (options) {
|
||||
if (options.filter.indexOf('published_at:>') > -1) {
|
||||
return Promise.resolve({posts: []});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -72,20 +73,20 @@ describe('{{next_post}} helper', function () {
|
|||
inverse = sinon.spy(),
|
||||
optionsData = {name: 'next_post', fn: fn, inverse: inverse};
|
||||
|
||||
helpers.prev_post
|
||||
helpers.next_post
|
||||
.call({
|
||||
html: 'content',
|
||||
status: 'published',
|
||||
mobiledoc: markdownToMobiledoc('ff'),
|
||||
title: 'post2',
|
||||
slug: 'current',
|
||||
created_at: new Date(0),
|
||||
published_at: new Date(0),
|
||||
url: '/current/'
|
||||
}, optionsData)
|
||||
.then(function () {
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
|
||||
console.log(inverse.firstCall.args);
|
||||
inverse.firstCall.args.should.have.lengthOf(2);
|
||||
inverse.firstCall.args[0].should.have.properties('slug', 'title');
|
||||
inverse.firstCall.args[1].should.be.an.Object().and.have.property('data');
|
||||
|
@ -98,8 +99,8 @@ describe('{{next_post}} helper', function () {
|
|||
|
||||
describe('for invalid post data', function () {
|
||||
beforeEach(function () {
|
||||
readPostStub = sandbox.stub(api.posts, 'read', function (options) {
|
||||
if (options.include.indexOf('next') === 0) {
|
||||
browsePostStub = sandbox.stub(api.posts, 'browse', function (options) {
|
||||
if (options.filter.indexOf('published_at:>') > -1) {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
});
|
||||
|
@ -110,25 +111,24 @@ describe('{{next_post}} helper', function () {
|
|||
inverse = sinon.spy(),
|
||||
optionsData = {name: 'next_post', fn: fn, inverse: inverse};
|
||||
|
||||
helpers.prev_post
|
||||
helpers.next_post
|
||||
.call({}, optionsData)
|
||||
.then(function () {
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
readPostStub.called.should.be.false();
|
||||
browsePostStub.called.should.be.false();
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('for unpublished post', function () {
|
||||
describe('for page', function () {
|
||||
beforeEach(function () {
|
||||
readPostStub = sandbox.stub(api.posts, 'read', function (options) {
|
||||
if (options.include.indexOf('next') === 0) {
|
||||
return Promise.resolve({
|
||||
posts: [{slug: '/current/', title: 'post 2', next: {slug: '/next/', title: 'post 3'}}]
|
||||
});
|
||||
browsePostStub = sandbox.stub(api.posts, 'browse', function (options) {
|
||||
if (options.filter.indexOf('published_at:>') > -1) {
|
||||
return Promise.resolve({posts: [{slug: '/previous/', title: 'post 1'}]});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -138,7 +138,42 @@ describe('{{next_post}} helper', function () {
|
|||
inverse = sinon.spy(),
|
||||
optionsData = {name: 'next_post', fn: fn, inverse: inverse};
|
||||
|
||||
helpers.prev_post
|
||||
helpers.next_post
|
||||
.call({
|
||||
html: 'content',
|
||||
status: 'published',
|
||||
mobiledoc: markdownToMobiledoc('ff'),
|
||||
title: 'post2',
|
||||
slug: 'current',
|
||||
published_at: new Date(0),
|
||||
url: '/current/',
|
||||
page: true
|
||||
}, optionsData)
|
||||
.then(function () {
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('for unpublished post', function () {
|
||||
beforeEach(function () {
|
||||
browsePostStub = sandbox.stub(api.posts, 'browse', function (options) {
|
||||
if (options.filter.indexOf('published_at:>') > -1) {
|
||||
return Promise.resolve({posts: [{slug: '/next/', title: 'post 3'}]});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('shows \'else\' template', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy(),
|
||||
optionsData = {name: 'next_post', fn: fn, inverse: inverse};
|
||||
|
||||
helpers.next_post
|
||||
.call({
|
||||
html: 'content',
|
||||
status: 'draft',
|
||||
|
@ -151,6 +186,7 @@ describe('{{next_post}} helper', function () {
|
|||
.then(function () {
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
|
@ -159,7 +195,7 @@ describe('{{next_post}} helper', function () {
|
|||
|
||||
describe('general error handling', function () {
|
||||
beforeEach(function () {
|
||||
readPostStub = sandbox.stub(api.posts, 'read', function () {
|
||||
browsePostStub = sandbox.stub(api.posts, 'browse', function () {
|
||||
return Promise.reject(new errors.NotFoundError({message: 'Something wasn\'t found'}));
|
||||
});
|
||||
});
|
||||
|
@ -169,14 +205,14 @@ describe('{{next_post}} helper', function () {
|
|||
inverse = sinon.spy(),
|
||||
optionsData = {name: 'next_post', fn: fn, inverse: inverse};
|
||||
|
||||
helpers.prev_post
|
||||
helpers.next_post
|
||||
.call({
|
||||
html: 'content',
|
||||
status: 'published',
|
||||
mobiledoc: markdownToMobiledoc('ff'),
|
||||
title: 'post2',
|
||||
slug: 'current',
|
||||
created_at: new Date(0),
|
||||
published_at: new Date(0),
|
||||
url: '/current/'
|
||||
}, optionsData)
|
||||
.then(function () {
|
||||
|
@ -197,7 +233,7 @@ describe('{{next_post}} helper', function () {
|
|||
inverse = sinon.spy(),
|
||||
optionsData = {name: 'next_post'};
|
||||
|
||||
helpers.prev_post
|
||||
helpers.next_post
|
||||
.call(
|
||||
{},
|
||||
optionsData
|
||||
|
|
|
@ -6,11 +6,12 @@ var should = require('should'), // jshint ignore:line
|
|||
// Stuff we are testing
|
||||
helpers = require('../../../server/helpers'),
|
||||
api = require('../../../server/api'),
|
||||
errors = require('../../../server/errors'),
|
||||
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
describe('{{prev_post}} helper', function () {
|
||||
var readPostStub;
|
||||
var browsePostStub;
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
|
@ -18,10 +19,10 @@ describe('{{prev_post}} helper', function () {
|
|||
|
||||
describe('with valid post data - ', function () {
|
||||
beforeEach(function () {
|
||||
readPostStub = sandbox.stub(api.posts, 'read', function (options) {
|
||||
if (options.include.indexOf('previous') === 0) {
|
||||
browsePostStub = sandbox.stub(api.posts, 'browse', function (options) {
|
||||
if (options.filter.indexOf('published_at:<=') > -1) {
|
||||
return Promise.resolve({
|
||||
posts: [{slug: '/current/', title: 'post 2', previous: {slug: '/previous/', title: 'post 1'}}]
|
||||
posts: [{slug: '/previous/', title: 'post 1'}]
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -32,34 +33,37 @@ describe('{{prev_post}} helper', function () {
|
|||
inverse = sinon.spy(),
|
||||
optionsData = {name: 'prev_post', fn: fn, inverse: inverse};
|
||||
|
||||
helpers.prev_post.call({
|
||||
html: 'content',
|
||||
status: 'published',
|
||||
mobiledoc: markdownToMobiledoc('ff'),
|
||||
title: 'post2',
|
||||
slug: 'current',
|
||||
created_at: new Date(0),
|
||||
url: '/current/'
|
||||
}, optionsData).then(function () {
|
||||
fn.calledOnce.should.be.true();
|
||||
inverse.calledOnce.should.be.false();
|
||||
helpers.prev_post
|
||||
.call({
|
||||
html: 'content',
|
||||
status: 'published',
|
||||
mobiledoc: markdownToMobiledoc('ff'),
|
||||
title: 'post2',
|
||||
slug: 'current',
|
||||
published_at: new Date(0),
|
||||
url: '/current/'
|
||||
}, optionsData)
|
||||
.then(function () {
|
||||
fn.calledOnce.should.be.true();
|
||||
inverse.calledOnce.should.be.false();
|
||||
|
||||
readPostStub.calledOnce.should.be.true();
|
||||
readPostStub.firstCall.args[0].include.should.eql('previous,previous.author,previous.tags');
|
||||
fn.firstCall.args.should.have.lengthOf(2);
|
||||
fn.firstCall.args[0].should.have.properties('slug', 'title');
|
||||
fn.firstCall.args[1].should.be.an.Object().and.have.property('data');
|
||||
browsePostStub.calledOnce.should.be.true();
|
||||
browsePostStub.firstCall.args[0].include.should.eql('author,tags');
|
||||
|
||||
done();
|
||||
}).catch(function (err) {
|
||||
console.log('err ', err);
|
||||
done(err);
|
||||
});
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('for valid post with no previous post', function () {
|
||||
beforeEach(function () {
|
||||
readPostStub = sandbox.stub(api.posts, 'read', function (options) {
|
||||
if (options.include.indexOf('previous') === 0) {
|
||||
return Promise.resolve({posts: [{slug: '/current/', title: 'post 2'}]});
|
||||
browsePostStub = sandbox.stub(api.posts, 'browse', function (options) {
|
||||
if (options.filter.indexOf('published_at:<=') > -1) {
|
||||
return Promise.resolve({posts: []});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -69,28 +73,34 @@ describe('{{prev_post}} helper', function () {
|
|||
inverse = sinon.spy(),
|
||||
optionsData = {name: 'prev_post', fn: fn, inverse: inverse};
|
||||
|
||||
helpers.prev_post.call({
|
||||
html: 'content',
|
||||
status: 'published',
|
||||
mobiledoc: markdownToMobiledoc('ff'),
|
||||
title: 'post2',
|
||||
slug: 'current',
|
||||
created_at: new Date(0),
|
||||
url: '/current/'
|
||||
}, optionsData).then(function () {
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
done();
|
||||
}).catch(function (err) {
|
||||
done(err);
|
||||
});
|
||||
helpers.prev_post
|
||||
.call({
|
||||
html: 'content',
|
||||
status: 'published',
|
||||
mobiledoc: markdownToMobiledoc('ff'),
|
||||
title: 'post2',
|
||||
slug: 'current',
|
||||
published_at: new Date(0),
|
||||
url: '/current/'
|
||||
}, optionsData)
|
||||
.then(function () {
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
|
||||
inverse.firstCall.args.should.have.lengthOf(2);
|
||||
inverse.firstCall.args[0].should.have.properties('slug', 'title');
|
||||
inverse.firstCall.args[1].should.be.an.Object().and.have.property('data');
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('for invalid post data', function () {
|
||||
beforeEach(function () {
|
||||
readPostStub = sandbox.stub(api.posts, 'read', function (options) {
|
||||
if (options.include.indexOf('previous') === 0) {
|
||||
browsePostStub = sandbox.stub(api.posts, 'browse', function (options) {
|
||||
if (options.filter.indexOf('published_at:<=') > -1) {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
});
|
||||
|
@ -101,28 +111,24 @@ describe('{{prev_post}} helper', function () {
|
|||
inverse = sinon.spy(),
|
||||
optionsData = {name: 'prev_post', fn: fn, inverse: inverse};
|
||||
|
||||
helpers.prev_post.call({}, optionsData).then(function () {
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
readPostStub.called.should.be.false();
|
||||
done();
|
||||
}).catch(function (err) {
|
||||
done(err);
|
||||
});
|
||||
helpers.prev_post
|
||||
.call({}, optionsData)
|
||||
.then(function () {
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
browsePostStub.called.should.be.false();
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('for unpublished post', function () {
|
||||
describe('for page', function () {
|
||||
beforeEach(function () {
|
||||
readPostStub = sandbox.stub(api.posts, 'read', function (options) {
|
||||
if (options.include.indexOf('previous') === 0) {
|
||||
return Promise.resolve({
|
||||
posts: [{
|
||||
slug: '/current/',
|
||||
title: 'post 2',
|
||||
previous: {slug: '/previous/', title: 'post 1'}
|
||||
}]
|
||||
});
|
||||
browsePostStub = sandbox.stub(api.posts, 'browse', function (options) {
|
||||
if (options.filter.indexOf('published_at:<=') > -1) {
|
||||
return Promise.resolve({posts: [{slug: '/previous/', title: 'post 1'}]});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -132,21 +138,113 @@ describe('{{prev_post}} helper', function () {
|
|||
inverse = sinon.spy(),
|
||||
optionsData = {name: 'prev_post', fn: fn, inverse: inverse};
|
||||
|
||||
helpers.prev_post.call({
|
||||
html: 'content',
|
||||
status: 'draft',
|
||||
mobiledoc: markdownToMobiledoc('ff'),
|
||||
title: 'post2',
|
||||
slug: 'current',
|
||||
created_at: new Date(0),
|
||||
url: '/current/'
|
||||
}, optionsData).then(function () {
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
done();
|
||||
}).catch(function (err) {
|
||||
done(err);
|
||||
helpers.prev_post
|
||||
.call({
|
||||
html: 'content',
|
||||
status: 'published',
|
||||
mobiledoc: markdownToMobiledoc('ff'),
|
||||
title: 'post2',
|
||||
slug: 'current',
|
||||
published_at: new Date(0),
|
||||
url: '/current/',
|
||||
page: true
|
||||
}, optionsData)
|
||||
.then(function () {
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('for unpublished post', function () {
|
||||
beforeEach(function () {
|
||||
browsePostStub = sandbox.stub(api.posts, 'browse', function (options) {
|
||||
if (options.filter.indexOf('published_at:<=') > -1) {
|
||||
return Promise.resolve({posts: [{slug: '/previous/', title: 'post 1'}]});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('shows \'else\' template', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy(),
|
||||
optionsData = {name: 'prev_post', fn: fn, inverse: inverse};
|
||||
|
||||
helpers.prev_post
|
||||
.call({
|
||||
html: 'content',
|
||||
status: 'draft',
|
||||
mobiledoc: markdownToMobiledoc('ff'),
|
||||
title: 'post2',
|
||||
slug: 'current',
|
||||
created_at: new Date(0),
|
||||
url: '/current/'
|
||||
}, optionsData)
|
||||
.then(function () {
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.true();
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('general error handling', function () {
|
||||
beforeEach(function () {
|
||||
browsePostStub = sandbox.stub(api.posts, 'browse', function () {
|
||||
return Promise.reject(new errors.NotFoundError({message: 'Something wasn\'t found'}));
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle error from the API', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy(),
|
||||
optionsData = {name: 'prev_post', fn: fn, inverse: inverse};
|
||||
|
||||
helpers.prev_post
|
||||
.call({
|
||||
html: 'content',
|
||||
status: 'published',
|
||||
mobiledoc: markdownToMobiledoc('ff'),
|
||||
title: 'post2',
|
||||
slug: 'current',
|
||||
published_at: new Date(0),
|
||||
url: '/current/'
|
||||
}, optionsData)
|
||||
.then(function () {
|
||||
fn.called.should.be.false();
|
||||
inverse.calledOnce.should.be.true();
|
||||
|
||||
inverse.firstCall.args[1].should.be.an.Object().and.have.property('data');
|
||||
inverse.firstCall.args[1].data.should.be.an.Object().and.have.property('error');
|
||||
inverse.firstCall.args[1].data.error.should.match(/^Something wasn't found/);
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should show warning for call without any options', function (done) {
|
||||
var fn = sinon.spy(),
|
||||
inverse = sinon.spy(),
|
||||
optionsData = {name: 'prev_post'};
|
||||
|
||||
helpers.prev_post
|
||||
.call(
|
||||
{},
|
||||
optionsData
|
||||
)
|
||||
.then(function () {
|
||||
fn.called.should.be.false();
|
||||
inverse.called.should.be.false();
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue