diff --git a/backend/src/middleware/handleHtmlContent/handleContentData.js b/backend/src/middleware/handleHtmlContent/handleContentData.js index 287ba34e6..c17d2e5fb 100644 --- a/backend/src/middleware/handleHtmlContent/handleContentData.js +++ b/backend/src/middleware/handleHtmlContent/handleContentData.js @@ -1,39 +1,57 @@ import extractMentionedUsers from './notifications/extractMentionedUsers' import extractHashtags from './hashtags/extractHashtags' -const notify = async (postId, idsOfMentionedUsers, context) => { +const notifyMentions = async (label, id, idsOfMentionedUsers, context) => { + if (!idsOfMentionedUsers.length) return + const session = context.driver.session() const createdAt = new Date().toISOString() - const cypher = ` - MATCH(p:Post {id: $postId})<-[:WROTE]-(author:User) - MATCH(u:User) - WHERE u.id in $idsOfMentionedUsers - AND NOT (u)<-[:BLOCKED]-(author) - CREATE(n:Notification{id: apoc.create.uuid(), read: false, createdAt: $createdAt}) - MERGE (p)-[:NOTIFIED]->(n)-[:NOTIFIED]->(u) + let cypher + if (label === 'Post') { + cypher = ` + MATCH (post: Post { id: $id })<-[:WROTE]-(author: User) + MATCH (user: User) + WHERE user.id in $idsOfMentionedUsers + AND NOT (user)<-[:BLOCKED]-(author) + CREATE (notification: Notification {id: apoc.create.uuid(), read: false, createdAt: $createdAt }) + MERGE (post)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user) ` + } else { + cypher = ` + MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User) + MATCH (user: User) + WHERE user.id in $idsOfMentionedUsers + AND NOT (user)<-[:BLOCKED]-(author) + AND NOT (user)<-[:BLOCKED]-(postAuthor) + CREATE (notification: Notification {id: apoc.create.uuid(), read: false, createdAt: $createdAt }) + MERGE (comment)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user) + ` + } await session.run(cypher, { idsOfMentionedUsers, + label, createdAt, - postId, + id, }) session.close() } const updateHashtagsOfPost = async (postId, hashtags, context) => { + if (!hashtags.length) return + const session = context.driver.session() // We need two Cypher statements, because the 'MATCH' in the 'cypherDeletePreviousRelations' statement // functions as an 'if'. In case there is no previous relation, the rest of the commands are omitted // and no new Hashtags and relations will be created. const cypherDeletePreviousRelations = ` - MATCH (p:Post { id: $postId })-[previousRelations:TAGGED]->(t:Tag) + MATCH (p: Post { id: $postId })-[previousRelations: TAGGED]->(t: Tag) DELETE previousRelations RETURN p, t ` const cypherCreateNewTagsAndRelations = ` - MATCH (p:Post { id: $postId}) + MATCH (p: Post { id: $postId}) UNWIND $hashtags AS tagName - MERGE (t:Tag { id: tagName, name: tagName, disabled: false, deleted: false }) + MERGE (t: Tag { id: tagName, name: tagName, disabled: false, deleted: false }) MERGE (p)-[:TAGGED]->(t) RETURN p, t ` @@ -47,24 +65,32 @@ const updateHashtagsOfPost = async (postId, hashtags, context) => { session.close() } -const handleContentData = async (resolve, root, args, context, resolveInfo) => { - // extract user ids before xss-middleware removes classes via the following "resolve" call +const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => { const idsOfMentionedUsers = extractMentionedUsers(args.content) - // extract tag (hashtag) ids before xss-middleware removes classes via the following "resolve" call const hashtags = extractHashtags(args.content) - // removes classes from the content const post = await resolve(root, args, context, resolveInfo) - await notify(post.id, idsOfMentionedUsers, context) + await notifyMentions('Post', post.id, idsOfMentionedUsers, context) await updateHashtagsOfPost(post.id, hashtags, context) return post } +const handleContentDataOfComment = async (resolve, root, args, context, resolveInfo) => { + const idsOfMentionedUsers = extractMentionedUsers(args.content) + const comment = await resolve(root, args, context, resolveInfo) + + await notifyMentions('Comment', comment.id, idsOfMentionedUsers, context) + + return comment +} + export default { Mutation: { - CreatePost: handleContentData, - UpdatePost: handleContentData, + CreatePost: handleContentDataOfPost, + UpdatePost: handleContentDataOfPost, + CreateComment: handleContentDataOfComment, + UpdateComment: handleContentDataOfComment, }, } diff --git a/backend/src/middleware/handleHtmlContent/handleContentData.spec.js b/backend/src/middleware/handleHtmlContent/handleContentData.spec.js index fb088e428..7d7a688f7 100644 --- a/backend/src/middleware/handleHtmlContent/handleContentData.spec.js +++ b/backend/src/middleware/handleHtmlContent/handleContentData.spec.js @@ -52,6 +52,9 @@ describe('notifications', () => { post { content } + comment { + content + } } } } @@ -63,12 +66,12 @@ describe('notifications', () => { }) describe('given another user', () => { - let author + let postAuthor beforeEach(async () => { - author = await instance.create('User', { - email: 'author@example.org', + postAuthor = await instance.create('User', { + email: 'post-author@example.org', password: '1234', - id: 'author', + id: 'postAuthor', }) }) @@ -87,10 +90,14 @@ describe('notifications', () => { } } ` - authenticatedUser = await author.toJson() + authenticatedUser = await postAuthor.toJson() await mutate({ mutation: createPostMutation, - variables: { id: 'p47', title, content }, + variables: { + id: 'p47', + title, + content, + }, }) authenticatedUser = await user.toJson() } @@ -101,12 +108,27 @@ describe('notifications', () => { 'Hey @al-capone how do you do?' const expected = expect.objectContaining({ data: { - currentUser: { notifications: [{ read: false, post: { content: expectedContent } }] }, + currentUser: { + notifications: [ + { + read: false, + post: { + content: expectedContent, + }, + comment: null, + }, + ], + }, }, }) const { query } = createTestClient(server) await expect( - query({ query: notificationQuery, variables: { read: false } }), + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), ).resolves.toEqual(expected) }) @@ -134,7 +156,7 @@ describe('notifications', () => { } } ` - authenticatedUser = await author.toJson() + authenticatedUser = await postAuthor.toJson() await mutate({ mutation: updatePostMutation, variables: { @@ -155,31 +177,114 @@ describe('notifications', () => { data: { currentUser: { notifications: [ - { read: false, post: { content: expectedContent } }, - { read: false, post: { content: expectedContent } }, + { + read: false, + post: { + content: expectedContent, + }, + comment: null, + }, + { + read: false, + post: { + content: expectedContent, + }, + comment: null, + }, ], }, }, }) await expect( - query({ query: notificationQuery, variables: { read: false } }), + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), ).resolves.toEqual(expected) }) }) describe('but the author of the post blocked me', () => { beforeEach(async () => { - await author.relateTo(user, 'blocked') + await postAuthor.relateTo(user, 'blocked') }) it('sends no notification', async () => { await createPostAction() const expected = expect.objectContaining({ - data: { currentUser: { notifications: [] } }, + data: { + currentUser: { + notifications: [], + }, + }, }) const { query } = createTestClient(server) await expect( - query({ query: notificationQuery, variables: { read: false } }), + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toEqual(expected) + }) + }) + + describe('but the author of the post blocked me and a mentioner mentions me in a comment', () => { + const createCommentOnPostAction = async () => { + await createPostAction() + const createCommentMutation = gql` + mutation($id: ID, $postId: ID!, $commentContent: String!) { + CreateComment(id: $id, postId: $postId, content: $commentContent) { + id + content + } + } + ` + authenticatedUser = await commentMentioner.toJson() + await mutate({ + mutation: createCommentMutation, + variables: { + id: 'c47', + postId: 'p47', + commentContent: + 'One mention of me with .', + }, + }) + authenticatedUser = await user.toJson() + } + let commentMentioner + + beforeEach(async () => { + await postAuthor.relateTo(user, 'blocked') + commentMentioner = await instance.create('User', { + id: 'mentioner', + name: 'Mr Mentioner', + slug: 'mr-mentioner', + email: 'mentioner@example.org', + password: '1234', + }) + }) + + it('sends no notification', async () => { + await createCommentOnPostAction() + const expected = expect.objectContaining({ + data: { + currentUser: { + notifications: [], + }, + }, + }) + const { query } = createTestClient(server) + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), ).resolves.toEqual(expected) }) }) @@ -235,7 +340,10 @@ describe('Hashtags', () => { it('both Hashtags are created with the "id" set to their "name"', async () => { const expected = [{ id: 'Democracy' }, { id: 'Liberty' }] await expect( - query({ query: postWithHastagsQuery, variables: postWithHastagsVariables }), + query({ + query: postWithHastagsQuery, + variables: postWithHastagsVariables, + }), ).resolves.toEqual( expect.objectContaining({ data: { @@ -275,11 +383,18 @@ describe('Hashtags', () => { const expected = [{ id: 'Elections' }, { id: 'Liberty' }] await expect( - query({ query: postWithHastagsQuery, variables: postWithHastagsVariables }), + query({ + query: postWithHastagsQuery, + variables: postWithHastagsVariables, + }), ).resolves.toEqual( expect.objectContaining({ data: { - Post: [{ tags: expect.arrayContaining(expected) }], + Post: [ + { + tags: expect.arrayContaining(expected), + }, + ], }, }), ) diff --git a/backend/src/schema/resolvers/notifications.spec.js b/backend/src/schema/resolvers/notifications.spec.js index 8d51ebfbb..5e7108be3 100644 --- a/backend/src/schema/resolvers/notifications.spec.js +++ b/backend/src/schema/resolvers/notifications.spec.js @@ -1,6 +1,6 @@ import { GraphQLClient } from 'graphql-request' import Factory from '../../seed/factories' -import { host, login } from '../../jest/helpers' +import { host, login, gql } from '../../jest/helpers' const factory = Factory() let client @@ -18,29 +18,36 @@ afterEach(async () => { await factory.cleanDatabase() }) -describe('Notification', () => { - const query = `{ - Notification { - id +describe('query for notification', () => { + const notificationQuery = gql` + { + Notification { + id + } } - }` + ` describe('unauthenticated', () => { it('throws authorization error', async () => { client = new GraphQLClient(host) - await expect(client.request(query)).rejects.toThrow('Not Authorised') + await expect(client.request(notificationQuery)).rejects.toThrow('Not Authorised') }) }) }) -describe('currentUser { notifications }', () => { +describe('currentUser notifications', () => { const variables = {} describe('authenticated', () => { let headers beforeEach(async () => { - headers = await login({ email: 'test@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) + headers = await login({ + email: 'test@example.org', + password: '1234', + }) + client = new GraphQLClient(host, { + headers, + }) }) describe('given some notifications', () => { @@ -52,65 +59,195 @@ describe('currentUser { notifications }', () => { } await Promise.all([ factory.create('User', neighborParams), - factory.create('Notification', { id: 'not-for-you' }), - factory.create('Notification', { id: 'already-seen', read: true }), + factory.create('Notification', { + id: 'post-mention-not-for-you', + }), + factory.create('Notification', { + id: 'post-mention-already-seen', + read: true, + }), + factory.create('Notification', { + id: 'post-mention-unseen', + }), + factory.create('Notification', { + id: 'comment-mention-not-for-you', + }), + factory.create('Notification', { + id: 'comment-mention-already-seen', + read: true, + }), + factory.create('Notification', { + id: 'comment-mention-unseen', + }), ]) - await factory.create('Notification', { id: 'unseen' }) await factory.authenticateAs(neighborParams) - await factory.create('Post', { id: 'p1' }) + // Post and its notifications await Promise.all([ - factory.relate('Notification', 'User', { from: 'not-for-you', to: 'neighbor' }), - factory.relate('Notification', 'Post', { from: 'p1', to: 'not-for-you' }), - factory.relate('Notification', 'User', { from: 'unseen', to: 'you' }), - factory.relate('Notification', 'Post', { from: 'p1', to: 'unseen' }), - factory.relate('Notification', 'User', { from: 'already-seen', to: 'you' }), - factory.relate('Notification', 'Post', { from: 'p1', to: 'already-seen' }), + factory.create('Post', { + id: 'p1', + }), + ]) + await Promise.all([ + factory.relate('Notification', 'User', { + from: 'post-mention-not-for-you', + to: 'neighbor', + }), + factory.relate('Notification', 'Post', { + from: 'p1', + to: 'post-mention-not-for-you', + }), + factory.relate('Notification', 'User', { + from: 'post-mention-unseen', + to: 'you', + }), + factory.relate('Notification', 'Post', { + from: 'p1', + to: 'post-mention-unseen', + }), + factory.relate('Notification', 'User', { + from: 'post-mention-already-seen', + to: 'you', + }), + factory.relate('Notification', 'Post', { + from: 'p1', + to: 'post-mention-already-seen', + }), + ]) + // Comment and its notifications + await Promise.all([ + factory.create('Comment', { + id: 'c1', + postId: 'p1', + }), + ]) + await Promise.all([ + factory.relate('Notification', 'User', { + from: 'comment-mention-not-for-you', + to: 'neighbor', + }), + factory.relate('Notification', 'Comment', { + from: 'c1', + to: 'comment-mention-not-for-you', + }), + factory.relate('Notification', 'User', { + from: 'comment-mention-unseen', + to: 'you', + }), + factory.relate('Notification', 'Comment', { + from: 'c1', + to: 'comment-mention-unseen', + }), + factory.relate('Notification', 'User', { + from: 'comment-mention-already-seen', + to: 'you', + }), + factory.relate('Notification', 'Comment', { + from: 'c1', + to: 'comment-mention-already-seen', + }), ]) }) describe('filter for read: false', () => { - const query = `query($read: Boolean) { - currentUser { - notifications(read: $read, orderBy: createdAt_desc) { - id - post { + const queryCurrentUserNotificationsFilterRead = gql` + query($read: Boolean) { + currentUser { + notifications(read: $read, orderBy: createdAt_desc) { id + post { + id + } + comment { + id + } } } } - }` - const variables = { read: false } + ` + const variables = { + read: false, + } it('returns only unread notifications of current user', async () => { const expected = { currentUser: { - notifications: [{ id: 'unseen', post: { id: 'p1' } }], + notifications: expect.arrayContaining([ + { + id: 'post-mention-unseen', + post: { + id: 'p1', + }, + comment: null, + }, + { + id: 'comment-mention-unseen', + post: null, + comment: { + id: 'c1', + }, + }, + ]), }, } - await expect(client.request(query, variables)).resolves.toEqual(expected) + await expect( + client.request(queryCurrentUserNotificationsFilterRead, variables), + ).resolves.toEqual(expected) }) }) describe('no filters', () => { - const query = `{ - currentUser { - notifications(orderBy: createdAt_desc) { - id - post { + const queryCurrentUserNotifications = gql` + { + currentUser { + notifications(orderBy: createdAt_desc) { id + post { + id + } + comment { + id + } } } } - }` + ` it('returns all notifications of current user', async () => { const expected = { currentUser: { - notifications: [ - { id: 'unseen', post: { id: 'p1' } }, - { id: 'already-seen', post: { id: 'p1' } }, - ], + notifications: expect.arrayContaining([ + { + id: 'post-mention-unseen', + post: { + id: 'p1', + }, + comment: null, + }, + { + id: 'post-mention-already-seen', + post: { + id: 'p1', + }, + comment: null, + }, + { + id: 'comment-mention-unseen', + comment: { + id: 'c1', + }, + post: null, + }, + { + id: 'comment-mention-already-seen', + comment: { + id: 'c1', + }, + post: null, + }, + ]), }, } - await expect(client.request(query, variables)).resolves.toEqual(expected) + await expect(client.request(queryCurrentUserNotifications, variables)).resolves.toEqual( + expected, + ) }) }) }) @@ -118,14 +255,24 @@ describe('currentUser { notifications }', () => { }) describe('UpdateNotification', () => { - const mutation = `mutation($id: ID!, $read: Boolean){ - UpdateNotification(id: $id, read: $read) { - id read + const mutationUpdateNotification = gql` + mutation($id: ID!, $read: Boolean) { + UpdateNotification(id: $id, read: $read) { + id + read + } } - }` - const variables = { id: 'to-be-updated', read: true } + ` + const variablesPostUpdateNotification = { + id: 'post-mention-to-be-updated', + read: true, + } + const variablesCommentUpdateNotification = { + id: 'comment-mention-to-be-updated', + read: true, + } - describe('given a notifications', () => { + describe('given some notifications', () => { let headers beforeEach(async () => { @@ -135,42 +282,110 @@ describe('UpdateNotification', () => { password: '1234', slug: 'mentioned', } - await factory.create('User', mentionedParams) - await factory.create('Notification', { id: 'to-be-updated' }) - await factory.authenticateAs(userParams) - await factory.create('Post', { id: 'p1' }) await Promise.all([ - factory.relate('Notification', 'User', { from: 'to-be-updated', to: 'mentioned-1' }), - factory.relate('Notification', 'Post', { from: 'p1', to: 'to-be-updated' }), + factory.create('User', mentionedParams), + factory.create('Notification', { + id: 'post-mention-to-be-updated', + }), + factory.create('Notification', { + id: 'comment-mention-to-be-updated', + }), + ]) + await factory.authenticateAs(userParams) + // Post and its notifications + await Promise.all([ + factory.create('Post', { + id: 'p1', + }), + ]) + await Promise.all([ + factory.relate('Notification', 'User', { + from: 'post-mention-to-be-updated', + to: 'mentioned-1', + }), + factory.relate('Notification', 'Post', { + from: 'p1', + to: 'post-mention-to-be-updated', + }), + ]) + // Comment and its notifications + await Promise.all([ + factory.create('Comment', { + id: 'c1', + postId: 'p1', + }), + ]) + await Promise.all([ + factory.relate('Notification', 'User', { + from: 'comment-mention-to-be-updated', + to: 'mentioned-1', + }), + factory.relate('Notification', 'Comment', { + from: 'p1', + to: 'comment-mention-to-be-updated', + }), ]) }) describe('unauthenticated', () => { it('throws authorization error', async () => { client = new GraphQLClient(host) - await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') + await expect( + client.request(mutationUpdateNotification, variablesPostUpdateNotification), + ).rejects.toThrow('Not Authorised') }) }) describe('authenticated', () => { beforeEach(async () => { - headers = await login({ email: 'test@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) + headers = await login({ + email: 'test@example.org', + password: '1234', + }) + client = new GraphQLClient(host, { + headers, + }) }) it('throws authorization error', async () => { - await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') + await expect( + client.request(mutationUpdateNotification, variablesPostUpdateNotification), + ).rejects.toThrow('Not Authorised') }) describe('and owner', () => { beforeEach(async () => { - headers = await login({ email: 'mentioned@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) + headers = await login({ + email: 'mentioned@example.org', + password: '1234', + }) + client = new GraphQLClient(host, { + headers, + }) }) - it('updates notification', async () => { - const expected = { UpdateNotification: { id: 'to-be-updated', read: true } } - await expect(client.request(mutation, variables)).resolves.toEqual(expected) + it('updates post notification', async () => { + const expected = { + UpdateNotification: { + id: 'post-mention-to-be-updated', + read: true, + }, + } + await expect( + client.request(mutationUpdateNotification, variablesPostUpdateNotification), + ).resolves.toEqual(expected) + }) + + it('updates comment notification', async () => { + const expected = { + UpdateNotification: { + id: 'comment-mention-to-be-updated', + read: true, + }, + } + await expect( + client.request(mutationUpdateNotification, variablesCommentUpdateNotification), + ).resolves.toEqual(expected) }) }) }) diff --git a/backend/src/schema/types/schema.gql b/backend/src/schema/types/schema.gql index 560669a7d..e0a2c328b 100644 --- a/backend/src/schema/types/schema.gql +++ b/backend/src/schema/types/schema.gql @@ -123,4 +123,3 @@ type SharedInboxEndpoint { id: ID! uri: String } - diff --git a/backend/src/schema/types/type/Notification.gql b/backend/src/schema/types/type/Notification.gql index e4bc16fec..0f94c2301 100644 --- a/backend/src/schema/types/type/Notification.gql +++ b/backend/src/schema/types/type/Notification.gql @@ -3,5 +3,6 @@ type Notification { read: Boolean user: User @relation(name: "NOTIFIED", direction: "OUT") post: Post @relation(name: "NOTIFIED", direction: "IN") + comment: Comment @relation(name: "NOTIFIED", direction: "IN") createdAt: String } diff --git a/backend/src/seed/seed-db.js b/backend/src/seed/seed-db.js index a0640f28e..86f755a24 100644 --- a/backend/src/seed/seed-db.js +++ b/backend/src/seed/seed-db.js @@ -44,42 +44,49 @@ import Factory from './factories' f.create('User', { id: 'u1', name: 'Peter Lustig', + slug: 'peter-lustig', role: 'admin', email: 'admin@example.org', }), f.create('User', { id: 'u2', name: 'Bob der Baumeister', + slug: 'bob-der-baumeister', role: 'moderator', email: 'moderator@example.org', }), f.create('User', { id: 'u3', name: 'Jenny Rostock', + slug: 'jenny-rostock', role: 'user', email: 'user@example.org', }), f.create('User', { id: 'u4', - name: 'Tick', + name: 'Huey (Tick)', + slug: 'huey-tick', role: 'user', - email: 'tick@example.org', + email: 'huey@example.org', }), f.create('User', { id: 'u5', - name: 'Trick', + name: 'Dewey (Trick)', + slug: 'dewey-trick', role: 'user', - email: 'trick@example.org', + email: 'dewey@example.org', }), f.create('User', { id: 'u6', - name: 'Track', + name: 'Louie (Track)', + slug: 'louie-track', role: 'user', - email: 'track@example.org', + email: 'louie@example.org', }), f.create('User', { id: 'u7', name: 'Dagobert', + slug: 'dagobert', role: 'user', email: 'dagobert@example.org', }), @@ -99,15 +106,15 @@ import Factory from './factories' password: '1234', }), Factory().authenticateAs({ - email: 'tick@example.org', + email: 'huey@example.org', password: '1234', }), Factory().authenticateAs({ - email: 'trick@example.org', + email: 'dewey@example.org', password: '1234', }), Factory().authenticateAs({ - email: 'track@example.org', + email: 'louie@example.org', password: '1234', }), ]) @@ -260,6 +267,10 @@ import Factory from './factories' 'Hey @jenny-rostock, what\'s up?' const mention2 = 'Hey @jenny-rostock, here is another notification for you!' + const hashtag1 = + 'See #NaturphilosophieYoga can really help you!' + const hashtagAndMention1 = + 'The new physics of #QuantenFlussTheorie can explain #QuantumGravity! @peter-lustig got that already. ;-)' await Promise.all([ asAdmin.create('Post', { @@ -272,6 +283,8 @@ import Factory from './factories' }), asUser.create('Post', { id: 'p2', + title: `Nature Philosophy Yoga`, + content: `${hashtag1}`, }), asTick.create('Post', { id: 'p3', @@ -293,6 +306,8 @@ import Factory from './factories' asUser.create('Post', { id: 'p8', image: faker.image.unsplash.nature(), + title: `Quantum Flow Theory explains Quantum Gravity`, + content: `${hashtagAndMention1}`, }), asTick.create('Post', { id: 'p9', @@ -639,6 +654,11 @@ import Factory from './factories' }), ]) + const mentionInComment1 = + 'I heard @jenny-rostock, practice it since 3 years now.' + const mentionInComment2 = + 'Did @peter-lustig told you?' + await Promise.all([ asUser.create('Comment', { id: 'c1', @@ -655,6 +675,12 @@ import Factory from './factories' asTrick.create('Comment', { id: 'c4', postId: 'p2', + content: `${mentionInComment1}`, + }), + asUser.create('Comment', { + id: 'c4-1', + postId: 'p2', + content: `${mentionInComment2}`, }), asModerator.create('Comment', { id: 'c5', diff --git a/cypress/integration/common/steps.js b/cypress/integration/common/steps.js index 608f6468d..7192b097b 100644 --- a/cypress/integration/common/steps.js +++ b/cypress/integration/common/steps.js @@ -1,6 +1,6 @@ import { Given, When, Then } from "cypress-cucumber-preprocessor/steps"; import { getLangByName } from "../../support/helpers"; -import slugify from 'slug' +import slugify from "slug"; /* global cy */ @@ -12,7 +12,7 @@ let loginCredentials = { }; const narratorParams = { name: "Peter Pan", - slug: 'peter-pan', + slug: "peter-pan", avatar: "https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg", ...loginCredentials }; @@ -174,10 +174,10 @@ When("I press {string}", label => { Given("we have the following posts in our database:", table => { table.hashes().forEach(({ Author, ...postAttributes }, i) => { - Author = Author || `author-${i}` + Author = Author || `author-${i}`; const userAttributes = { name: Author, - email: `${slugify(Author, {lower: true})}@example.org`, + email: `${slugify(Author, { lower: true })}@example.org`, password: "1234" }; postAttributes.deleted = Boolean(postAttributes.deleted); @@ -363,95 +363,106 @@ Then("there are no notifications in the top menu", () => { cy.get(".notifications-menu").should("contain", "0"); }); -Given("there is an annoying user called {string}", (name) => { +Given("there is an annoying user called {string}", name => { const annoyingParams = { - email: 'spammy-spammer@example.org', - password: '1234', - } - cy.factory().create('User', { + email: "spammy-spammer@example.org", + password: "1234" + }; + cy.factory().create("User", { ...annoyingParams, - id: 'annoying-user', + id: "annoying-user", name - }) -}) + }); +}); -Given("I am on the profile page of the annoying user", (name) => { - cy.openPage('/profile/annoying-user/spammy-spammer'); -}) +Given("I am on the profile page of the annoying user", name => { + cy.openPage("/profile/annoying-user/spammy-spammer"); +}); -When("I visit the profile page of the annoying user", (name) => { - cy.openPage('/profile/annoying-user'); -}) +When("I visit the profile page of the annoying user", name => { + cy.openPage("/profile/annoying-user"); +}); -When("I ", (name) => { - cy.openPage('/profile/annoying-user'); -}) +When("I ", name => { + cy.openPage("/profile/annoying-user"); +}); -When("I click on {string} from the content menu in the user info box", (button) => { - cy.get('.user-content-menu .content-menu-trigger') - .click() - cy.get('.popover .ds-menu-item-link') - .contains(button) - .click() -}) +When( + "I click on {string} from the content menu in the user info box", + button => { + cy.get(".user-content-menu .content-menu-trigger").click(); + cy.get(".popover .ds-menu-item-link") + .contains(button) + .click({ force: true }); + } +); -When ("I navigate to my {string} settings page", (settingsPage) => { +When("I navigate to my {string} settings page", settingsPage => { cy.get(".avatar-menu").click(); cy.get(".avatar-menu-popover") - .find('a[href]').contains("Settings").click() - cy.contains('.ds-menu-item-link', settingsPage).click() -}) + .find("a[href]") + .contains("Settings") + .click(); + cy.contains(".ds-menu-item-link", settingsPage).click(); +}); -Given("I follow the user {string}", (name) => { +Given("I follow the user {string}", name => { cy.neode() - .first('User', { name }).then((followed) => { + .first("User", { name }) + .then(followed => { cy.neode() - .first('User', {name: narratorParams.name}) - .relateTo(followed, 'following') - }) -}) + .first("User", { name: narratorParams.name }) + .relateTo(followed, "following"); + }); +}); -Given("\"Spammy Spammer\" wrote a post {string}", (title) => { +Given('"Spammy Spammer" wrote a post {string}', title => { cy.factory() .authenticateAs({ - email: 'spammy-spammer@example.org', - password: '1234', + email: "spammy-spammer@example.org", + password: "1234" }) - .create("Post", { title }) -}) + .create("Post", { title }); +}); Then("the list of posts of this user is empty", () => { - cy.get('.ds-card-content').not('.post-link') - cy.get('.main-container').find('.ds-space.hc-empty') -}) + cy.get(".ds-card-content").not(".post-link"); + cy.get(".main-container").find(".ds-space.hc-empty"); +}); Then("nobody is following the user profile anymore", () => { - cy.get('.ds-card-content').not('.post-link') - cy.get('.main-container').contains('.ds-card-content', 'is not followed by anyone') -}) + cy.get(".ds-card-content").not(".post-link"); + cy.get(".main-container").contains( + ".ds-card-content", + "is not followed by anyone" + ); +}); -Given("I wrote a post {string}", (title) => { +Given("I wrote a post {string}", title => { cy.factory() .authenticateAs(loginCredentials) - .create("Post", { title }) -}) + .create("Post", { title }); +}); -When("I block the user {string}", (name) => { +When("I block the user {string}", name => { cy.neode() - .first('User', { name }).then((blocked) => { + .first("User", { name }) + .then(blocked => { cy.neode() - .first('User', {name: narratorParams.name}) - .relateTo(blocked, 'blocked') - }) -}) + .first("User", { name: narratorParams.name }) + .relateTo(blocked, "blocked"); + }); +}); -When("I log in with:", (table) => { - const [firstRow] = table.hashes() - const { Email, Password } = firstRow - cy.login({email: Email, password: Password}) -}) +When("I log in with:", table => { + const [firstRow] = table.hashes(); + const { Email, Password } = firstRow; + cy.login({ email: Email, password: Password }); +}); -Then("I see only one post with the title {string}", (title) => { - cy.get('.main-container').find('.post-link').should('have.length', 1) - cy.get('.main-container').contains('.post-link', title) -}) +Then("I see only one post with the title {string}", title => { + cy.get(".main-container") + .find(".post-link") + .should("have.length", 1); + cy.get(".main-container").contains(".post-link", title); +}); diff --git a/webapp/components/Comment.vue b/webapp/components/Comment.vue index c53d0a231..6d3b05eff 100644 --- a/webapp/components/Comment.vue +++ b/webapp/components/Comment.vue @@ -10,7 +10,7 @@