Merge branch 'master' of https://github.com/Human-Connection/Human-Connection into 1062-notification-about-comment-on-post

# Conflicts:
#	backend/src/middleware/handleNotifications/handleNotificationsMiddleware.spec.js

Refactored there tests a little
This commit is contained in:
Wolfgang Huß 2019-08-22 10:58:52 +02:00
commit 04fe6b0a8c
38 changed files with 1366 additions and 1103 deletions

View File

@ -56,7 +56,7 @@
"cheerio": "~1.0.0-rc.3", "cheerio": "~1.0.0-rc.3",
"cors": "~2.8.5", "cors": "~2.8.5",
"cross-env": "~5.2.0", "cross-env": "~5.2.0",
"date-fns": "2.0.0-beta.5", "date-fns": "2.0.0",
"debug": "~4.1.1", "debug": "~4.1.1",
"dotenv": "~8.1.0", "dotenv": "~8.1.0",
"express": "^4.17.1", "express": "^4.17.1",
@ -110,7 +110,7 @@
"@babel/plugin-proposal-throw-expressions": "^7.2.0", "@babel/plugin-proposal-throw-expressions": "^7.2.0",
"@babel/preset-env": "~7.5.5", "@babel/preset-env": "~7.5.5",
"@babel/register": "~7.5.5", "@babel/register": "~7.5.5",
"apollo-server-testing": "~2.8.1", "apollo-server-testing": "~2.8.2",
"babel-core": "~7.0.0-0", "babel-core": "~7.0.0-0",
"babel-eslint": "~10.0.2", "babel-eslint": "~10.0.2",
"babel-jest": "~24.9.0", "babel-jest": "~24.9.0",

View File

