Merge branch 'master' into 17-Admin-Remove-user-profile

This commit is contained in:
Alexander Friedland 2020-08-14 17:23:16 +02:00 committed by GitHub
commit 6d856a3edc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 196 additions and 99 deletions

View File

@ -6,11 +6,13 @@ import { createTestClient } from 'apollo-server-testing'
const categoryIds = ['cat9'] const categoryIds = ['cat9']
let user let user
let anotherUser
let moderator
let admin let admin
let authenticatedUser
let query let query
let mutate let mutate
let authenticatedUser
let variables let variables
const driver = getDriver() const driver = getDriver()
@ -65,19 +67,22 @@ beforeEach(async () => {
describe('User', () => { describe('User', () => {
describe('query by email address', () => { describe('query by email address', () => {
let userQuery
beforeEach(async () => { beforeEach(async () => {
userQuery = gql`
query($email: String) {
User(email: $email) {
name
}
}
`
variables = {
email: 'any-email-address@example.org',
}
await Factory.build('user', { name: 'Johnny' }, { email: 'any-email-address@example.org' }) await Factory.build('user', { name: 'Johnny' }, { email: 'any-email-address@example.org' })
}) })
const userQuery = gql`
query($email: String) {
User(email: $email) {
name
}
}
`
variables = { email: 'any-email-address@example.org' }
it('is forbidden', async () => { it('is forbidden', async () => {
await expect(query({ query: userQuery, variables })).resolves.toMatchObject({ await expect(query({ query: userQuery, variables })).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorised!' }],
@ -125,38 +130,35 @@ describe('User', () => {
}) })
describe('UpdateUser', () => { describe('UpdateUser', () => {
let variables let updateUserMutation
beforeEach(async () => { beforeEach(async () => {
updateUserMutation = gql`
mutation(
$id: ID!
$name: String
$termsAndConditionsAgreedVersion: String
$locationName: String
) {
UpdateUser(
id: $id
name: $name
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
locationName: $locationName
) {
id
name
termsAndConditionsAgreedVersion
termsAndConditionsAgreedAt
locationName
}
}
`
variables = { variables = {
id: 'u47', id: 'u47',
name: 'John Doughnut', name: 'John Doughnut',
} }
})
const updateUserMutation = gql`
mutation(
$id: ID!
$name: String
$termsAndConditionsAgreedVersion: String
$locationName: String
) {
UpdateUser(
id: $id
name: $name
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
locationName: $locationName
) {
id
name
termsAndConditionsAgreedVersion
termsAndConditionsAgreedAt
locationName
}
}
`
beforeEach(async () => {
user = await Factory.build( user = await Factory.build(
'user', 'user',
{ {
@ -288,6 +290,8 @@ describe('Delete a User as admin', () => {
describe('authenticated as Admin', () => { describe('authenticated as Admin', () => {
beforeEach(async () => { beforeEach(async () => {
admin = await Factory.build( admin = await Factory.build(
'user', 'user',
{ {
role: 'admin', role: 'admin',
@ -453,6 +457,7 @@ describe('Delete a User as admin', () => {
await expect(neode.all('SocialMedia')).resolves.toHaveLength(1) await expect(neode.all('SocialMedia')).resolves.toHaveLength(1)
await mutate({ mutation: deleteUserMutation, variables }) await mutate({ mutation: deleteUserMutation, variables })
await expect(neode.all('SocialMedia')).resolves.toHaveLength(0) await expect(neode.all('SocialMedia')).resolves.toHaveLength(0)
}) })
}) })
}) })

View File

@ -1,6 +1,5 @@
import { config, mount } from '@vue/test-utils' import { config, mount } from '@vue/test-utils'
import CommentList from './CommentList' import CommentList from './CommentList'
import CommentCard from '~/components/CommentCard/CommentCard'
import Vuex from 'vuex' import Vuex from 'vuex'
import Vue from 'vue' import Vue from 'vue'
@ -26,6 +25,23 @@ describe('CommentList.vue', () => {
id: 'comment134', id: 'comment134',
contentExcerpt: 'this is a comment', contentExcerpt: 'this is a comment',
content: 'this is a comment', content: 'this is a comment',
author: {
id: 'some-user',
slug: 'some-slug',
},
},
{
id: 'comment135',
contentExcerpt: 'this is a deleted comment',
content: 'this is a deleted comment',
deleted: true,
author: { id: 'some-user' },
},
{
id: 'comment136',
contentExcerpt: 'this is a disabled comment',
content: 'this is a disabled comment',
disabled: true,
author: { id: 'some-user' }, author: { id: 'some-user' },
}, },
], ],
@ -35,7 +51,7 @@ describe('CommentList.vue', () => {
getters: { getters: {
'auth/isModerator': () => false, 'auth/isModerator': () => false,
'auth/user': () => { 'auth/user': () => {
return {} return { id: 'some-user' }
}, },
}, },
}) })
@ -70,7 +86,7 @@ describe('CommentList.vue', () => {
}) })
} }
it('displays a comments counter', () => { it('displays a comments counter that ignores disabled and deleted comments', () => {
wrapper = Wrapper() wrapper = Wrapper()
expect(wrapper.find('.count').text()).toEqual('1') expect(wrapper.find('.count').text()).toEqual('1')
}) })
@ -101,26 +117,63 @@ describe('CommentList.vue', () => {
}) })
}) })
describe('Comment', () => { describe('Respond to Comment', () => {
beforeEach(() => { beforeEach(() => {
wrapper = Wrapper() wrapper = Wrapper()
}) })
it('Comment emitted reply()', () => { it('emits reply to comment', () => {
wrapper.find(CommentCard).vm.$emit('reply', { wrapper.find('.reply-button').trigger('click')
id: 'commentAuthorId',
slug: 'ogerly',
})
Vue.nextTick()
expect(wrapper.emitted('reply')).toEqual([ expect(wrapper.emitted('reply')).toEqual([
[ [
{ {
id: 'commentAuthorId', id: 'some-user',
slug: 'ogerly', slug: 'some-slug',
}, },
], ],
]) ])
}) })
}) })
describe('edit Comment', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('updates comment after edit', () => {
wrapper.vm.updateCommentList({
id: 'comment134',
contentExcerpt: 'this is an edited comment',
content: 'this is an edited comment',
author: {
id: 'some-user',
slug: 'some-slug',
},
})
expect(wrapper.props('post').comments[0].content).toEqual('this is an edited comment')
})
})
describe('delete Comment', () => {
beforeEach(() => {
wrapper = Wrapper()
})
// TODO: Test does not find .count = 0 but 1. Can't understand why...
it.skip('sets counter to 0', async () => {
wrapper.vm.updateCommentList({
id: 'comment134',
contentExcerpt: 'this is another deleted comment',
content: 'this is an another deleted comment',
deleted: true,
author: {
id: 'some-user',
slug: 'some-slug',
},
})
await Vue.nextTick()
await expect(wrapper.find('.count').text()).toEqual('0')
})
})
}) })
}) })

View File

@ -1,12 +1,12 @@
<template> <template>
<div id="comments" class="comment-list"> <div id="comments" class="comment-list">
<h3 class="title"> <h3 class="title">
<counter-icon icon="comments" :count="postComments.length" /> <counter-icon icon="comments" :count="commentsCount" />
{{ $t('common.comment', null, 0) }} {{ $t('common.comment', null, 0) }}
</h3> </h3>
<div v-if="postComments" id="comments" class="comments"> <div v-if="post.comments" id="comments" class="comments">
<comment-card <comment-card
v-for="comment in postComments" v-for="comment in post.comments"
:key="comment.id" :key="comment.id"
:comment="comment" :comment="comment"
:postId="post.id" :postId="post.id"
@ -36,8 +36,13 @@ export default {
}, },
}, },
computed: { computed: {
postComments() { commentsCount() {
return (this.post && this.post.comments) || [] return (
(this.post &&
this.post.comments &&
this.post.comments.filter((comment) => !comment.deleted && !comment.disabled).length) ||
0
)
}, },
}, },
methods: { methods: {
@ -48,7 +53,7 @@ export default {
return anchor === '#comments' return anchor === '#comments'
}, },
updateCommentList(updatedComment) { updateCommentList(updatedComment) {
this.postComments = this.postComments.map((comment) => { this.post.comments = this.post.comments.map((comment) => {
return comment.id === updatedComment.id ? updatedComment : comment return comment.id === updatedComment.id ? updatedComment : comment
}) })
}, },

View File

@ -15,12 +15,10 @@ describe('DeleteData.vue', () => {
let enableContributionDeletionCheckbox let enableContributionDeletionCheckbox
let enableCommentDeletionCheckbox let enableCommentDeletionCheckbox
const deleteAccountName = 'Delete MyAccount' const deleteAccountName = 'Delete MyAccount'
const deleteContributionsMessage = 'Delete my 2 posts'
const deleteCommentsMessage = 'Delete my 3 comments'
beforeEach(() => { beforeEach(() => {
mocks = { mocks = {
$t: jest.fn(), $t: jest.fn((a) => a),
$apollo: { $apollo: {
mutate: jest mutate: jest
.fn() .fn()
@ -45,7 +43,7 @@ describe('DeleteData.vue', () => {
} }
getters = { getters = {
'auth/user': () => { 'auth/user': () => {
return { id: 'u343', name: deleteAccountName, contributionsCount: 2, commentedCount: 3 } return { id: 'u343', name: deleteAccountName }
}, },
} }
actions = { 'auth/logout': jest.fn() } actions = { 'auth/logout': jest.fn() }
@ -68,15 +66,15 @@ describe('DeleteData.vue', () => {
jest.clearAllMocks() jest.clearAllMocks()
}) })
it('defaults to deleteContributions to false', () => { it('checkbox deleteContributions defaults be false', () => {
expect(wrapper.vm.deleteContributions).toEqual(false) expect(wrapper.vm.deleteContributions).toEqual(false)
}) })
it('defaults to deleteComments to false', () => { it('checkbox deleteComments defaults be false', () => {
expect(wrapper.vm.deleteComments).toEqual(false) expect(wrapper.vm.deleteComments).toEqual(false)
}) })
it('defaults to deleteEnabled to false', () => { it('deleteButton defaults be false', () => {
expect(wrapper.vm.deleteEnabled).toEqual(false) expect(wrapper.vm.deleteEnabled).toEqual(false)
}) })
@ -93,7 +91,7 @@ describe('DeleteData.vue', () => {
deleteAccountBtn = wrapper.find('[data-test="delete-button"]') deleteAccountBtn = wrapper.find('[data-test="delete-button"]')
}) })
it('if deleteEnabled is true and only deletes user by default', () => { it('if deleteEnabled is true and only deletes user ', () => {
deleteAccountBtn.trigger('click') deleteAccountBtn.trigger('click')
expect(mocks.$apollo.mutate).toHaveBeenCalledWith( expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
@ -105,9 +103,28 @@ describe('DeleteData.vue', () => {
) )
}) })
it("deletes user's posts and comments if requested by default ", () => {
enableContributionDeletionCheckbox = wrapper.find(
'[data-test="contributions-deletion-checkbox"]',
)
enableContributionDeletionCheckbox.trigger('click')
enableCommentDeletionCheckbox = wrapper.find('[data-test="comments-deletion-checkbox"]')
enableCommentDeletionCheckbox.trigger('click')
deleteAccountBtn.trigger('click')
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
expect.objectContaining({
variables: {
id: 'u343',
resource: ['Post', 'Comment'],
},
}),
)
})
it("deletes a user's posts if requested", () => { it("deletes a user's posts if requested", () => {
mocks.$t.mockImplementation(() => deleteContributionsMessage) enableContributionDeletionCheckbox = wrapper.find(
enableContributionDeletionCheckbox = wrapper.findAll('input[type="checkbox"]').at(0) '[data-test="contributions-deletion-checkbox"]',
)
enableContributionDeletionCheckbox.trigger('click') enableContributionDeletionCheckbox.trigger('click')
deleteAccountBtn.trigger('click') deleteAccountBtn.trigger('click')
expect(mocks.$apollo.mutate).toHaveBeenCalledWith( expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
@ -121,8 +138,7 @@ describe('DeleteData.vue', () => {
}) })
it("deletes a user's comments if requested", () => { it("deletes a user's comments if requested", () => {
mocks.$t.mockImplementation(() => deleteCommentsMessage) enableCommentDeletionCheckbox = wrapper.find('[data-test="comments-deletion-checkbox"]')
enableCommentDeletionCheckbox = wrapper.findAll('input[type="checkbox"]').at(1)
enableCommentDeletionCheckbox.trigger('click') enableCommentDeletionCheckbox.trigger('click')
deleteAccountBtn.trigger('click') deleteAccountBtn.trigger('click')
expect(mocks.$apollo.mutate).toHaveBeenCalledWith( expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
@ -135,24 +151,6 @@ describe('DeleteData.vue', () => {
) )
}) })
it("deletes a user's posts and comments if requested", () => {
mocks.$t.mockImplementation(() => deleteContributionsMessage)
enableContributionDeletionCheckbox = wrapper.findAll('input[type="checkbox"]').at(0)
enableContributionDeletionCheckbox.trigger('click')
mocks.$t.mockImplementation(() => deleteCommentsMessage)
enableCommentDeletionCheckbox = wrapper.findAll('input[type="checkbox"]').at(1)
enableCommentDeletionCheckbox.trigger('click')
deleteAccountBtn.trigger('click')
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(
expect.objectContaining({
variables: {
id: 'u343',
resource: ['Post', 'Comment'],
},
}),
)
})
it('shows a success toaster after successful mutation', async () => { it('shows a success toaster after successful mutation', async () => {
await deleteAccountBtn.trigger('click') await deleteAccountBtn.trigger('click')
expect(mocks.$toast.success).toHaveBeenCalledTimes(1) expect(mocks.$toast.success).toHaveBeenCalledTimes(1)

View File

@ -14,17 +14,25 @@
<label v-if="currentUser.contributionsCount" class="checkbox"> <label v-if="currentUser.contributionsCount" class="checkbox">
<input type="checkbox" v-model="deleteContributions" /> <input type="checkbox" v-model="deleteContributions" />
{{ {{
$t('settings.deleteUserAccount.contributionsCount', { $t(
count: currentUser.contributionsCount, 'settings.deleteUserAccount.contributionsCount',
}) {
count: currentUserCounts.contributionsCount,
},
currentUserCounts.contributionsCount,
)
}} }}
</label> </label>
<label v-if="currentUser.commentedCount" class="checkbox"> <label class="checkbox">
<input type="checkbox" v-model="deleteComments" /> <input type="checkbox" v-model="deleteComments" data-test="comments-deletion-checkbox" />
{{ {{
$t('settings.deleteUserAccount.commentedCount', { $t(
count: currentUser.commentedCount, 'settings.deleteUserAccount.commentedCount',
}) {
count: currentUserCounts.commentedCount,
},
currentUserCounts.commentedCount,
)
}} }}
</label> </label>
<section class="warning"> <section class="warning">
@ -44,8 +52,9 @@
</template> </template>
<script> <script>
import { mapGetters, mapActions } from 'vuex' import { mapActions, mapGetters } from 'vuex'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import { currentUserCountQuery } from '~/graphql/User'
export default { export default {
name: 'DeleteData', name: 'DeleteData',
@ -54,8 +63,19 @@ export default {
deleteContributions: false, deleteContributions: false,
deleteComments: false, deleteComments: false,
enableDeletionValue: null, enableDeletionValue: null,
currentUserCounts: {},
} }
}, },
apollo: {
currentUser: {
query() {
return currentUserCountQuery()
},
update(currentUser) {
this.currentUserCounts = currentUser.currentUser
},
},
},
computed: { computed: {
...mapGetters({ ...mapGetters({
currentUser: 'auth/user', currentUser: 'auth/user',

View File

@ -39,3 +39,10 @@ export default {
}, },
} }
</script> </script>
<style lang="scss">
.ProseMirror h3,
.ProseMirror h4,
.ProseMirror hr {
margin: 24px 0 8px;
}
</style>

View File

@ -24,12 +24,12 @@
<h3>{{ $t('editor.embed.data_privacy_warning') }}</h3> <h3>{{ $t('editor.embed.data_privacy_warning') }}</h3>
<ds-text>{{ $t('editor.embed.data_privacy_info') }} {{ embedPublisher }}</ds-text> <ds-text>{{ $t('editor.embed.data_privacy_info') }} {{ embedPublisher }}</ds-text>
<div class="buttons"> <div class="buttons">
<base-button primary @click="allowEmbed()" data-test="play-now-button"> <base-button @click="closeOverlay()" data-test="cancel-button" danger>
{{ $t('editor.embed.play_now') }}
</base-button>
<base-button @click="closeOverlay()" data-test="cancel-button">
{{ $t('actions.cancel') }} {{ $t('actions.cancel') }}
</base-button> </base-button>
<base-button @click="allowEmbed()" data-test="play-now-button" filled>
{{ $t('editor.embed.play_now') }}
</base-button>
</div> </div>
<label class="checkbox"> <label class="checkbox">
<input type="checkbox" v-model="checkedAlwaysAllowEmbeds" /> <input type="checkbox" v-model="checkedAlwaysAllowEmbeds" />

View File

@ -283,3 +283,12 @@ export const currentUserQuery = gql`
} }
} }
` `
export const currentUserCountQuery = () => gql`
${userCountsFragment}
query {
currentUser {
...userCounts
}
}
`

View File

@ -306,7 +306,7 @@
"editor": { "editor": {
"embed": { "embed": {
"always_allow": "Einzubettende Inhalte von Drittanbietern immer erlauben (diese Einstellung ist jederzeit änderbar)", "always_allow": "Einzubettende Inhalte von Drittanbietern immer erlauben (diese Einstellung ist jederzeit änderbar)",
"data_privacy_info": "Deine Daten wurden noch nicht an Drittanbieter weitergegeben. Wenn Du dieses Video jetzt abspielst, registriert der folgende Anbieter wahrscheinlich Deine Nutzerdaten:", "data_privacy_info": "Deine Daten wurden noch nicht an Drittanbieter weitergegeben. Wenn Du diesen Inhalt jetzt abspielst, registriert der folgende Anbieter wahrscheinlich Deine Nutzerdaten:",
"data_privacy_warning": "Achte auf Deine Daten!", "data_privacy_warning": "Achte auf Deine Daten!",
"play_now": "Jetzt ansehen" "play_now": "Jetzt ansehen"
}, },

View File

@ -306,9 +306,9 @@
"editor": { "editor": {
"embed": { "embed": {
"always_allow": "Always allow embedded content by third party providers (this setting can be changed any time)", "always_allow": "Always allow embedded content by third party providers (this setting can be changed any time)",
"data_privacy_info": "Your data has not yet been shared with any third party providers. If you proceed to watch this video the following provider will likely collect user data:", "data_privacy_info": "Your data has not yet been shared with any third party providers. If you proceed to play this content the following provider will likely collect user data:",
"data_privacy_warning": "Data Privacy Warning!", "data_privacy_warning": "Data Privacy Warning!",
"play_now": "Watch now" "play_now": "Continue"
}, },
"hashtag": { "hashtag": {
"addHashtag": "New hashtag", "addHashtag": "New hashtag",