Refactor after Roberts suggestions, number I

Start writing tests
This commit is contained in:
Wolfgang Huß 2019-08-16 16:56:14 +02:00
parent f0af231975
commit efb5c75c24
12 changed files with 359 additions and 203 deletions

View File

@ -10,12 +10,15 @@ const notifyMentions = async (label, id, idsOfMentionedUsers, context) => {
MATCH (source)
WHERE source.id = $id AND $label IN LABELS(source)
MATCH (source)<-[:WROTE]-(author: User)
MATCH (u: User)
WHERE u.id in $idsOfMentionedUsers
AND NOT (u)<-[:BLOCKED]-(author)
CREATE (n: Notification {id: apoc.create.uuid(), read: false, createdAt: $createdAt })
MERGE (source)-[:NOTIFIED]->(n)-[:NOTIFIED]->(u)
MATCH (user: User)
WHERE user.id in $idsOfMentionedUsers
AND NOT (user)<-[:BLOCKED]-(author)
CREATE (notification: Notification {id: apoc.create.uuid(), read: false, createdAt: $createdAt })
MERGE (source)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user)
`
// "author" of comment, blocked Peter: Jenny
// "user" mentioned on post by Jenny: Peter
// owner of post: Bob
await session.run(cypher, {
idsOfMentionedUsers,
label,
@ -88,4 +91,4 @@ export default {
CreateComment: handleContentDataOfComment,
// UpdateComment: handleContentDataOfComment,
},
}
}

View File

@ -1,7 +1,14 @@
import { gql } from '../../jest/helpers'
import {
gql
} from '../../jest/helpers'
import Factory from '../../seed/factories'
import { createTestClient } from 'apollo-server-testing'
import { neode, getDriver } from '../../bootstrap/neo4j'
import {
createTestClient
} from 'apollo-server-testing'
import {
neode,
getDriver
} from '../../bootstrap/neo4j'
import createServer from '../../server'
const factory = Factory()
@ -44,7 +51,7 @@ afterEach(async () => {
})
describe('notifications', () => {
const notificationQuery = gql`
const notificationQuery = gql `
query($read: Boolean) {
currentUser {
notifications(read: $read, orderBy: createdAt_desc) {
@ -63,12 +70,12 @@ describe('notifications', () => {
})
describe('given another user', () => {
let author
let postAuthor
beforeEach(async () => {
author = await instance.create('User', {
email: 'author@example.org',
postAuthor = await instance.create('User', {
email: 'post-author@example.org',
password: '1234',
id: 'author',
id: 'postAuthor',
})
})
@ -78,7 +85,7 @@ describe('notifications', () => {
'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone">@al-capone</a> how do you do?'
const createPostAction = async () => {
const createPostMutation = gql`
const createPostMutation = gql `
mutation($id: ID, $title: String!, $content: String!) {
CreatePost(id: $id, title: $title, content: $content) {
id
@ -87,10 +94,14 @@ describe('notifications', () => {
}
}
`
authenticatedUser = await author.toJson()
authenticatedUser = await postAuthor.toJson()
await mutate({
mutation: createPostMutation,
variables: { id: 'p47', title, content },
variables: {
id: 'p47',
title,
content
},
})
authenticatedUser = await user.toJson()
}
@ -101,12 +112,26 @@ describe('notifications', () => {
'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone" target="_blank">@al-capone</a> how do you do?'
const expected = expect.objectContaining({
data: {
currentUser: { notifications: [{ read: false, post: { content: expectedContent } }] },
currentUser: {
notifications: [{
read: false,
post: {
content: expectedContent
}
}]
},
},
})
const { query } = createTestClient(server)
const {
query
} = createTestClient(server)
await expect(
query({ query: notificationQuery, variables: { read: false } }),
query({
query: notificationQuery,
variables: {
read: false
}
}),
).resolves.toEqual(expected)
})
@ -126,7 +151,7 @@ describe('notifications', () => {
@al-capone
</a>
`
const updatePostMutation = gql`
const updatePostMutation = gql `
mutation($id: ID!, $title: String!, $content: String!) {
UpdatePost(id: $id, content: $content, title: $title) {
title
@ -134,7 +159,7 @@ describe('notifications', () => {
}
}
`
authenticatedUser = await author.toJson()
authenticatedUser = await postAuthor.toJson()
await mutate({
mutation: updatePostMutation,
variables: {
@ -154,32 +179,115 @@ describe('notifications', () => {
const expected = expect.objectContaining({
data: {
currentUser: {
notifications: [
{ read: false, post: { content: expectedContent } },
{ read: false, post: { content: expectedContent } },
notifications: [{
read: false,
post: {
content: expectedContent
}
},
{
read: false,
post: {
content: expectedContent
}
},
],
},
},
})
await expect(
query({ query: notificationQuery, variables: { read: false } }),
query({
query: notificationQuery,
variables: {
read: false
}
}),
).resolves.toEqual(expected)
})
})
describe('but the author of the post blocked me', () => {
beforeEach(async () => {
await author.relateTo(user, 'blocked')
await postAuthor.relateTo(user, 'blocked')
})
it('sends no notification', async () => {
await createPostAction()
const expected = expect.objectContaining({
data: { currentUser: { notifications: [] } },
data: {
currentUser: {
notifications: []
}
},
})
const { query } = createTestClient(server)
const {
query
} = createTestClient(server)
await expect(
query({ query: notificationQuery, variables: { read: false } }),
query({
query: notificationQuery,
variables: {
read: false
}
}),
).resolves.toEqual(expected)
})
})
describe('but the author of the post blocked a user I mention in a comment', () => {
const createCommentOnPostAction = async () => {
await createPostAction()
const createCommentMutation = gql `
mutation($id: ID, $title: String!, $content: String!) {
CreateComment(id: $id, postId: $postId, content: $commentContent) {
id
content
}
}
`
// authenticatedUser = await postAuthor.toJson()
// authenticatedUser = await user.toJson()
await mutate({
mutation: createCommentMutation,
variables: {
id: 'c47',
postId: 'p47',
commentContent: 'One mention of me with <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">.'
},
})
}
let mentioner
beforeEach(async () => {
await postAuthor.relateTo(user, 'blocked')
mentioner = await instance.create('User', {
id: 'mentioner',
name: 'Mr Mentioner',
slug: 'mr-mentioner',
email: 'mentioner@example.org',
password: '1234',
})
})
it('sends no notification', async () => {
await createPostAction()
const expected = expect.objectContaining({
data: {
currentUser: {
notifications: []
}
},
})
const {
query
} = createTestClient(server)
await expect(
query({
query: notificationQuery,
variables: {
read: false
}
}),
).resolves.toEqual(expected)
})
})
@ -193,7 +301,7 @@ describe('Hashtags', () => {
const postTitle = 'Two Hashtags'
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>'
const postWithHastagsQuery = gql`
const postWithHastagsQuery = gql `
query($id: ID) {
Post(id: $id) {
tags {
@ -206,7 +314,7 @@ describe('Hashtags', () => {
const postWithHastagsVariables = {
id: postId,
}
const createPostMutation = gql`
const createPostMutation = gql `
mutation($postId: ID, $postTitle: String!, $postContent: String!) {
CreatePost(id: $postId, title: $postTitle, content: $postContent) {
id
@ -234,20 +342,26 @@ describe('Hashtags', () => {
})
it('both Hashtags are created with the "id" set to their "name"', async () => {
const expected = [
{ id: 'Democracy', name: 'Democracy' },
{ id: 'Liberty', name: 'Liberty' },
const expected = [{
id: 'Democracy',
name: 'Democracy'
},
{
id: 'Liberty',
name: 'Liberty'
},
]
await expect(
query({ query: postWithHastagsQuery, variables: postWithHastagsVariables }),
query({
query: postWithHastagsQuery,
variables: postWithHastagsVariables
}),
).resolves.toEqual(
expect.objectContaining({
data: {
Post: [
{
tags: expect.arrayContaining(expected),
},
],
Post: [{
tags: expect.arrayContaining(expected),
}, ],
},
}),
)
@ -257,7 +371,7 @@ describe('Hashtags', () => {
// The already existing Hashtag has no class at this point.
const updatedPostContent =
'<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`
const updatePostMutation = gql `
mutation($postId: ID!, $postTitle: String!, $updatedPostContent: String!) {
UpdatePost(id: $postId, title: $postTitle, content: $updatedPostContent) {
id
@ -277,16 +391,26 @@ describe('Hashtags', () => {
},
})
const expected = [
{ id: 'Elections', name: 'Elections' },
{ id: 'Liberty', name: 'Liberty' },
const expected = [{
id: 'Elections',
name: 'Elections'
},
{
id: 'Liberty',
name: 'Liberty'
},
]
await expect(
query({ query: postWithHastagsQuery, variables: postWithHastagsVariables }),
query({
query: postWithHastagsQuery,
variables: postWithHastagsVariables
}),
).resolves.toEqual(
expect.objectContaining({
data: {
Post: [{ tags: expect.arrayContaining(expected) }],
Post: [{
tags: expect.arrayContaining(expected)
}],
},
}),
)
@ -294,4 +418,4 @@ describe('Hashtags', () => {
})
})
})
})
})

View File

@ -3,19 +3,21 @@
<no-ssr>
<ds-space margin-bottom="x-small">
<hc-user
:user="post.author || comment.author"
:date-time="post.createdAt || comment.createdAt"
v-if="resourceType == 'Post'"
:user="post.author"
:date-time="post.createdAt"
:trunc="35"
/>
<hc-user v-else :user="comment.author" :date-time="comment.createdAt" :trunc="35" />
</ds-space>
<ds-text color="soft">
{{ $t('notifications.menu.mentioned', { resource: post.id ? 'post' : 'comment' }) }}
{{ $t('notifications.menu.mentioned', { resource: resourceType }) }}
</ds-text>
</no-ssr>
<ds-space margin-bottom="x-small" />
<nuxt-link
class="notification-mention-post"
:to="{ name: 'post-id-slug', params: postParams, ...hashParam }"
:to="{ name: 'post-id-slug', params, ...hashParam }"
@click.native="$emit('read')"
>
<ds-space margin-bottom="x-small">
@ -26,9 +28,14 @@
class="notifications-card"
>
<ds-space margin-bottom="x-small" />
<!-- eslint-disable vue/no-v-html -->
<div v-html="excerpt" />
<!-- eslint-enable vue/no-v-html -->
<div v-if="resourceType == 'Post'">{{ post.contentExcerpt | removeHtml }}</div>
<div v-else>
<b>
Comment:
<nbsp />
</b>
{{ comment.contentExcerpt | removeHtml }}
</div>
</ds-card>
</ds-space>
</nuxt-link>
@ -50,11 +57,8 @@ export default {
},
},
computed: {
excerpt() {
const excerpt = this.post.id ? this.post.contentExcerpt : this.comment.contentExcerpt
return (
(!this.post.id ? '<b>Comment: </b>' : '') + excerpt.replace(/<(?:.|\n)*?>/gm, '').trim()
)
resourceType() {
return this.post.id ? 'Post' : 'Comment'
},
post() {
return this.notification.post || {}
@ -62,7 +66,7 @@ export default {
comment() {
return this.notification.comment || {}
},
postParams() {
params() {
return {
id: this.post.id || this.comment.post.id,
slug: this.post.slug || this.comment.post.slug,

View File

@ -1,5 +1,5 @@
import { config, shallowMount, createLocalVue } from '@vue/test-utils'
import NotificationMenu from '.'
import NotificationMenu from './NotificationMenu'
import Styleguide from '@human-connection/styleguide'
import Filters from '~/plugins/vue-filters'

View File

@ -17,80 +17,9 @@
</template>
<script>
import NotificationList from '../NotificationList/NotificationList'
import Dropdown from '~/components/Dropdown'
import gql from 'graphql-tag'
const MARK_AS_READ = gql`
mutation($id: ID!, $read: Boolean!) {
UpdateNotification(id: $id, read: $read) {
id
read
}
}
`
const NOTIFICATIONS = gql`
{
currentUser {
id
notifications(read: false, orderBy: createdAt_desc) {
id
read
createdAt
post {
id
createdAt
disabled
deleted
title
contentExcerpt
slug
author {
id
slug
name
disabled
deleted
avatar
}
}
comment {
id
createdAt
disabled
deleted
contentExcerpt
author {
id
slug
name
disabled
deleted
avatar
}
post {
id
createdAt
disabled
deleted
title
contentExcerpt
slug
author {
id
slug
name
disabled
deleted
avatar
}
}
}
}
}
}
`
import { currentUserNotificationsQuery, updateNotificationMutation } from '~/graphql/User'
import NotificationList from '../NotificationList/NotificationList'
export default {
name: 'NotificationMenu',
@ -111,7 +40,7 @@ export default {
const variables = { id: notificationId, read: true }
try {
await this.$apollo.mutate({
mutation: MARK_AS_READ,
mutation: updateNotificationMutation(),
variables,
})
} catch (err) {
@ -121,7 +50,7 @@ export default {
},
apollo: {
notifications: {
query: NOTIFICATIONS,
query: currentUserNotificationsQuery(),
update: data => {
const {
currentUser: { notifications },

View File

@ -75,3 +75,78 @@ export default i18n => {
}
`
}
export const currentUserNotificationsQuery = () => {
return gql`
{
currentUser {
id
notifications(read: false, orderBy: createdAt_desc) {
id
read
createdAt
post {
id
createdAt
disabled
deleted
title
contentExcerpt
slug
author {
id
slug
name
disabled
deleted
avatar
}
}
comment {
id
createdAt
disabled
deleted
contentExcerpt
author {
id
slug
name
disabled
deleted
avatar
}
post {
id
createdAt
disabled
deleted
title
contentExcerpt
slug
author {
id
slug
name
disabled
deleted
avatar
}
}
}
}
}
}
`
}
export const updateNotificationMutation = () => {
return gql`
mutation($id: ID!, $read: Boolean!) {
UpdateNotification(id: $id, read: $read) {
id
read
}
}
`
}

View File

@ -88,9 +88,11 @@
{{ $t('login.hello') }}
<b>{{ userName }}</b>
<template v-if="user.role !== 'user'">
<ds-text color="softer" size="small" style="margin-bottom: 0">
{{ user.role | camelCase }}
</ds-text>
<ds-text
color="softer"
size="small"
style="margin-bottom: 0"
>{{ user.role | camelCase }}</ds-text>
</template>
<hr />
<ds-menu :routes="routes" :matcher="matcher">
@ -129,14 +131,10 @@
<div id="footer" class="ds-footer">
<a href="https://human-connection.org" target="_blank" v-html="$t('site.made')"></a>
&nbsp;-&nbsp;
<nuxt-link to="/imprint">{{ $t('site.imprint') }}</nuxt-link>
&nbsp;&nbsp;
<nuxt-link to="/terms-and-conditions">{{ $t('site.termsAndConditions') }}</nuxt-link>
&nbsp;&nbsp;
<nuxt-link to="/code-of-conduct">{{ $t('site.code-of-conduct') }}</nuxt-link>
&nbsp;&nbsp;
<nuxt-link to="/data-privacy">{{ $t('site.data-privacy') }}</nuxt-link>
&nbsp;&nbsp;
<nuxt-link to="/imprint">{{ $t('site.imprint') }}</nuxt-link>&nbsp;&nbsp;
<nuxt-link to="/terms-and-conditions">{{ $t('site.termsAndConditions') }}</nuxt-link>&nbsp;&nbsp;
<nuxt-link to="/code-of-conduct">{{ $t('site.code-of-conduct') }}</nuxt-link>&nbsp;&nbsp;
<nuxt-link to="/data-privacy">{{ $t('site.data-privacy') }}</nuxt-link>&nbsp;&nbsp;
<nuxt-link to="/changelog">{{ $t('site.changelog') }}</nuxt-link>
</div>
<div id="overlay" />
@ -151,7 +149,7 @@ import { mapGetters, mapActions, mapMutations } from 'vuex'
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
import SearchInput from '~/components/SearchInput.vue'
import Modal from '~/components/Modal'
import NotificationMenu from '~/components/notifications/NotificationMenu'
import NotificationMenu from '~/components/notifications/NotificationMenu/NotificationMenu'
import Dropdown from '~/components/Dropdown'
import HcAvatar from '~/components/Avatar/Avatar.vue'
import seo from '~/mixins/seo'

View File

@ -110,12 +110,10 @@ export default {
}
},
data() {
// Wolle: const { commentId = null } = this.$route.query
return {
post: null,
ready: false,
title: 'loading',
// Wolle: commentId,
}
},
watch: {

View File

@ -248,7 +248,7 @@ import ContentMenu from '~/components/ContentMenu'
import HcUpload from '~/components/Upload'
import HcAvatar from '~/components/Avatar/Avatar.vue'
import PostQuery from '~/graphql/UserProfile/Post.js'
import UserQuery from '~/graphql/UserProfile/User.js'
import UserQuery from '~/graphql/User.js'
import { Block, Unblock } from '~/graphql/settings/BlockedUsers.js'
const tabToFilterMapping = ({ tab, id }) => {

View File

@ -83,6 +83,15 @@ export default ({ app = {} }) => {
return excerpt
},
removeHtml: content => {
if (!content) return ''
// replace linebreaks with spaces first
let contentExcerpt = content.replace(/<br>/gim, ' ').trim()
// remove the rest of the HTML
contentExcerpt = contentExcerpt.replace(/<(?:.|\n)*?>/gm, '').trim()
return contentExcerpt
},
proxyApiUrl: url => {
if (!url) return url
return url.startsWith('/') ? url.replace('/', '/api/') : url

View File

@ -69,41 +69,43 @@ export const actions = {
const {
data: { currentUser },
} = await client.query({
query: gql(`{
currentUser {
id
name
slug
email
avatar
role
about
locationName
contributionsCount
commentsCount
socialMedia {
query: gql`
{
currentUser {
id
url
}
notifications(read: false, orderBy: createdAt_desc) {
id
read
createdAt
post {
author {
id
name
slug
email
avatar
role
about
locationName
contributionsCount
commentsCount
socialMedia {
id
url
}
notifications(read: false, orderBy: createdAt_desc) {
id
read
createdAt
post {
author {
id
slug
name
disabled
deleted
}
title
contentExcerpt
slug
name
disabled
deleted
}
title
contentExcerpt
slug
}
}
}
}`),
`,
})
if (!currentUser) return dispatch('logout')
commit('SET_USER', currentUser)
@ -122,7 +124,10 @@ export const actions = {
login(email: $email, password: $password)
}
`),
variables: { email, password },
variables: {
email,
password,
},
})
await this.app.$apolloHelpers.onLogin(login)
commit('SET_TOKEN', login)

View File

@ -19,7 +19,10 @@ export const mutations = {
const toBeUpdated = notifications.find(n => {
return n.id === notification.id
})
state.notifications = { ...toBeUpdated, ...notification }
state.notifications = {
...toBeUpdated,
...notification,
}
},
}
export const getters = {
@ -38,28 +41,30 @@ export const actions = {
const {
data: { currentUser },
} = await client.query({
query: gql(`{
currentUser {
id
notifications(orderBy: createdAt_desc) {
query: gql`
{
currentUser {
id
read
createdAt
post {
author {
id
notifications(orderBy: createdAt_desc) {
id
read
createdAt
post {
author {
id
slug
name
disabled
deleted
}
title
contentExcerpt
slug
name
disabled
deleted
}
title
contentExcerpt
slug
}
}
}
}`),
`,
})
notifications = currentUser.notifications
commit('SET_NOTIFICATIONS', notifications)
@ -71,18 +76,24 @@ export const actions = {
async markAsRead({ commit, rootGetters }, notificationId) {
const client = this.app.apolloProvider.defaultClient
const mutation = gql(`
const mutation = gql`
mutation($id: ID!, $read: Boolean!) {
UpdateNotification(id: $id, read: $read) {
id
read
}
}
`)
const variables = { id: notificationId, read: true }
`
const variables = {
id: notificationId,
read: true,
}
const {
data: { UpdateNotification },
} = await client.mutate({ mutation, variables })
} = await client.mutate({
mutation,
variables,
})
commit('UPDATE_NOTIFICATIONS', UpdateNotification)
},
}