Merge pull request #6920 from ErisDS/tag-save-fix

Fix post model deletes tags when editing post only
This commit is contained in:
Sebastian Gierlinger 2016-06-03 09:37:58 +02:00
commit cf0835c829
4 changed files with 1091 additions and 602 deletions

View File

@ -141,7 +141,8 @@ Post = ghostBookshelf.Model.extend({
prevTitle = this._previousAttributes.title,
prevSlug = this._previousAttributes.slug,
tagsToCheck = this.get('tags'),
publishedAt = this.get('published_at');
publishedAt = this.get('published_at'),
tags = [];
// both page and post can get scheduled
if (newStatus === 'scheduled') {
@ -164,18 +165,22 @@ Post = ghostBookshelf.Model.extend({
}
}
// keep tags for 'saved' event and deduplicate upper/lowercase tags
this.myTags = [];
_.each(tagsToCheck, function each(item) {
for (i = 0; i < self.myTags.length; i = i + 1) {
if (self.myTags[i].name.toLocaleLowerCase() === item.name.toLocaleLowerCase()) {
return;
// If we have a tags property passed in
if (!_.isUndefined(tagsToCheck) && !_.isNull(tagsToCheck)) {
// and deduplicate upper/lowercase tags
_.each(tagsToCheck, function each(item) {
for (i = 0; i < tags.length; i = i + 1) {
if (tags[i].name.toLocaleLowerCase() === item.name.toLocaleLowerCase()) {
return;
}
}
}
self.myTags.push(item);
});
tags.push(item);
});
// keep tags for 'saved' event
this.tagsToSave = tags;
}
ghostBookshelf.Model.prototype.saving.call(this, model, attr, options);
@ -252,7 +257,12 @@ Post = ghostBookshelf.Model.extend({
* @return {Promise(ghostBookshelf.Models.Post)} Updated Post model
*/
updateTags: function updateTags(savedModel, response, options) {
var newTags = this.myTags,
if (_.isUndefined(this.tagsToSave)) {
// The tag property was not set, so we shouldn't be doing any playing with tags on this request
return Promise.resolve();
}
var newTags = this.tagsToSave,
TagModel = ghostBookshelf.model('Tag');
options = options || {};

View File

@ -584,4 +584,161 @@ describe('Post API', function () {
});
});
});
describe('Edit', function () {
// These tests are for #6920
it('should update post & not delete tags with `tags` not included', function (done) {
var options = {context: {user: 1}, id: 1},
includeOptions = {include: 'tags'},
startTags;
// Step 1, fetch a post from the API with tags
PostAPI.read(_.extend({}, options, includeOptions)).then(function (results) {
var postWithoutTags = results.posts[0];
should.exist(results.posts[0]);
should.exist(results.posts[0].tags);
results.posts[0].tags.should.have.lengthOf(2);
// Save the tags for testing against later
startTags = _.clone(results.posts[0].tags);
// Remove the tags from the object we're sending - we'll send no `tags` property at all
delete postWithoutTags.tags;
// Update a single property so we can see the post does get updated
postWithoutTags.title = 'HTML Ipsum Updated';
// Step 2, call edit but don't include tags in the response
return PostAPI.edit({posts: [postWithoutTags]}, options);
}).then(function (results) {
should.exist(results.posts[0]);
should.not.exist(results.posts[0].tags);
results.posts[0].title.should.eql('HTML Ipsum Updated');
// Step 3, request the post with its tags again, to check they are still present
return PostAPI.read(_.extend({}, options, includeOptions));
}).then(function (results) {
should.exist(results.posts[0]);
should.exist(results.posts[0].tags);
results.posts[0].tags.should.have.lengthOf(2);
results.posts[0].tags.should.eql(startTags);
done();
}).catch(done);
});
it('should update post & not delete tags with `tags` set to undefined', function (done) {
var options = {context: {user: 1}, id: 1},
includeOptions = {include: 'tags'},
startTags;
// Step 1, fetch a post from the API with tags
PostAPI.read(_.extend({}, options, includeOptions)).then(function (results) {
var postWithoutTags = results.posts[0];
should.exist(results.posts[0]);
should.exist(results.posts[0].tags);
results.posts[0].tags.should.have.lengthOf(2);
// Save the tags for testing against later
startTags = _.clone(results.posts[0].tags);
// Remove the tags from the object we're sending - we'll send no `tags` property at all
postWithoutTags.tags = undefined;
// Update a single property so we can see the post does get updated
postWithoutTags.title = 'HTML Ipsum Updated';
// Step 2, call edit but don't include tags in the response
return PostAPI.edit({posts: [postWithoutTags]}, options);
}).then(function (results) {
should.exist(results.posts[0]);
should.not.exist(results.posts[0].tags);
results.posts[0].title.should.eql('HTML Ipsum Updated');
// Step 3, request the post with its tags again, to check they are still present
return PostAPI.read(_.extend({}, options, includeOptions));
}).then(function (results) {
should.exist(results.posts[0]);
should.exist(results.posts[0].tags);
results.posts[0].tags.should.have.lengthOf(2);
results.posts[0].tags.should.eql(startTags);
done();
}).catch(done);
});
it('should update post & not delete tags with `tags` set to null', function (done) {
var options = {context: {user: 1}, id: 1},
includeOptions = {include: 'tags'},
startTags;
// Step 1, fetch a post from the API with tags
PostAPI.read(_.extend({}, options, includeOptions)).then(function (results) {
var postWithoutTags = results.posts[0];
should.exist(results.posts[0]);
should.exist(results.posts[0].tags);
results.posts[0].tags.should.have.lengthOf(2);
// Save the tags for testing against later
startTags = _.clone(results.posts[0].tags);
// Remove the tags from the object we're sending - we'll send no `tags` property at all
postWithoutTags.tags = null;
// Update a single property so we can see the post does get updated
postWithoutTags.title = 'HTML Ipsum Updated';
// Step 2, call edit but don't include tags in the response
return PostAPI.edit({posts: [postWithoutTags]}, options);
}).then(function (results) {
should.exist(results.posts[0]);
should.not.exist(results.posts[0].tags);
results.posts[0].title.should.eql('HTML Ipsum Updated');
// Step 3, request the post with its tags again, to check they are still present
return PostAPI.read(_.extend({}, options, includeOptions));
}).then(function (results) {
should.exist(results.posts[0]);
should.exist(results.posts[0].tags);
results.posts[0].tags.should.have.lengthOf(2);
results.posts[0].tags.should.eql(startTags);
done();
}).catch(done);
});
it('should update post & should delete tags with `tags` set to []', function (done) {
var options = {context: {user: 1}, id: 1},
includeOptions = {include: 'tags'};
// Step 1, fetch a post from the API with tags
PostAPI.read(_.extend({}, options, includeOptions)).then(function (results) {
var postWithoutTags = results.posts[0];
should.exist(results.posts[0]);
should.exist(results.posts[0].tags);
results.posts[0].tags.should.have.lengthOf(2);
// Remove the tags from the object we're sending - we'll send no `tags` property at all
postWithoutTags.tags = [];
// Update a single property so we can see the post does get updated
postWithoutTags.title = 'HTML Ipsum Updated';
// Step 2, call edit but don't include tags in the response
return PostAPI.edit({posts: [postWithoutTags]}, options);
}).then(function (results) {
should.exist(results.posts[0]);
should.not.exist(results.posts[0].tags);
results.posts[0].title.should.eql('HTML Ipsum Updated');
// Step 3, request the post with its tags again, to check they are still present
return PostAPI.read(_.extend({}, options, includeOptions));
}).then(function (results) {
should.exist(results.posts[0]);
results.posts[0].tags.should.eql([]);
done();
}).catch(done);
});
});
});

View File

@ -3,39 +3,47 @@ var testUtils = require('../../utils'),
should = require('should'),
moment = require('moment'),
_ = require('lodash'),
Promise = require('bluebird'),
sinon = require('sinon'),
// Stuff we are testing
sequence = require('../../../server/utils/sequence'),
ghostBookshelf = require('../../../server/models/base'),
PostModel = require('../../../server/models/post').Post,
TagModel = require('../../../server/models/tag').Tag,
events = require('../../../server/events'),
errors = require('../../../server/errors'),
DataGenerator = testUtils.DataGenerator,
context = testUtils.context.owner,
sandbox = sinon.sandbox.create(),
config = require('../../../server/config'),
origConfig = _.cloneDeep(config);
configUtils = require('../../utils/configUtils'),
sandbox = sinon.sandbox.create();
describe('Post Model', function () {
var eventSpy;
// Keep the DB clean
before(testUtils.teardown);
afterEach(testUtils.teardown);
beforeEach(function () {
eventSpy = sandbox.spy(events, 'emit');
});
afterEach(function () {
sandbox.restore();
});
should.exist(TagModel);
should.exist(PostModel);
describe('Single author posts', function () {
var eventSpy;
before(testUtils.teardown);
afterEach(testUtils.teardown);
afterEach(function () {
sandbox.restore();
config.set(origConfig);
configUtils.restore();
});
beforeEach(testUtils.setup('owner', 'posts', 'apps'));
beforeEach(function () {
should.exist(PostModel);
eventSpy = sandbox.spy(events, 'emit');
});
beforeEach(testUtils.setup('owner', 'posts', 'apps'));
function extractFirstPost(posts) {
return _.filter(posts, {id: 1})[0];
@ -61,7 +69,7 @@ describe('Post Model', function () {
describe('findAll', function () {
beforeEach(function () {
config.set({theme: {
configUtils.set({theme: {
permalinks: '/:slug/'
}});
});
@ -96,7 +104,7 @@ describe('Post Model', function () {
describe('findPage', function () {
beforeEach(function () {
config.set({theme: {
configUtils.set({theme: {
permalinks: '/:slug/'
}});
});
@ -287,7 +295,7 @@ describe('Post Model', function () {
describe('findOne', function () {
beforeEach(function () {
config.set({theme: {
configUtils.set({theme: {
permalinks: '/:slug/'
}});
});
@ -345,7 +353,7 @@ describe('Post Model', function () {
yyyy = today.getFullYear(),
postLink = '/' + yyyy + '/' + mm + '/' + dd + '/html-ipsum/';
config.set({theme: {
configUtils.set({theme: {
permalinks: '/:year/:month/:day/:slug/'
}});
@ -1316,10 +1324,6 @@ describe('Post Model', function () {
afterEach(testUtils.teardown);
beforeEach(testUtils.setup('posts:mu'));
before(function () {
should.exist(PostModel);
});
it('can destroy multiple posts by author', function (done) {
// We're going to delete all posts by user 1
var authorData = {id: 1};
@ -1340,6 +1344,892 @@ describe('Post Model', function () {
});
});
describe('Post tag handling', function () {
beforeEach(testUtils.setup());
describe('Post with tags', function () {
var postJSON,
tagJSON,
editOptions,
createTag = testUtils.DataGenerator.forKnex.createTag;
beforeEach(function (done) {
tagJSON = [];
var post = _.cloneDeep(testUtils.DataGenerator.forModel.posts[0]),
postTags = [
createTag({name: 'tag1'}),
createTag({name: 'tag2'}),
createTag({name: 'tag3'})
],
extraTags = [
createTag({name: 'existing tag a'}),
createTag({name: 'existing-tag-b'}),
createTag({name: 'existing_tag_c'})
];
post.tags = postTags;
post.status = 'published';
return Promise.props({
post: PostModel.add(post, _.extend({}, context, {withRelated: ['tags']})),
tag1: TagModel.add(extraTags[0], context),
tag2: TagModel.add(extraTags[1], context),
tag3: TagModel.add(extraTags[2], context)
}).then(function (result) {
postJSON = result.post.toJSON({include: ['tags']});
tagJSON.push(result.tag1.toJSON());
tagJSON.push(result.tag2.toJSON());
tagJSON.push(result.tag3.toJSON());
editOptions = _.extend({}, context, {id: postJSON.id, withRelated: ['tags']});
// reset the eventSpy here
sandbox.restore();
done();
});
});
it('should create the test data correctly', function (done) {
// creates a test tag
should.exist(tagJSON);
tagJSON.should.be.an.Array().with.lengthOf(3);
tagJSON.should.have.enumerable(0).with.property('name', 'existing tag a');
tagJSON.should.have.enumerable(1).with.property('name', 'existing-tag-b');
tagJSON.should.have.enumerable(2).with.property('name', 'existing_tag_c');
// creates a test post with an array of tags in the correct order
should.exist(postJSON);
postJSON.title.should.eql('HTML Ipsum');
should.exist(postJSON.tags);
postJSON.tags.should.be.an.Array().and.have.lengthOf(3);
postJSON.tags.should.have.enumerable(0).with.property('name', 'tag1');
postJSON.tags.should.have.enumerable(1).with.property('name', 'tag2');
postJSON.tags.should.have.enumerable(2).with.property('name', 'tag3');
done();
});
describe('Adding brand new tags', function () {
it('can add a single tag to the end of the tags array', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add a single tag to the end of the array
newJSON.tags.push(createTag({name: 'tag4'}));
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(4);
updatedPost.tags.should.have.enumerable(0).with.properties({
name: 'tag1',
id: postJSON.tags[0].id
});
updatedPost.tags.should.have.enumerable(1).with.properties({
name: 'tag2',
id: postJSON.tags[1].id
});
updatedPost.tags.should.have.enumerable(2).with.properties({
name: 'tag3',
id: postJSON.tags[2].id
});
updatedPost.tags.should.have.enumerable(3).with.property('name', 'tag4');
done();
}).catch(done);
});
it('can add a single tag to the beginning of the tags array', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add a single tag to the beginning of the array
newJSON.tags = [createTag({name: 'tag4'})].concat(postJSON.tags);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(4);
updatedPost.tags.should.have.enumerable(0).with.property('name', 'tag4');
updatedPost.tags.should.have.enumerable(1).with.properties({
name: 'tag1',
id: postJSON.tags[0].id
});
updatedPost.tags.should.have.enumerable(2).with.properties({
name: 'tag2',
id: postJSON.tags[1].id
});
updatedPost.tags.should.have.enumerable(3).with.properties({
name: 'tag3',
id: postJSON.tags[2].id
});
done();
}).catch(done);
});
});
describe('Adding pre-existing tags', function () {
it('can add a single tag to the end of the tags array', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add a single pre-existing tag to the end of the array
newJSON.tags.push(tagJSON[0]);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(4);
updatedPost.tags.should.have.enumerable(0).with.properties({
name: 'tag1',
id: postJSON.tags[0].id
});
updatedPost.tags.should.have.enumerable(1).with.properties({
name: 'tag2',
id: postJSON.tags[1].id
});
updatedPost.tags.should.have.enumerable(2).with.properties({
name: 'tag3',
id: postJSON.tags[2].id
});
updatedPost.tags.should.have.enumerable(3).with.properties({
name: 'existing tag a',
id: tagJSON[0].id
});
done();
}).catch(done);
});
it('can add a single tag to the beginning of the tags array', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add an existing tag to the beginning of the array
newJSON.tags = [tagJSON[0]].concat(postJSON.tags);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(4);
updatedPost.tags.should.have.enumerable(0).with.properties({
name: 'existing tag a',
id: tagJSON[0].id
});
updatedPost.tags.should.have.enumerable(1).with.properties({
name: 'tag1',
id: postJSON.tags[0].id
});
updatedPost.tags.should.have.enumerable(2).with.properties({
name: 'tag2',
id: postJSON.tags[1].id
});
updatedPost.tags.should.have.enumerable(3).with.properties({
name: 'tag3',
id: postJSON.tags[2].id
});
done();
}).catch(done);
});
it('can add a single tag to the middle of the tags array', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add a single pre-existing tag to the middle of the array
newJSON.tags = postJSON.tags.slice(0, 1).concat([tagJSON[0]]).concat(postJSON.tags.slice(1));
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(4);
updatedPost.tags.should.have.enumerable(0).with.properties({
name: 'tag1',
id: postJSON.tags[0].id
});
updatedPost.tags.should.have.enumerable(1).with.properties({
name: 'existing tag a',
id: tagJSON[0].id
});
updatedPost.tags.should.have.enumerable(2).with.properties({
name: 'tag2',
id: postJSON.tags[1].id
});
updatedPost.tags.should.have.enumerable(3).with.properties({
name: 'tag3',
id: postJSON.tags[2].id
});
done();
}).catch(done);
});
});
describe('Removing tags', function () {
it('can remove a single tag from the end of the tags array', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Remove a single tag from the end of the array
newJSON.tags = postJSON.tags.slice(0, -1);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(2);
updatedPost.tags.should.have.enumerable(0).with.properties({
name: 'tag1',
id: postJSON.tags[0].id
});
updatedPost.tags.should.have.enumerable(1).with.properties({
name: 'tag2',
id: postJSON.tags[1].id
});
done();
}).catch(done);
});
it('can remove a single tag from the beginning of the tags array', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Remove a single tag from the beginning of the array
newJSON.tags = postJSON.tags.slice(1);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(2);
updatedPost.tags.should.have.enumerable(0).with.properties({
name: 'tag2',
id: postJSON.tags[1].id
});
updatedPost.tags.should.have.enumerable(1).with.properties({
name: 'tag3',
id: postJSON.tags[2].id
});
done();
}).catch(done);
});
it('can remove all tags', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Remove all the tags
newJSON.tags = [];
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(0);
done();
}).catch(done);
});
});
describe('Reordering tags', function () {
it('can reorder the first tag to be the last', function (done) {
var newJSON = _.cloneDeep(postJSON),
firstTag = [postJSON.tags[0]];
// Reorder the tags, so that the first tag is moved to the end
newJSON.tags = postJSON.tags.slice(1).concat(firstTag);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(3);
updatedPost.tags.should.have.enumerable(0).with.properties({
name: 'tag2',
id: postJSON.tags[1].id
});
updatedPost.tags.should.have.enumerable(1).with.properties({
name: 'tag3',
id: postJSON.tags[2].id
});
updatedPost.tags.should.have.enumerable(2).with.properties({
name: 'tag1',
id: postJSON.tags[0].id
});
done();
}).catch(done);
});
it('can reorder the last tag to be the first', function (done) {
var newJSON = _.cloneDeep(postJSON),
lastTag = [postJSON.tags[2]];
// Reorder the tags, so that the last tag is moved to the beginning
newJSON.tags = lastTag.concat(postJSON.tags.slice(0, -1));
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(3);
updatedPost.tags.should.have.enumerable(0).with.properties({
name: 'tag3',
id: postJSON.tags[2].id
});
updatedPost.tags.should.have.enumerable(1).with.properties({
name: 'tag1',
id: postJSON.tags[0].id
});
updatedPost.tags.should.have.enumerable(2).with.properties({
name: 'tag2',
id: postJSON.tags[1].id
});
done();
}).catch(done);
});
});
describe('Edit post', function () {
// These tests are for #6920
it('can edit a post SAFELY when tags are not included', function (done) {
var postId = postJSON.id,
toJSONOpts = {include: ['tags']},
startTags;
// Step 1, fetch a post with its tags, just to see what tags we have
PostModel.findOne({id: postId}, {withRelated: ['tags']}).then(function (results) {
var post = results.toJSON(toJSONOpts);
should.exist(results);
post.title.should.not.equal('new title');
post.tags.should.have.lengthOf(3);
// Save a copy of these tags to test later
startTags = _.cloneDeep(post.tags);
// Step 2, edit a single property of the post... we aren't doing anything with tags here...
return PostModel.edit({title: 'new title'}, _.extend({}, context, {id: postId}));
}).then(function (edited) {
should.exist(edited);
var post = edited.toJSON(toJSONOpts);
post.title.should.equal('new title');
// edit didn't include tags, so they should be blank
should.not.exist(post.tags);
// Step 3, request the same post again, including tags... they should still be present
return PostModel.findOne({id: postId}, {withRelated: ['tags']}).then(function (results) {
var post = results.toJSON(toJSONOpts);
post.tags.should.have.lengthOf(3);
post.tags.should.eql(startTags);
done();
});
}).catch(done);
});
it('can edit a post SAFELY when tags is undefined', function (done) {
var postId = postJSON.id,
toJSONOpts = {include: ['tags']},
startTags;
// Step 1, fetch a post with its tags, just to see what tags we have
PostModel.findOne({id: postId}, {withRelated: ['tags']}).then(function (results) {
var post = results.toJSON(toJSONOpts);
should.exist(results);
post.title.should.not.equal('new title');
post.tags.should.have.lengthOf(3);
// Save a copy of these tags to test later
startTags = _.cloneDeep(post.tags);
// Step 2, edit a single property of the post... we aren't doing anything with tags here...
return PostModel.edit({title: 'new title', tags: undefined}, _.extend({}, context, {id: postId}));
}).then(function (edited) {
should.exist(edited);
var post = edited.toJSON(toJSONOpts);
post.title.should.equal('new title');
// edit didn't include tags, so they should be blank
should.not.exist(post.tags);
// Step 3, request the same post again, including tags... they should still be present
return PostModel.findOne({id: postId}, {withRelated: ['tags']}).then(function (results) {
var post = results.toJSON(toJSONOpts);
post.tags.should.have.lengthOf(3);
post.tags.should.eql(startTags);
done();
});
}).catch(done);
});
it('can edit a post SAFELY when tags is null', function (done) {
var postId = postJSON.id,
toJSONOpts = {include: ['tags']},
startTags;
// Step 1, fetch a post with its tags, just to see what tags we have
PostModel.findOne({id: postId}, {withRelated: ['tags']}).then(function (results) {
var post = results.toJSON(toJSONOpts);
should.exist(results);
post.title.should.not.equal('new title');
post.tags.should.have.lengthOf(3);
// Save a copy of these tags to test later
startTags = _.cloneDeep(post.tags);
// Step 2, edit a single property of the post... we aren't doing anything with tags here...
return PostModel.edit({title: 'new title', tags: null}, _.extend({}, context, {id: postId}));
}).then(function (edited) {
should.exist(edited);
var post = edited.toJSON(toJSONOpts);
post.title.should.equal('new title');
// edit didn't include tags, so they should be blank
should.not.exist(post.tags);
// Step 3, request the same post again, including tags... they should still be present
return PostModel.findOne({id: postId}, {withRelated: ['tags']}).then(function (results) {
var post = results.toJSON(toJSONOpts);
post.tags.should.have.lengthOf(3);
post.tags.should.eql(startTags);
done();
});
}).catch(done);
});
it('can remove all tags when sent an empty array', function (done) {
var postId = postJSON.id,
toJSONOpts = {include: ['tags']};
// Step 1, fetch a post with its tags, just to see what tags we have
PostModel.findOne({id: postId}, {withRelated: ['tags']}).then(function (results) {
var post = results.toJSON(toJSONOpts);
should.exist(results);
post.title.should.not.equal('new title');
post.tags.should.have.lengthOf(3);
// Step 2, edit a single property of the post... we aren't doing anything with tags here...
return PostModel.edit({title: 'new title', tags: []}, _.extend({}, context, {id: postId}));
}).then(function (edited) {
should.exist(edited);
var post = edited.toJSON(toJSONOpts);
post.title.should.equal('new title');
// edit didn't include tags, so they should be blank
should.not.exist(post.tags);
// Step 3, request the same post again, including tags... they should be gone
return PostModel.findOne({id: postId}, {withRelated: ['tags']}).then(function (results) {
var post = results.toJSON(toJSONOpts);
// Tags should be gone
post.tags.should.eql([]);
done();
});
}).catch(done);
});
});
describe('Combination updates', function () {
it('can add a combination of new and pre-existing tags', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Push a bunch of new and existing tags to the end of the array
newJSON.tags.push({name: 'tag4'});
newJSON.tags.push({name: 'existing tag a'});
newJSON.tags.push({name: 'tag5'});
newJSON.tags.push({name: 'existing-tag-b'});
newJSON.tags.push({name: 'bob'});
newJSON.tags.push({name: 'existing_tag_c'});
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(9);
updatedPost.tags.should.have.enumerable(0).with.properties({
name: 'tag1',
id: postJSON.tags[0].id
});
updatedPost.tags.should.have.enumerable(1).with.properties({
name: 'tag2',
id: postJSON.tags[1].id
});
updatedPost.tags.should.have.enumerable(2).with.properties({
name: 'tag3',
id: postJSON.tags[2].id
});
updatedPost.tags.should.have.enumerable(3).with.property('name', 'tag4');
updatedPost.tags.should.have.enumerable(4).with.properties({
name: 'existing tag a',
id: tagJSON[0].id
});
updatedPost.tags.should.have.enumerable(5).with.property('name', 'tag5');
updatedPost.tags.should.have.enumerable(6).with.properties({
name: 'existing-tag-b',
id: tagJSON[1].id
});
updatedPost.tags.should.have.enumerable(7).with.property('name', 'bob');
updatedPost.tags.should.have.enumerable(8).with.properties({
name: 'existing_tag_c',
id: tagJSON[2].id
});
done();
}).catch(done);
});
it('can reorder the first tag to be the last and add a tag to the beginning', function (done) {
var newJSON = _.cloneDeep(postJSON),
firstTag = [postJSON.tags[0]];
// Add a new tag to the beginning, and move the original first tag to the end
newJSON.tags = [tagJSON[0]].concat(postJSON.tags.slice(1)).concat(firstTag);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(4);
updatedPost.tags.should.have.enumerable(0).with.properties({
name: 'existing tag a',
id: tagJSON[0].id
});
updatedPost.tags.should.have.enumerable(1).with.properties({
name: 'tag2',
id: postJSON.tags[1].id
});
updatedPost.tags.should.have.enumerable(2).with.properties({
name: 'tag3',
id: postJSON.tags[2].id
});
updatedPost.tags.should.have.enumerable(3).with.properties({
name: 'tag1',
id: postJSON.tags[0].id
});
done();
}).catch(done);
});
it('can reorder the first tag to be the last, remove the original last tag & add a tag to the beginning', function (done) {
var newJSON = _.cloneDeep(postJSON),
firstTag = [newJSON.tags[0]];
// And an existing tag to the beginning of the array, move the original first tag to the end and remove the original last tag
newJSON.tags = [tagJSON[0]].concat(newJSON.tags.slice(1, -1)).concat(firstTag);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(3);
updatedPost.tags.should.have.enumerable(0).with.properties({
name: 'existing tag a',
id: tagJSON[0].id
});
updatedPost.tags.should.have.enumerable(1).with.properties({
name: 'tag2',
id: postJSON.tags[1].id
});
updatedPost.tags.should.have.enumerable(2).with.properties({
name: 'tag1',
id: postJSON.tags[0].id
});
done();
}).catch(done);
});
it('can reorder original tags, remove one, and add new and existing tags', function (done) {
var newJSON = _.cloneDeep(postJSON),
firstTag = [newJSON.tags[0]];
// Reorder original 3 so that first is at the end
newJSON.tags = newJSON.tags.slice(1).concat(firstTag);
// add an existing tag in the middle
newJSON.tags = newJSON.tags.slice(0, 1).concat({name: 'existing-tag-b'}).concat(newJSON.tags.slice(1));
// add a brand new tag in the middle
newJSON.tags = newJSON.tags.slice(0, 3).concat({name: 'betty'}).concat(newJSON.tags.slice(3));
// Add some more tags to the end
newJSON.tags.push({name: 'bob'});
newJSON.tags.push({name: 'existing tag a'});
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(7);
updatedPost.tags.should.have.enumerable(0).with.properties({
name: 'tag2',
id: postJSON.tags[1].id
});
updatedPost.tags.should.have.enumerable(1).with.properties({
name: 'existing-tag-b',
id: tagJSON[1].id
});
updatedPost.tags.should.have.enumerable(2).with.properties({
name: 'tag3',
id: postJSON.tags[2].id
});
updatedPost.tags.should.have.enumerable(3).with.property('name', 'betty');
updatedPost.tags.should.have.enumerable(4).with.properties({
name: 'tag1',
id: postJSON.tags[0].id
});
updatedPost.tags.should.have.enumerable(5).with.property('name', 'bob');
updatedPost.tags.should.have.enumerable(6).with.properties({
name: 'existing tag a',
id: tagJSON[0].id
});
done();
}).catch(done);
});
});
});
describe('Posts with NO tags', function () {
var postJSON,
tagJSON,
editOptions,
createTag = testUtils.DataGenerator.forKnex.createTag;
beforeEach(function (done) {
tagJSON = [];
var post = _.cloneDeep(testUtils.DataGenerator.forModel.posts[0]),
extraTag1 = createTag({name: 'existing tag a'}),
extraTag2 = createTag({name: 'existing-tag-b'}),
extraTag3 = createTag({name: 'existing_tag_c'});
return Promise.props({
post: PostModel.add(post, _.extend({}, context, {withRelated: ['tags']})),
tag1: TagModel.add(extraTag1, context),
tag2: TagModel.add(extraTag2, context),
tag3: TagModel.add(extraTag3, context)
}).then(function (result) {
postJSON = result.post.toJSON({include: ['tags']});
tagJSON.push(result.tag1.toJSON());
tagJSON.push(result.tag2.toJSON());
tagJSON.push(result.tag3.toJSON());
editOptions = _.extend({}, context, {id: postJSON.id, withRelated: ['tags']});
done();
}).catch(done);
});
it('should create the test data correctly', function () {
// creates two test tags
should.exist(tagJSON);
tagJSON.should.be.an.Array().with.lengthOf(3);
tagJSON.should.have.enumerable(0).with.property('name', 'existing tag a');
tagJSON.should.have.enumerable(1).with.property('name', 'existing-tag-b');
tagJSON.should.have.enumerable(2).with.property('name', 'existing_tag_c');
// creates a test post with no tags
should.exist(postJSON);
postJSON.title.should.eql('HTML Ipsum');
should.exist(postJSON.tags);
});
describe('Adding brand new tags', function () {
it('can add a single tag', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add a single tag to the end of the array
newJSON.tags.push(createTag({name: 'tag1'}));
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(1);
updatedPost.tags.should.have.enumerable(0).with.property('name', 'tag1');
done();
}).catch(done);
});
it('can add multiple tags', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add a bunch of tags to the end of the array
newJSON.tags.push(createTag({name: 'tag1'}));
newJSON.tags.push(createTag({name: 'tag2'}));
newJSON.tags.push(createTag({name: 'tag3'}));
newJSON.tags.push(createTag({name: 'tag4'}));
newJSON.tags.push(createTag({name: 'tag5'}));
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(5);
updatedPost.tags.should.have.enumerable(0).with.property('name', 'tag1');
updatedPost.tags.should.have.enumerable(1).with.property('name', 'tag2');
updatedPost.tags.should.have.enumerable(2).with.property('name', 'tag3');
updatedPost.tags.should.have.enumerable(3).with.property('name', 'tag4');
updatedPost.tags.should.have.enumerable(4).with.property('name', 'tag5');
done();
}).catch(done);
});
it('can add multiple tags with conflicting slugs', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add conflicting tags to the end of the array
newJSON.tags.push({name: 'C'});
newJSON.tags.push({name: 'C++'});
newJSON.tags.push({name: 'C#'});
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(3);
updatedPost.tags.should.have.enumerable(0).with.properties({name: 'C', slug: 'c'});
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'C++', slug: 'c-2'});
updatedPost.tags.should.have.enumerable(2).with.properties({name: 'C#', slug: 'c-3'});
done();
}).catch(done);
});
});
describe('Adding pre-existing tags', function () {
it('can add a single tag', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add a single pre-existing tag
newJSON.tags.push(tagJSON[0]);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(1);
updatedPost.tags.should.have.enumerable(0).with.properties({
name: 'existing tag a',
id: tagJSON[0].id
});
done();
}).catch(done);
});
it('can add multiple tags', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add many preexisting tags
newJSON.tags.push(tagJSON[0]);
newJSON.tags.push(tagJSON[1]);
newJSON.tags.push(tagJSON[2]);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(3);
updatedPost.tags.should.have.enumerable(0).with.properties({
name: 'existing tag a',
id: tagJSON[0].id
});
updatedPost.tags.should.have.enumerable(1).with.properties({
name: 'existing-tag-b',
id: tagJSON[1].id
});
updatedPost.tags.should.have.enumerable(2).with.properties({
name: 'existing_tag_c',
id: tagJSON[2].id
});
done();
}).catch(done);
});
it('can add multiple tags in wrong order', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add tags to the array
newJSON.tags.push(tagJSON[2]);
newJSON.tags.push(tagJSON[0]);
newJSON.tags.push(tagJSON[1]);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(3);
updatedPost.tags.should.have.enumerable(0).with.properties({
name: 'existing_tag_c',
id: tagJSON[2].id
});
updatedPost.tags.should.have.enumerable(1).with.properties({
name: 'existing tag a',
id: tagJSON[0].id
});
updatedPost.tags.should.have.enumerable(2).with.properties({
name: 'existing-tag-b',
id: tagJSON[1].id
});
done();
}).catch(done);
});
});
describe('Adding combinations', function () {
it('can add a combination of new and pre-existing tags', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add a bunch of new and existing tags to the array
newJSON.tags.push({name: 'tag1'});
newJSON.tags.push({name: 'existing tag a'});
newJSON.tags.push({name: 'tag3'});
newJSON.tags.push({name: 'existing-tag-b'});
newJSON.tags.push({name: 'tag5'});
newJSON.tags.push({name: 'existing_tag_c'});
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(6);
updatedPost.tags.should.have.enumerable(0).with.property('name', 'tag1');
updatedPost.tags.should.have.enumerable(1).with.properties({
name: 'existing tag a',
id: tagJSON[0].id
});
updatedPost.tags.should.have.enumerable(2).with.property('name', 'tag3');
updatedPost.tags.should.have.enumerable(3).with.properties({
name: 'existing-tag-b',
id: tagJSON[1].id
});
updatedPost.tags.should.have.enumerable(4).with.property('name', 'tag5');
updatedPost.tags.should.have.enumerable(5).with.properties({
name: 'existing_tag_c',
id: tagJSON[2].id
});
done();
}).catch(done);
});
});
});
});
// disabling sanitization until we can implement a better version
// it('should sanitize the title', function (done) {
// new PostModel().fetch().then(function (model) {

