diff --git a/backend/src/middleware/handleHtmlContent/handleContentData.spec.js b/backend/src/middleware/handleHtmlContent/handleContentData.spec.js
index 6fe5d3891..1effeb5d6 100644
--- a/backend/src/middleware/handleHtmlContent/handleContentData.spec.js
+++ b/backend/src/middleware/handleHtmlContent/handleContentData.spec.js
@@ -1,12 +1,36 @@
-import { GraphQLClient } from 'graphql-request'
-import { host, login, gql } from '../../jest/helpers'
+import { gql } from '../../jest/helpers'
import Factory from '../../seed/factories'
+import { createTestClient } from 'apollo-server-testing'
+import { neode, getDriver } from '../../bootstrap/neo4j'
+import createServer from '../../server'
const factory = Factory()
-let client
+const driver = getDriver()
+const instance = neode()
+let server
+let query
+let mutate
+let user
+let authenticatedUser
+
+beforeAll(() => {
+ const createServerResult = createServer({
+ context: () => {
+ return {
+ user: authenticatedUser,
+ neode: instance,
+ driver,
+ }
+ },
+ })
+ server = createServerResult.server
+ const createTestClientResult = createTestClient(server)
+ query = createTestClientResult.query
+ mutate = createTestClientResult.mutate
+})
beforeEach(async () => {
- await factory.create('User', {
+ user = await instance.create('User', {
id: 'you',
name: 'Al Capone',
slug: 'al-capone',
@@ -19,8 +43,8 @@ afterEach(async () => {
await factory.cleanDatabase()
})
-describe('currentUser { notifications }', () => {
- const query = gql`
+describe('notifications', () => {
+ const notificationQuery = gql`
query($read: Boolean) {
currentUser {
notifications(read: $read, orderBy: createdAt_desc) {
@@ -34,77 +58,54 @@ describe('currentUser { notifications }', () => {
`
describe('authenticated', () => {
- let headers
beforeEach(async () => {
- headers = await login({
- email: 'test@example.org',
- password: '1234',
- })
- client = new GraphQLClient(host, {
- headers,
- })
+ authenticatedUser = user
})
describe('given another user', () => {
- let authorClient
- let authorParams
- let authorHeaders
-
+ let author
beforeEach(async () => {
- authorParams = {
+ author = await instance.create('User', {
email: 'author@example.org',
password: '1234',
id: 'author',
- }
- await factory.create('User', authorParams)
- authorHeaders = await login(authorParams)
+ })
})
describe('who mentions me in a post', () => {
- let post
const title = 'Mentioning Al Capone'
const content =
'Hey @al-capone how do you do?'
beforeEach(async () => {
const createPostMutation = gql`
- mutation($title: String!, $content: String!) {
- CreatePost(title: $title, content: $content) {
+ mutation($id: ID, $title: String!, $content: String!) {
+ CreatePost(id: $id, title: $title, content: $content) {
id
title
content
}
}
`
- authorClient = new GraphQLClient(host, {
- headers: authorHeaders,
+ authenticatedUser = await author.toJson()
+ await mutate({
+ mutation: createPostMutation,
+ variables: { id: 'p47', title, content },
})
- const { CreatePost } = await authorClient.request(createPostMutation, {
- title,
- content,
- })
- post = CreatePost
+ authenticatedUser = await user.toJson()
})
it('sends you a notification', async () => {
const expectedContent =
'Hey @al-capone how do you do?'
- const expected = {
- currentUser: {
- notifications: [
- {
- read: false,
- post: {
- content: expectedContent,
- },
- },
- ],
+ const expected = expect.objectContaining({
+ data: {
+ currentUser: { notifications: [{ read: false, post: { content: expectedContent } }] },
},
- }
+ })
+ const { query } = createTestClient(server)
await expect(
- client.request(query, {
- read: false,
- }),
+ query({ query: notificationQuery, variables: { read: false } }),
).resolves.toEqual(expected)
})
@@ -132,41 +133,33 @@ describe('currentUser { notifications }', () => {
}
}
`
- authorClient = new GraphQLClient(host, {
- headers: authorHeaders,
- })
- await authorClient.request(updatePostMutation, {
- id: post.id,
- title: post.title,
- content: updatedContent,
+ authenticatedUser = await author.toJson()
+ await mutate({
+ mutation: updatePostMutation,
+ variables: {
+ id: 'p47',
+ title,
+ content: updatedContent,
+ },
})
+ authenticatedUser = await user.toJson()
})
it('creates exactly one more notification', async () => {
const expectedContent =
'
One more mention to
@al-capone
and again:
@al-capone
and again
@al-capone
'
- const expected = {
- currentUser: {
- notifications: [
- {
- read: false,
- post: {
- content: expectedContent,
- },
- },
- {
- read: false,
- post: {
- content: expectedContent,
- },
- },
- ],
+ const expected = expect.objectContaining({
+ data: {
+ currentUser: {
+ notifications: [
+ { read: false, post: { content: expectedContent } },
+ { read: false, post: { content: expectedContent } },
+ ],
+ },
},
- }
+ })
await expect(
- client.request(query, {
- read: false,
- }),
+ query({ query: notificationQuery, variables: { read: false } }),
).resolves.toEqual(expected)
})
})
@@ -204,46 +197,40 @@ describe('Hashtags', () => {
`
describe('authenticated', () => {
- let headers
beforeEach(async () => {
- headers = await login({
- email: 'test@example.org',
- password: '1234',
- })
- client = new GraphQLClient(host, {
- headers,
- })
+ authenticatedUser = await user.toJson()
})
describe('create a Post with Hashtags', () => {
beforeEach(async () => {
- await client.request(createPostMutation, {
- postId,
- postTitle,
- postContent,
+ await mutate({
+ mutation: createPostMutation,
+ variables: {
+ postId,
+ postTitle,
+ postContent,
+ },
})
})
- it('both Hashtags are created with the "id" set to thier "name"', async () => {
+ it('both Hashtags are created with the "id" set to their "name"', async () => {
const expected = [
- {
- id: 'Democracy',
- name: 'Democracy',
- },
- {
- id: 'Liberty',
- name: 'Liberty',
- },
+ { id: 'Democracy', name: 'Democracy' },
+ { id: 'Liberty', name: 'Liberty' },
]
await expect(
- client.request(postWithHastagsQuery, postWithHastagsVariables),
- ).resolves.toEqual({
- Post: [
- {
- tags: expect.arrayContaining(expected),
+ query({ query: postWithHastagsQuery, variables: postWithHastagsVariables }),
+ ).resolves.toEqual(
+ expect.objectContaining({
+ data: {
+ Post: [
+ {
+ tags: expect.arrayContaining(expected),
+ },
+ ],
},
- ],
- })
+ }),
+ )
})
describe('afterwards update the Post by removing a Hashtag, leaving a Hashtag and add a Hashtag', () => {
@@ -261,31 +248,28 @@ describe('Hashtags', () => {
`
it('only one previous Hashtag and the new Hashtag exists', async () => {
- await client.request(updatePostMutation, {
- postId,
- postTitle,
- updatedPostContent,
+ await mutate({
+ mutation: updatePostMutation,
+ variables: {
+ postId,
+ postTitle,
+ updatedPostContent,
+ },
})
const expected = [
- {
- id: 'Elections',
- name: 'Elections',
- },
- {
- id: 'Liberty',
- name: 'Liberty',
- },
+ { id: 'Elections', name: 'Elections' },
+ { id: 'Liberty', name: 'Liberty' },
]
await expect(
- client.request(postWithHastagsQuery, postWithHastagsVariables),
- ).resolves.toEqual({
- Post: [
- {
- tags: expect.arrayContaining(expected),
+ query({ query: postWithHastagsQuery, variables: postWithHastagsVariables }),
+ ).resolves.toEqual(
+ expect.objectContaining({
+ data: {
+ Post: [{ tags: expect.arrayContaining(expected) }],
},
- ],
- })
+ }),
+ )
})
})
})
diff --git a/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.js b/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.js
index d08309f0b..e245e84b5 100644
--- a/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.js
+++ b/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.js
@@ -3,11 +3,13 @@ import cheerio from 'cheerio'
export default function(content) {
if (!content) return []
const $ = cheerio.load(content)
- let userIds = $('a.mention[data-mention-id]')
+ const userIds = $('a.mention[data-mention-id]')
.map((_, el) => {
return $(el).attr('data-mention-id')
})
.get()
- userIds = userIds.map(id => id.trim()).filter(id => !!id)
return userIds
+ .map(id => id.trim())
+ .filter(id => !!id)
+ .filter((id, index, allIds) => allIds.indexOf(id) === index)
}
diff --git a/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.spec.js b/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.spec.js
index a631b64a3..9865eab0d 100644
--- a/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.spec.js
+++ b/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.spec.js
@@ -6,6 +6,8 @@ const contentEmptyMentions =
'
Something inspirational about @bob-der-baumeister and @jenny-rostock.
' const contentWithPlainLinks = 'Something inspirational about @bob-der-baumeister and @jenny-rostock.
' +const contentWithDuplicateIds = + 'One more mention to @al-capone and again: @al-capone and again @al-capone ' describe('extractMentionedUsers', () => { describe('content undefined', () => { @@ -18,6 +20,10 @@ describe('extractMentionedUsers', () => { expect(extractMentionedUsers(contentWithPlainLinks)).toEqual([]) }) + it('removes duplicates', () => { + expect(extractMentionedUsers(contentWithDuplicateIds)).toEqual(['you']) + }) + describe('given a link with .mention class and `data-mention-id` attribute ', () => { it('extracts ids', () => { expect(extractMentionedUsers(contentWithMentions)).toEqual(['u3']) diff --git a/backend/src/models/Notification.js b/backend/src/models/Notification.js new file mode 100644 index 000000000..b8690b8c1 --- /dev/null +++ b/backend/src/models/Notification.js @@ -0,0 +1,19 @@ +import uuid from 'uuid/v4' + +module.exports = { + id: { type: 'uuid', primary: true, default: uuid }, + createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, + read: { type: 'boolean', default: false }, + user: { + type: 'relationship', + relationship: 'NOTIFIED', + target: 'User', + direction: 'out', + }, + post: { + type: 'relationship', + relationship: 'NOTIFIED', + target: 'Post', + direction: 'in', + }, +} diff --git a/backend/src/models/User.js b/backend/src/models/User.js index 3dfcef06a..fa578f8ad 100644 --- a/backend/src/models/User.js +++ b/backend/src/models/User.js @@ -77,4 +77,10 @@ module.exports = { target: 'User', direction: 'out', }, + notifications: { + type: 'relationship', + relationship: 'NOTIFIED', + target: 'Notification', + direction: 'in', + }, } diff --git a/backend/src/models/index.js b/backend/src/models/index.js index b8dc451d7..6f6b300f8 100644 --- a/backend/src/models/index.js +++ b/backend/src/models/index.js @@ -7,4 +7,5 @@ export default { EmailAddress: require('./EmailAddress.js'), SocialMedia: require('./SocialMedia.js'), Post: require('./Post.js'), + Notification: require('./Notification.js'), } diff --git a/backend/src/schema/types/schema.gql b/backend/src/schema/types/schema.gql index 41fe8f4e6..b8e5f5290 100644 --- a/backend/src/schema/types/schema.gql +++ b/backend/src/schema/types/schema.gql @@ -50,14 +50,6 @@ type Statistics { countShouts: Int! } -type Notification { - id: ID! - read: Boolean - user: User @relation(name: "NOTIFIED", direction: "OUT") - post: Post @relation(name: "NOTIFIED", direction: "IN") - createdAt: String -} - type Location { id: ID! name: String! diff --git a/backend/src/schema/types/type/Notification.gql b/backend/src/schema/types/type/Notification.gql new file mode 100644 index 000000000..e4bc16fec --- /dev/null +++ b/backend/src/schema/types/type/Notification.gql @@ -0,0 +1,7 @@ +type Notification { + id: ID! + read: Boolean + user: User @relation(name: "NOTIFIED", direction: "OUT") + post: Post @relation(name: "NOTIFIED", direction: "IN") + createdAt: String +}