@ -1,8 +1,10 @@
import { GraphQLClient } from 'graphql-request' import { GraphQLClient } from 'graphql-request'
import { host, login } from '../../jest/helpers' import { host, login } from '../../jest/helpers'
import Factory from '../../seed/factories' import Factory from '../../seed/factories'
import { neode } from '../../bootstrap/neo4j'
const factory = Factory() const factory = Factory()
const instance = neode()
const currentUserParams = { const currentUserParams = {
id: 'u1', id: 'u1',
@ -21,6 +23,7 @@ const randomAuthorParams = {
name: 'Someone else', name: 'Someone else',
password: 'else', password: 'else',
} }
const categoryIds = ['cat9']
beforeEach(async () => { beforeEach(async () => {
await Promise.all([ await Promise.all([
@ -28,14 +31,19 @@ beforeEach(async () => {
factory.create('User', followedAuthorParams), factory.create('User', followedAuthorParams),
factory.create('User', randomAuthorParams), factory.create('User', randomAuthorParams),
]) ])
await instance.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
})
const [asYourself, asFollowedUser, asSomeoneElse] = await Promise.all([ const [asYourself, asFollowedUser, asSomeoneElse] = await Promise.all([
Factory().authenticateAs(currentUserParams), Factory().authenticateAs(currentUserParams),
Factory().authenticateAs(followedAuthorParams), Factory().authenticateAs(followedAuthorParams),
Factory().authenticateAs(randomAuthorParams), Factory().authenticateAs(randomAuthorParams),
]) ])
await asYourself.follow({ id: 'u2', type: 'User' }) await asYourself.follow({ id: 'u2', type: 'User' })
await asFollowedUser.create('Post', { title: 'This is the post of a followed user' }) await asFollowedUser.create('Post', { title: 'This is the post of a followed user', categoryIds })
await asSomeoneElse.create('Post', { title: 'This is some random post' }) await asSomeoneElse.create('Post', { title: 'This is some random post', categoryIds })
}) })
afterEach(async () => { afterEach(async () => {

View File

@ -1,17 +1,50 @@
import { gql } from '../../jest/helpers' import {
gql
} from '../../jest/helpers'
import Factory from '../../seed/factories' import Factory from '../../seed/factories'
import { createTestClient } from 'apollo-server-testing' import {
import { neode, getDriver } from '../../bootstrap/neo4j' createTestClient
} from 'apollo-server-testing'
import {
neode,
getDriver
} from '../../bootstrap/neo4j'
import createServer from '../../server' import createServer from '../../server'
const factory = Factory()
const driver = getDriver()
const instance = neode()
let server let server
let query let query
let mutate let mutate
let user let notifiedUser
let authenticatedUser let authenticatedUser
const factory = Factory()
const driver = getDriver()
const instance = neode()
const categoryIds = ['cat9']
const createPostMutation = gql `
mutation($id: ID, $title: String!, $postContent: String!, $categoryIds: [ID]!) {
CreatePost(id: $id, title: $title, content: $postContent, categoryIds: $categoryIds) {
id
title
content
}
}
`
const updatePostMutation = gql `
mutation($id: ID!, $title: String!, $postContent: String!, $categoryIds: [ID]!) {
UpdatePost(id: $id, content: $postContent, title: $title, categoryIds: $categoryIds) {
title
content
}
}
`
const createCommentMutation = gql `
mutation($id: ID, $postId: ID!, $commentContent: String!) {
CreateComment(id: $id, postId: $postId, content: $commentContent) {
id
content
}
}
`
beforeAll(() => { beforeAll(() => {
const createServerResult = createServer({ const createServerResult = createServer({
@ -30,13 +63,18 @@ beforeAll(() => {
}) })
beforeEach(async () => { beforeEach(async () => {
user = await instance.create('User', { notifiedUser = await instance.create('User', {
id: 'you', id: 'you',
name: 'Al Capone', name: 'Al Capone',
slug: 'al-capone', slug: 'al-capone',
email: 'test@example.org', email: 'test@example.org',
password: '1234', password: '1234',
}) })
await instance.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
})
}) })
afterEach(async () => { afterEach(async () => {
@ -44,7 +82,7 @@ afterEach(async () => {
}) })
describe('notifications', () => { describe('notifications', () => {
const notificationQuery = gql` const notificationQuery = gql `
query($read: Boolean) { query($read: Boolean) {
currentUser { currentUser {
notifications(read: $read, orderBy: createdAt_desc) { notifications(read: $read, orderBy: createdAt_desc) {
@ -63,47 +101,31 @@ describe('notifications', () => {
describe('authenticated', () => { describe('authenticated', () => {
beforeEach(async () => { beforeEach(async () => {
authenticatedUser = await user.toJson() authenticatedUser = await notifiedUser.toJson()
}) })
describe('given another user', () => { describe('given another user', () => {
let postTitle let title
let postContent let postContent
let postAuthor let postAuthor
const createPostAction = async () => { const createPostAction = async () => {
const createPostMutation = gql`
mutation($id: ID, $postTitle: String!, $postContent: String!) {
CreatePost(id: $id, title: $postTitle, content: $postContent) {
id
title
content
}
}
`
authenticatedUser = await postAuthor.toJson() authenticatedUser = await postAuthor.toJson()
await mutate({ await mutate({
mutation: createPostMutation, mutation: createPostMutation,
variables: { variables: {
id: 'p47', id: 'p47',
postTitle, title,
postContent, postContent,
categoryIds,
}, },
}) })
authenticatedUser = await user.toJson() authenticatedUser = await notifiedUser.toJson()
} }
let commentContent let commentContent
let commentAuthor let commentAuthor
const createCommentOnPostAction = async () => { const createCommentOnPostAction = async () => {
await createPostAction() await createPostAction()
const createCommentMutation = gql`
mutation($id: ID, $postId: ID!, $commentContent: String!) {
CreateComment(id: $id, postId: $postId, content: $commentContent) {
id
content
}
}
`
authenticatedUser = await commentAuthor.toJson() authenticatedUser = await commentAuthor.toJson()
await mutate({ await mutate({
mutation: createCommentMutation, mutation: createCommentMutation,
@ -113,14 +135,14 @@ describe('notifications', () => {
commentContent, commentContent,
}, },
}) })
authenticatedUser = await user.toJson() authenticatedUser = await notifiedUser.toJson()
} }
describe('comments on my post', () => { describe('comments on my post', () => {
beforeEach(async () => { beforeEach(async () => {
postTitle = 'My post' title = 'My post'
postContent = 'My post content.' postContent = 'My post content.'
postAuthor = user postAuthor = notifiedUser
}) })
describe('commenter is not me', () => { describe('commenter is not me', () => {
@ -140,20 +162,20 @@ describe('notifications', () => {
const expected = expect.objectContaining({ const expected = expect.objectContaining({
data: { data: {
currentUser: { currentUser: {
notifications: [ notifications: [{
{ read: false,
read: false, reason: 'comment_on_post',
reason: 'comment_on_post', post: null,
post: null, comment: {
comment: { content: commentContent,
content: commentContent,
},
}, },
], }, ],
}, },
}, },
}) })
const { query } = createTestClient(server) const {
query
} = createTestClient(server)
await expect( await expect(
query({ query({
query: notificationQuery, query: notificationQuery,
@ -164,8 +186,8 @@ describe('notifications', () => {
).resolves.toEqual(expected) ).resolves.toEqual(expected)
}) })
it('sends me no notification if I block the comment author', async () => { it('sends me no notification if I have blocked the comment author', async () => {
await user.relateTo(commentAuthor, 'blocked') await notifiedUser.relateTo(commentAuthor, 'blocked')
await createCommentOnPostAction() await createCommentOnPostAction()
const expected = expect.objectContaining({ const expected = expect.objectContaining({
data: { data: {
@ -174,7 +196,9 @@ describe('notifications', () => {
}, },
}, },
}) })
const { query } = createTestClient(server) const {
query
} = createTestClient(server)
await expect( await expect(
query({ query({
query: notificationQuery, query: notificationQuery,
@ -189,11 +213,11 @@ describe('notifications', () => {
describe('commenter is me', () => { describe('commenter is me', () => {
beforeEach(async () => { beforeEach(async () => {
commentContent = 'My comment.' commentContent = 'My comment.'
commentAuthor = user commentAuthor = notifiedUser
}) })
it('sends me no notification', async () => { it('sends me no notification', async () => {
await user.relateTo(commentAuthor, 'blocked') await notifiedUser.relateTo(commentAuthor, 'blocked')
await createCommentOnPostAction() await createCommentOnPostAction()
const expected = expect.objectContaining({ const expected = expect.objectContaining({
data: { data: {
@ -202,7 +226,9 @@ describe('notifications', () => {
}, },
}, },
}) })
const { query } = createTestClient(server) const {
query
} = createTestClient(server)
await expect( await expect(
query({ query({
query: notificationQuery, query: notificationQuery,
@ -227,7 +253,7 @@ describe('notifications', () => {
describe('mentions me in a post', () => { describe('mentions me in a post', () => {
beforeEach(async () => { beforeEach(async () => {
postTitle = 'Mentioning Al Capone' title = 'Mentioning Al Capone'
postContent = postContent =
'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone">@al-capone</a> how do you do?' 'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone">@al-capone</a> how do you do?'
}) })
@ -239,20 +265,20 @@ describe('notifications', () => {
const expected = expect.objectContaining({ const expected = expect.objectContaining({
data: { data: {
currentUser: { currentUser: {
notifications: [ notifications: [{
{ read: false,
read: false, reason: 'mentioned_in_post',
reason: 'mentioned_in_post', post: {
post: { content: expectedContent,
content: expectedContent,
},
comment: null,
}, },
], comment: null,
}, ],
}, },
}, },
}) })
const { query } = createTestClient(server) const {
query
} = createTestClient(server)
await expect( await expect(
query({ query({
query: notificationQuery, query: notificationQuery,
@ -263,7 +289,7 @@ describe('notifications', () => {
).resolves.toEqual(expected) ).resolves.toEqual(expected)
}) })
describe('who mentions me many times', () => { describe('many times', () => {
const updatePostAction = async () => { const updatePostAction = async () => {
const updatedContent = ` const updatedContent = `
One more mention to One more mention to
@ -279,24 +305,17 @@ describe('notifications', () => {
@al-capone @al-capone
</a> </a>
` `
const updatePostMutation = gql`
mutation($id: ID!, $postTitle: String!, $postContent: String!) {
UpdatePost(id: $id, content: $postContent, title: $postTitle) {
title
content
}
}
`
authenticatedUser = await postAuthor.toJson() authenticatedUser = await postAuthor.toJson()
await mutate({ await mutate({
mutation: updatePostMutation, mutation: updatePostMutation,
variables: { variables: {
id: 'p47', id: 'p47',
postTitle, title,
postContent: updatedContent, postContent: updatedContent,
categoryIds,
}, },
}) })
authenticatedUser = await user.toJson() authenticatedUser = await notifiedUser.toJson()
} }
it('creates exactly one more notification', async () => { it('creates exactly one more notification', async () => {
@ -307,8 +326,7 @@ describe('notifications', () => {
const expected = expect.objectContaining({ const expected = expect.objectContaining({
data: { data: {
currentUser: { currentUser: {
notifications: [ notifications: [{
{
read: false, read: false,
reason: 'mentioned_in_post', reason: 'mentioned_in_post',
post: { post: {
@ -341,7 +359,7 @@ describe('notifications', () => {
describe('but the author of the post blocked me', () => { describe('but the author of the post blocked me', () => {
beforeEach(async () => { beforeEach(async () => {
await postAuthor.relateTo(user, 'blocked') await postAuthor.relateTo(notifiedUser, 'blocked')
}) })
it('sends no notification', async () => { it('sends no notification', async () => {
@ -353,7 +371,9 @@ describe('notifications', () => {
}, },
}, },
}) })
const { query } = createTestClient(server) const {
query
} = createTestClient(server)
await expect( await expect(
query({ query({
query: notificationQuery, query: notificationQuery,
@ -368,7 +388,7 @@ describe('notifications', () => {
describe('mentions me in a comment', () => { describe('mentions me in a comment', () => {
beforeEach(async () => { beforeEach(async () => {
postTitle = 'Post where I get mentioned in a comment' title = 'Post where I get mentioned in a comment'
postContent = 'Content of post where I get mentioned in a comment.' postContent = 'Content of post where I get mentioned in a comment.'
}) })
@ -390,20 +410,20 @@ describe('notifications', () => {
const expected = expect.objectContaining({ const expected = expect.objectContaining({
data: { data: {
currentUser: { currentUser: {
notifications: [ notifications: [{
{ read: false,
read: false, reason: 'mentioned_in_comment',
reason: 'mentioned_in_comment', post: null,
post: null, comment: {
comment: { content: commentContent,
content: commentContent,
},
}, },
], }, ],
}, },
}, },
}) })
const { query } = createTestClient(server) const {
query
} = createTestClient(server)
await expect( await expect(
query({ query({
query: notificationQuery, query: notificationQuery,
@ -417,7 +437,7 @@ describe('notifications', () => {
describe('but the author of the post blocked me', () => { describe('but the author of the post blocked me', () => {
beforeEach(async () => { beforeEach(async () => {
await postAuthor.relateTo(user, 'blocked') await postAuthor.relateTo(notifiedUser, 'blocked')
commentContent = commentContent =
'One mention about me with <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">@al-capone</a>.' 'One mention about me with <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">@al-capone</a>.'
commentAuthor = await instance.create('User', { commentAuthor = await instance.create('User', {
@ -438,7 +458,9 @@ describe('notifications', () => {
}, },
}, },
}) })
const { query } = createTestClient(server) const {
query
} = createTestClient(server)
await expect( await expect(
query({ query({
query: notificationQuery, query: notificationQuery,
@ -455,11 +477,11 @@ describe('notifications', () => {
}) })
describe('Hashtags', () => { describe('Hashtags', () => {
const postId = 'p135' const id = 'p135'
const postTitle = 'Two Hashtags' const title = 'Two Hashtags'
const postContent = const postContent =
'<p>Hey Dude, <a class="hashtag" href="/search/hashtag/Democracy">#Democracy</a> should work equal for everybody!? That seems to be the only way to have equal <a class="hashtag" href="/search/hashtag/Liberty">#Liberty</a> for everyone.</p>' '<p>Hey Dude, <a class="hashtag" href="/search/hashtag/Democracy">#Democracy</a> should work equal for everybody!? That seems to be the only way to have equal <a class="hashtag" href="/search/hashtag/Liberty">#Liberty</a> for everyone.</p>'
const postWithHastagsQuery = gql` const postWithHastagsQuery = gql `
query($id: ID) { query($id: ID) {
Post(id: $id) { Post(id: $id) {
tags { tags {
@ -469,21 +491,12 @@ describe('Hashtags', () => {
} }
` `
const postWithHastagsVariables = { const postWithHastagsVariables = {
id: postId, id,
} }
const createPostMutation = gql`
mutation($postId: ID, $postTitle: String!, $postContent: String!) {
CreatePost(id: $postId, title: $postTitle, content: $postContent) {
id
title
content
}
}
`
describe('authenticated', () => { describe('authenticated', () => {
beforeEach(async () => { beforeEach(async () => {
authenticatedUser = await user.toJson() authenticatedUser = await notifiedUser.toJson()
}) })
describe('create a Post with Hashtags', () => { describe('create a Post with Hashtags', () => {
@ -491,16 +504,16 @@ describe('Hashtags', () => {
await mutate({ await mutate({
mutation: createPostMutation, mutation: createPostMutation,
variables: { variables: {
postId, id,
postTitle, title,
postContent, postContent,
categoryIds,
}, },
}) })
}) })
it('both Hashtags are created with the "id" set to their "name"', async () => { it('both Hashtags are created with the "id" set to their "name"', async () => {
const expected = [ const expected = [{
{
id: 'Democracy', id: 'Democracy',
}, },
{ {
@ -515,11 +528,9 @@ describe('Hashtags', () => {
).resolves.toEqual( ).resolves.toEqual(
expect.objectContaining({ expect.objectContaining({
data: { data: {
Post: [ Post: [{
{ tags: expect.arrayContaining(expected),
tags: expect.arrayContaining(expected), }, ],
},
],
}, },
}), }),
) )
@ -527,30 +538,21 @@ describe('Hashtags', () => {
describe('afterwards update the Post by removing a Hashtag, leaving a Hashtag and add a Hashtag', () => { describe('afterwards update the Post by removing a Hashtag, leaving a Hashtag and add a Hashtag', () => {
// The already existing Hashtag has no class at this point. // The already existing Hashtag has no class at this point.
const updatedPostContent = const postContent =
'<p>Hey Dude, <a class="hashtag" href="/search/hashtag/Elections">#Elections</a> should work equal for everybody!? That seems to be the only way to have equal <a href="/search/hashtag/Liberty">#Liberty</a> for everyone.</p>' '<p>Hey Dude, <a class="hashtag" href="/search/hashtag/Elections">#Elections</a> should work equal for everybody!? That seems to be the only way to have equal <a href="/search/hashtag/Liberty">#Liberty</a> for everyone.</p>'
const updatePostMutation = gql`
mutation($postId: ID!, $postTitle: String!, $updatedPostContent: String!) {
UpdatePost(id: $postId, title: $postTitle, content: $updatedPostContent) {
id
title
content
}
}
`
it('only one previous Hashtag and the new Hashtag exists', async () => { it('only one previous Hashtag and the new Hashtag exists', async () => {
await mutate({ await mutate({
mutation: updatePostMutation, mutation: updatePostMutation,
variables: { variables: {
postId, id,
postTitle, title,
updatedPostContent, postContent,
categoryIds,
}, },
}) })
const expected = [ const expected = [{
{
id: 'Elections', id: 'Elections',
}, },
{ {
@ -565,11 +567,9 @@ describe('Hashtags', () => {
).resolves.toEqual( ).resolves.toEqual(
expect.objectContaining({ expect.objectContaining({
data: { data: {
Post: [ Post: [{
{ tags: expect.arrayContaining(expected),
tags: expect.arrayContaining(expected), }, ],
},
],
}, },
}), }),
) )
@ -577,4 +577,4 @@ describe('Hashtags', () => {
}) })
}) })
}) })
}) })

View File

@ -1,13 +1,25 @@
import { GraphQLClient } from 'graphql-request' import { GraphQLClient } from 'graphql-request'
import Factory from '../seed/factories' import Factory from '../seed/factories'
import { host, login } from '../jest/helpers' import { host, login, gql } from '../jest/helpers'
import { neode } from '../bootstrap/neo4j' import { neode } from '../bootstrap/neo4j'
let authenticatedClient let authenticatedClient
let headers let headers
const factory = Factory() const factory = Factory()
const instance = neode() const instance = neode()
const categoryIds = ['cat9']
const createPostMutation = gql`
mutation($title: String!, $content: String!, $categoryIds: [ID]!, $slug: String) {
CreatePost(title: $title, content: $content, categoryIds: $categoryIds, slug: $slug) {
slug
}
}
`
let createPostVariables = {
title: 'I am a brand new post',
content: 'Some content',
categoryIds,
}
beforeEach(async () => { beforeEach(async () => {
const adminParams = { role: 'admin', email: 'admin@example.org', password: '1234' } const adminParams = { role: 'admin', email: 'admin@example.org', password: '1234' }
await factory.create('User', adminParams) await factory.create('User', adminParams)
@ -15,6 +27,11 @@ beforeEach(async () => {
email: 'someone@example.org', email: 'someone@example.org',
password: '1234', password: '1234',
}) })
await instance.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
})
// we need to be an admin, otherwise we're not authorized to create a user // we need to be an admin, otherwise we're not authorized to create a user
headers = await login(adminParams) headers = await login(adminParams)
authenticatedClient = new GraphQLClient(host, { headers }) authenticatedClient = new GraphQLClient(host, { headers })
@ -27,12 +44,7 @@ afterEach(async () => {
describe('slugify', () => { describe('slugify', () => {
describe('CreatePost', () => { describe('CreatePost', () => {
it('generates a slug based on title', async () => { it('generates a slug based on title', async () => {
const response = await authenticatedClient.request(`mutation { const response = await authenticatedClient.request(createPostMutation, createPostVariables)
CreatePost(
title: "I am a brand new post",
content: "Some content"
) { slug }
}`)
expect(response).toEqual({ expect(response).toEqual({
CreatePost: { slug: 'i-am-a-brand-new-post' }, CreatePost: { slug: 'i-am-a-brand-new-post' },
}) })
@ -47,16 +59,14 @@ describe('slugify', () => {
await asSomeoneElse.create('Post', { await asSomeoneElse.create('Post', {
title: 'Pre-existing post', title: 'Pre-existing post',
slug: 'pre-existing-post', slug: 'pre-existing-post',
content: 'as Someone else content',
categoryIds,
}) })
}) })
it('chooses another slug', async () => { it('chooses another slug', async () => {
const response = await authenticatedClient.request(`mutation { createPostVariables = { title: 'Pre-existing post', content: 'Some content', categoryIds }
CreatePost( const response = await authenticatedClient.request(createPostMutation, createPostVariables)
title: "Pre-existing post",
content: "Some content"
) { slug }
}`)
expect(response).toEqual({ expect(response).toEqual({
CreatePost: { slug: 'pre-existing-post-1' }, CreatePost: { slug: 'pre-existing-post-1' },
}) })
@ -64,14 +74,14 @@ describe('slugify', () => {
describe('but if the client specifies a slug', () => { describe('but if the client specifies a slug', () => {
it('rejects CreatePost', async () => { it('rejects CreatePost', async () => {
createPostVariables = {
title: 'Pre-existing post',
content: 'Some content',
slug: 'pre-existing-post',
categoryIds,
}
await expect( await expect(
authenticatedClient.request(`mutation { authenticatedClient.request(createPostMutation, createPostVariables),
CreatePost(
title: "Pre-existing post",
content: "Some content",
slug: "pre-existing-post"
) { slug }
}`),
).rejects.toThrow('already exists') ).rejects.toThrow('already exists')
}) })
}) })

View File

@ -1,11 +1,15 @@
import { GraphQLClient } from 'graphql-request' import { GraphQLClient } from 'graphql-request'
import Factory from '../seed/factories' import Factory from '../seed/factories'
import { host, login } from '../jest/helpers' import { host, login } from '../jest/helpers'
import { neode } from '../bootstrap/neo4j'
const factory = Factory() const factory = Factory()
const instance = neode()
let client let client
let query let query
let action let action
const categoryIds = ['cat9']
beforeAll(async () => { beforeAll(async () => {
// For performance reasons we do this only once // For performance reasons we do this only once
@ -26,13 +30,23 @@ beforeAll(async () => {
email: 'troll@example.org', email: 'troll@example.org',
password: '1234', password: '1234',
}), }),
instance.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
}),
]) ])
await factory.authenticateAs({ email: 'user@example.org', password: '1234' }) await factory.authenticateAs({ email: 'user@example.org', password: '1234' })
await Promise.all([ await Promise.all([
factory.follow({ id: 'u2', type: 'User' }), factory.follow({ id: 'u2', type: 'User' }),
factory.create('Post', { id: 'p1', title: 'Deleted post', deleted: true }), factory.create('Post', { id: 'p1', title: 'Deleted post', deleted: true, categoryIds }),
factory.create('Post', { id: 'p3', title: 'Publicly visible post', deleted: false }), factory.create('Post', {
id: 'p3',
title: 'Publicly visible post',
deleted: false,
categoryIds,
}),
]) ])
await Promise.all([ await Promise.all([
@ -53,6 +67,7 @@ beforeAll(async () => {
content: 'This is an offensive post content', content: 'This is an offensive post content',
image: '/some/offensive/image.jpg', image: '/some/offensive/image.jpg',
deleted: false, deleted: false,
categoryIds,
}) })
await asTroll.create('Comment', { id: 'c1', postId: 'p3', content: 'Disabled comment' }) await asTroll.create('Comment', { id: 'c1', postId: 'p3', content: 'Disabled comment' })
await Promise.all([asTroll.relate('Comment', 'Author', { from: 'u2', to: 'c1' })]) await Promise.all([asTroll.relate('Comment', 'Author', { from: 'u2', to: 'c1' })])

View File

@ -40,9 +40,21 @@ const validateUpdateComment = async (resolve, root, args, context, info) => {
return resolve(root, args, context, info) return resolve(root, args, context, info)
} }
const validatePost = async (resolve, root, args, context, info) => {
const { categoryIds } = args
if (!Array.isArray(categoryIds) || !categoryIds.length || categoryIds.length > 3) {
throw new UserInputError(
'You cannot save a post without at least one category or more than three',
)
}
return resolve(root, args, context, info)
}
export default { export default {
Mutation: { Mutation: {
CreateComment: validateCommentCreation, CreateComment: validateCommentCreation,
UpdateComment: validateUpdateComment, UpdateComment: validateUpdateComment,
CreatePost: validatePost,
UpdatePost: validatePost,
}, },
} }

View File

@ -0,0 +1,21 @@
import uuid from 'uuid/v4'
module.exports = {
id: { type: 'string', primary: true, default: uuid },
name: { type: 'string', required: true, default: false },
slug: { type: 'string' },
icon: { type: 'string', required: true, default: false },
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
updatedAt: {
type: 'string',
isoDate: true,
required: true,
default: () => new Date().toISOString(),
},
post: {
type: 'relationship',
relationship: 'CATEGORIZED',
target: 'Post',
direction: 'in',
},
}

View File

@ -8,4 +8,5 @@ export default {
SocialMedia: require('./SocialMedia.js'), SocialMedia: require('./SocialMedia.js'),
Post: require('./Post.js'), Post: require('./Post.js'),
Notification: require('./Notification.js'), Notification: require('./Notification.js'),
Category: require('./Category.js'),
} }

View File

@ -1,18 +1,21 @@
import { GraphQLClient } from 'graphql-request' import { GraphQLClient } from 'graphql-request'
import Factory from '../../seed/factories' import Factory from '../../seed/factories'
import { host, login, gql } from '../../jest/helpers' import { host, login, gql } from '../../jest/helpers'
import { neode } from '../../bootstrap/neo4j'
const factory = Factory()
let client let client
let createCommentVariables let createCommentVariables
let createCommentVariablesSansPostId let createCommentVariablesSansPostId
let createCommentVariablesWithNonExistentPost let createCommentVariablesWithNonExistentPost
let userParams let userParams
let headers let headers
const factory = Factory()
const instance = neode()
const categoryIds = ['cat9']
const createPostMutation = gql` const createPostMutation = gql`
mutation($id: ID!, $title: String!, $content: String!) { mutation($id: ID, $title: String!, $content: String!, $categoryIds: [ID]!) {
CreatePost(id: $id, title: $title, content: $content) { CreatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) {
id id
} }
} }
@ -29,6 +32,7 @@ const createPostVariables = {
id: 'p1', id: 'p1',
title: 'post to comment on', title: 'post to comment on',
content: 'please comment on me', content: 'please comment on me',
categoryIds,
} }
beforeEach(async () => { beforeEach(async () => {
@ -38,6 +42,11 @@ beforeEach(async () => {
password: '1234', password: '1234',
} }
await factory.create('User', userParams) await factory.create('User', userParams)
await instance.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
})
}) })
afterEach(async () => { afterEach(async () => {
@ -199,6 +208,7 @@ describe('ManageComments', () => {
await asAuthor.create('Post', { await asAuthor.create('Post', {
id: 'p1', id: 'p1',
content: 'Post to be commented', content: 'Post to be commented',
categoryIds,
}) })
await asAuthor.create('Comment', { await asAuthor.create('Comment', {
id: 'c456', id: 'c456',

View File

@ -1,9 +1,12 @@
import { GraphQLClient } from 'graphql-request' import { GraphQLClient } from 'graphql-request'
import Factory from '../../seed/factories' import Factory from '../../seed/factories'
import { host, login } from '../../jest/helpers' import { host, login, gql } from '../../jest/helpers'
import { neode } from '../../bootstrap/neo4j'
const factory = Factory()
let client let client
const factory = Factory()
const instance = neode()
const categoryIds = ['cat9']
const setupAuthenticateClient = params => { const setupAuthenticateClient = params => {
const authenticateClient = async () => { const authenticateClient = async () => {
@ -19,11 +22,16 @@ let authenticateClient
let createPostVariables let createPostVariables
let createCommentVariables let createCommentVariables
beforeEach(() => { beforeEach(async () => {
createResource = () => {} createResource = () => {}
authenticateClient = () => { authenticateClient = () => {
client = new GraphQLClient(host) client = new GraphQLClient(host)
} }
await instance.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
})
}) })
const setup = async () => { const setup = async () => {
@ -36,7 +44,7 @@ afterEach(async () => {
}) })
describe('disable', () => { describe('disable', () => {
const mutation = ` const mutation = gql`
mutation($id: ID!) { mutation($id: ID!) {
disable(id: $id) disable(id: $id)
} }
@ -108,6 +116,7 @@ describe('disable', () => {
id: 'p3', id: 'p3',
title: 'post to comment on', title: 'post to comment on',
content: 'please comment on me', content: 'please comment on me',
categoryIds,
} }
createCommentVariables = { createCommentVariables = {
id: 'c47', id: 'c47',
@ -173,6 +182,7 @@ describe('disable', () => {
await factory.authenticateAs({ email: 'author@example.org', password: '1234' }) await factory.authenticateAs({ email: 'author@example.org', password: '1234' })
await factory.create('Post', { await factory.create('Post', {
id: 'p9', // that's the ID we will look for id: 'p9', // that's the ID we will look for
categoryIds,
}) })
} }
}) })
@ -214,7 +224,7 @@ describe('disable', () => {
}) })
describe('enable', () => { describe('enable', () => {
const mutation = ` const mutation = gql`
mutation($id: ID!) { mutation($id: ID!) {
enable(id: $id) enable(id: $id)
} }
@ -286,6 +296,7 @@ describe('enable', () => {
id: 'p9', id: 'p9',
title: 'post to comment on', title: 'post to comment on',
content: 'please comment on me', content: 'please comment on me',
categoryIds,
} }
createCommentVariables = { createCommentVariables = {
id: 'c456', id: 'c456',
@ -305,7 +316,7 @@ describe('enable', () => {
await asAuthenticatedUser.create('Post', createPostVariables) await asAuthenticatedUser.create('Post', createPostVariables)
await asAuthenticatedUser.create('Comment', createCommentVariables) await asAuthenticatedUser.create('Comment', createCommentVariables)
const disableMutation = ` const disableMutation = gql`
mutation { mutation {
disable(id: "c456") disable(id: "c456")
} }
@ -362,9 +373,10 @@ describe('enable', () => {
await factory.authenticateAs({ email: 'author@example.org', password: '1234' }) await factory.authenticateAs({ email: 'author@example.org', password: '1234' })
await factory.create('Post', { await factory.create('Post', {
id: 'p9', // that's the ID we will look for id: 'p9', // that's the ID we will look for
categoryIds,
}) })
const disableMutation = ` const disableMutation = gql`
mutation { mutation {
disable(id: "p9") disable(id: "p9")
} }

View File

@ -1,26 +1,34 @@
import { GraphQLClient } from 'graphql-request' import { GraphQLClient } from 'graphql-request'
import Factory from '../../seed/factories' import Factory from '../../seed/factories'
import { host, login, gql } from '../../jest/helpers' import { host, login, gql } from '../../jest/helpers'
import { neode } from '../../bootstrap/neo4j'
const factory = Factory()
let client let client
const factory = Factory()
const instance = neode()
const userParams = { const userParams = {
id: 'you', id: 'you',
email: 'test@example.org', email: 'test@example.org',
password: '1234', password: '1234',
} }
const categoryIds = ['cat9']
beforeEach(async () => { beforeEach(async () => {
await factory.create('User', userParams) await factory.create('User', userParams)
await instance.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
})
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await factory.cleanDatabase()
}) })
describe('query for notification', () => { describe('Notification', () => {
const notificationQuery = gql` const notificationQuery = gql`
{ query {
Notification { Notification {
id id
} }
@ -87,12 +95,7 @@ describe('currentUser notifications', () => {
}), }),
]) ])
await factory.authenticateAs(neighborParams) await factory.authenticateAs(neighborParams)
// Post and its notifications await factory.create('Post', { id: 'p1', categoryIds })
await Promise.all([
factory.create('Post', {
id: 'p1',
}),
])
await Promise.all([ await Promise.all([
factory.relate('Notification', 'User', { factory.relate('Notification', 'User', {
from: 'post-mention-not-for-you', from: 'post-mention-not-for-you',
@ -170,9 +173,7 @@ describe('currentUser notifications', () => {
} }
} }
` `
const variables = { const variables = { read: false }
read: false,
}
it('returns only unread notifications of current user', async () => { it('returns only unread notifications of current user', async () => {
const expected = { const expected = {
currentUser: { currentUser: {
@ -202,7 +203,7 @@ describe('currentUser notifications', () => {
describe('no filters', () => { describe('no filters', () => {
const queryCurrentUserNotifications = gql` const queryCurrentUserNotifications = gql`
{ query {
currentUser { currentUser {
notifications(orderBy: createdAt_desc) { notifications(orderBy: createdAt_desc) {
id id
@ -300,12 +301,7 @@ describe('UpdateNotification', () => {
}), }),
]) ])
await factory.authenticateAs(userParams) await factory.authenticateAs(userParams)
// Post and its notifications await factory.create('Post', { id: 'p1', categoryIds })
await Promise.all([
factory.create('Post', {
id: 'p1',
}),
])
await Promise.all([ await Promise.all([
factory.relate('Notification', 'User', { factory.relate('Notification', 'User', {
from: 'post-mention-to-be-updated', from: 'post-mention-to-be-updated',

View File

@ -83,17 +83,14 @@ export default {
await session.run(cypherDeletePreviousRelations, { params }) await session.run(cypherDeletePreviousRelations, { params })
let updatePostCypher = `MATCH (post:Post {id: $params.id}) const updatePostCypher = `MATCH (post:Post {id: $params.id})
SET post = $params SET post = $params
` WITH post
if (categoryIds && categoryIds.length) {
updatePostCypher += `WITH post
UNWIND $categoryIds AS categoryId UNWIND $categoryIds AS categoryId
MATCH (category:Category {id: categoryId}) MATCH (category:Category {id: categoryId})
MERGE (post)-[:CATEGORIZED]->(category) MERGE (post)-[:CATEGORIZED]->(category)
` RETURN post`
}
updatePostCypher += `RETURN post`
const updatePostVariables = { categoryIds, params } const updatePostVariables = { categoryIds, params }
const transactionRes = await session.run(updatePostCypher, updatePostVariables) const transactionRes = await session.run(updatePostCypher, updatePostVariables)
@ -112,19 +109,16 @@ export default {
params = await fileUpload(params, { file: 'imageUpload', url: 'image' }) params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
params.id = params.id || uuid() params.id = params.id || uuid()
let createPostCypher = `CREATE (post:Post {params}) const createPostCypher = `CREATE (post:Post {params})
WITH post WITH post
MATCH (author:User {id: $userId}) MATCH (author:User {id: $userId})
MERGE (post)<-[:WROTE]-(author) MERGE (post)<-[:WROTE]-(author)
` WITH post
if (categoryIds) {
createPostCypher += `WITH post
UNWIND $categoryIds AS categoryId UNWIND $categoryIds AS categoryId
MATCH (category:Category {id: categoryId}) MATCH (category:Category {id: categoryId})
MERGE (post)-[:CATEGORIZED]->(category) MERGE (post)-[:CATEGORIZED]->(category)
` RETURN post`
}
createPostCypher += `RETURN post`
const createPostVariables = { userId: context.user.id, categoryIds, params } const createPostVariables = { userId: context.user.id, categoryIds, params }
const session = context.driver.session() const session = context.driver.session()

View File

@ -19,20 +19,10 @@ const oldTitle = 'Old title'
const oldContent = 'Old content' const oldContent = 'Old content'
const newTitle = 'New title' const newTitle = 'New title'
const newContent = 'New content' const newContent = 'New content'
const createPostVariables = { title: postTitle, content: postContent } const postSaveError = 'You cannot save a post without at least one category or more than three'
const createPostWithCategoriesMutation = gql` const categoryIds = ['cat9', 'cat4', 'cat15']
mutation($title: String!, $content: String!, $categoryIds: [ID]) { let createPostVariables
CreatePost(title: $title, content: $content, categoryIds: $categoryIds) {
id
title
}
}
`
const createPostWithCategoriesVariables = {
title: postTitle,
content: postContent,
categoryIds: ['cat9', 'cat4', 'cat15'],
}
const postQueryWithCategories = gql` const postQueryWithCategories = gql`
query($id: ID) { query($id: ID) {
Post(id: $id) { Post(id: $id) {
@ -42,11 +32,6 @@ const postQueryWithCategories = gql`
} }
} }
` `
const createPostWithoutCategoriesVariables = {
title: 'This is a post without categories',
content: 'I should be able to filter it out',
categoryIds: null,
}
const postQueryFilteredByCategory = gql` const postQueryFilteredByCategory = gql`
query Post($filter: _PostFilter) { query Post($filter: _PostFilter) {
Post(filter: $filter) { Post(filter: $filter) {
@ -58,14 +43,14 @@ const postQueryFilteredByCategory = gql`
} }
} }
` `
const postCategoriesFilterParam = { categories_some: { id_in: ['cat4'] } } const postCategoriesFilterParam = { categories_some: { id_in: categoryIds } }
const postQueryFilteredByCategoryVariables = { const postQueryFilteredByCategoryVariables = {
filter: postCategoriesFilterParam, filter: postCategoriesFilterParam,
} }
const createPostMutation = gql` const createPostMutation = gql`
mutation($title: String!, $content: String!) { mutation($id: ID, $title: String!, $content: String!, $categoryIds: [ID]) {
CreatePost(title: $title, content: $content) { CreatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) {
id id
title title
content content
@ -88,6 +73,34 @@ beforeEach(async () => {
password: '1234', password: '1234',
} }
await factory.create('User', userParams) await factory.create('User', userParams)
await Promise.all([
instance.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
}),
instance.create('Category', {
id: 'cat4',
name: 'Environment & Nature',
icon: 'tree',
}),
instance.create('Category', {
id: 'cat15',
name: 'Consumption & Sustainability',
icon: 'shopping-cart',
}),
instance.create('Category', {
id: 'cat27',
name: 'Animal Protection',
icon: 'paw',
}),
])
createPostVariables = {
id: 'p3589',
title: postTitle,
content: postContent,
categoryIds,
}
}) })
afterEach(async () => { afterEach(async () => {
@ -152,8 +165,13 @@ describe('CreatePost', () => {
describe('language', () => { describe('language', () => {
it('allows a user to set the language of the post', async () => { it('allows a user to set the language of the post', async () => {
const createPostWithLanguageMutation = gql` const createPostWithLanguageMutation = gql`
mutation($title: String!, $content: String!, $language: String) { mutation($title: String!, $content: String!, $language: String, $categoryIds: [ID]) {
CreatePost(title: $title, content: $content, language: $language) { CreatePost(
title: $title
content: $content
language: $language
categoryIds: $categoryIds
) {
language language
} }
} }
@ -162,6 +180,7 @@ describe('CreatePost', () => {
title: postTitle, title: postTitle,
content: postContent, content: postContent,
language: 'en', language: 'en',
categoryIds,
} }
const expected = { CreatePost: { language: 'en' } } const expected = { CreatePost: { language: 'en' } }
await expect( await expect(
@ -171,51 +190,36 @@ describe('CreatePost', () => {
}) })
describe('categories', () => { describe('categories', () => {
let postWithCategories it('throws an error if categoryIds is not an array', async () => {
beforeEach(async () => { createPostVariables.categoryIds = null
await Promise.all([ await expect(client.request(createPostMutation, createPostVariables)).rejects.toThrow(
factory.create('Category', { postSaveError,
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
}),
factory.create('Category', {
id: 'cat4',
name: 'Environment & Nature',
icon: 'tree',
}),
factory.create('Category', {
id: 'cat15',
name: 'Consumption & Sustainability',
icon: 'shopping-cart',
}),
])
postWithCategories = await client.request(
createPostWithCategoriesMutation,
createPostWithCategoriesVariables,
) )
}) })
it('allows a user to set the categories of the post', async () => { it('requires at least one category for successful creation', async () => {
const expected = [{ id: 'cat9' }, { id: 'cat4' }, { id: 'cat15' }] createPostVariables.categoryIds = []
const postQueryWithCategoriesVariables = { await expect(client.request(createPostMutation, createPostVariables)).rejects.toThrow(
id: postWithCategories.CreatePost.id, postSaveError,
} )
})
await expect( it('allows a maximum of three category for successful update', async () => {
client.request(postQueryWithCategories, postQueryWithCategoriesVariables), createPostVariables.categoryIds = ['cat9', 'cat27', 'cat15', 'cat4']
).resolves.toEqual({ Post: [{ categories: expect.arrayContaining(expected) }] }) await expect(client.request(createPostMutation, createPostVariables)).rejects.toThrow(
postSaveError,
)
}) })
it('allows a user to filter for posts by category', async () => { it('allows a user to filter for posts by category', async () => {
await client.request(createPostWithCategoriesMutation, createPostWithoutCategoriesVariables) await client.request(createPostMutation, createPostVariables)
const categoryIds = [{ id: 'cat4' }, { id: 'cat15' }, { id: 'cat9' }] const categoryIdsArray = [{ id: 'cat4' }, { id: 'cat15' }, { id: 'cat9' }]
const expected = { const expected = {
Post: [ Post: [
{ {
title: postTitle, title: postTitle,
id: postWithCategories.CreatePost.id, id: 'p3589',
categories: expect.arrayContaining(categoryIds), categories: expect.arrayContaining(categoryIdsArray),
}, },
], ],
} }
@ -228,8 +232,15 @@ describe('CreatePost', () => {
}) })
describe('UpdatePost', () => { describe('UpdatePost', () => {
let updatePostMutation
let updatePostVariables let updatePostVariables
const updatePostMutation = gql`
mutation($id: ID!, $title: String!, $content: String!, $categoryIds: [ID]) {
UpdatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) {
id
content
}
}
`
beforeEach(async () => { beforeEach(async () => {
const asAuthor = Factory() const asAuthor = Factory()
await asAuthor.create('User', authorParams) await asAuthor.create('User', authorParams)
@ -238,15 +249,8 @@ describe('UpdatePost', () => {
id: 'p1', id: 'p1',
title: oldTitle, title: oldTitle,
content: oldContent, content: oldContent,
categoryIds,
}) })
updatePostMutation = gql`
mutation($id: ID!, $title: String!, $content: String!, $categoryIds: [ID]) {
UpdatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) {
id
content
}
}
`
updatePostVariables = { updatePostVariables = {
id: 'p1', id: 'p1',
@ -287,6 +291,7 @@ describe('UpdatePost', () => {
}) })
it('updates a post', async () => { it('updates a post', async () => {
updatePostVariables.categoryIds = ['cat9']
const expected = { UpdatePost: { id: 'p1', content: newContent } } const expected = { UpdatePost: { id: 'p1', content: newContent } }
await expect(client.request(updatePostMutation, updatePostVariables)).resolves.toEqual( await expect(client.request(updatePostMutation, updatePostVariables)).resolves.toEqual(
expected, expected,
@ -294,36 +299,10 @@ describe('UpdatePost', () => {
}) })
describe('categories', () => { describe('categories', () => {
let postWithCategories
beforeEach(async () => { beforeEach(async () => {
await Promise.all([ await client.request(createPostMutation, createPostVariables)
factory.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
}),
factory.create('Category', {
id: 'cat4',
name: 'Environment & Nature',
icon: 'tree',
}),
factory.create('Category', {
id: 'cat15',
name: 'Consumption & Sustainability',
icon: 'shopping-cart',
}),
factory.create('Category', {
id: 'cat27',
name: 'Animal Protection',
icon: 'paw',
}),
])
postWithCategories = await client.request(
createPostWithCategoriesMutation,
createPostWithCategoriesVariables,
)
updatePostVariables = { updatePostVariables = {
id: postWithCategories.CreatePost.id, id: 'p3589',
title: newTitle, title: newTitle,
content: newContent, content: newContent,
categoryIds: ['cat27'], categoryIds: ['cat27'],
@ -334,12 +313,33 @@ describe('UpdatePost', () => {
await client.request(updatePostMutation, updatePostVariables) await client.request(updatePostMutation, updatePostVariables)
const expected = [{ id: 'cat27' }] const expected = [{ id: 'cat27' }]
const postQueryWithCategoriesVariables = { const postQueryWithCategoriesVariables = {
id: postWithCategories.CreatePost.id, id: 'p3589',
} }
await expect( await expect(
client.request(postQueryWithCategories, postQueryWithCategoriesVariables), client.request(postQueryWithCategories, postQueryWithCategoriesVariables),
).resolves.toEqual({ Post: [{ categories: expect.arrayContaining(expected) }] }) ).resolves.toEqual({ Post: [{ categories: expect.arrayContaining(expected) }] })
}) })
it('throws an error if categoryIds is not an array', async () => {
updatePostVariables.categoryIds = null
await expect(client.request(updatePostMutation, updatePostVariables)).rejects.toThrow(
postSaveError,
)
})
it('requires at least one category for successful update', async () => {
updatePostVariables.categoryIds = []
await expect(client.request(updatePostMutation, updatePostVariables)).rejects.toThrow(
postSaveError,
)
})
it('allows a maximum of three category for a successful update', async () => {
updatePostVariables.categoryIds = ['cat9', 'cat27', 'cat15', 'cat4']
await expect(client.request(updatePostMutation, updatePostVariables)).rejects.toThrow(
postSaveError,
)
})
}) })
}) })
}) })
@ -365,6 +365,7 @@ describe('DeletePost', () => {
await asAuthor.create('Post', { await asAuthor.create('Post', {
id: 'p1', id: 'p1',
content: 'To be deleted', content: 'To be deleted',
categoryIds,
}) })
}) })
@ -411,7 +412,7 @@ describe('emotions', () => {
postQueryAction, postQueryAction,
postToEmote, postToEmote,
postToEmoteNode postToEmoteNode
const PostsEmotionsCountQuery = ` const PostsEmotionsCountQuery = gql`
query($id: ID!) { query($id: ID!) {
Post(id: $id) { Post(id: $id) {
emotionsCount emotionsCount

View File

@ -1,8 +1,10 @@
import { GraphQLClient } from 'graphql-request' import { GraphQLClient } from 'graphql-request'
import Factory from '../../seed/factories' import Factory from '../../seed/factories'
import { host, login } from '../../jest/helpers' import { host, login } from '../../jest/helpers'
import { neode } from '../../bootstrap/neo4j'
const factory = Factory() const factory = Factory()
const instance = neode()
describe('report', () => { describe('report', () => {
let mutation let mutation
@ -10,6 +12,7 @@ describe('report', () => {
let returnedObject let returnedObject
let variables let variables
let createPostVariables let createPostVariables
const categoryIds = ['cat9']
beforeEach(async () => { beforeEach(async () => {
returnedObject = '{ description }' returnedObject = '{ description }'
@ -28,6 +31,11 @@ describe('report', () => {
role: 'user', role: 'user',
email: 'abusive-user@example.org', email: 'abusive-user@example.org',
}) })
await instance.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
})
}) })
afterEach(async () => { afterEach(async () => {
@ -126,6 +134,7 @@ describe('report', () => {
await factory.create('Post', { await factory.create('Post', {
id: 'p23', id: 'p23',
title: 'Matt and Robert having a pair-programming', title: 'Matt and Robert having a pair-programming',
categoryIds,
}) })
variables = { variables = {
id: 'p23', id: 'p23',
@ -171,6 +180,7 @@ describe('report', () => {
id: 'p1', id: 'p1',
title: 'post to comment on', title: 'post to comment on',
content: 'please comment on me', content: 'please comment on me',
categoryIds,
} }
const asAuthenticatedUser = await factory.authenticateAs({ const asAuthenticatedUser = await factory.authenticateAs({
email: 'test@example.org', email: 'test@example.org',

View File

@ -1,22 +1,39 @@
import { GraphQLClient } from 'graphql-request' import { GraphQLClient } from 'graphql-request'
import Factory from '../../seed/factories' import Factory from '../../seed/factories'
import { host, login } from '../../jest/helpers' import { host, login, gql } from '../../jest/helpers'
import { neode } from '../../bootstrap/neo4j'
const factory = Factory()
let clientUser1, clientUser2 let clientUser1, clientUser2
let headersUser1, headersUser2 let headersUser1, headersUser2
const factory = Factory()
const instance = neode()
const categoryIds = ['cat9']
const mutationShoutPost = id => ` const mutationShoutPost = gql`
mutation { mutation($id: ID!) {
shout(id: "${id}", type: Post) shout(id: $id, type: Post)
} }
` `
const mutationUnshoutPost = id => ` const mutationUnshoutPost = gql`
mutation { mutation($id: ID!) {
unshout(id: "${id}", type: Post) unshout(id: $id, type: Post)
} }
` `
const createPostMutation = gql`
mutation($id: ID, $title: String!, $content: String!, $categoryIds: [ID]!) {
CreatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) {
id
title
content
}
}
`
const createPostVariables = {
id: 'p1234',
title: 'Post Title 1234',
content: 'Some Post Content 1234',
categoryIds,
}
beforeEach(async () => { beforeEach(async () => {
await factory.create('User', { await factory.create('User', {
id: 'u1', id: 'u1',
@ -28,28 +45,23 @@ beforeEach(async () => {
email: 'test2@example.org', email: 'test2@example.org',
password: '1234', password: '1234',
}) })
await instance.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
})
headersUser1 = await login({ email: 'test@example.org', password: '1234' }) headersUser1 = await login({ email: 'test@example.org', password: '1234' })
headersUser2 = await login({ email: 'test2@example.org', password: '1234' }) headersUser2 = await login({ email: 'test2@example.org', password: '1234' })
clientUser1 = new GraphQLClient(host, { headers: headersUser1 }) clientUser1 = new GraphQLClient(host, { headers: headersUser1 })
clientUser2 = new GraphQLClient(host, { headers: headersUser2 }) clientUser2 = new GraphQLClient(host, { headers: headersUser2 })
await clientUser1.request(` await clientUser1.request(createPostMutation, createPostVariables)
mutation { await clientUser2.request(createPostMutation, {
CreatePost(id: "p1", title: "Post Title 1", content: "Some Post Content 1") { id: 'p12345',
id title: 'Post Title 12345',
title content: 'Some Post Content 12345',
} categoryIds,
} })
`)
await clientUser2.request(`
mutation {
CreatePost(id: "p2", title: "Post Title 2", content: "Some Post Content 2") {
id
title
}
}
`)
}) })
afterEach(async () => { afterEach(async () => {
@ -61,22 +73,26 @@ describe('shout', () => {
describe('unauthenticated shout', () => { describe('unauthenticated shout', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
const client = new GraphQLClient(host) const client = new GraphQLClient(host)
await expect(client.request(mutationShoutPost('p1'))).rejects.toThrow('Not Authorised') await expect(client.request(mutationShoutPost, { id: 'p1234' })).rejects.toThrow(
'Not Authorised',
)
}) })
}) })
it('I shout a post of another user', async () => { it('I shout a post of another user', async () => {
const res = await clientUser1.request(mutationShoutPost('p2')) const res = await clientUser1.request(mutationShoutPost, { id: 'p12345' })
const expected = { const expected = {
shout: true, shout: true,
} }
expect(res).toMatchObject(expected) expect(res).toMatchObject(expected)
const { Post } = await clientUser1.request(`{ const { Post } = await clientUser1.request(gql`
Post(id: "p2") { query {
shoutedByCurrentUser Post(id: "p12345") {
shoutedByCurrentUser
}
} }
}`) `)
const expected2 = { const expected2 = {
shoutedByCurrentUser: true, shoutedByCurrentUser: true,
} }
@ -84,17 +100,19 @@ describe('shout', () => {
}) })
it('I can`t shout my own post', async () => { it('I can`t shout my own post', async () => {
const res = await clientUser1.request(mutationShoutPost('p1')) const res = await clientUser1.request(mutationShoutPost, { id: 'p1234' })
const expected = { const expected = {
shout: false, shout: false,
} }
expect(res).toMatchObject(expected) expect(res).toMatchObject(expected)
const { Post } = await clientUser1.request(`{ const { Post } = await clientUser1.request(gql`
Post(id: "p1") { query {
shoutedByCurrentUser Post(id: "p1234") {
shoutedByCurrentUser
}
} }
}`) `)
const expected2 = { const expected2 = {
shoutedByCurrentUser: false, shoutedByCurrentUser: false,
} }
@ -106,28 +124,32 @@ describe('shout', () => {
describe('unauthenticated shout', () => { describe('unauthenticated shout', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
// shout // shout
await clientUser1.request(mutationShoutPost('p2')) await clientUser1.request(mutationShoutPost, { id: 'p12345' })
// unshout // unshout
const client = new GraphQLClient(host) const client = new GraphQLClient(host)
await expect(client.request(mutationUnshoutPost('p2'))).rejects.toThrow('Not Authorised') await expect(client.request(mutationUnshoutPost, { id: 'p12345' })).rejects.toThrow(
'Not Authorised',
)
}) })
}) })
it('I unshout a post of another user', async () => { it('I unshout a post of another user', async () => {
// shout // shout
await clientUser1.request(mutationShoutPost('p2')) await clientUser1.request(mutationShoutPost, { id: 'p12345' })
const expected = { const expected = {
unshout: true, unshout: true,
} }
// unshout // unshout
const res = await clientUser1.request(mutationUnshoutPost('p2')) const res = await clientUser1.request(mutationUnshoutPost, { id: 'p12345' })
expect(res).toMatchObject(expected) expect(res).toMatchObject(expected)
const { Post } = await clientUser1.request(`{ const { Post } = await clientUser1.request(gql`
Post(id: "p2") { query {
shoutedByCurrentUser Post(id: "p12345") {
shoutedByCurrentUser
}
} }
}`) `)
const expected2 = { const expected2 = {
shoutedByCurrentUser: false, shoutedByCurrentUser: false,
} }

View File

@ -1,9 +1,12 @@
import { GraphQLClient } from 'graphql-request' import { GraphQLClient } from 'graphql-request'
import Factory from '../../seed/factories' import Factory from '../../seed/factories'
import { host, login, gql } from '../../jest/helpers' import { host, login, gql } from '../../jest/helpers'
import { neode } from '../../bootstrap/neo4j'
const factory = Factory()
let client let client
const factory = Factory()
const instance = neode()
const categoryIds = ['cat9']
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await factory.cleanDatabase()
@ -195,9 +198,15 @@ describe('users', () => {
email: 'test@example.org', email: 'test@example.org',
password: '1234', password: '1234',
}) })
await instance.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
})
await asAuthor.create('Post', { await asAuthor.create('Post', {
id: 'p139', id: 'p139',
content: 'Post by user u343', content: 'Post by user u343',
categoryIds,
}) })
await asAuthor.create('Comment', { await asAuthor.create('Comment', {
id: 'c155', id: 'c155',

View File

@ -16,6 +16,7 @@ export default function(params) {
image = faker.image.unsplash.imageUrl(), image = faker.image.unsplash.imageUrl(),
visibility = 'public', visibility = 'public',
deleted = false, deleted = false,
categoryIds,
} = params } = params
return { return {
@ -28,6 +29,7 @@ export default function(params) {
$image: String $image: String
$visibility: Visibility $visibility: Visibility
$deleted: Boolean $deleted: Boolean
$categoryIds: [ID]
) { ) {
CreatePost( CreatePost(
id: $id id: $id
@ -37,12 +39,13 @@ export default function(params) {
image: $image image: $image
visibility: $visibility visibility: $visibility
deleted: $deleted deleted: $deleted
categoryIds: $categoryIds
) { ) {
title title
content content
} }
} }
`, `,
variables: { id, slug, title, content, image, visibility, deleted }, variables: { id, slug, title, content, image, visibility, deleted, categoryIds },
} }
} }

View File

@ -276,131 +276,82 @@ import Factory from './factories'
asAdmin.create('Post', { asAdmin.create('Post', {
id: 'p0', id: 'p0',
image: faker.image.unsplash.food(), image: faker.image.unsplash.food(),
categoryIds: ['cat16'],
}), }),
asModerator.create('Post', { asModerator.create('Post', {
id: 'p1', id: 'p1',
image: faker.image.unsplash.technology(), image: faker.image.unsplash.technology(),
categoryIds: ['cat1'],
}), }),
asUser.create('Post', { asUser.create('Post', {
id: 'p2', id: 'p2',
title: `Nature Philosophy Yoga`, title: `Nature Philosophy Yoga`,
content: `${hashtag1}`, content: `${hashtag1}`,
categoryIds: ['cat2'],
}), }),
asTick.create('Post', { asTick.create('Post', {
id: 'p3', id: 'p3',
categoryIds: ['cat3'],
}), }),
asTrick.create('Post', { asTrick.create('Post', {
id: 'p4', id: 'p4',
categoryIds: ['cat4'],
}), }),
asTrack.create('Post', { asTrack.create('Post', {
id: 'p5', id: 'p5',
categoryIds: ['cat5'],
}), }),
asAdmin.create('Post', { asAdmin.create('Post', {
id: 'p6', id: 'p6',
image: faker.image.unsplash.buildings(), image: faker.image.unsplash.buildings(),
categoryIds: ['cat6'],
}), }),
asModerator.create('Post', { asModerator.create('Post', {
id: 'p7', id: 'p7',
content: `${mention1} ${faker.lorem.paragraph()}`, content: `${mention1} ${faker.lorem.paragraph()}`,
categoryIds: ['cat7'],
}), }),
asUser.create('Post', { asUser.create('Post', {
id: 'p8', id: 'p8',
image: faker.image.unsplash.nature(), image: faker.image.unsplash.nature(),
title: `Quantum Flow Theory explains Quantum Gravity`, title: `Quantum Flow Theory explains Quantum Gravity`,
content: `${hashtagAndMention1}`, content: `${hashtagAndMention1}`,
categoryIds: ['cat8'],
}), }),
asTick.create('Post', { asTick.create('Post', {
id: 'p9', id: 'p9',
categoryIds: ['cat9'],
}), }),
asTrick.create('Post', { asTrick.create('Post', {
id: 'p10', id: 'p10',
categoryIds: ['cat10'],
}), }),
asTrack.create('Post', { asTrack.create('Post', {
id: 'p11', id: 'p11',
image: faker.image.unsplash.people(), image: faker.image.unsplash.people(),
categoryIds: ['cat11'],
}), }),
asAdmin.create('Post', { asAdmin.create('Post', {
id: 'p12', id: 'p12',
content: `${mention2} ${faker.lorem.paragraph()}`, content: `${mention2} ${faker.lorem.paragraph()}`,
categoryIds: ['cat12'],
}), }),
asModerator.create('Post', { asModerator.create('Post', {
id: 'p13', id: 'p13',
categoryIds: ['cat13'],
}), }),
asUser.create('Post', { asUser.create('Post', {
id: 'p14', id: 'p14',
image: faker.image.unsplash.objects(), image: faker.image.unsplash.objects(),
categoryIds: ['cat14'],
}), }),
asTick.create('Post', { asTick.create('Post', {
id: 'p15', id: 'p15',
categoryIds: ['cat15'],
}), }),
]) ])
await Promise.all([ await Promise.all([
f.relate('Post', 'Categories', {
from: 'p0',
to: 'cat16',
}),
f.relate('Post', 'Categories', {
from: 'p1',
to: 'cat1',
}),
f.relate('Post', 'Categories', {
from: 'p2',
to: 'cat2',
}),
f.relate('Post', 'Categories', {
from: 'p3',
to: 'cat3',
}),
f.relate('Post', 'Categories', {
from: 'p4',
to: 'cat4',
}),
f.relate('Post', 'Categories', {
from: 'p5',
to: 'cat5',
}),
f.relate('Post', 'Categories', {
from: 'p6',
to: 'cat6',
}),
f.relate('Post', 'Categories', {
from: 'p7',
to: 'cat7',
}),
f.relate('Post', 'Categories', {
from: 'p8',
to: 'cat8',
}),
f.relate('Post', 'Categories', {
from: 'p9',
to: 'cat9',
}),
f.relate('Post', 'Categories', {
from: 'p10',
to: 'cat10',
}),
f.relate('Post', 'Categories', {
from: 'p11',
to: 'cat11',
}),
f.relate('Post', 'Categories', {
from: 'p12',
to: 'cat12',
}),
f.relate('Post', 'Categories', {
from: 'p13',
to: 'cat13',
}),
f.relate('Post', 'Categories', {
from: 'p14',
to: 'cat14',
}),
f.relate('Post', 'Categories', {
from: 'p15',
to: 'cat15',
}),
f.relate('Post', 'Tags', { f.relate('Post', 'Tags', {
from: 'p0', from: 'p0',
to: 'Freiheit', to: 'Freiheit',

View File

@ -1826,12 +1826,12 @@ apollo-server-plugin-base@0.6.1:
dependencies: dependencies:
apollo-server-types "0.2.1" apollo-server-types "0.2.1"
apollo-server-testing@~2.8.1: apollo-server-testing@~2.8.2:
version "2.8.1" version "2.8.2"
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.8.1.tgz#70026e1b6abab1ca51ffee21bfda61f5b5ad92c1" resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.8.2.tgz#8faa8f1778fa4893f5bf705f7cea84a69477aa3f"
integrity sha512-bWKczu9HPBWBOz3GDtPA1pykmIvK2TOTLaK03AVSZODvZX0YLWizB0bq5I5Ox6rG+wmW638v1Kq+BhADVHovdg== integrity sha512-ccp1DpmjdmLT98ww4NtSiDPbeIPlVZJ5Iy408ToyhAGwNXRHk5f8Czf+JAgSayvgt4cxCm1fzxnVe1OjO8oIvA==
dependencies: dependencies:
apollo-server-core "2.8.1" apollo-server-core "2.8.2"
apollo-server-types@0.2.1: apollo-server-types@0.2.1:
version "0.2.1" version "0.2.1"
@ -2954,10 +2954,10 @@ data-urls@^1.0.0:
whatwg-mimetype "^2.2.0" whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0" whatwg-url "^7.0.0"
date-fns@2.0.0-beta.5: date-fns@2.0.0:
version "2.0.0-beta.5" version "2.0.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-beta.5.tgz#90885db3772802d55519cd12acd49de56aca1059" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0.tgz#52f05c6ae1fe0e395670082c72b690ab781682d0"
integrity sha512-GS5yi964NDFNoja9yOdWFj9T97T67yLrUeJZgddHaVfc/6tHWtX7RXocuubmZkNzrZUZ9BqBOW7jTR5OoWjJ1w== integrity sha512-nGZDA64Ktq5uTWV4LEH3qX+foV4AguT5qxwRlJDzJtf57d4xLNwtwrfb7SzKCoikoae8Bvxf0zdaEG/xWssp/w==
debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
version "2.6.9" version "2.6.9"

View File

@ -24,8 +24,8 @@ Feature: Tags and Categories
Then I can see the following table: Then I can see the following table:
| | Name | Posts | | | Name | Posts |
| | Just For Fun | 2 | | | Just For Fun | 2 |
| | Happyness & Values | 1 | | | Happiness & Values | 1 |
| | Health & Wellbeing | 0 | | | Health & Wellbeing | 1 |
Scenario: See an overview of tags Scenario: See an overview of tags
When I navigate to the administration dashboard When I navigate to the administration dashboard

View File

@ -21,31 +21,17 @@ Given("I am logged in", () => {
cy.login(loginCredentials); cy.login(loginCredentials);
}); });
Given("we have a selection of categories", () => {
cy.createCategories("cat0", "just-for-fun");
});
Given("we have a selection of tags and categories as well as posts", () => { Given("we have a selection of tags and categories as well as posts", () => {
cy.factory() cy.createCategories("cat12")
.factory()
.authenticateAs(loginCredentials) .authenticateAs(loginCredentials)
.create("Category", {
id: "cat1",
name: "Just For Fun",
slug: "justforfun",
icon: "smile"
})
.create("Category", {
id: "cat2",
name: "Happyness & Values",
slug: "happyness-values",
icon: "heart-o"
})
.create("Category", {
id: "cat3",
name: "Health & Wellbeing",
slug: "health-wellbeing",
icon: "medkit"
})
.create("Tag", { id: "Ecology" }) .create("Tag", { id: "Ecology" })
.create("Tag", { id: "Nature" }) .create("Tag", { id: "Nature" })
.create("Tag", { id: "Democracy" }); .create("Tag", { id: "Democracy" });
const someAuthor = { const someAuthor = {
id: "authorId", id: "authorId",
email: "author@example.org", email: "author@example.org",
@ -59,18 +45,15 @@ Given("we have a selection of tags and categories as well as posts", () => {
cy.factory() cy.factory()
.create("User", someAuthor) .create("User", someAuthor)
.authenticateAs(someAuthor) .authenticateAs(someAuthor)
.create("Post", { id: "p0" }) .create("Post", { id: "p0", categoryIds: ["cat12"] })
.create("Post", { id: "p1" }); .create("Post", { id: "p1", categoryIds: ["cat121"] });
cy.factory() cy.factory()
.create("User", yetAnotherAuthor) .create("User", yetAnotherAuthor)
.authenticateAs(yetAnotherAuthor) .authenticateAs(yetAnotherAuthor)
.create("Post", { id: "p2" }); .create("Post", { id: "p2", categoryIds: ["cat12"] });
cy.factory() cy.factory()
.authenticateAs(loginCredentials) .authenticateAs(loginCredentials)
.create("Post", { id: "p3" }) .create("Post", { id: "p3", categoryIds: ["cat122"] })
.relate("Post", "Categories", { from: "p0", to: "cat1" })
.relate("Post", "Categories", { from: "p1", to: "cat2" })
.relate("Post", "Categories", { from: "p2", to: "cat1" })
.relate("Post", "Tags", { from: "p0", to: "Ecology" }) .relate("Post", "Tags", { from: "p0", to: "Ecology" })
.relate("Post", "Tags", { from: "p0", to: "Nature" }) .relate("Post", "Tags", { from: "p0", to: "Nature" })
.relate("Post", "Tags", { from: "p0", to: "Democracy" }) .relate("Post", "Tags", { from: "p0", to: "Democracy" })
@ -182,9 +165,17 @@ Given("we have the following posts in our database:", table => {
}; };
postAttributes.deleted = Boolean(postAttributes.deleted); postAttributes.deleted = Boolean(postAttributes.deleted);
const disabled = Boolean(postAttributes.disabled); const disabled = Boolean(postAttributes.disabled);
postAttributes.categoryIds = [`cat${i}`];
postAttributes;
cy.factory() cy.factory()
.create("User", userAttributes) .create("User", userAttributes)
.authenticateAs(userAttributes) .authenticateAs(userAttributes)
.create("Category", {
id: `cat${i}`,
name: "Just For Fun",
slug: `just-for-fun-${i}`,
icon: "smile"
})
.create("Post", postAttributes); .create("Post", postAttributes);
if (disabled) { if (disabled) {
const moderatorParams = { const moderatorParams = {
@ -218,6 +209,7 @@ When(
Given("I previously created a post", () => { Given("I previously created a post", () => {
lastPost.title = "previously created post"; lastPost.title = "previously created post";
lastPost.content = "with some content"; lastPost.content = "with some content";
lastPost.categoryIds = "cat0";
cy.factory() cy.factory()
.authenticateAs(loginCredentials) .authenticateAs(loginCredentials)
.create("Post", lastPost); .create("Post", lastPost);
@ -233,6 +225,12 @@ When("I type in the following text:", text => {
cy.get(".editor .ProseMirror").type(lastPost.content); cy.get(".editor .ProseMirror").type(lastPost.content);
}); });
Then("I select a category", () => {
cy.get("span")
.contains("Just for Fun")
.click();
});
Then("the post shows up on the landing page at position {int}", index => { Then("the post shows up on the landing page at position {int}", index => {
cy.openPage("landing"); cy.openPage("landing");
const selector = `.post-card:nth-child(${index}) > .ds-card-content`; const selector = `.post-card:nth-child(${index}) > .ds-card-content`;
@ -260,7 +258,9 @@ Then("the first post on the landing page has the title:", title => {
Then( Then(
"the page {string} returns a 404 error with a message:", "the page {string} returns a 404 error with a message:",
(route, message) => { (route, message) => {
cy.request({ url: route, failOnStatusCode: false }).its('status').should('eq', 404) cy.request({ url: route, failOnStatusCode: false })
.its("status")
.should("eq", 404);
cy.visit(route, { failOnStatusCode: false }); cy.visit(route, { failOnStatusCode: false });
cy.get(".error").should("contain", message); cy.get(".error").should("contain", message);
} }
@ -354,7 +354,7 @@ When("mention {string} in the text", mention => {
}); });
Then("the notification gets marked as read", () => { Then("the notification gets marked as read", () => {
cy.get(".notification") cy.get(".post.createdAt")
.first() .first()
.should("have.class", "read"); .should("have.class", "read");
}); });
@ -417,12 +417,13 @@ Given("I follow the user {string}", name => {
}); });
Given('"Spammy Spammer" wrote a post {string}', title => { Given('"Spammy Spammer" wrote a post {string}', title => {
cy.factory() cy.createCategories("cat21")
.factory()
.authenticateAs({ .authenticateAs({
email: "spammy-spammer@example.org", email: "spammy-spammer@example.org",
password: "1234" password: "1234"
}) })
.create("Post", { title }); .create("Post", { title, categoryIds: ["cat21"] });
}); });
Then("the list of posts of this user is empty", () => { Then("the list of posts of this user is empty", () => {
@ -439,9 +440,10 @@ Then("nobody is following the user profile anymore", () => {
}); });
Given("I wrote a post {string}", title => { Given("I wrote a post {string}", title => {
cy.factory() cy.createCategories(`cat213`, title)
.factory()
.authenticateAs(loginCredentials) .authenticateAs(loginCredentials)
.create("Post", { title }); .create("Post", { title, categoryIds: ["cat213"] });
}); });
When("I block the user {string}", name => { When("I block the user {string}", name => {

View File

@ -7,20 +7,20 @@ Feature: Hide Posts
Given we have the following posts in our database: Given we have the following posts in our database:
| id | title | deleted | disabled | | id | title | deleted | disabled |
| p1 | This post should be visible | | | | p1 | This post should be visible | | |
| p2 | This post is disabled | | x | | p2 | This post is disabled | | x |
| p3 | This post is deleted | x | | | p3 | This post is deleted | x | |
Scenario: Disabled posts don't show up on the landing page Scenario: Disabled posts don't show up on the landing page
Given I am logged in with a "user" role Given I am logged in with a "user" role
Then I should see only 1 post on the landing page Then I should see only 1 post on the landing page
And the first post on the landing page has the title: And the first post on the landing page has the title:
""" """
This post should be visible This post should be visible
""" """
Scenario: Visiting a disabled post's page should return 404 Scenario: Visiting a disabled post's page should return 404
Given I am logged in with a "user" role Given I am logged in with a "user" role
Then the page "/post/this-post-is-disabled" returns a 404 error with a message: Then the page "/post/this-post-is-disabled" returns a 404 error with a message:
""" """
This post could not be found This post could not be found
""" """

View File

@ -4,25 +4,27 @@ Feature: Notifications for a mentions
In order join conversations about or related to me In order join conversations about or related to me
Background: Background:
Given we have the following user accounts: Given we have a selection of categories
| name | slug | email | password | And we have the following user accounts:
| Wolle aus Hamburg | wolle-aus-hamburg | wolle@example.org | 1234 | | name | slug | email | password |
| Matt Rider | matt-rider | matt@example.org | 4321 | | Wolle aus Hamburg | wolle-aus-hamburg | wolle@example.org | 1234 |
| Matt Rider | matt-rider | matt@example.org | 4321 |
Scenario: Mention another user, re-login as this user and see notifications Scenario: Mention another user, re-login as this user and see notifications
Given I log in with the following credentials: Given I log in with the following credentials:
| email | password | | email | password |
| wolle@example.org | 1234 | | wolle@example.org | 1234 |
And I start to write a new post with the title "Hey Matt" beginning with: And I start to write a new post with the title "Hey Matt" beginning with:
""" """
Big shout to our fellow contributor Big shout to our fellow contributor
""" """
And mention "@matt-rider" in the text And mention "@matt-rider" in the text
And I select a category
And I click on "Save" And I click on "Save"
When I log out When I log out
And I log in with the following credentials: And I log in with the following credentials:
| email | password | | email | password |
| matt@example.org | 4321 | | matt@example.org | 4321 |
And see 1 unread notifications in the top menu And see 1 unread notifications in the top menu
And open the notification menu and click on the first item And open the notification menu and click on the first item
Then I get to the post page of ".../hey-matt" Then I get to the post page of ".../hey-matt"

View File

@ -6,6 +6,7 @@ Feature: Create a post
Background: Background:
Given I have a user account Given I have a user account
And I am logged in And I am logged in
And we have a selection of categories
And I am on the "landing" page And I am on the "landing" page
Scenario: Create a post Scenario: Create a post
@ -16,6 +17,7 @@ Feature: Create a post
Human Connection is a free and open-source social network Human Connection is a free and open-source social network
for active citizenship. for active citizenship.
""" """
Then I select a category
And I click on "Save" And I click on "Save"
Then I get redirected to ".../my-first-post" Then I get redirected to ".../my-first-post"
And the post was saved successfully And the post was saved successfully

View File

@ -7,6 +7,7 @@ Feature: Block a User
Given I have a user account Given I have a user account
And there is an annoying user called "Spammy Spammer" And there is an annoying user called "Spammy Spammer"
And I am logged in And I am logged in
And we have a selection of categories
Scenario: Block a user Scenario: Block a user
Given I am on the profile page of the annoying user Given I am on the profile page of the annoying user

View File

@ -13,55 +13,74 @@
// Cypress.Commands.add('login', (email, password) => { ... }) // Cypress.Commands.add('login', (email, password) => { ... })
/* globals Cypress cy */ /* globals Cypress cy */
import 'cypress-file-upload' import "cypress-file-upload";
import { getLangByName } from './helpers' import { getLangByName } from "./helpers";
import users from '../fixtures/users.json' import users from "../fixtures/users.json";
const switchLang = name => { const switchLang = name => {
cy.get('.locale-menu').click() cy.get(".locale-menu").click();
cy.contains('.locale-menu-popover a', name).click() cy.contains(".locale-menu-popover a", name).click();
} };
Cypress.Commands.add('switchLanguage', (name, force) => { Cypress.Commands.add("switchLanguage", (name, force) => {
const code = getLangByName(name).code const code = getLangByName(name).code;
if (force) { if (force) {
switchLang(name) switchLang(name);
} else { } else {
cy.get('html').then($html => { cy.get("html").then($html => {
if ($html && $html.attr('lang') !== code) { if ($html && $html.attr("lang") !== code) {
switchLang(name) switchLang(name);
} }
});
}
});
Cypress.Commands.add("login", ({ email, password }) => {
cy.visit(`/login`);
cy.get("input[name=email]")
.trigger("focus")
.type(email);
cy.get("input[name=password]")
.trigger("focus")
.type(password);
cy.get("button[name=submit]")
.as("submitButton")
.click();
cy.get(".iziToast-message").should("contain", "You are logged in!");
cy.get(".iziToast-close").click();
});
Cypress.Commands.add("logout", (email, password) => {
cy.visit(`/logout`);
cy.location("pathname").should("contain", "/login"); // we're out
});
Cypress.Commands.add("openPage", page => {
if (page === "landing") {
page = "";
}
cy.visit(`/${page}`);
});
Cypress.Commands.add("createCategories", (id, slug) => {
cy.neode()
.create("Category", {
id: `${id}`,
name: "Just For Fun",
slug: `${slug}`,
icon: "smile"
}) })
} .create("Category", {
}) id: `${id}1`,
name: "Happiness & Values",
Cypress.Commands.add('login', ({ email, password }) => { icon: "heart-o"
cy.visit(`/login`) })
cy.get('input[name=email]') .create("Category", {
.trigger('focus') id: `${id}2`,
.type(email) name: "Health & Wellbeing",
cy.get('input[name=password]') icon: "medkit"
.trigger('focus') });
.type(password) });
cy.get('button[name=submit]')
.as('submitButton')
.click()
cy.get('.iziToast-message').should('contain', 'You are logged in!')
cy.get('.iziToast-close').click()
})
Cypress.Commands.add('logout', (email, password) => {
cy.visit(`/logout`)
cy.location('pathname').should('contain', '/login') // we're out
})
Cypress.Commands.add('openPage', page => {
if (page === 'landing') {
page = ''
}
cy.visit(`/${page}`)
})
// //
// //
// -- This is a child command -- // -- This is a child command --

View File

@ -27,7 +27,7 @@
</template> </template>
<script> <script>
import CategoryQuery from '~/graphql/CategoryQuery.js' import CategoryQuery from '~/graphql/CategoryQuery'
export default { export default {
props: { props: {
@ -87,8 +87,8 @@ export default {
query() { query() {
return CategoryQuery() return CategoryQuery()
}, },
result(result) { result({ data: { Category } }) {
this.categories = result.data.Category this.categories = Category
}, },
}, },
}, },

View File

@ -83,7 +83,7 @@ export default {
update: (store, { data: { CreateComment } }) => { update: (store, { data: { CreateComment } }) => {
const data = store.readQuery({ const data = store.readQuery({
query: PostQuery(this.$i18n), query: PostQuery(this.$i18n),
variables: { slug: this.post.slug }, variables: { id: this.post.id },
}) })
data.Post[0].comments.push(CreateComment) data.Post[0].comments.push(CreateComment)
store.writeQuery({ query: PostQuery(this.$i18n), data }) store.writeQuery({ query: PostQuery(this.$i18n), data })

View File

@ -28,6 +28,7 @@ describe('ContributionForm.vue', () => {
let cancelBtn let cancelBtn
let mocks let mocks
let propsData let propsData
let categoryIds
const postTitle = 'this is a title for a post' const postTitle = 'this is a title for a post'
const postTitleTooShort = 'xx' const postTitleTooShort = 'xx'
let postTitleTooLong = '' let postTitleTooLong = ''
@ -60,6 +61,7 @@ describe('ContributionForm.vue', () => {
content: postContent, content: postContent,
contentExcerpt: postContent, contentExcerpt: postContent,
language: 'en', language: 'en',
categoryIds,
}, },
}, },
}), }),
@ -175,6 +177,23 @@ describe('ContributionForm.vue', () => {
wrapper.find('.submit-button-for-test').trigger('click') wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).not.toHaveBeenCalled() expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
}) })
it('should have at least one category', async () => {
postTitleInput = wrapper.find('.ds-input')
postTitleInput.setValue(postTitle)
await wrapper.vm.updateEditorContent(postContent)
wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
})
it('should have not have more than three categories', async () => {
postTitleInput = wrapper.find('.ds-input')
postTitleInput.setValue(postTitle)
await wrapper.vm.updateEditorContent(postContent)
wrapper.vm.form.categoryIds = ['cat4', 'cat9', 'cat15', 'cat27']
wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
})
}) })
describe('valid form submission', () => { describe('valid form submission', () => {
@ -186,7 +205,7 @@ describe('ContributionForm.vue', () => {
content: postContent, content: postContent,
language: 'en', language: 'en',
id: null, id: null,
categoryIds: null, categoryIds: ['cat12'],
imageUpload: null, imageUpload: null,
image: null, image: null,
}, },
@ -194,11 +213,13 @@ describe('ContributionForm.vue', () => {
postTitleInput = wrapper.find('.ds-input') postTitleInput = wrapper.find('.ds-input')
postTitleInput.setValue(postTitle) postTitleInput.setValue(postTitle)
await wrapper.vm.updateEditorContent(postContent) await wrapper.vm.updateEditorContent(postContent)
categoryIds = ['cat12']
wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds)
}) })
it('with title and content', () => { it('creates a post with valid title, content, and at least one category', async () => {
wrapper.find('.submit-button-for-test').trigger('click') await wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1) expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
}) })
it("sends a fallback language based on a user's locale", () => { it("sends a fallback language based on a user's locale", () => {
@ -214,14 +235,6 @@ describe('ContributionForm.vue', () => {
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams)) expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
}) })
it('supports adding categories', async () => {
const categoryIds = ['cat12', 'cat15', 'cat37']
expectedParams.variables.categoryIds = categoryIds
wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds)
await wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
})
it('supports adding a teaser image', async () => { it('supports adding a teaser image', async () => {
expectedParams.variables.imageUpload = imageUpload expectedParams.variables.imageUpload = imageUpload
wrapper.find(TeaserImage).vm.$emit('addTeaserImage', imageUpload) wrapper.find(TeaserImage).vm.$emit('addTeaserImage', imageUpload)
@ -260,6 +273,8 @@ describe('ContributionForm.vue', () => {
postTitleInput = wrapper.find('.ds-input') postTitleInput = wrapper.find('.ds-input')
postTitleInput.setValue(postTitle) postTitleInput.setValue(postTitle)
await wrapper.vm.updateEditorContent(postContent) await wrapper.vm.updateEditorContent(postContent)
categoryIds = ['cat12']
wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds)
}) })
it('shows an error toaster when apollo mutation rejects', async () => { it('shows an error toaster when apollo mutation rejects', async () => {
@ -307,35 +322,54 @@ describe('ContributionForm.vue', () => {
expect(wrapper.vm.form.content).toEqual(propsData.contribution.content) expect(wrapper.vm.form.content).toEqual(propsData.contribution.content)
}) })
it('calls the UpdatePost apollo mutation', async () => { describe('valid update', () => {
expectedParams = { beforeEach(() => {
mutation: PostMutations().UpdatePost, mocks.$apollo.mutate = jest.fn().mockResolvedValueOnce({
variables: { data: {
title: postTitle, UpdatePost: {
content: postContent, title: postTitle,
language: propsData.contribution.language, slug: 'this-is-a-title-for-a-post',
id: propsData.contribution.id, content: postContent,
categoryIds: ['cat12'], contentExcerpt: postContent,
image, language: 'en',
imageUpload: null, categoryIds,
}, },
} },
postTitleInput = wrapper.find('.ds-input') })
postTitleInput.setValue(postTitle) wrapper = Wrapper()
wrapper.vm.updateEditorContent(postContent) expectedParams = {
await wrapper.find('.submit-button-for-test').trigger('click') mutation: PostMutations().UpdatePost,
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams)) variables: {
}) title: postTitle,
content: postContent,
language: propsData.contribution.language,
id: propsData.contribution.id,
categoryIds,
image,
imageUpload: null,
},
}
})
it('supports updating categories', async () => { it('calls the UpdatePost apollo mutation', async () => {
const categoryIds = ['cat3', 'cat51', 'cat37'] postTitleInput = wrapper.find('.ds-input')
postTitleInput = wrapper.find('.ds-input') postTitleInput.setValue(postTitle)
postTitleInput.setValue(postTitle) wrapper.vm.updateEditorContent(postContent)
wrapper.vm.updateEditorContent(postContent) wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds)
expectedParams.variables.categoryIds = categoryIds wrapper.find('.submit-button-for-test').trigger('click')
wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds) expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
await wrapper.find('.submit-button-for-test').trigger('click') })
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
it('supports updating categories', async () => {
const categoryIds = ['cat3', 'cat51', 'cat37']
postTitleInput = wrapper.find('.ds-input')
postTitleInput.setValue(postTitle)
wrapper.vm.updateEditorContent(postContent)
expectedParams.variables.categoryIds = categoryIds
wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds)
await wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
})
}) })
}) })
}) })

View File

@ -17,8 +17,8 @@
<no-ssr> <no-ssr>
<hc-editor <hc-editor
:users="users" :users="users"
:hashtags="hashtags"
:value="form.content" :value="form.content"
:hashtags="hashtags"
@input="updateEditorContent" @input="updateEditorContent"
/> />
<small class="smallTag">{{ form.contentLength }}/{{ contentMax }}</small> <small class="smallTag">{{ form.contentLength }}/{{ contentMax }}</small>
@ -57,7 +57,7 @@
type="submit" type="submit"
icon="check" icon="check"
:loading="loading" :loading="loading"
:disabled="disabledByContent || errors" :disabled="failsValidations || errors"
primary primary
@click.prevent="submit" @click.prevent="submit"
> >
@ -101,7 +101,7 @@ export default {
image: null, image: null,
language: null, language: null,
languageOptions: [], languageOptions: [],
categoryIds: null, categoryIds: [],
}, },
formSchema: { formSchema: {
title: { required: true, min: 3, max: 64 }, title: { required: true, min: 3, max: 64 },
@ -109,12 +109,11 @@ export default {
}, },
id: null, id: null,
loading: false, loading: false,
disabledByContent: true,
slug: null, slug: null,
users: [], users: [],
contentMin: 3, contentMin: 3,
contentMax: 2000, contentMax: 2000,
failsValidations: true,
hashtags: [], hashtags: [],
} }
}, },
@ -129,9 +128,9 @@ export default {
this.slug = contribution.slug this.slug = contribution.slug
this.form.title = contribution.title this.form.title = contribution.title
this.form.content = contribution.content this.form.content = contribution.content
this.manageContent(this.form.content)
this.form.image = contribution.image this.form.image = contribution.image
this.form.categoryIds = this.categoryIds(contribution.categories) this.form.categoryIds = this.categoryIds(contribution.categories)
this.manageContent(this.form.content)
}, },
}, },
}, },
@ -175,11 +174,11 @@ export default {
imageUpload: teaserImage, imageUpload: teaserImage,
}, },
}) })
.then(res => { .then(({ data }) => {
this.loading = false this.loading = false
this.$toast.success(this.$t('contribution.success')) this.$toast.success(this.$t('contribution.success'))
this.disabledByContent = true const result = data[this.id ? 'UpdatePost' : 'CreatePost']
const result = res.data[this.id ? 'UpdatePost' : 'CreatePost'] this.failedValidations = false
this.$router.push({ this.$router.push({
name: 'post-id-slug', name: 'post-id-slug',
@ -189,7 +188,7 @@ export default {
.catch(err => { .catch(err => {
this.$toast.error(err.message) this.$toast.error(err.message)
this.loading = false this.loading = false
this.disabledByContent = false this.failedValidations = true
}) })
}, },
updateEditorContent(value) { updateEditorContent(value) {
@ -202,8 +201,7 @@ export default {
const str = content.replace(/<\/?[^>]+(>|$)/gm, '') const str = content.replace(/<\/?[^>]+(>|$)/gm, '')
// Set counter length of text // Set counter length of text
this.form.contentLength = str.length this.form.contentLength = str.length
// Enable save button if requirements are met this.validatePost()
this.disabledByContent = !(this.contentMin <= str.length && str.length <= this.contentMax)
}, },
availableLocales() { availableLocales() {
orderBy(locales, 'name').map(locale => { orderBy(locales, 'name').map(locale => {
@ -212,6 +210,7 @@ export default {
}, },
updateCategories(ids) { updateCategories(ids) {
this.form.categoryIds = ids this.form.categoryIds = ids
this.validatePost()
}, },
addTeaserImage(file) { addTeaserImage(file) {
this.form.teaserImage = file this.form.teaserImage = file
@ -223,12 +222,19 @@ export default {
}) })
return categoryIds return categoryIds
}, },
validatePost() {
const passesContentValidations =
this.form.contentLength >= this.contentMin && this.form.contentLength <= this.contentMax
const passesCategoryValidations =
this.form.categoryIds.length > 0 && this.form.categoryIds.length <= 3
this.failsValidations = !(passesContentValidations && passesCategoryValidations)
},
}, },
apollo: { apollo: {
User: { User: {
query() { query() {
return gql` return gql`
{ query {
User(orderBy: slug_asc) { User(orderBy: slug_asc) {
id id
slug slug
@ -236,22 +242,22 @@ export default {
} }
` `
}, },
result(result) { result({ data: { User } }) {
this.users = result.data.User this.users = User
}, },
}, },
Tag: { Tag: {
query() { query() {
return gql` return gql`
{ query {
Tag(orderBy: id_asc) { Tag(orderBy: id_asc) {
id id
} }
} }
` `
}, },
result(result) { result({ data: { Tag } }) {
this.hashtags = result.data.Tag this.hashtags = Tag
}, },
}, },
}, },

View File

@ -1,22 +1,28 @@
import { shallowMount } from '@vue/test-utils' import { mount, createLocalVue } from '@vue/test-utils'
import Styleguide from '@human-connection/styleguide'
import MasonryGrid from './MasonryGrid' import MasonryGrid from './MasonryGrid'
const localVue = createLocalVue()
localVue.use(Styleguide)
describe('MasonryGrid', () => { describe('MasonryGrid', () => {
let wrapper let wrapper
let masonryGrid
beforeEach(() => { beforeEach(() => {
wrapper = shallowMount(MasonryGrid) wrapper = mount(MasonryGrid, { localVue })
masonryGrid = wrapper.vm.$children[0]
}) })
it('adds the "reset-grid-height" class when one or more children are updating', () => { it('adds the "reset-grid-height" class when one or more children are updating', () => {
wrapper.trigger('calculating-item-height') masonryGrid.$emit('calculating-item-height')
expect(wrapper.classes()).toContain('reset-grid-height') expect(wrapper.classes()).toContain('reset-grid-height')
}) })
it('removes the "reset-grid-height" class when all children have completed updating', () => { it('removes the "reset-grid-height" class when all children have completed updating', () => {
wrapper.setData({ itemsCalculating: 1 }) wrapper.setData({ itemsCalculating: 1 })
wrapper.trigger('finished-calculating-item-height') masonryGrid.$emit('finished-calculating-item-height')
expect(wrapper.classes()).not.toContain('reset-grid-height') expect(wrapper.classes()).not.toContain('reset-grid-height')
}) })

View File

@ -1,11 +1,17 @@
import { shallowMount } from '@vue/test-utils' import { config, shallowMount, createLocalVue } from '@vue/test-utils'
import Styleguide from '@human-connection/styleguide'
import MasonryGridItem from './MasonryGridItem' import MasonryGridItem from './MasonryGridItem'
const localVue = createLocalVue()
localVue.use(Styleguide)
config.stubs['ds-grid-item'] = '<span><slot /></span>'
describe('MasonryGridItem', () => { describe('MasonryGridItem', () => {
let wrapper let wrapper
beforeEach(() => { beforeEach(() => {
wrapper = shallowMount(MasonryGridItem) wrapper = shallowMount(MasonryGridItem, { localVue })
wrapper.vm.$parent.$emit = jest.fn() wrapper.vm.$parent.$emit = jest.fn()
}) })

View File

@ -1,11 +1,13 @@
import gql from 'graphql-tag' import gql from 'graphql-tag'
export default () => { export default () => {
return gql(`{ return gql`
Category { query {
id Category {
slug id
icon slug
icon
}
} }
}`) `
} }

View File

@ -3,16 +3,46 @@ import gql from 'graphql-tag'
export default i18n => { export default i18n => {
const lang = i18n.locale().toUpperCase() const lang = i18n.locale().toUpperCase()
return gql` return gql`
query Post($slug: String!) { query Post($id: ID!) {
Post(slug: $slug) { Post(id: $id) {
id
title
content
createdAt
disabled
deleted
slug
image
author {
id id
title slug
name
avatar
disabled
deleted
shoutedCount
contributionsCount
commentedCount
followedByCount
followedByCurrentUser
location {
name: name${lang}
}
badges {
id
icon
}
}
tags {
id
}
comments(orderBy: createdAt_asc) {
id
contentExcerpt
content content
createdAt createdAt
disabled disabled
deleted deleted
slug
image
author { author {
id id
slug slug
@ -33,48 +63,18 @@ export default i18n => {
icon icon
} }
} }
tags {
id
}
comments(orderBy: createdAt_asc) {
id
contentExcerpt
content
createdAt
disabled
deleted
author {
id
slug
name
avatar
disabled
deleted
shoutedCount
contributionsCount
commentedCount
followedByCount
followedByCurrentUser
location {
name: name${lang}
}
badges {
id
icon
}
}
}
categories {
id
name
icon
}
shoutedCount
shoutedByCurrentUser
emotionsCount
} }
categories {
id
name
icon
}
shoutedCount
shoutedByCurrentUser
emotionsCount
} }
` }
`
} }
export const filterPosts = i18n => { export const filterPosts = i18n => {

View File

@ -52,7 +52,7 @@
"dependencies": { "dependencies": {
"@human-connection/styleguide": "0.5.19", "@human-connection/styleguide": "0.5.19",
"@nuxtjs/apollo": "^4.0.0-rc11", "@nuxtjs/apollo": "^4.0.0-rc11",
"@nuxtjs/axios": "~5.5.4", "@nuxtjs/axios": "~5.6.0",
"@nuxtjs/dotenv": "~1.4.0", "@nuxtjs/dotenv": "~1.4.0",
"@nuxtjs/sentry": "^3.0.0", "@nuxtjs/sentry": "^3.0.0",
"@nuxtjs/style-resources": "~1.0.0", "@nuxtjs/style-resources": "~1.0.0",
@ -68,13 +68,13 @@
"jsonwebtoken": "~8.5.1", "jsonwebtoken": "~8.5.1",
"linkify-it": "~2.2.0", "linkify-it": "~2.2.0",
"node-fetch": "^2.6.0", "node-fetch": "^2.6.0",
"nuxt": "~2.8.1", "nuxt": "~2.9.1",
"nuxt-dropzone": "^1.0.2", "nuxt-dropzone": "^1.0.3",
"nuxt-env": "~0.1.0", "nuxt-env": "~0.1.0",
"stack-utils": "^1.0.2", "stack-utils": "^1.0.2",
"string-hash": "^1.1.3", "string-hash": "^1.1.3",
"tiptap": "~1.24.2", "tiptap": "~1.25.0",
"tiptap-extensions": "~1.26.2", "tiptap-extensions": "~1.27.0",
"v-tooltip": "~2.0.2", "v-tooltip": "~2.0.2",
"vue-count-to": "~1.0.13", "vue-count-to": "~1.0.13",
"vue-infinite-scroll": "^2.0.2", "vue-infinite-scroll": "^2.0.2",
@ -100,7 +100,7 @@
"babel-loader": "~8.0.6", "babel-loader": "~8.0.6",
"babel-preset-vue": "~2.0.2", "babel-preset-vue": "~2.0.2",
"core-js": "~2.6.9", "core-js": "~2.6.9",
"css-loader": "~2.1.1", "css-loader": "~3.2.0",
"eslint": "~5.16.0", "eslint": "~5.16.0",
"eslint-config-prettier": "~6.1.0", "eslint-config-prettier": "~6.1.0",
"eslint-config-standard": "~12.0.0", "eslint-config-standard": "~12.0.0",

View File

@ -159,7 +159,7 @@ export default {
}, },
variables() { variables() {
return { return {
slug: this.$route.params.slug, id: this.$route.params.id,
} }
}, },
fetchPolicy: 'cache-and-network', fetchPolicy: 'cache-and-network',

View File

@ -8,8 +8,8 @@
</template> </template>
<script> <script>
import gql from 'graphql-tag'
import HcContributionForm from '~/components/ContributionForm/ContributionForm' import HcContributionForm from '~/components/ContributionForm/ContributionForm'
import PostQuery from '~/graphql/PostQuery'
export default { export default {
components: { components: {
@ -36,38 +36,11 @@ export default {
apollo: { apollo: {
Post: { Post: {
query() { query() {
return gql(` return PostQuery(this.$i18n)
query($id: ID!) {
Post(id: $id) {
id
title
content
createdAt
disabled
deleted
slug
image
language
author {
id
disabled
deleted
}
tags {
name
}
categories {
id
name
icon
}
}
}
`)
}, },
variables() { variables() {
return { return {
id: this.$route.params.id || 'p1', id: this.$route.params.id,
} }
}, },
fetchPolicy: 'cache-and-network', fetchPolicy: 'cache-and-network',

File diff suppressed because it is too large Load Diff