View File

@ -2,8 +2,6 @@
var testUtils = require('../../utils'),
should = require('should'),
sinon = require('sinon'),
Promise = require('bluebird'),
_ = require('lodash'),
// Stuff we are testing
ModelsTag = require('../../../server/models/tag'),
@ -114,570 +112,4 @@ describe('Tag Model', function () {
}).catch(done);
});
});
describe('Post tag handling, post with NO tags', function () {
var postJSON,
tagJSON,
editOptions,
createTag = testUtils.DataGenerator.forKnex.createTag;
beforeEach(function (done) {
tagJSON = [];
var post = testUtils.DataGenerator.forModel.posts[0],
extraTag1 = createTag({name: 'existing tag a'}),
extraTag2 = createTag({name: 'existing-tag-b'}),
extraTag3 = createTag({name: 'existing_tag_c'});
return Promise.props({
post: PostModel.add(post, _.extend({}, context, {withRelated: ['tags']})),
tag1: TagModel.add(extraTag1, context),
tag2: TagModel.add(extraTag2, context),
tag3: TagModel.add(extraTag3, context)
}).then(function (result) {
postJSON = result.post.toJSON({include: ['tags']});
tagJSON.push(result.tag1.toJSON());
tagJSON.push(result.tag2.toJSON());
tagJSON.push(result.tag3.toJSON());
editOptions = _.extend({}, context, {id: postJSON.id, withRelated: ['tags']});
done();
}).catch(done);
});
it('should create the test data correctly', function () {
// creates two test tags
should.exist(tagJSON);
tagJSON.should.be.an.Array().with.lengthOf(3);
tagJSON.should.have.enumerable(0).with.property('name', 'existing tag a');
tagJSON.should.have.enumerable(1).with.property('name', 'existing-tag-b');
tagJSON.should.have.enumerable(2).with.property('name', 'existing_tag_c');
// creates a test post with no tags
should.exist(postJSON);
postJSON.title.should.eql('HTML Ipsum');
should.exist(postJSON.tags);
});
describe('Adding brand new tags', function () {
it('can add a single tag', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add a single tag to the end of the array
newJSON.tags.push(createTag({name: 'tag1'}));
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(1);
updatedPost.tags.should.have.enumerable(0).with.property('name', 'tag1');
done();
}).catch(done);
});
it('can add multiple tags', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add a bunch of tags to the end of the array
newJSON.tags.push(createTag({name: 'tag1'}));
newJSON.tags.push(createTag({name: 'tag2'}));
newJSON.tags.push(createTag({name: 'tag3'}));
newJSON.tags.push(createTag({name: 'tag4'}));
newJSON.tags.push(createTag({name: 'tag5'}));
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(5);
updatedPost.tags.should.have.enumerable(0).with.property('name', 'tag1');
updatedPost.tags.should.have.enumerable(1).with.property('name', 'tag2');
updatedPost.tags.should.have.enumerable(2).with.property('name', 'tag3');
updatedPost.tags.should.have.enumerable(3).with.property('name', 'tag4');
updatedPost.tags.should.have.enumerable(4).with.property('name', 'tag5');
done();
}).catch(done);
});
it('can add multiple tags with conflicting slugs', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add conflicting tags to the end of the array
newJSON.tags.push({name: 'C'});
newJSON.tags.push({name: 'C++'});
newJSON.tags.push({name: 'C#'});
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(3);
updatedPost.tags.should.have.enumerable(0).with.properties({name: 'C', slug: 'c'});
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'C++', slug: 'c-2'});
updatedPost.tags.should.have.enumerable(2).with.properties({name: 'C#', slug: 'c-3'});
done();
}).catch(done);
});
});
describe('Adding pre-existing tags', function () {
it('can add a single tag', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add a single pre-existing tag
newJSON.tags.push(tagJSON[0]);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(1);
updatedPost.tags.should.have.enumerable(0).with.properties({name: 'existing tag a', id: tagJSON[0].id});
done();
}).catch(done);
});
it('can add multiple tags', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add many preexisting tags
newJSON.tags.push(tagJSON[0]);
newJSON.tags.push(tagJSON[1]);
newJSON.tags.push(tagJSON[2]);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(3);
updatedPost.tags.should.have.enumerable(0).with.properties({name: 'existing tag a', id: tagJSON[0].id});
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'existing-tag-b', id: tagJSON[1].id});
updatedPost.tags.should.have.enumerable(2).with.properties({name: 'existing_tag_c', id: tagJSON[2].id});
done();
}).catch(done);
});
it('can add multiple tags in wrong order', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add tags to the array
newJSON.tags.push(tagJSON[2]);
newJSON.tags.push(tagJSON[0]);
newJSON.tags.push(tagJSON[1]);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(3);
updatedPost.tags.should.have.enumerable(0).with.properties({name: 'existing_tag_c', id: tagJSON[2].id});
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'existing tag a', id: tagJSON[0].id});
updatedPost.tags.should.have.enumerable(2).with.properties({name: 'existing-tag-b', id: tagJSON[1].id});
done();
}).catch(done);
});
});
describe('Adding combinations', function () {
it('can add a combination of new and pre-existing tags', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add a bunch of new and existing tags to the array
newJSON.tags.push({name: 'tag1'});
newJSON.tags.push({name: 'existing tag a'});
newJSON.tags.push({name: 'tag3'});
newJSON.tags.push({name: 'existing-tag-b'});
newJSON.tags.push({name: 'tag5'});
newJSON.tags.push({name: 'existing_tag_c'});
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(6);
updatedPost.tags.should.have.enumerable(0).with.property('name', 'tag1');
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'existing tag a', id: tagJSON[0].id});
updatedPost.tags.should.have.enumerable(2).with.property('name', 'tag3');
updatedPost.tags.should.have.enumerable(3).with.properties({name: 'existing-tag-b', id: tagJSON[1].id});
updatedPost.tags.should.have.enumerable(4).with.property('name', 'tag5');
updatedPost.tags.should.have.enumerable(5).with.properties({name: 'existing_tag_c', id: tagJSON[2].id});
done();
}).catch(done);
});
});
});
describe('Post tag handling, post with tags', function () {
var postJSON,
tagJSON,
editOptions,
createTag = testUtils.DataGenerator.forKnex.createTag;
beforeEach(function (done) {
tagJSON = [];
var post = testUtils.DataGenerator.forModel.posts[0],
postTags = [
createTag({name: 'tag1'}),
createTag({name: 'tag2'}),
createTag({name: 'tag3'})
],
extraTags = [
createTag({name: 'existing tag a'}),
createTag({name: 'existing-tag-b'}),
createTag({name: 'existing_tag_c'})
];
post.tags = postTags;
return Promise.props({
post: PostModel.add(post, _.extend({}, context, {withRelated: ['tags']})),
tag1: TagModel.add(extraTags[0], context),
tag2: TagModel.add(extraTags[1], context),
tag3: TagModel.add(extraTags[2], context)
}).then(function (result) {
postJSON = result.post.toJSON({include: ['tags']});
tagJSON.push(result.tag1.toJSON());
tagJSON.push(result.tag2.toJSON());
tagJSON.push(result.tag3.toJSON());
editOptions = _.extend({}, context, {id: postJSON.id, withRelated: ['tags']});
done();
});
});
it('should create the test data correctly', function () {
// creates a test tag
should.exist(tagJSON);
tagJSON.should.be.an.Array().with.lengthOf(3);
tagJSON.should.have.enumerable(0).with.property('name', 'existing tag a');
tagJSON.should.have.enumerable(1).with.property('name', 'existing-tag-b');
tagJSON.should.have.enumerable(2).with.property('name', 'existing_tag_c');
// creates a test post with an array of tags in the correct order
should.exist(postJSON);
postJSON.title.should.eql('HTML Ipsum');
should.exist(postJSON.tags);
postJSON.tags.should.be.an.Array().and.have.lengthOf(3);
postJSON.tags.should.have.enumerable(0).with.property('name', 'tag1');
postJSON.tags.should.have.enumerable(1).with.property('name', 'tag2');
postJSON.tags.should.have.enumerable(2).with.property('name', 'tag3');
});
describe('Adding brand new tags', function () {
it('can add a single tag to the end of the tags array', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add a single tag to the end of the array
newJSON.tags.push(createTag({name: 'tag4'}));
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(4);
updatedPost.tags.should.have.enumerable(0).with.properties({name: 'tag1', id: postJSON.tags[0].id});
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'tag2', id: postJSON.tags[1].id});
updatedPost.tags.should.have.enumerable(2).with.properties({name: 'tag3', id: postJSON.tags[2].id});
updatedPost.tags.should.have.enumerable(3).with.property('name', 'tag4');
done();
}).catch(done);
});
it('can add a single tag to the beginning of the tags array', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add a single tag to the beginning of the array
newJSON.tags = [createTag({name: 'tag4'})].concat(postJSON.tags);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(4);
updatedPost.tags.should.have.enumerable(0).with.property('name', 'tag4');
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'tag1', id: postJSON.tags[0].id});
updatedPost.tags.should.have.enumerable(2).with.properties({name: 'tag2', id: postJSON.tags[1].id});
updatedPost.tags.should.have.enumerable(3).with.properties({name: 'tag3', id: postJSON.tags[2].id});
done();
}).catch(done);
});
});
describe('Adding pre-existing tags', function () {
it('can add a single tag to the end of the tags array', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add a single pre-existing tag to the end of the array
newJSON.tags.push(tagJSON[0]);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(4);
updatedPost.tags.should.have.enumerable(0).with.properties({name: 'tag1', id: postJSON.tags[0].id});
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'tag2', id: postJSON.tags[1].id});
updatedPost.tags.should.have.enumerable(2).with.properties({name: 'tag3', id: postJSON.tags[2].id});
updatedPost.tags.should.have.enumerable(3).with.properties({name: 'existing tag a', id: tagJSON[0].id});
done();
}).catch(done);
});
it('can add a single tag to the beginning of the tags array', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add an existing tag to the beginning of the array
newJSON.tags = [tagJSON[0]].concat(postJSON.tags);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(4);
updatedPost.tags.should.have.enumerable(0).with.properties({name: 'existing tag a', id: tagJSON[0].id});
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'tag1', id: postJSON.tags[0].id});
updatedPost.tags.should.have.enumerable(2).with.properties({name: 'tag2', id: postJSON.tags[1].id});
updatedPost.tags.should.have.enumerable(3).with.properties({name: 'tag3', id: postJSON.tags[2].id});
done();
}).catch(done);
});
it('can add a single tag to the middle of the tags array', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Add a single pre-existing tag to the middle of the array
newJSON.tags = postJSON.tags.slice(0, 1).concat([tagJSON[0]]).concat(postJSON.tags.slice(1));
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(4);
updatedPost.tags.should.have.enumerable(0).with.properties({name: 'tag1', id: postJSON.tags[0].id});
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'existing tag a', id: tagJSON[0].id});
updatedPost.tags.should.have.enumerable(2).with.properties({name: 'tag2', id: postJSON.tags[1].id});
updatedPost.tags.should.have.enumerable(3).with.properties({name: 'tag3', id: postJSON.tags[2].id});
done();
}).catch(done);
});
});
describe('Removing tags', function () {
it('can remove a single tag from the end of the tags array', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Remove a single tag from the end of the array
newJSON.tags = postJSON.tags.slice(0, -1);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(2);
updatedPost.tags.should.have.enumerable(0).with.properties({name: 'tag1', id: postJSON.tags[0].id});
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'tag2', id: postJSON.tags[1].id});
done();
}).catch(done);
});
it('can remove a single tag from the beginning of the tags array', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Remove a single tag from the beginning of the array
newJSON.tags = postJSON.tags.slice(1);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(2);
updatedPost.tags.should.have.enumerable(0).with.properties({name: 'tag2', id: postJSON.tags[1].id});
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'tag3', id: postJSON.tags[2].id});
done();
}).catch(done);
});
it('can remove all tags', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Remove all the tags
newJSON.tags = [];
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(0);
done();
}).catch(done);
});
});
describe('Reordering tags', function () {
it('can reorder the first tag to be the last', function (done) {
var newJSON = _.cloneDeep(postJSON),
firstTag = [postJSON.tags[0]];
// Reorder the tags, so that the first tag is moved to the end
newJSON.tags = postJSON.tags.slice(1).concat(firstTag);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(3);
updatedPost.tags.should.have.enumerable(0).with.properties({name: 'tag2', id: postJSON.tags[1].id});
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'tag3', id: postJSON.tags[2].id});
updatedPost.tags.should.have.enumerable(2).with.properties({name: 'tag1', id: postJSON.tags[0].id});
done();
}).catch(done);
});
it('can reorder the last tag to be the first', function (done) {
var newJSON = _.cloneDeep(postJSON),
lastTag = [postJSON.tags[2]];
// Reorder the tags, so that the last tag is moved to the beginning
newJSON.tags = lastTag.concat(postJSON.tags.slice(0, -1));
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(3);
updatedPost.tags.should.have.enumerable(0).with.properties({name: 'tag3', id: postJSON.tags[2].id});
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'tag1', id: postJSON.tags[0].id});
updatedPost.tags.should.have.enumerable(2).with.properties({name: 'tag2', id: postJSON.tags[1].id});
done();
}).catch(done);
});
});
describe('Combination updates', function () {
it('can add a combination of new and pre-existing tags', function (done) {
var newJSON = _.cloneDeep(postJSON);
// Push a bunch of new and existing tags to the end of the array
newJSON.tags.push({name: 'tag4'});
newJSON.tags.push({name: 'existing tag a'});
newJSON.tags.push({name: 'tag5'});
newJSON.tags.push({name: 'existing-tag-b'});
newJSON.tags.push({name: 'bob'});
newJSON.tags.push({name: 'existing_tag_c'});
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(9);
updatedPost.tags.should.have.enumerable(0).with.properties({name: 'tag1', id: postJSON.tags[0].id});
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'tag2', id: postJSON.tags[1].id});
updatedPost.tags.should.have.enumerable(2).with.properties({name: 'tag3', id: postJSON.tags[2].id});
updatedPost.tags.should.have.enumerable(3).with.property('name', 'tag4');
updatedPost.tags.should.have.enumerable(4).with.properties({name: 'existing tag a', id: tagJSON[0].id});
updatedPost.tags.should.have.enumerable(5).with.property('name', 'tag5');
updatedPost.tags.should.have.enumerable(6).with.properties({name: 'existing-tag-b', id: tagJSON[1].id});
updatedPost.tags.should.have.enumerable(7).with.property('name', 'bob');
updatedPost.tags.should.have.enumerable(8).with.properties({name: 'existing_tag_c', id: tagJSON[2].id});
done();
}).catch(done);
});
it('can reorder the first tag to be the last and add a tag to the beginning', function (done) {
var newJSON = _.cloneDeep(postJSON),
firstTag = [postJSON.tags[0]];
// Add a new tag to the beginning, and move the original first tag to the end
newJSON.tags = [tagJSON[0]].concat(postJSON.tags.slice(1)).concat(firstTag);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(4);
updatedPost.tags.should.have.enumerable(0).with.properties({name: 'existing tag a', id: tagJSON[0].id});
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'tag2', id: postJSON.tags[1].id});
updatedPost.tags.should.have.enumerable(2).with.properties({name: 'tag3', id: postJSON.tags[2].id});
updatedPost.tags.should.have.enumerable(3).with.properties({name: 'tag1', id: postJSON.tags[0].id});
done();
}).catch(done);
});
it('can reorder the first tag to be the last, remove the original last tag & add a tag to the beginning', function (done) {
var newJSON = _.cloneDeep(postJSON),
firstTag = [newJSON.tags[0]];
// And an existing tag to the beginning of the array, move the original first tag to the end and remove the original last tag
newJSON.tags = [tagJSON[0]].concat(newJSON.tags.slice(1, -1)).concat(firstTag);
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(3);
updatedPost.tags.should.have.enumerable(0).with.properties({name: 'existing tag a', id: tagJSON[0].id});
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'tag2', id: postJSON.tags[1].id});
updatedPost.tags.should.have.enumerable(2).with.properties({name: 'tag1', id: postJSON.tags[0].id});
done();
}).catch(done);
});
it('can reorder original tags, remove one, and add new and existing tags', function (done) {
var newJSON = _.cloneDeep(postJSON),
firstTag = [newJSON.tags[0]];
// Reorder original 3 so that first is at the end
newJSON.tags = newJSON.tags.slice(1).concat(firstTag);
// add an existing tag in the middle
newJSON.tags = newJSON.tags.slice(0, 1).concat({name: 'existing-tag-b'}).concat(newJSON.tags.slice(1));
// add a brand new tag in the middle
newJSON.tags = newJSON.tags.slice(0, 3).concat({name: 'betty'}).concat(newJSON.tags.slice(3));
// Add some more tags to the end
newJSON.tags.push({name: 'bob'});
newJSON.tags.push({name: 'existing tag a'});
// Edit the post
return PostModel.edit(newJSON, editOptions).then(function (updatedPost) {
updatedPost = updatedPost.toJSON({include: ['tags']});
updatedPost.tags.should.have.lengthOf(7);
updatedPost.tags.should.have.enumerable(0).with.properties({name: 'tag2', id: postJSON.tags[1].id});
updatedPost.tags.should.have.enumerable(1).with.properties({name: 'existing-tag-b', id: tagJSON[1].id});
updatedPost.tags.should.have.enumerable(2).with.properties({name: 'tag3', id: postJSON.tags[2].id});
updatedPost.tags.should.have.enumerable(3).with.property('name', 'betty');
updatedPost.tags.should.have.enumerable(4).with.properties({name: 'tag1', id: postJSON.tags[0].id});
updatedPost.tags.should.have.enumerable(5).with.property('name', 'bob');
updatedPost.tags.should.have.enumerable(6).with.properties({name: 'existing tag a', id: tagJSON[0].id});
done();
}).catch(done);
});
});
});
});