mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge pull request #536 from Human-Connection/352-add-authorship-to-comments
Add authorship to comments at creation
This commit is contained in:
commit
b8cecd7fe3
@ -2,44 +2,47 @@ import { neo4jgraphql } from 'neo4j-graphql-js'
|
|||||||
import { UserInputError } from 'apollo-server'
|
import { UserInputError } from 'apollo-server'
|
||||||
|
|
||||||
const COMMENT_MIN_LENGTH = 1
|
const COMMENT_MIN_LENGTH = 1
|
||||||
|
const NO_POST_ERR_MESSAGE = 'Comment cannot be created without a post!'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Query: {
|
|
||||||
CommentByPost: async (object, params, context, resolveInfo) => {
|
|
||||||
const { postId } = params
|
|
||||||
|
|
||||||
const session = context.driver.session()
|
|
||||||
const transactionRes = await session.run(`
|
|
||||||
MATCH (comment:Comment)-[:COMMENTS]->(post:Post {id: $postId})
|
|
||||||
RETURN comment {.id, .contentExcerpt, .createdAt} ORDER BY comment.createdAt ASC`, {
|
|
||||||
postId
|
|
||||||
})
|
|
||||||
|
|
||||||
session.close()
|
|
||||||
let comments = []
|
|
||||||
transactionRes.records.map(record => {
|
|
||||||
comments.push(record.get('comment'))
|
|
||||||
})
|
|
||||||
|
|
||||||
return comments
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Mutation: {
|
Mutation: {
|
||||||
CreateComment: async (object, params, context, resolveInfo) => {
|
CreateComment: async (object, params, context, resolveInfo) => {
|
||||||
const content = params.content.replace(/<(?:.|\n)*?>/gm, '').trim()
|
const content = params.content.replace(/<(?:.|\n)*?>/gm, '').trim()
|
||||||
|
const { postId } = params
|
||||||
|
// Adding relationship from comment to post by passing in the postId,
|
||||||
|
// but we do not want to create the comment with postId as an attribute
|
||||||
|
// because we use relationships for this. So, we are deleting it from params
|
||||||
|
// before comment creation.
|
||||||
|
delete params.postId
|
||||||
|
|
||||||
if (!params.content || content.length < COMMENT_MIN_LENGTH) {
|
if (!params.content || content.length < COMMENT_MIN_LENGTH) {
|
||||||
throw new UserInputError(`Comment must be at least ${COMMENT_MIN_LENGTH} character long!`)
|
throw new UserInputError(`Comment must be at least ${COMMENT_MIN_LENGTH} character long!`)
|
||||||
}
|
}
|
||||||
const { postId } = params
|
if (!postId.trim()) {
|
||||||
delete params.postId
|
throw new UserInputError(NO_POST_ERR_MESSAGE)
|
||||||
const comment = await neo4jgraphql(object, params, context, resolveInfo, false)
|
}
|
||||||
|
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
|
const postQueryRes = await session.run(`
|
||||||
|
MATCH (post:Post {id: $postId})
|
||||||
|
RETURN post`, {
|
||||||
|
postId
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const [post] = postQueryRes.records.map(record => {
|
||||||
|
return record.get('post')
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!post) {
|
||||||
|
throw new UserInputError(NO_POST_ERR_MESSAGE)
|
||||||
|
}
|
||||||
|
const comment = await neo4jgraphql(object, params, context, resolveInfo, false)
|
||||||
|
|
||||||
await session.run(`
|
await session.run(`
|
||||||
MATCH (post:Post {id: $postId}), (comment:Comment {id: $commentId})
|
MATCH (post:Post {id: $postId}), (comment:Comment {id: $commentId}), (author:User {id: $userId})
|
||||||
MERGE (post)<-[:COMMENTS]-(comment)
|
MERGE (post)<-[:COMMENTS]-(comment)<-[:WROTE]-(author)
|
||||||
RETURN comment {.id, .content}`, {
|
RETURN post`, {
|
||||||
|
userId: context.user.id,
|
||||||
postId,
|
postId,
|
||||||
commentId: comment.id
|
commentId: comment.id
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,10 @@ import { host, login } from '../jest/helpers'
|
|||||||
|
|
||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
let client
|
let client
|
||||||
let variables
|
let createCommentVariables
|
||||||
|
let createPostVariables
|
||||||
|
let createCommentVariablesSansPostId
|
||||||
|
let createCommentVariablesWithNonExistentPost
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await factory.create('User', {
|
await factory.create('User', {
|
||||||
@ -18,22 +21,36 @@ afterEach(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('CreateComment', () => {
|
describe('CreateComment', () => {
|
||||||
const mutation = `
|
const createCommentMutation = `
|
||||||
mutation($postId: ID, $content: String!) {
|
mutation($postId: ID, $content: String!) {
|
||||||
CreateComment(postId: $postId, content: $content) {
|
CreateComment(postId: $postId, content: $content) {
|
||||||
id
|
id
|
||||||
content
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const createPostMutation = `
|
||||||
|
mutation($id: ID!, $title: String!, $content: String!) {
|
||||||
|
CreatePost(id: $id, title: $title, content: $content) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const commentQueryForPostId = `
|
||||||
|
query($content: String) {
|
||||||
|
Comment(content: $content) {
|
||||||
|
postId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
describe('unauthenticated', () => {
|
describe('unauthenticated', () => {
|
||||||
it('throws authorization error', async () => {
|
it('throws authorization error', async () => {
|
||||||
variables = {
|
createCommentVariables = {
|
||||||
postId: 'p1',
|
postId: 'p1',
|
||||||
content: 'I\'m not authorised to comment'
|
content: 'I\'m not authorised to comment'
|
||||||
}
|
}
|
||||||
client = new GraphQLClient(host)
|
client = new GraphQLClient(host)
|
||||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
await expect(client.request(createCommentMutation, createCommentVariables)).rejects.toThrow('Not Authorised')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -42,40 +59,120 @@ describe('CreateComment', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
headers = await login({ email: 'test@example.org', password: '1234' })
|
headers = await login({ email: 'test@example.org', password: '1234' })
|
||||||
client = new GraphQLClient(host, { headers })
|
client = new GraphQLClient(host, { headers })
|
||||||
})
|
createCommentVariables = {
|
||||||
|
|
||||||
it('creates a comment', async () => {
|
|
||||||
variables = {
|
|
||||||
postId: 'p1',
|
postId: 'p1',
|
||||||
content: 'I\'m authorised to comment'
|
content: 'I\'m authorised to comment'
|
||||||
}
|
}
|
||||||
|
createPostVariables = {
|
||||||
|
id: 'p1',
|
||||||
|
title: 'post to comment on',
|
||||||
|
content: 'please comment on me'
|
||||||
|
}
|
||||||
|
await client.request(createPostMutation, createPostVariables)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates a comment', async () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
CreateComment: {
|
CreateComment: {
|
||||||
content: 'I\'m authorised to comment'
|
content: 'I\'m authorised to comment'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await expect(client.request(mutation, variables)).resolves.toMatchObject(expected)
|
await expect(client.request(createCommentMutation, createCommentVariables)).resolves.toMatchObject(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throw an error if an empty string is sent as content', async () => {
|
it('assigns the authenticated user as author', async () => {
|
||||||
variables = {
|
await client.request(createCommentMutation, createCommentVariables)
|
||||||
|
|
||||||
|
const { User } = await client.request(`{
|
||||||
|
User(email: "test@example.org") {
|
||||||
|
comments {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
expect(User).toEqual([ { comments: [ { content: 'I\'m authorised to comment' } ] } ])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throw an error if an empty string is sent from the editor as content', async () => {
|
||||||
|
createCommentVariables = {
|
||||||
postId: 'p1',
|
postId: 'p1',
|
||||||
content: '<p></p>'
|
content: '<p></p>'
|
||||||
}
|
}
|
||||||
|
|
||||||
await expect(client.request(mutation, variables))
|
await expect(client.request(createCommentMutation, createCommentVariables))
|
||||||
.rejects.toThrow('Comment must be at least 1 character long!')
|
.rejects.toThrow('Comment must be at least 1 character long!')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws an error if a comment does not contain a single character', async () => {
|
it('throws an error if a comment sent from the editor does not contain a single character', async () => {
|
||||||
variables = {
|
createCommentVariables = {
|
||||||
postId: 'p1',
|
postId: 'p1',
|
||||||
content: '<p> </p>'
|
content: '<p> </p>'
|
||||||
}
|
}
|
||||||
|
|
||||||
await expect(client.request(mutation, variables))
|
await expect(client.request(createCommentMutation, createCommentVariables))
|
||||||
.rejects.toThrow('Comment must be at least 1 character long!')
|
.rejects.toThrow('Comment must be at least 1 character long!')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('throws an error if postId is sent as an empty string', async () => {
|
||||||
|
createCommentVariables = {
|
||||||
|
postId: 'p1',
|
||||||
|
content: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(client.request(createCommentMutation, createCommentVariables))
|
||||||
|
.rejects.toThrow('Comment must be at least 1 character long!')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error if content is sent as an string of empty characters', async () => {
|
||||||
|
createCommentVariables = {
|
||||||
|
postId: 'p1',
|
||||||
|
content: ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(client.request(createCommentMutation, createCommentVariables))
|
||||||
|
.rejects.toThrow('Comment must be at least 1 character long!')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error if postId is sent as an empty string', async () => {
|
||||||
|
createCommentVariablesSansPostId = {
|
||||||
|
postId: '',
|
||||||
|
content: 'this comment should not be created'
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(client.request(createCommentMutation, createCommentVariablesSansPostId))
|
||||||
|
.rejects.toThrow('Comment cannot be created without a post!')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error if postId is sent as an string of empty characters', async () => {
|
||||||
|
createCommentVariablesSansPostId = {
|
||||||
|
postId: ' ',
|
||||||
|
content: 'this comment should not be created'
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(client.request(createCommentMutation, createCommentVariablesSansPostId))
|
||||||
|
.rejects.toThrow('Comment cannot be created without a post!')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error if the post does not exist in the database', async () => {
|
||||||
|
createCommentVariablesWithNonExistentPost = {
|
||||||
|
postId: 'p2',
|
||||||
|
content: 'comment should not be created cause the post doesn\'t exist'
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(client.request(createCommentMutation, createCommentVariablesWithNonExistentPost))
|
||||||
|
.rejects.toThrow('Comment cannot be created without a post!')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not create the comment with the postId as an attribute', async () => {
|
||||||
|
const commentQueryVariablesByContent = {
|
||||||
|
content: 'I\'m authorised to comment'
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.request(createCommentMutation, createCommentVariables)
|
||||||
|
const { Comment } = await client.request(commentQueryForPostId, commentQueryVariablesByContent)
|
||||||
|
expect(Comment).toEqual([{ postId: null }])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -16,6 +16,9 @@ const setupAuthenticateClient = (params) => {
|
|||||||
|
|
||||||
let createResource
|
let createResource
|
||||||
let authenticateClient
|
let authenticateClient
|
||||||
|
let createPostVariables
|
||||||
|
let createCommentVariables
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createResource = () => {}
|
createResource = () => {}
|
||||||
authenticateClient = () => {
|
authenticateClient = () => {
|
||||||
@ -103,18 +106,21 @@ describe('disable', () => {
|
|||||||
variables = {
|
variables = {
|
||||||
id: 'c47'
|
id: 'c47'
|
||||||
}
|
}
|
||||||
|
createPostVariables = {
|
||||||
|
id: 'p3',
|
||||||
|
title: 'post to comment on',
|
||||||
|
content: 'please comment on me'
|
||||||
|
}
|
||||||
|
createCommentVariables = {
|
||||||
|
id: 'c47',
|
||||||
|
postId: 'p3',
|
||||||
|
content: 'this comment was created for this post'
|
||||||
|
}
|
||||||
createResource = async () => {
|
createResource = async () => {
|
||||||
await factory.create('User', { id: 'u45', email: 'commenter@example.org', password: '1234' })
|
await factory.create('User', { id: 'u45', email: 'commenter@example.org', password: '1234' })
|
||||||
await factory.authenticateAs({ email: 'commenter@example.org', password: '1234' })
|
const asAuthenticatedUser = await factory.authenticateAs({ email: 'commenter@example.org', password: '1234' })
|
||||||
await Promise.all([
|
await asAuthenticatedUser.create('Post', createPostVariables)
|
||||||
factory.create('Post', { id: 'p3' }),
|
await asAuthenticatedUser.create('Comment', createCommentVariables)
|
||||||
factory.create('Comment', { id: 'c47', postId: 'p3', content: 'this comment was created for this post' })
|
|
||||||
])
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
factory.relate('Comment', 'Author', { from: 'u45', to: 'c47' })
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -277,17 +283,21 @@ describe('enable', () => {
|
|||||||
variables = {
|
variables = {
|
||||||
id: 'c456'
|
id: 'c456'
|
||||||
}
|
}
|
||||||
|
createPostVariables = {
|
||||||
|
id: 'p9',
|
||||||
|
title: 'post to comment on',
|
||||||
|
content: 'please comment on me'
|
||||||
|
}
|
||||||
|
createCommentVariables = {
|
||||||
|
id: 'c456',
|
||||||
|
postId: 'p9',
|
||||||
|
content: 'this comment was created for this post'
|
||||||
|
}
|
||||||
createResource = async () => {
|
createResource = async () => {
|
||||||
await factory.create('User', { id: 'u123', email: 'author@example.org', password: '1234' })
|
await factory.create('User', { id: 'u123', email: 'author@example.org', password: '1234' })
|
||||||
await factory.authenticateAs({ email: 'author@example.org', password: '1234' })
|
const asAuthenticatedUser = await factory.authenticateAs({ email: 'author@example.org', password: '1234' })
|
||||||
await Promise.all([
|
await asAuthenticatedUser.create('Post', createPostVariables)
|
||||||
factory.create('Post', { id: 'p9' }),
|
await asAuthenticatedUser.create('Comment', createCommentVariables)
|
||||||
factory.create('Comment', { id: 'c456' })
|
|
||||||
])
|
|
||||||
await Promise.all([
|
|
||||||
factory.relate('Comment', 'Author', { from: 'u123', to: 'c456' })
|
|
||||||
])
|
|
||||||
|
|
||||||
const disableMutation = `
|
const disableMutation = `
|
||||||
mutation {
|
mutation {
|
||||||
|
|||||||
@ -9,6 +9,7 @@ describe('report', () => {
|
|||||||
let headers
|
let headers
|
||||||
let returnedObject
|
let returnedObject
|
||||||
let variables
|
let variables
|
||||||
|
let createPostVariables
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
returnedObject = '{ description }'
|
returnedObject = '{ description }'
|
||||||
@ -128,8 +129,14 @@ describe('report', () => {
|
|||||||
|
|
||||||
describe('reported resource is a comment', () => {
|
describe('reported resource is a comment', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await factory.authenticateAs({ email: 'test@example.org', password: '1234' })
|
createPostVariables = {
|
||||||
await factory.create('Comment', { id: 'c34', content: 'Robert getting tired.' })
|
id: 'p1',
|
||||||
|
title: 'post to comment on',
|
||||||
|
content: 'please comment on me'
|
||||||
|
}
|
||||||
|
const asAuthenticatedUser = await factory.authenticateAs({ email: 'test@example.org', password: '1234' })
|
||||||
|
await asAuthenticatedUser.create('Post', createPostVariables)
|
||||||
|
await asAuthenticatedUser.create('Comment', { postId: 'p1', id: 'c34', content: 'Robert getting tired.' })
|
||||||
variables = { id: 'c34' }
|
variables = { id: 'c34' }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -189,33 +189,18 @@ import Factory from './factories'
|
|||||||
])
|
])
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
f.create('Comment', { id: 'c1', postId: 'p1' }),
|
asUser.create('Comment', { id: 'c1', postId: 'p1' }),
|
||||||
f.create('Comment', { id: 'c2', postId: 'p1' }),
|
asTick.create('Comment', { id: 'c2', postId: 'p1' }),
|
||||||
f.create('Comment', { id: 'c3', postId: 'p3' }),
|
asTrack.create('Comment', { id: 'c3', postId: 'p3' }),
|
||||||
f.create('Comment', { id: 'c4', postId: 'p2' }),
|
asTrick.create('Comment', { id: 'c4', postId: 'p2' }),
|
||||||
f.create('Comment', { id: 'c5', postId: 'p3' }),
|
asModerator.create('Comment', { id: 'c5', postId: 'p3' }),
|
||||||
f.create('Comment', { id: 'c6', postId: 'p4' }),
|
asAdmin.create('Comment', { id: 'c6', postId: 'p4' }),
|
||||||
f.create('Comment', { id: 'c7', postId: 'p2' }),
|
asUser.create('Comment', { id: 'c7', postId: 'p2' }),
|
||||||
f.create('Comment', { id: 'c8', postId: 'p15' }),
|
asTick.create('Comment', { id: 'c8', postId: 'p15' }),
|
||||||
f.create('Comment', { id: 'c9', postId: 'p15' }),
|
asTrick.create('Comment', { id: 'c9', postId: 'p15' }),
|
||||||
f.create('Comment', { id: 'c10', postId: 'p15' }),
|
asTrack.create('Comment', { id: 'c10', postId: 'p15' }),
|
||||||
f.create('Comment', { id: 'c11', postId: 'p15' }),
|
asUser.create('Comment', { id: 'c11', postId: 'p15' }),
|
||||||
f.create('Comment', { id: 'c12', postId: 'p15' })
|
asUser.create('Comment', { id: 'c12', postId: 'p15' })
|
||||||
])
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
f.relate('Comment', 'Author', { from: 'u3', to: 'c1' }),
|
|
||||||
f.relate('Comment', 'Author', { from: 'u1', to: 'c2' }),
|
|
||||||
f.relate('Comment', 'Author', { from: 'u1', to: 'c3' }),
|
|
||||||
f.relate('Comment', 'Author', { from: 'u4', to: 'c4' }),
|
|
||||||
f.relate('Comment', 'Author', { from: 'u4', to: 'c5' }),
|
|
||||||
f.relate('Comment', 'Author', { from: 'u3', to: 'c6' }),
|
|
||||||
f.relate('Comment', 'Author', { from: 'u2', to: 'c7' }),
|
|
||||||
f.relate('Comment', 'Author', { from: 'u5', to: 'c8' }),
|
|
||||||
f.relate('Comment', 'Author', { from: 'u6', to: 'c9' }),
|
|
||||||
f.relate('Comment', 'Author', { from: 'u7', to: 'c10' }),
|
|
||||||
f.relate('Comment', 'Author', { from: 'u5', to: 'c11' }),
|
|
||||||
f.relate('Comment', 'Author', { from: 'u6', to: 'c12' })
|
|
||||||
])
|
])
|
||||||
|
|
||||||
const disableMutation = 'mutation($id: ID!) { disable(id: $id) }'
|
const disableMutation = 'mutation($id: ID!) { disable(id: $id) }'
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { When, Then } from 'cypress-cucumber-preprocessor/steps'
|
import { When, Then } from 'cypress-cucumber-preprocessor/steps'
|
||||||
|
|
||||||
|
const narratorAvatar = 'https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg'
|
||||||
|
|
||||||
Then('I click on the {string} button', text => {
|
Then('I click on the {string} button', text => {
|
||||||
cy.get('button').contains(text).click()
|
cy.get('button').contains(text).click()
|
||||||
})
|
})
|
||||||
@ -12,6 +14,9 @@ Then('my comment should be successfully created', () => {
|
|||||||
Then('I should see my comment', () => {
|
Then('I should see my comment', () => {
|
||||||
cy.get('div.comment p')
|
cy.get('div.comment p')
|
||||||
.should('contain', 'Human Connection rocks')
|
.should('contain', 'Human Connection rocks')
|
||||||
|
.get('.ds-avatar img')
|
||||||
|
.should('have.attr', 'src')
|
||||||
|
.and('contain', narratorAvatar)
|
||||||
})
|
})
|
||||||
|
|
||||||
Then('the editor should be cleared', () => {
|
Then('the editor should be cleared', () => {
|
||||||
|
|||||||
@ -11,6 +11,7 @@ let loginCredentials = {
|
|||||||
}
|
}
|
||||||
const narratorParams = {
|
const narratorParams = {
|
||||||
name: 'Peter Pan',
|
name: 'Peter Pan',
|
||||||
|
avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg',
|
||||||
...loginCredentials
|
...loginCredentials
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
<ds-button
|
<ds-button
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
ghost
|
ghost
|
||||||
|
class="cancelBtn"
|
||||||
@click.prevent="clear"
|
@click.prevent="clear"
|
||||||
>
|
>
|
||||||
{{ $t('actions.cancel') }}
|
{{ $t('actions.cancel') }}
|
||||||
@ -28,6 +29,7 @@
|
|||||||
<ds-flex-item :width="{ base: '40%', md: '20%', sm: '40%', xs: '40%' }">
|
<ds-flex-item :width="{ base: '40%', md: '20%', sm: '40%', xs: '40%' }">
|
||||||
<ds-button
|
<ds-button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
:loading="loading"
|
||||||
:disabled="disabled || errors"
|
:disabled="disabled || errors"
|
||||||
primary
|
primary
|
||||||
>
|
>
|
||||||
@ -55,6 +57,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
disabled: true,
|
disabled: true,
|
||||||
|
loading: false,
|
||||||
form: {
|
form: {
|
||||||
content: ''
|
content: ''
|
||||||
},
|
},
|
||||||
@ -75,6 +78,8 @@ export default {
|
|||||||
this.$refs.editor.clear()
|
this.$refs.editor.clear()
|
||||||
},
|
},
|
||||||
handleSubmit() {
|
handleSubmit() {
|
||||||
|
this.loading = true
|
||||||
|
this.disabled = true
|
||||||
this.$apollo
|
this.$apollo
|
||||||
.mutate({
|
.mutate({
|
||||||
mutation: gql`
|
mutation: gql`
|
||||||
@ -91,9 +96,11 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.$emit('addComment', res.data.CreateComment)
|
this.loading = false
|
||||||
|
this.$root.$emit('refetchPostComments', res.data.CreateComment)
|
||||||
this.$refs.editor.clear()
|
this.$refs.editor.clear()
|
||||||
this.$toast.success(this.$t('post.comment.submitted'))
|
this.$toast.success(this.$t('post.comment.submitted'))
|
||||||
|
this.disabled = false
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
this.$toast.error(err.message)
|
this.$toast.error(err.message)
|
||||||
69
webapp/components/comments/CommentList/CommentList.spec.js
Normal file
69
webapp/components/comments/CommentList/CommentList.spec.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { config, mount, createLocalVue } from '@vue/test-utils'
|
||||||
|
import CommentList from '.'
|
||||||
|
import Empty from '~/components/Empty'
|
||||||
|
import Vue from 'vue'
|
||||||
|
import Vuex from 'vuex'
|
||||||
|
import Filters from '~/plugins/vue-filters'
|
||||||
|
import Styleguide from '@human-connection/styleguide'
|
||||||
|
|
||||||
|
const localVue = createLocalVue()
|
||||||
|
|
||||||
|
localVue.use(Styleguide)
|
||||||
|
localVue.use(Vuex)
|
||||||
|
localVue.filter('truncate', string => string)
|
||||||
|
|
||||||
|
config.stubs['v-popover'] = '<span><slot /></span>'
|
||||||
|
config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||||
|
config.stubs['no-ssr'] = '<span><slot /></span>'
|
||||||
|
|
||||||
|
describe('CommentList.vue', () => {
|
||||||
|
let mocks
|
||||||
|
let store
|
||||||
|
let wrapper
|
||||||
|
let propsData
|
||||||
|
let data
|
||||||
|
|
||||||
|
propsData = {
|
||||||
|
post: { id: 1 }
|
||||||
|
}
|
||||||
|
store = new Vuex.Store({
|
||||||
|
getters: {
|
||||||
|
'auth/user': () => {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mocks = {
|
||||||
|
$t: jest.fn()
|
||||||
|
}
|
||||||
|
data = () => {
|
||||||
|
return {
|
||||||
|
comments: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('shallowMount', () => {
|
||||||
|
const Wrapper = () => {
|
||||||
|
return mount(CommentList, { store, mocks, localVue, propsData, data })
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
wrapper.setData({
|
||||||
|
comments: [{ id: 'c1', contentExcerpt: 'this is a comment' }]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('displays a message icon when there are no comments to display', () => {
|
||||||
|
expect(Wrapper().findAll(Empty)).toHaveLength(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('displays a comments counter', () => {
|
||||||
|
expect(wrapper.find('span.ds-tag').text()).toEqual('1')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('displays comments when there are comments to display', () => {
|
||||||
|
expect(wrapper.find('div#comments').text()).toEqual('this is a comment')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
80
webapp/components/comments/CommentList/index.vue
Normal file
80
webapp/components/comments/CommentList/index.vue
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h3 style="margin-top: -10px;">
|
||||||
|
<span>
|
||||||
|
<ds-icon name="comments" />
|
||||||
|
<ds-tag
|
||||||
|
v-if="comments"
|
||||||
|
style="margin-top: -4px; margin-left: -12px; position: absolute;"
|
||||||
|
color="primary"
|
||||||
|
size="small"
|
||||||
|
round
|
||||||
|
>{{ comments.length }}</ds-tag> Comments
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
<ds-space margin-bottom="large" />
|
||||||
|
<div
|
||||||
|
v-if="comments && comments.length"
|
||||||
|
id="comments"
|
||||||
|
class="comments"
|
||||||
|
>
|
||||||
|
<comment
|
||||||
|
v-for="comment in comments"
|
||||||
|
:key="comment.id"
|
||||||
|
:comment="comment"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<hc-empty
|
||||||
|
v-else
|
||||||
|
name="empty"
|
||||||
|
icon="messages"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Comment from '~/components/Comment.vue'
|
||||||
|
import HcEmpty from '~/components/Empty.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Comment,
|
||||||
|
HcEmpty
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
post: { type: Object, default: () => {} }
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
comments: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
Post(post) {
|
||||||
|
this.comments = post[0].comments || []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$root.$on('refetchPostComments', comment => {
|
||||||
|
this.refetchPostComments(comment)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
refetchPostComments(comment) {
|
||||||
|
this.$apollo.queries.Post.refetch()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
apollo: {
|
||||||
|
Post: {
|
||||||
|
query() {
|
||||||
|
return require('~/graphql/PostCommentsQuery.js').default(this)
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
return {
|
||||||
|
slug: this.post.slug
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchPolicy: 'cache-and-network'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -1,12 +1,34 @@
|
|||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
export default app => {
|
export default app => {
|
||||||
|
const lang = app.$i18n.locale().toUpperCase()
|
||||||
return gql(`
|
return gql(`
|
||||||
query CommentByPost($postId: ID!) {
|
query Comment($postId: ID) {
|
||||||
CommentByPost(postId: $postId) {
|
Comment(postId: $postId) {
|
||||||
id
|
id
|
||||||
contentExcerpt
|
contentExcerpt
|
||||||
createdAt
|
createdAt
|
||||||
|
author {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
name
|
||||||
|
avatar
|
||||||
|
disabled
|
||||||
|
deleted
|
||||||
|
shoutedCount
|
||||||
|
contributionsCount
|
||||||
|
commentsCount
|
||||||
|
followedByCount
|
||||||
|
followedByCurrentUser
|
||||||
|
location {
|
||||||
|
name: name${lang}
|
||||||
|
}
|
||||||
|
badges {
|
||||||
|
id
|
||||||
|
key
|
||||||
|
icon
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
|||||||
39
webapp/graphql/PostCommentsQuery.js
Normal file
39
webapp/graphql/PostCommentsQuery.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
export default app => {
|
||||||
|
const lang = app.$i18n.locale().toUpperCase()
|
||||||
|
return gql(`
|
||||||
|
query Post($slug: String!) {
|
||||||
|
Post(slug: $slug) {
|
||||||
|
comments(orderBy: createdAt_asc) {
|
||||||
|
id
|
||||||
|
contentExcerpt
|
||||||
|
createdAt
|
||||||
|
disabled
|
||||||
|
deleted
|
||||||
|
author {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
name
|
||||||
|
avatar
|
||||||
|
disabled
|
||||||
|
deleted
|
||||||
|
shoutedCount
|
||||||
|
contributionsCount
|
||||||
|
commentsCount
|
||||||
|
followedByCount
|
||||||
|
followedByCurrentUser
|
||||||
|
location {
|
||||||
|
name: name${lang}
|
||||||
|
}
|
||||||
|
badges {
|
||||||
|
id
|
||||||
|
key
|
||||||
|
icon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
}
|
||||||
@ -96,39 +96,9 @@
|
|||||||
<ds-space margin="small" />
|
<ds-space margin="small" />
|
||||||
<!-- Comments -->
|
<!-- Comments -->
|
||||||
<ds-section slot="footer">
|
<ds-section slot="footer">
|
||||||
<h3 style="margin-top: -10px;">
|
<hc-comment-list :post="post" />
|
||||||
<span>
|
|
||||||
<ds-icon name="comments" />
|
|
||||||
<ds-tag
|
|
||||||
v-if="comments"
|
|
||||||
style="margin-top: -4px; margin-left: -12px; position: absolute;"
|
|
||||||
color="primary"
|
|
||||||
size="small"
|
|
||||||
round
|
|
||||||
>{{ comments.length }}</ds-tag> Comments
|
|
||||||
</span>
|
|
||||||
</h3>
|
|
||||||
<ds-space margin-bottom="large" />
|
<ds-space margin-bottom="large" />
|
||||||
<div
|
<hc-comment-form :post="post" />
|
||||||
v-if="comments && comments.length"
|
|
||||||
id="comments"
|
|
||||||
class="comments"
|
|
||||||
>
|
|
||||||
<comment
|
|
||||||
v-for="comment in comments"
|
|
||||||
:key="comment.id"
|
|
||||||
:comment="comment"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<hc-empty
|
|
||||||
v-else
|
|
||||||
icon="messages"
|
|
||||||
/>
|
|
||||||
<ds-space margin-bottom="large" />
|
|
||||||
<hc-comment-form
|
|
||||||
:post="post"
|
|
||||||
@addComment="addComment"
|
|
||||||
/>
|
|
||||||
</ds-section>
|
</ds-section>
|
||||||
</ds-card>
|
</ds-card>
|
||||||
</transition>
|
</transition>
|
||||||
@ -142,9 +112,8 @@ import HcTag from '~/components/Tag'
|
|||||||
import ContentMenu from '~/components/ContentMenu'
|
import ContentMenu from '~/components/ContentMenu'
|
||||||
import HcUser from '~/components/User'
|
import HcUser from '~/components/User'
|
||||||
import HcShoutButton from '~/components/ShoutButton.vue'
|
import HcShoutButton from '~/components/ShoutButton.vue'
|
||||||
import HcEmpty from '~/components/Empty.vue'
|
import HcCommentForm from '~/components/comments/CommentForm'
|
||||||
import HcCommentForm from '~/components/CommentForm'
|
import HcCommentList from '~/components/comments/CommentList'
|
||||||
import Comment from '~/components/Comment.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
transition: {
|
transition: {
|
||||||
@ -156,10 +125,9 @@ export default {
|
|||||||
HcCategory,
|
HcCategory,
|
||||||
HcUser,
|
HcUser,
|
||||||
HcShoutButton,
|
HcShoutButton,
|
||||||
HcEmpty,
|
|
||||||
Comment,
|
|
||||||
ContentMenu,
|
ContentMenu,
|
||||||
HcCommentForm
|
HcCommentForm,
|
||||||
|
HcCommentList
|
||||||
},
|
},
|
||||||
head() {
|
head() {
|
||||||
return {
|
return {
|
||||||
@ -169,7 +137,6 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
post: null,
|
post: null,
|
||||||
comments: null,
|
|
||||||
ready: false,
|
ready: false,
|
||||||
title: 'loading'
|
title: 'loading'
|
||||||
}
|
}
|
||||||
@ -178,9 +145,6 @@ export default {
|
|||||||
Post(post) {
|
Post(post) {
|
||||||
this.post = post[0] || {}
|
this.post = post[0] || {}
|
||||||
this.title = this.post.title
|
this.title = this.post.title
|
||||||
},
|
|
||||||
CommentByPost(comments) {
|
|
||||||
this.comments = comments || []
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async asyncData(context) {
|
async asyncData(context) {
|
||||||
@ -289,22 +253,6 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
isAuthor(id) {
|
isAuthor(id) {
|
||||||
return this.$store.getters['auth/user'].id === id
|
return this.$store.getters['auth/user'].id === id
|
||||||
},
|
|
||||||
addComment(comment) {
|
|
||||||
this.$apollo.queries.CommentByPost.refetch()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
apollo: {
|
|
||||||
CommentByPost: {
|
|
||||||
query() {
|
|
||||||
return require('~/graphql/CommentQuery.js').default(this)
|
|
||||||
},
|
|
||||||
variables() {
|
|
||||||
return {
|
|
||||||
postId: this.post.id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fetchPolicy: 'cache-and-network'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user