diff --git a/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts
index a46de2830..ad336596d 100644
--- a/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts
+++ b/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts
@@ -118,7 +118,7 @@ afterAll(async () => {
})
describe('notify group members of new posts in group', () => {
- beforeAll(async () => {
+ beforeEach(async () => {
postAuthor = await Factory.build(
'user',
{
@@ -193,8 +193,12 @@ describe('notify group members of new posts in group', () => {
})
})
+ afterEach(async () => {
+ await cleanDatabase()
+ })
+
describe('group owner posts in group', () => {
- beforeAll(async () => {
+ beforeEach(async () => {
jest.clearAllMocks()
authenticatedUser = await groupMember.toJson()
await markAllAsRead()
@@ -275,29 +279,15 @@ describe('notify group members of new posts in group', () => {
})
describe('group member mutes group', () => {
- it('sets the muted status correctly', async () => {
+ beforeEach(async () => {
authenticatedUser = await groupMember.toJson()
- await expect(
- mutate({
- mutation: muteGroupMutation,
- variables: {
- groupId: 'g-1',
- },
- }),
- ).resolves.toMatchObject({
- data: {
- muteGroup: {
- isMutedByMe: true,
- },
+ await mutate({
+ mutation: muteGroupMutation,
+ variables: {
+ groupId: 'g-1',
},
- errors: undefined,
})
- })
-
- it('sends NO notification when another post is posted', async () => {
jest.clearAllMocks()
- authenticatedUser = await groupMember.toJson()
- await markAllAsRead()
authenticatedUser = await postAuthor.toJson()
await mutate({
mutation: createPostMutation,
@@ -308,7 +298,9 @@ describe('notify group members of new posts in group', () => {
groupId: 'g-1',
},
})
- authenticatedUser = await groupMember.toJson()
+ })
+
+ it('sends NO notification when another post is posted', async () => {
await expect(
query({
query: notificationQuery,
@@ -329,30 +321,18 @@ describe('notify group members of new posts in group', () => {
})
describe('group member unmutes group again but disables email', () => {
- beforeAll(async () => {
+ beforeEach(async () => {
+ authenticatedUser = await groupMember.toJson()
+ await mutate({
+ mutation: unmuteGroupMutation,
+ variables: {
+ groupId: 'g-1',
+ },
+ })
jest.clearAllMocks()
await groupMember.update({ emailNotificationsPostInGroup: false })
})
- it('sets the muted status correctly', async () => {
- authenticatedUser = await groupMember.toJson()
- await expect(
- mutate({
- mutation: unmuteGroupMutation,
- variables: {
- groupId: 'g-1',
- },
- }),
- ).resolves.toMatchObject({
- data: {
- unmuteGroup: {
- isMutedByMe: false,
- },
- },
- errors: undefined,
- })
- })
-
it('sends notification when another post is posted', async () => {
authenticatedUser = await groupMember.toJson()
await markAllAsRead()
@@ -396,5 +376,85 @@ describe('notify group members of new posts in group', () => {
})
})
})
+
+ describe('group member blocks author', () => {
+ beforeEach(async () => {
+ await groupMember.relateTo(postAuthor, 'blocked')
+ authenticatedUser = await groupMember.toJson()
+ await markAllAsRead()
+ jest.clearAllMocks()
+ authenticatedUser = await postAuthor.toJson()
+ await mutate({
+ mutation: createPostMutation,
+ variables: {
+ id: 'post-1',
+ title: 'This is another post in the group',
+ content: 'This is the content of another post in the group',
+ groupId: 'g-1',
+ },
+ })
+ })
+
+ it('sends no notification to the user', async () => {
+ authenticatedUser = await groupMember.toJson()
+ await expect(
+ query({
+ query: notificationQuery,
+ variables: {
+ read: false,
+ },
+ }),
+ ).resolves.toMatchObject({
+ data: {
+ notifications: [],
+ },
+ errors: undefined,
+ })
+ })
+
+ it('sends NO email', () => {
+ expect(sendMailMock).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('group member mutes author', () => {
+ beforeEach(async () => {
+ await groupMember.relateTo(postAuthor, 'muted')
+ authenticatedUser = await groupMember.toJson()
+ await markAllAsRead()
+ jest.clearAllMocks()
+ authenticatedUser = await postAuthor.toJson()
+ await mutate({
+ mutation: createPostMutation,
+ variables: {
+ id: 'post-1',
+ title: 'This is another post in the group',
+ content: 'This is the content of another post in the group',
+ groupId: 'g-1',
+ },
+ })
+ })
+
+ it('sends no notification to the user', async () => {
+ authenticatedUser = await groupMember.toJson()
+ await expect(
+ query({
+ query: notificationQuery,
+ variables: {
+ read: false,
+ },
+ }),
+ ).resolves.toMatchObject({
+ data: {
+ notifications: [],
+ },
+ errors: undefined,
+ })
+ })
+
+ it('sends NO email', () => {
+ expect(sendMailMock).not.toHaveBeenCalled()
+ })
+ })
})
})
diff --git a/backend/src/middleware/notifications/notificationsMiddleware.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.spec.ts
index 90888cf8b..c58acc5e2 100644
--- a/backend/src/middleware/notifications/notificationsMiddleware.spec.ts
+++ b/backend/src/middleware/notifications/notificationsMiddleware.spec.ts
@@ -294,6 +294,25 @@ describe('notifications', () => {
).resolves.toEqual(expected)
})
})
+
+ describe('if I have muted the comment author', () => {
+ it('sends me no notification', async () => {
+ await notifiedUser.relateTo(commentAuthor, 'muted')
+ await createCommentOnPostAction()
+ const expected = expect.objectContaining({
+ data: { notifications: [] },
+ })
+
+ await expect(
+ query({
+ query: notificationQuery,
+ variables: {
+ read: false,
+ },
+ }),
+ ).resolves.toEqual(expected)
+ })
+ })
})
describe('commenter is me', () => {
@@ -581,6 +600,48 @@ describe('notifications', () => {
expect(pubsubSpy).not.toHaveBeenCalled()
})
})
+
+ describe('but the author of the post muted me', () => {
+ beforeEach(async () => {
+ await postAuthor.relateTo(notifiedUser, 'muted')
+ })
+
+ it('sends me a notification', async () => {
+ await createPostAction()
+ const expected = expect.objectContaining({
+ data: {
+ notifications: [
+ {
+ createdAt: expect.any(String),
+ from: {
+ __typename: 'Post',
+ content:
+ 'Hey @al-capone how do you do?',
+ id: 'p47',
+ },
+ read: false,
+ reason: 'mentioned_in_post',
+ relatedUser: null,
+ },
+ ],
+ },
+ })
+
+ await expect(
+ query({
+ query: notificationQuery,
+ variables: {
+ read: false,
+ },
+ }),
+ ).resolves.toEqual(expected)
+ })
+
+ it('publishes `NOTIFICATION_ADDED`', async () => {
+ await createPostAction()
+ expect(pubsubSpy).toHaveBeenCalled()
+ })
+ })
})
describe('mentions me in a comment', () => {
@@ -736,6 +797,72 @@ describe('notifications', () => {
expect(pubsubSpy).toHaveBeenCalledTimes(1)
})
})
+
+ describe('but the author of the post muted me', () => {
+ beforeEach(async () => {
+ await postAuthor.relateTo(notifiedUser, 'muted')
+ commentContent =
+ 'One mention about me with @al-capone.'
+ commentAuthor = await neode.create(
+ 'User',
+ {
+ id: 'commentAuthor',
+ name: 'Mrs Comment',
+ slug: 'mrs-comment',
+ },
+ {
+ email: 'comment-author@example.org',
+ password: '1234',
+ },
+ )
+ })
+
+ it('sends me a notification', async () => {
+ await createCommentOnPostAction()
+ await expect(
+ query({
+ query: notificationQuery,
+ variables: {
+ read: false,
+ },
+ }),
+ ).resolves.toMatchObject({
+ data: {
+ notifications: [
+ {
+ createdAt: expect.any(String),
+ from: {
+ __typename: 'Comment',
+ content:
+ 'One mention about me with @al-capone.',
+ id: 'c47',
+ },
+ read: false,
+ reason: 'mentioned_in_comment',
+ relatedUser: null,
+ },
+ ],
+ },
+ errors: undefined,
+ })
+ })
+
+ it('publishes `NOTIFICATION_ADDED` to authenticated user and me', async () => {
+ await createCommentOnPostAction()
+ expect(pubsubSpy).toHaveBeenCalledWith(
+ 'NOTIFICATION_ADDED',
+ expect.objectContaining({
+ notificationAdded: expect.objectContaining({
+ reason: 'commented_on_post',
+ to: expect.objectContaining({
+ id: 'postAuthor', // that's expected, it's not me but the post author
+ }),
+ }),
+ }),
+ )
+ expect(pubsubSpy).toHaveBeenCalledTimes(2)
+ })
+ })
})
})
})
diff --git a/backend/src/middleware/notifications/notificationsMiddleware.ts b/backend/src/middleware/notifications/notificationsMiddleware.ts
index 4fb8cba93..a8d95b284 100644
--- a/backend/src/middleware/notifications/notificationsMiddleware.ts
+++ b/backend/src/middleware/notifications/notificationsMiddleware.ts
@@ -245,6 +245,8 @@ const notifyGroupMembersOfNewPost = async (postId, groupId, context) => {
MATCH (post)-[:IN]->(group:Group { id: $groupId })<-[membership:MEMBER_OF]-(user:User)
WHERE NOT membership.role = 'pending'
AND NOT (user)-[:MUTED]->(group)
+ AND NOT (user)-[:MUTED]->(author)
+ AND NOT (user)-[:BLOCKED]-(author)
AND NOT user.id = $userId
WITH post, author, user
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
@@ -360,7 +362,10 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
case 'mentioned_in_post': {
mentionedCypher = `
MATCH (post: Post { id: $id })<-[:WROTE]-(author: User)
- MATCH (user: User) WHERE user.id in $idsOfUsers AND NOT (user)-[:BLOCKED]-(author)
+ MATCH (user: User)
+ WHERE user.id in $idsOfUsers
+ AND NOT (user)-[:BLOCKED]-(author)
+ AND NOT (user)-[:MUTED]->(author)
OPTIONAL MATCH (post)-[:IN]->(group:Group)
OPTIONAL MATCH (group)<-[membership:MEMBER_OF]-(user)
WITH post, author, user, group WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner']
@@ -376,6 +381,8 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
WHERE user.id in $idsOfUsers
AND NOT (user)-[:BLOCKED]-(commenter)
AND NOT (user)-[:BLOCKED]-(postAuthor)
+ AND NOT (user)-[:MUTED]->(commenter)
+ AND NOT (user)-[:MUTED]->(postAuthor)
OPTIONAL MATCH (post)-[:IN]->(group:Group)
OPTIONAL MATCH (group)<-[membership:MEMBER_OF]-(user)
WITH comment, user, group WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner']
@@ -422,7 +429,9 @@ const notifyUsersOfComment = async (label, commentId, reason, context) => {
const notificationTransactionResponse = await transaction.run(
`
MATCH (observingUser:User)-[:OBSERVES { active: true }]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User)
- WHERE NOT (observingUser)-[:BLOCKED]-(commenter) AND NOT observingUser.id = $userId
+ WHERE NOT (observingUser)-[:BLOCKED]-(commenter)
+ AND NOT (observingUser)-[:MUTED]->(commenter)
+ AND NOT observingUser.id = $userId
WITH observingUser, post, comment, commenter
MATCH (postAuthor:User)-[:WROTE]->(post)
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(observingUser)