Merge branch 'master' into story-138187767-mod-flag-names

This commit is contained in:
gaba
2017-03-07 13:04:28 +01:00
135 changed files with 3248 additions and 1534 deletions
@@ -20,6 +20,10 @@ describe('coral-plugin-history/CommentHistory', () => {
'closedAt':null
};
comments.forEach((comment) => {
comment.asset = asset;
});
beforeEach(() => {
render = shallow(<CommentHistory comments={comments} asset={asset} link={()=>{}}/>);
});
+5
View File
@@ -8,6 +8,8 @@ const embedStreamCommands = {
},
approveComment() {
return this
.waitForElementVisible('@moderateNav')
.click('@moderateNav')
.waitForElementVisible('@moderationList')
.waitForElementVisible('@approveButton')
.click('@approveButton');
@@ -17,6 +19,9 @@ const embedStreamCommands = {
module.exports = {
commands: [embedStreamCommands],
elements: {
moderateNav: {
selector: '#moderateNav'
},
moderationList: {
selector: '#moderationList'
},
+20 -14
View File
@@ -117,49 +117,55 @@ module.exports = {
selector: '#commentBox .coral-plugin-commentbox-button'
},
likeButton: {
selector: '.comment .coral-plugin-likes-container .coral-plugin-likes-button'
selector: '.embed__stream .comment .coral-plugin-likes-container .coral-plugin-likes-button'
},
likeText: {
selector: '.comment .coral-plugin-likes-container .coral-plugin-likes-button .coral-plugin-likes-button-text'
selector: '.embed__stream .comment .coral-plugin-likes-container .coral-plugin-likes-button .coral-plugin-likes-button-text'
},
likesCount: {
selector: '.comment .coral-plugin-likes-container .coral-plugin-likes-button .coral-plugin-likes-like-count'
selector: '.embed__stream .comment .coral-plugin-likes-container .coral-plugin-likes-button .coral-plugin-likes-like-count'
},
flagButton: {
selector: '.comment .coral-plugin-flags-container .coral-plugin-flags-button'
selector: '.embed__stream .comment .coral-plugin-flags-container .coral-plugin-flags-button'
},
flagPopUp: {
selector: '.comment .coral-plugin-flags-popup'
selector: '.embed__stream .comment .coral-plugin-flags-popup'
},
flagCommentOption: {
selector: '.comment .coral-plugin-flags-popup .coral-plugin-flags-popup-radio-label[for="COMMENTS"]'
selector: '.embed__stream .comment .coral-plugin-flags-popup .coral-plugin-flags-popup-radio-label[for="COMMENTS"]'
},
flagUsernameOption: {
selector: '.comment .coral-plugin-flags-popup .coral-plugin-flags-popup-radio-label[for="USERS"]'
selector: '.embed__stream .comment .coral-plugin-flags-popup .coral-plugin-flags-popup-radio-label[for="USERS"]'
},
flagOtherOption: {
selector: '.comment .coral-plugin-flags-popup .coral-plugin-flags-popup-radio-label[for="other"]'
selector: '.embed__stream .comment .coral-plugin-flags-popup .coral-plugin-flags-popup-radio-label[for="other"]'
},
flagHeaderMessage: {
selector: '.comment .coral-plugin-flags-popup .coral-plugin-flags-popup-header'
selector: '.embed__stream .comment .coral-plugin-flags-popup .coral-plugin-flags-popup-header'
},
flagButtonText: {
selector: '.comment .coral-plugin-flags-button-text'
selector: '.embed__stream .comment .coral-plugin-flags-button-text'
},
flagDoneButton: {
selector: '.comment .coral-plugin-flags-popup .coral-plugin-flags-popup-button'
selector: '.embed__stream .comment .coral-plugin-flags-popup .coral-plugin-flags-popup-button'
},
permalinkButton: {
selector: '.comment .coral-plugin-permalinks-button'
selector: '.embed__stream .comment .coral-plugin-permalinks-button'
},
permalinkPopUp: {
selector: '.comment .coral-plugin-permalinks-popover.active'
selector: '.embed__stream .comment .coral-plugin-permalinks-popover.active'
},
permalinkInput: {
selector: '.comment .coral-plugin-permalinks-popover.active input'
selector: '.embed__stream .comment .coral-plugin-permalinks-popover.active input'
},
registerButton: {
selector: '#signInDialog #coralRegister'
},
setBestButton: {
selector: '.e2e__set-best-comment'
},
unsetBestButton: {
selector: '.e2e__unset-best-comment'
}
}
};
@@ -0,0 +1,25 @@
module.exports = {
'@tags': ['like', 'comments', 'commenter'],
before: client => {
const embedStreamPage = client.page.embedStreamPage();
const {users} = client.globals;
embedStreamPage
.navigate()
.ready();
embedStreamPage
.login(users.commenter);
},
'Commenters should not see the set-best-comment button': client => {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.postComment('Hi everyone. Isn\'t this the BEST comment!?')
.waitForElementVisible('@likeButton')
.expect.element('@setBestButton').to.not.be.present;
},
after: client => {
client.end();
}
};
@@ -16,6 +16,7 @@ module.exports = {
embedStreamPage
.flagUsername()
.click('@flagButton')
.waitForElementVisible('@flagPopUp')
.waitForElementVisible('@flagUsernameOption')
.click('@flagUsernameOption')
@@ -15,6 +15,7 @@ module.exports = {
const embedStreamPage = client.page.embedStreamPage();
embedStreamPage
.postComment(`hi ${Math.random()}`)
.likeComment()
.waitForElementVisible('@likesCount', 2000)
.expect.element('@likeText').text.to.equal('Liked');
+10 -14
View File
@@ -41,7 +41,6 @@ module.exports = {
.setValue('#password', mockUser.pw)
.setValue('#confirmPassword', mockUser.pw)
.click('#coralSignUpButton')
.pause(5000)
.waitForElementVisible('#coralLogInButton', 10000)
.click('#coralLogInButton')
.waitForElementVisible('.coral-plugin-commentbox-button', 4000)
@@ -49,10 +48,10 @@ module.exports = {
// Post a comment
.setValue('.coral-plugin-commentbox-textarea', mockComment)
.click('.coral-plugin-commentbox-button')
.waitForElementVisible('.coral-plugin-content-text', 1000)
.waitForElementVisible('.embed__stream .coral-plugin-commentcontent-text', 1000)
// Verify that it appears
.assert.containsText('.coral-plugin-content-text', mockComment);
.assert.containsText('.embed__stream .coral-plugin-commentcontent-text', mockComment);
done();
})
.catch((err) => {
@@ -73,11 +72,7 @@ module.exports = {
// Post a comment
client.waitForElementVisible('.coral-plugin-commentbox-button', 2000)
.setValue('.coral-plugin-commentbox-textarea', mockComment)
.click('.coral-plugin-commentbox-button')
.waitForElementVisible('#coral-notif', 1000)
// Verify that it appears
.assert.containsText('#coral-notif', 'moderation team');
.click('.coral-plugin-commentbox-button');
done();
})
.catch((err) => {
@@ -102,15 +97,16 @@ module.exports = {
.click('.coral-plugin-commentbox-button')
// Post a reply
.waitForElementVisible('.coral-plugin-replies-reply-button', 5000)
.click('.coral-plugin-replies-reply-button')
.waitForElementVisible('.embed__stream .coral-plugin-replies-reply-button', 5000)
.click('.embed__stream .coral-plugin-replies-reply-button')
.waitForElementVisible('#replyText')
.setValue('#replyText', mockReply)
.click('.coral-plugin-replies-textarea .coral-plugin-commentbox-button')
.waitForElementVisible('.reply', 20000)
.click('.embed__stream .coral-plugin-replies-textarea .coral-plugin-commentbox-button')
.waitForElementVisible('.embed__stream .reply', 20000)
// Verify that it appears
.assert.containsText('.reply', mockReply);
.assert.containsText('.embed__stream .reply', mockReply);
done();
})
.catch((err) => {
@@ -171,7 +167,7 @@ module.exports = {
// Verify that comment count is correct
client.waitForElementVisible('.coral-plugin-comment-count-text', 2000)
.assert.containsText('.coral-plugin-comment-count-text', '4 Comments');
.assert.containsText('.coral-plugin-comment-count-text', '5 Comments');
done();
});
},
@@ -0,0 +1,50 @@
module.exports = {
'@tags': ['like', 'comments', 'commenter'],
before: client => {
const embedStreamPage = client.page.embedStreamPage();
const {users} = client.globals;
embedStreamPage
.navigate()
.ready();
embedStreamPage
.login(users.moderator);
},
'Moderator marks/unmarks their comment as BEST': client => {
const embedStreamPage = client.page.embedStreamPage();
const setBestCommentButton = '.e2e__set-best-comment';
const unsetBestCommentButton = '.e2e__unset-best-comment';
embedStreamPage
.postComment(`Hi everyone. Isn't this the BEST comment!? ${String(Math.random()).slice(2)}`)
.waitForElementVisible(setBestCommentButton, 2000)
.click(setBestCommentButton)
.waitForElementVisible(unsetBestCommentButton, 2000);
// on refresh, it should still be tagged as best :)
client.refresh();
embedStreamPage.ready()
// (bengo) I have no idea why, but if the selector here is '@unsetBestButton', it doesn't find it... I think nightwatch bug?
// this is why I am not using @elements. Advice appreciated.
.waitForElementVisible(unsetBestCommentButton, 2000);
// now remove the best tag
embedStreamPage
.click(unsetBestCommentButton);
embedStreamPage
.waitForElementVisible(setBestCommentButton, 2000);
// on refresh it should still be untagged best
client.refresh();
embedStreamPage.ready()
.waitForElementVisible(setBestCommentButton);
},
after: client => {
client.end();
}
};
+151
View File
@@ -0,0 +1,151 @@
const {expect} = require('chai');
const {graphql} = require('graphql');
const schema = require('../../../graph/schema');
const Context = require('../../../graph/context');
const UserModel = require('../../../models/user');
const AssetModel = require('../../../models/asset');
const SettingsService = require('../../../services/settings');
const ActionModel = require('../../../models/action');
const CommentModel = require('../../../models/comment');
describe('graph.loaders.Metrics', () => {
beforeEach(() => SettingsService.init());
describe('#Comments', () => {
const query = `
query CommentMetrics($from: Date!, $to: Date!) {
liked: commentMetrics(from: $from, to: $to, sort: LIKE) {
id
}
flagged: commentMetrics(from: $from, to: $to, sort: FLAG) {
id
}
}
`;
describe('different comment states', () => {
beforeEach(() => CommentModel.create([
{id: '1', body: 'a new comment!'},
{id: '2', body: 'a new comment!'},
{id: '3', body: 'a new comment!'}
]));
[
{liked: 0, flagged: 0, actions: []},
{liked: 1, flagged: 0, actions: [{action_type: 'LIKE', item_id: '1', item_type: 'COMMENTS'}]},
{liked: 0, flagged: 1, actions: [{action_type: 'FLAG', item_id: '1', item_type: 'COMMENTS'}]},
{liked: 1, flagged: 1, actions: [
{action_type: 'FLAG', item_id: '1', item_type: 'COMMENTS'},
{action_type: 'LIKE', item_id: '1', item_type: 'COMMENTS'}
]},
{liked: 3, flagged: 1, actions: [
{action_type: 'LIKE', item_id: '1', item_type: 'COMMENTS'},
{action_type: 'LIKE', item_id: '2', item_type: 'COMMENTS'},
{action_type: 'LIKE', item_id: '3', item_type: 'COMMENTS'},
{action_type: 'FLAG', item_id: '3', item_type: 'COMMENTS'}
]}
].forEach(({liked, flagged, actions}) => {
describe(`with actions=${actions.length}`, () => {
beforeEach(() => ActionModel.create(actions));
it(`returns the correct amount of metrics liked=${liked} flagged=${flagged}`, () => {
const context = new Context({user: new UserModel({roles: ['ADMIN']})});
return graphql(schema, query, {}, context, {
from: (new Date()).setMinutes((new Date()).getMinutes() - 5),
to: (new Date()).setMinutes((new Date()).getMinutes() + 5)
})
.then(({data, errors}) => {
expect(errors).to.be.undefined;
expect(data.liked).to.have.length(liked);
expect(data.flagged).to.have.length(flagged);
});
});
});
});
});
});
describe('#Assets', () => {
const query = `
fragment metrics on Asset {
id
action_summaries {
type: __typename
actionCount
actionableItemCount
}
}
query Metrics($from: Date!, $to: Date!) {
assetsByFlag: assetMetrics(from: $from, to: $to, sort: FLAG) {
...metrics
}
assetsByLike: assetMetrics(from: $from, to: $to, sort: LIKE) {
...metrics
}
}
`;
describe('different comment states', () => {
beforeEach(() => Promise.all([
AssetModel.create([
{id: 'a1', url: 'http://localhost:3030/article/1'},
{id: 'a2', url: 'http://localhost:3030/article/2'}
]),
CommentModel.create([
{id: 'c1', asset_id: 'a1', body: 'a new comment!'},
{id: 'c2', asset_id: 'a1', body: 'a new comment!'},
{id: 'c3', asset_id: 'a1', body: 'a new comment!'}
])
]));
[
{liked: 0, flagged: 0, actions: []},
{liked: 1, flagged: 0, actions: [{action_type: 'LIKE', item_id: 'c1', item_type: 'COMMENTS'}]},
{liked: 0, flagged: 1, actions: [{action_type: 'FLAG', item_id: 'c1', item_type: 'COMMENTS'}]},
{liked: 1, flagged: 1, actions: [
{action_type: 'FLAG', item_id: 'c1', item_type: 'COMMENTS'},
{action_type: 'LIKE', item_id: 'c1', item_type: 'COMMENTS'}
]},
{liked: 1, flagged: 1, actions: [
{action_type: 'LIKE', item_id: 'c1', item_type: 'COMMENTS'},
{action_type: 'LIKE', item_id: 'c2', item_type: 'COMMENTS'},
{action_type: 'LIKE', item_id: 'c3', item_type: 'COMMENTS'},
{action_type: 'FLAG', item_id: 'c3', item_type: 'COMMENTS'}
]}
].forEach(({liked, flagged, actions}) => {
describe(`with actions=${actions.length}`, () => {
beforeEach(() => ActionModel.create(actions));
it(`returns the correct amount of metrics liked=${liked} flagged=${flagged}`, () => {
const context = new Context({user: new UserModel({roles: ['ADMIN']})});
return graphql(schema, query, {}, context, {
from: (new Date()).setMinutes((new Date()).getMinutes() - 5),
to: (new Date()).setMinutes((new Date()).getMinutes() + 5)
})
.then(({data, errors}) => {
expect(errors).to.be.undefined;
expect(data.assetsByLike).to.have.length(liked);
expect(data.assetsByFlag).to.have.length(flagged);
});
});
});
});
});
});
});
+62
View File
@@ -0,0 +1,62 @@
const expect = require('chai').expect;
const {graphql} = require('graphql');
const schema = require('../../../graph/schema');
const Context = require('../../../graph/context');
const UserModel = require('../../../models/user');
const SettingsService = require('../../../services/settings');
const CommentsService = require('../../../services/comments');
describe('graph.mutations.addCommentTag', () => {
let comment;
beforeEach(async () => {
await SettingsService.init();
comment = await CommentsService.publicCreate({body: `hello there! ${ String(Math.random()).slice(2)}`});
});
const query = `
mutation AddCommentTag ($id: ID!, $tag: String!) {
addCommentTag(id:$id, tag:$tag) {
comment {
tags {
name
}
}
errors {
translation_key
}
}
}
`;
it('moderators can add tags to comments', async () => {
const user = new UserModel({roles: ['MODERATOR' ]});
const context = new Context({user});
const response = await graphql(schema, query, {}, context, {id: comment.id, tag: 'BEST'});
if (response.errors && response.errors.length) {
console.error(response.errors);
}
expect(response.errors).to.be.empty;
expect(response.data.addCommentTag.comment.tags).to.deep.equal([{name: 'BEST'}]);
});
describe('users who cant add tags', () => {
Object.entries({
'anonymous': undefined,
'regular commenter': new UserModel({}),
'banned moderator': new UserModel({roles: ['MODERATOR'], status: 'BANNED'})
}).forEach(([ userDescription, user ]) => {
it(userDescription, async function () {
const context = new Context({user});
const response = await graphql(schema, query, {}, context, {id: comment.id, tag: 'BEST'});
if (response.errors && response.errors.length) {
console.error(response.errors);
}
expect(response.errors).to.be.empty;
expect(response.data.addCommentTag.errors).to.deep.equal([{'translation_key':'NOT_AUTHORIZED'}]);
expect(response.data.addCommentTag.comment).to.be.null;
});
});
});
});
+69
View File
@@ -0,0 +1,69 @@
const expect = require('chai').expect;
const {graphql} = require('graphql');
const schema = require('../../../graph/schema');
const Context = require('../../../graph/context');
const UserModel = require('../../../models/user');
const SettingsService = require('../../../services/settings');
const CommentsService = require('../../../services/comments');
describe('graph.mutations.removeCommentTag', () => {
let comment;
beforeEach(async () => {
await SettingsService.init();
comment = await CommentsService.publicCreate({body: `hello there! ${ String(Math.random()).slice(2)}`});
});
const query = `
mutation RemoveCommentTag ($id: ID!, $tag: String!) {
removeCommentTag(id:$id, tag:$tag) {
comment {
tags {
name
}
}
errors {
translation_key
}
}
}
`;
it('moderators can add remove tags from comments', async () => {
const user = new UserModel({roles: ['MODERATOR' ]});
const context = new Context({user});
// add a tag first
await CommentsService.addTag(comment.id, 'BEST');
const response = await graphql(schema, query, {}, context, {id: comment.id, tag: 'BEST'});
if (response.errors && response.errors.length) {
console.error(response.errors);
}
expect(response.errors).to.be.empty;
expect(response.data.removeCommentTag.errors).to.be.null;
expect(response.data.removeCommentTag.comment.tags).to.deep.equal([]);
});
describe('users who cant remove tags', () => {
Object.entries({
'anonymous': undefined,
'regular commenter': new UserModel({}),
'banned moderator': new UserModel({roles: ['MODERATOR'], status: 'BANNED'})
}).forEach(([ userDescription, user ]) => {
it(userDescription, async function () {
const context = new Context({user});
// add a tag first
await CommentsService.addTag(comment.id, 'BEST');
const response = await graphql(schema, query, {}, context, {id: comment.id, tag: 'BEST'});
if (response.errors && response.errors.length) {
console.error(response.errors);
}
expect(response.errors).to.be.empty;
expect(response.data.removeCommentTag.errors).to.deep.equal([{'translation_key':'NOT_AUTHORIZED'}]);
expect(response.data.removeCommentTag.comment).to.be.null;
});
});
});
});
+60 -1
View File
@@ -8,7 +8,7 @@ const CommentsService = require('../../services/comments');
const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}};
const expect = require('chai').expect;
const expect = require('chai').use(require('chai-as-promised')).expect;
describe('services.CommentsService', () => {
const comments = [{
@@ -68,6 +68,7 @@ describe('services.CommentsService', () => {
}];
const users = [{
id: 'u1',
email: 'stampi@gmail.com',
username: 'Stampi',
password: '1Coral!!'
@@ -218,6 +219,64 @@ describe('services.CommentsService', () => {
});
describe('#addTag', () => {
it('adds a tag', async () => {
const commentId = comments[0].id;
const tagName = 'BEST';
const userId = users[0].id;
await CommentsService.addTag(commentId, tagName, userId);
const updatedComment = await CommentsService.findById(commentId);
expect(updatedComment.tags.length).to.equal(1);
expect(updatedComment.tags[0].name).to.equal(tagName);
expect(updatedComment.tags[0].assigned_by).to.equal(userId);
expect(updatedComment.tags[0].created_at).to.be.an.instanceof(Date);
});
it('can\'t add a tag to comment id that doesn\'t exist', async () => {
const commentId = 'fakenews';
const tagName = 'BEST';
const userId = users[0].id;
await expect(CommentsService.addTag(commentId, tagName, userId)).to.be.rejected;
});
it('can\'t add same tag.name twice', async () => {
const commentId = comments[0].id;
const tagName = 'BEST';
const userId = users[0].id;
// first time
await CommentsService.addTag(commentId, tagName, userId);
// second time should fail
await expect(CommentsService.addTag(commentId, tagName, userId)).to.be.rejected;
});
});
describe('#removeTag', () => {
it('removes a tag', async () => {
const commentId = comments[0].id;
const tagName = 'BEST';
await CommentsService.addTag(commentId, tagName, users[0].id);
const updatedComment = await CommentsService.findById(commentId);
expect(updatedComment.tags.length).to.equal(1);
// ok now to remove it
await CommentsService.removeTag(commentId, tagName);
const updatedComment2 = await CommentsService.findById(commentId);
expect(updatedComment2.tags.length).to.equal(0);
});
it('throws if removing a tag that isn\'t there', async () => {
const commentId = comments[0].id;
// just make sure it has no tags to start
const updatedComment2 = await CommentsService.findById(commentId);
expect(updatedComment2.tags.length).to.equal(0);
const tagName = 'BEST';
// ok now to remove it
await expect(CommentsService.removeTag(commentId, tagName)).to.be.rejected;
});
});
describe('#changeStatus', () => {
it('should change the status of a comment from no status', () => {