Merge pull request #763 from Human-Connection/734-authorization-problem-disabling-post

Authorisation problem when disabling a post
This commit is contained in:
Wolfgang Huß 2019-06-14 20:45:02 +02:00 committed by GitHub
commit 60bbd3f408
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 378 additions and 309 deletions

View File

@ -25,6 +25,9 @@ describe('Comment.vue', () => {
success: jest.fn(),
error: jest.fn(),
},
$filters: {
truncate: a => a,
},
$apollo: {
mutate: jest.fn().mockResolvedValue(),
},

View File

@ -9,12 +9,13 @@
<ds-space margin-bottom="x-small">
<hc-user :user="author" :date-time="comment.createdAt" />
</ds-space>
<!-- Content Menu (can open Modals) -->
<no-ssr>
<content-menu
placement="bottom-end"
resource-type="comment"
:resource="comment"
:callbacks="{ confirm: deleteCommentCallback, cancel: null }"
:modalsData="menuModalsData"
style="float-right"
:is-owner="isAuthor(author.id)"
/>
@ -59,6 +60,30 @@ export default {
if (this.deleted) return {}
return this.comment.author || {}
},
menuModalsData() {
return {
delete: {
titleIdent: 'delete.comment.title',
messageIdent: 'delete.comment.message',
messageParams: {
name: this.$filters.truncate(this.comment.contentExcerpt, 30),
},
buttons: {
confirm: {
danger: true,
icon: 'trash',
textIdent: 'delete.submit',
callback: this.deleteCommentCallback,
},
cancel: {
icon: 'close',
textIdent: 'delete.cancel',
callback: () => {},
},
},
},
}
},
},
methods: {
isAuthor(id) {

View File

@ -43,7 +43,13 @@ export default {
return value.match(/(contribution|comment|organization|user)/)
},
},
callbacks: { type: Object, required: true },
modalsData: {
type: Object,
required: false,
// default: () => {
// return {}
// },
},
},
computed: {
routes() {
@ -145,7 +151,7 @@ export default {
data: {
type: this.resourceType,
resource: this.resource,
callbacks: this.callbacks,
modalsData: this.modalsData,
},
})
},

View File

@ -49,6 +49,7 @@ import gql from 'graphql-tag'
import HcEditor from '~/components/Editor'
import orderBy from 'lodash/orderBy'
import locales from '~/locales'
import PostMutations from '~/graphql/PostMutations.js'
export default {
components: {
@ -106,11 +107,10 @@ export default {
},
methods: {
submit() {
const postMutations = require('~/graphql/PostMutations.js').default(this)
this.loading = true
this.$apollo
.mutate({
mutation: this.id ? postMutations.UpdatePost : postMutations.CreatePost,
mutation: this.id ? PostMutations().UpdatePost : PostMutations().CreatePost,
variables: {
id: this.id,
title: this.form.title,

View File

@ -1,6 +1,6 @@
import { shallowMount, createLocalVue } from '@vue/test-utils'
import Modal from './Modal.vue'
import DeleteModal from './Modal/DeleteModal.vue'
import ConfirmModal from './Modal/ConfirmModal.vue'
import DisableModal from './Modal/DisableModal.vue'
import ReportModal from './Modal/ReportModal.vue'
import Vuex from 'vuex'
@ -60,7 +60,7 @@ describe('Modal.vue', () => {
it('initially empty', () => {
wrapper = Wrapper()
expect(wrapper.contains(DeleteModal)).toBe(false)
expect(wrapper.contains(ConfirmModal)).toBe(false)
expect(wrapper.contains(DisableModal)).toBe(false)
expect(wrapper.contains(ReportModal)).toBe(false)
})
@ -75,10 +75,6 @@ describe('Modal.vue', () => {
id: 'c456',
title: 'some title',
},
callbacks: {
confirm: null,
cancel: null,
},
},
}
wrapper = Wrapper()
@ -93,10 +89,6 @@ describe('Modal.vue', () => {
type: 'contribution',
name: 'some title',
id: 'c456',
callbacks: {
confirm: null,
cancel: null,
},
})
})
@ -117,20 +109,12 @@ describe('Modal.vue', () => {
name: 'Author name',
},
},
callbacks: {
confirm: null,
cancel: null,
},
}
wrapper = Wrapper()
expect(wrapper.find(DisableModal).props()).toEqual({
type: 'comment',
name: 'Author name',
id: 'c456',
callbacks: {
confirm: null,
cancel: null,
},
})
})
@ -140,20 +124,12 @@ describe('Modal.vue', () => {
resource: {
id: 'c456',
},
callbacks: {
confirm: null,
cancel: null,
},
}
wrapper = Wrapper()
expect(wrapper.find(DisableModal).props()).toEqual({
type: 'comment',
name: '',
id: 'c456',
callbacks: {
confirm: null,
cancel: null,
},
})
})
})

View File

@ -6,7 +6,6 @@
:id="data.resource.id"
:type="data.type"
:name="name"
:callbacks="data.callbacks"
@close="close"
/>
<release-modal
@ -21,22 +20,21 @@
:id="data.resource.id"
:type="data.type"
:name="name"
:callbacks="data.callbacks"
@close="close"
/>
<delete-modal
<confirm-modal
v-if="open === 'delete'"
:id="data.resource.id"
:type="data.type"
:name="name"
:callbacks="data.callbacks"
:modalData="data.modalsData.delete"
@close="close"
/>
</div>
</template>
<script>
import DeleteModal from '~/components/Modal/DeleteModal'
import ConfirmModal from '~/components/Modal/ConfirmModal'
import DisableModal from '~/components/Modal/DisableModal'
import ReleaseModal from '~/components/ReleaseModal/ReleaseModal.vue'
import ReportModal from '~/components/Modal/ReportModal'
@ -48,7 +46,7 @@ export default {
DisableModal,
ReleaseModal,
ReportModal,
DeleteModal,
ConfirmModal,
},
computed: {
...mapGetters({
@ -63,7 +61,7 @@ export default {
switch (this.data.type) {
case 'user':
return name
case 'contribution':
case 'contribution': // REFACTORING: In ConfirmModal Already replaced "title" by "this.menuModalsData.delete.messageParams".
return title
case 'comment':
return author && author.name

View File

@ -1,28 +1,29 @@
import { shallowMount, mount, createLocalVue } from '@vue/test-utils'
import DeleteModal from './DeleteModal.vue'
import Vuex from 'vuex'
import Styleguide from '@human-connection/styleguide'
import ConfirmModal from './ConfirmModal.vue'
import { postMenuModalsData } from '~/components/utils/PostHelpers'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Styleguide)
describe('DeleteModal.vue', () => {
describe('ConfirmModal.vue', () => {
let Wrapper
let wrapper
let propsData
let mocks
const postName = 'It is a post'
const confirmCallback = jest.fn()
const cancelCallback = jest.fn()
beforeEach(() => {
propsData = {
type: 'contribution',
id: 'p23',
name: 'It is a post',
callbacks: {
confirm: jest.fn(),
cancel: jest.fn(),
},
name: postName,
modalData: postMenuModalsData(postName, confirmCallback, cancelCallback).delete,
}
mocks = {
$t: jest.fn(),
@ -32,9 +33,13 @@ describe('DeleteModal.vue', () => {
}
})
afterEach(() => {
jest.clearAllMocks()
})
describe('shallowMount', () => {
Wrapper = () => {
return shallowMount(DeleteModal, {
return shallowMount(ConfirmModal, {
propsData,
mocks,
localVue,
@ -61,7 +66,7 @@ describe('DeleteModal.vue', () => {
...propsData,
type: 'contribution',
id: 'p23',
name: 'It is a post',
name: postName,
}
wrapper = Wrapper()
})
@ -72,32 +77,7 @@ describe('DeleteModal.vue', () => {
[
'delete.contribution.message',
{
name: 'It is a post',
},
],
]
expect(calls).toEqual(expect.arrayContaining(expected))
})
})
describe('given a comment', () => {
beforeEach(() => {
propsData = {
...propsData,
type: 'comment',
id: 'c4',
name: 'It is the user of the comment',
}
wrapper = Wrapper()
})
it('mentions comments user name', () => {
const calls = mocks.$t.mock.calls
const expected = [
[
'delete.comment.message',
{
name: 'It is the user of the comment',
name: postName,
},
],
]
@ -108,7 +88,7 @@ describe('DeleteModal.vue', () => {
describe('mount', () => {
Wrapper = () => {
return mount(DeleteModal, {
return mount(ConfirmModal, {
propsData,
mocks,
localVue,
@ -135,7 +115,7 @@ describe('DeleteModal.vue', () => {
})
it('does call the cancel callback', () => {
expect(propsData.callbacks.cancel).toHaveBeenCalledTimes(1)
expect(cancelCallback).toHaveBeenCalledTimes(1)
})
it('emits "close"', () => {
@ -161,10 +141,11 @@ describe('DeleteModal.vue', () => {
})
it('does call the confirm callback', () => {
expect(propsData.callbacks.confirm).toHaveBeenCalledTimes(1)
expect(confirmCallback).toHaveBeenCalledTimes(1)
})
it('emits close', () => {
expect(wrapper.emitted().close).toBeTruthy()
it('emits "close"', () => {
expect(wrapper.emitted().close).toHaveLength(1)
})
it('resets success', () => {

View File

@ -10,10 +10,18 @@
<p v-html="message" />
<template slot="footer">
<ds-button class="cancel" icon="close" @click="cancel">{{ $t('delete.cancel') }}</ds-button>
<ds-button class="cancel" :icon="modalData.buttons.cancel.icon" @click="cancel">
{{ $t(modalData.buttons.cancel.textIdent) }}
</ds-button>
<ds-button danger class="confirm" icon="trash" :loading="loading" @click="confirm">
{{ $t('delete.submit') }}
<ds-button
:danger="modalData.buttons.confirm.danger"
class="confirm"
:icon="modalData.buttons.confirm.icon"
:loading="loading"
@click="confirm"
>
{{ $t(modalData.buttons.confirm.textIdent) }}
</ds-button>
</template>
</ds-modal>
@ -23,14 +31,14 @@
import { SweetalertIcon } from 'vue-sweetalert-icons'
export default {
name: 'DeleteModal',
name: 'ConfirmModal',
components: {
SweetalertIcon,
},
props: {
name: { type: String, default: '' },
type: { type: String, required: true },
callbacks: { type: Object, required: true },
modalData: { type: Object, required: true },
id: { type: String, required: true },
},
data() {
@ -42,18 +50,15 @@ export default {
},
computed: {
title() {
return this.$t(`delete.${this.type}.title`)
return this.$t(this.modalData.titleIdent)
},
message() {
const name = this.$filters.truncate(this.name, 30)
return this.$t(`delete.${this.type}.message`, { name })
return this.$t(this.modalData.messageIdent, this.modalData.messageParams)
},
},
methods: {
async cancel() {
if (this.callbacks.cancel) {
await this.callbacks.cancel()
}
await this.modalData.buttons.cancel.callback()
this.isOpen = false
setTimeout(() => {
this.$emit('close')
@ -62,9 +67,7 @@ export default {
async confirm() {
this.loading = true
try {
if (this.callbacks.confirm) {
await this.callbacks.confirm()
}
await this.modalData.buttons.confirm.callback()
this.success = true
setTimeout(() => {
this.isOpen = false

View File

@ -16,10 +16,6 @@ describe('DisableModal.vue', () => {
type: 'contribution',
id: 'c42',
name: 'blah',
callbacks: {
confirm: jest.fn(),
cancel: jest.fn(),
},
}
mocks = {
$filters: {
@ -33,8 +29,12 @@ describe('DisableModal.vue', () => {
$apollo: {
mutate: jest
.fn()
.mockResolvedValueOnce({ enable: 'u4711' })
.mockRejectedValue({ message: 'Not Authorised!' }),
.mockResolvedValueOnce({
enable: 'u4711',
})
.mockRejectedValue({
message: 'Not Authorised!',
}),
},
location: {
reload: jest.fn(),

View File

@ -21,7 +21,6 @@ export default {
props: {
name: { type: String, default: '' },
type: { type: String, required: true },
callbacks: { type: Object, required: true },
id: { type: String, required: true },
},
data() {
@ -42,9 +41,8 @@ export default {
},
methods: {
async cancel() {
if (this.callbacks.cancel) {
await this.callbacks.cancel()
}
// TODO: Use the "modalData" structure introduced in "ConfirmModal" and refactor this here. Be aware that all the Jest tests have to be refactored as well !!!
// await this.modalData.buttons.cancel.callback()
this.isOpen = false
setTimeout(() => {
this.$emit('close')
@ -52,9 +50,8 @@ export default {
},
async confirm() {
try {
if (this.callbacks.confirm) {
await this.callbacks.confirm()
}
// TODO: Use the "modalData" structure introduced in "ConfirmModal" and refactor this here. Be aware that all the Jest tests have to be refactored as well !!!
// await this.modalData.buttons.confirm.callback()
await this.$apollo.mutate({
mutation: gql`
mutation($id: ID!) {

View File

@ -17,10 +17,6 @@ describe('ReportModal.vue', () => {
propsData = {
type: 'contribution',
id: 'c43',
callbacks: {
confirm: jest.fn(),
cancel: jest.fn(),
},
}
mocks = {
$t: jest.fn(),

View File

@ -10,9 +10,7 @@
<p v-html="message" />
<template slot="footer">
<ds-button class="cancel" icon="close" @click="cancel">
{{ $t('report.cancel') }}
</ds-button>
<ds-button class="cancel" icon="close" @click="cancel">{{ $t('report.cancel') }}</ds-button>
<ds-button
danger
@ -39,7 +37,6 @@ export default {
props: {
name: { type: String, default: '' },
type: { type: String, required: true },
callbacks: { type: Object, required: true },
id: { type: String, required: true },
},
data() {
@ -60,9 +57,8 @@ export default {
},
methods: {
async cancel() {
if (this.callbacks.cancel) {
await this.callbacks.cancel()
}
// TODO: Use the "modalData" structure introduced in "ConfirmModal" and refactor this here. Be aware that all the Jest tests have to be refactored as well !!!
// await this.modalData.buttons.cancel.callback()
this.isOpen = false
setTimeout(() => {
this.$emit('close')
@ -71,9 +67,8 @@ export default {
async confirm() {
this.loading = true
try {
if (this.callbacks.confirm) {
await this.callbacks.confirm()
}
// TODO: Use the "modalData" structure introduced in "ConfirmModal" and refactor this here. Be aware that all the Jest tests have to be refactored as well !!!
// await this.modalData.buttons.confirm.callback()
await this.$apollo.mutate({
mutation: gql`
mutation($id: ID!) {

View File

@ -0,0 +1,130 @@
import { config, shallowMount, mount, createLocalVue, RouterLinkStub } from '@vue/test-utils'
import Styleguide from '@human-connection/styleguide'
import Vuex from 'vuex'
import Filters from '~/plugins/vue-filters'
import PostCard from '.'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Styleguide)
localVue.use(Filters)
config.stubs['no-ssr'] = '<span><slot /></span>'
config.stubs['v-popover'] = '<span><slot /></span>'
describe('PostCard', () => {
let store
let stubs
let mocks
let propsData
let getters
let Wrapper
let wrapper
beforeEach(() => {
propsData = {
post: {
id: 'p23',
name: 'It is a post',
author: {
id: 'u1',
},
disabled: false,
},
}
store = new Vuex.Store({
getters: {
'auth/user': () => {
return {}
},
},
})
stubs = {
NuxtLink: RouterLinkStub,
}
mocks = {
$t: jest.fn(),
$toast: {
success: jest.fn(),
error: jest.fn(),
},
$apollo: {
mutate: jest.fn().mockResolvedValue(),
},
}
getters = {
'auth/user': () => {
return {}
},
}
})
describe('shallowMount', () => {
Wrapper = () => {
return shallowMount(PostCard, {
store,
propsData,
mocks,
localVue,
})
}
beforeEach(jest.useFakeTimers)
describe('test Post callbacks', () => {
beforeEach(() => {
wrapper = Wrapper()
})
describe('deletion of Post from Page by invoking "deletePostCallback()"', () => {
beforeEach(() => {
wrapper.vm.deletePostCallback()
})
describe('after timeout', () => {
beforeEach(jest.runAllTimers)
it('does call mutation', () => {
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
})
it('mutation is successful', () => {
expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
})
it('emits "removePostFromList"', () => {
expect(wrapper.emitted().removePostFromList).toHaveLength(1)
})
})
})
})
})
describe('mount', () => {
Wrapper = () => {
const store = new Vuex.Store({
getters,
})
return mount(PostCard, {
stubs,
mocks,
propsData,
store,
localVue,
})
}
describe('given a post', () => {
beforeEach(() => {
propsData.post = {
title: "It's a title",
}
})
it('renders title', () => {
expect(Wrapper().text()).toContain("It's a title")
})
})
})
})

View File

@ -21,9 +21,7 @@
</div>
<ds-space margin-bottom="small" />
<!-- Post Title -->
<ds-heading tag="h3" no-margin>
{{ post.title }}
</ds-heading>
<ds-heading tag="h3" no-margin>{{ post.title }}</ds-heading>
<ds-space margin-bottom="small" />
<!-- Post Content Excerpt -->
<!-- eslint-disable vue/no-v-html -->
@ -58,7 +56,7 @@
<content-menu
resource-type="contribution"
:resource="post"
:callbacks="{ confirm: deletePostCallback, cancel: null }"
:modalsData="menuModalsData"
:is-owner="isAuthor"
/>
</div>
@ -75,7 +73,7 @@ import HcCategory from '~/components/Category'
import HcRibbon from '~/components/Ribbon'
// import { randomBytes } from 'crypto'
import { mapGetters } from 'vuex'
import PostMutationHelpers from '~/mixins/PostMutationHelpers'
import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers'
export default {
name: 'HcPostCard',
@ -85,7 +83,6 @@ export default {
HcRibbon,
ContentMenu,
},
mixins: [PostMutationHelpers],
props: {
post: {
type: Object,
@ -108,6 +105,24 @@ export default {
if (!author) return false
return this.user.id === this.post.author.id
},
menuModalsData() {
return postMenuModalsData(
// "this.post" may not always be defined at the beginning
this.post ? this.$filters.truncate(this.post.title, 30) : '',
this.deletePostCallback,
)
},
},
methods: {
async deletePostCallback() {
try {
await this.$apollo.mutate(deletePostMutation(this.post.id))
this.$toast.success(this.$t('delete.contribution.success'))
this.$emit('removePostFromList')
} catch (err) {
this.$toast.error(err.message)
}
},
},
}
</script>

View File

@ -1,61 +0,0 @@
import { config, mount, createLocalVue, RouterLinkStub } from '@vue/test-utils'
import PostCard from '.'
import Styleguide from '@human-connection/styleguide'
import Vuex from 'vuex'
import Filters from '~/plugins/vue-filters'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Styleguide)
localVue.use(Filters)
config.stubs['no-ssr'] = '<span><slot /></span>'
config.stubs['v-popover'] = '<span><slot /></span>'
describe('PostCard', () => {
let stubs
let mocks
let propsData
let getters
beforeEach(() => {
propsData = {}
stubs = {
NuxtLink: RouterLinkStub,
}
mocks = {
$t: jest.fn(),
}
getters = {
'auth/user': () => {
return {}
},
}
})
const Wrapper = () => {
const store = new Vuex.Store({
getters,
})
return mount(PostCard, {
stubs,
mocks,
propsData,
store,
localVue,
})
}
describe('given a post', () => {
beforeEach(() => {
propsData.post = {
title: "It's a title",
}
})
it('renders title', () => {
expect(Wrapper().text()).toContain("It's a title")
})
})
})

View File

@ -40,6 +40,8 @@ export default {
},
methods: {
cancel() {
// TODO: Use the "modalData" structure introduced in "ConfirmModal" and refactor this here. Be aware that all the Jest tests have to be refactored as well !!!
// await this.modalData.buttons.cancel.callback()
this.isOpen = false
setTimeout(() => {
this.$emit('close')
@ -47,6 +49,8 @@ export default {
},
async confirm() {
try {
// TODO: Use the "modalData" structure introduced in "ConfirmModal" and refactor this here. Be aware that all the Jest tests have to be refactored as well !!!
// await this.modalData.buttons.confirm.callback()
await this.$apollo.mutate({
mutation: gql`
mutation($id: ID!) {

View File

@ -24,7 +24,9 @@ describe('CommentList.vue', () => {
let data
propsData = {
post: { id: 1 },
post: {
id: 1,
},
}
store = new Vuex.Store({
getters: {
@ -35,6 +37,9 @@ describe('CommentList.vue', () => {
})
mocks = {
$t: jest.fn(),
$filters: {
truncate: a => a,
},
$apollo: {
queries: {
Post: {
@ -51,13 +56,24 @@ describe('CommentList.vue', () => {
describe('shallowMount', () => {
const Wrapper = () => {
return mount(CommentList, { store, mocks, localVue, propsData, data })
return mount(CommentList, {
store,
mocks,
localVue,
propsData,
data,
})
}
beforeEach(() => {
wrapper = Wrapper()
wrapper.setData({
comments: [{ id: 'c1', contentExcerpt: 'this is a comment' }],
comments: [
{
id: 'c1',
contentExcerpt: 'this is a comment',
},
],
})
})

View File

@ -0,0 +1,35 @@
import PostMutations from '~/graphql/PostMutations.js'
export function postMenuModalsData(truncatedPostName, confirmCallback, cancelCallback = () => {}) {
return {
delete: {
titleIdent: 'delete.contribution.title',
messageIdent: 'delete.contribution.message',
messageParams: {
name: truncatedPostName,
},
buttons: {
confirm: {
danger: true,
icon: 'trash',
textIdent: 'delete.submit',
callback: confirmCallback,
},
cancel: {
icon: 'close',
textIdent: 'delete.cancel',
callback: cancelCallback,
},
},
},
}
}
export function deletePostMutation(postId) {
return {
mutation: PostMutations().DeletePost,
variables: {
id: postId,
},
}
}

View File

@ -1,8 +1,8 @@
import gql from 'graphql-tag'
export default app => {
export default () => {
return {
CreatePost: gql(`
CreatePost: gql`
mutation($title: String!, $content: String!, $language: String) {
CreatePost(title: $title, content: $content, language: $language) {
id
@ -13,8 +13,8 @@ export default app => {
language
}
}
`),
UpdatePost: gql(`
`,
UpdatePost: gql`
mutation($id: ID!, $title: String!, $content: String!, $language: String) {
UpdatePost(id: $id, title: $title, content: $content, language: $language) {
id
@ -25,6 +25,13 @@ export default app => {
language
}
}
`),
`,
DeletePost: gql`
mutation($id: ID!) {
DeletePost(id: $id) {
id
}
}
`,
}
}

View File

@ -245,7 +245,7 @@
"comment": {
"title": "Lösche Kommentar",
"type": "Comment",
"message": "Bist du sicher, dass du den Kommentar von \"<b>{name}</b>\" löschen möchtest?",
"message": "Bist du sicher, dass du den Kommentar \"<b>{name}</b>\" löschen möchtest?",
"success": "Kommentar erfolgreich gelöscht!"
}
},

View File

@ -245,7 +245,7 @@
"comment": {
"title": "Delete Comment",
"type": "Comment",
"message": "Do you really want to delete the comment from \"<b>{name}</b>\"?",
"message": "Do you really want to delete the comment \"<b>{name}</b>\"?",
"success": "Comment successfully deleted!"
}
},

View File

@ -1,39 +0,0 @@
import gql from 'graphql-tag'
export default {
methods: {
async deletePostCallback(postDisplayType = 'list') {
// console.log('inside "deletePostCallback" !!! ', this.post)
try {
var gqlMutation = gql`
mutation($id: ID!) {
DeletePost(id: $id) {
id
}
}
`
await this.$apollo.mutate({
mutation: gqlMutation,
variables: {
id: this.post.id,
},
})
this.$toast.success(this.$t('delete.contribution.success'))
// console.log('called "this.$t" !!!')
switch (postDisplayType) {
case 'list':
this.$emit('deletePost')
// console.log('emitted "deletePost" !!!')
break
default:
// case 'page'
// console.log('called "this.$router.history.push" !!!')
this.$router.history.push('/') // Single page type: Redirect to index (main) page
break
}
} catch (err) {
this.$toast.error(err.message)
}
},
},
}

View File

@ -9,7 +9,7 @@
:key="post.id"
:post="post"
:width="{ base: '100%', xs: '100%', md: '50%', xl: '33%' }"
@deletePost="deletePost(index, post.id)"
@removePostFromList="deletePost(index, post.id)"
/>
</ds-flex>
<no-ssr>

View File

@ -56,7 +56,7 @@ describe('PostSlug', () => {
beforeEach(jest.useFakeTimers)
describe('test mixin "PostMutationHelpers"', () => {
describe('test Post callbacks', () => {
beforeEach(() => {
wrapper = Wrapper()
wrapper.setData({
@ -70,22 +70,14 @@ describe('PostSlug', () => {
})
})
describe('deletion of Post from Page by invoking "deletePostCallback(`page`)"', () => {
describe('deletion of Post from Page by invoking "deletePostCallback()"', () => {
beforeEach(() => {
wrapper.vm.deletePostCallback('page')
wrapper.vm.deletePostCallback()
})
describe('after timeout', () => {
beforeEach(jest.runAllTimers)
it('not emits "deletePost"', () => {
expect(wrapper.emitted().deletePost).toBeFalsy()
})
it('does go to index (main) page', () => {
expect(mocks.$router.history.push).toHaveBeenCalledTimes(1)
})
it('does call mutation', () => {
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
})
@ -93,6 +85,10 @@ describe('PostSlug', () => {
it('mutation is successful', () => {
expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
})
it('does go to index (main) page', () => {
expect(mocks.$router.history.push).toHaveBeenCalledTimes(1)
})
})
})
})

View File

@ -7,12 +7,13 @@
>
<ds-space margin-bottom="small" />
<hc-user :user="post.author" :date-time="post.createdAt" />
<!-- Content Menu (can open Modals) -->
<no-ssr>
<content-menu
placement="bottom-end"
resource-type="contribution"
:resource="post"
:callbacks="{ confirm: () => deletePostCallback('page'), cancel: null }"
:modalsData="menuModalsData"
:is-owner="isAuthor(post.author ? post.author.id : null)"
/>
</no-ssr>
@ -70,7 +71,7 @@ import HcUser from '~/components/User'
import HcShoutButton from '~/components/ShoutButton.vue'
import HcCommentForm from '~/components/comments/CommentForm'
import HcCommentList from '~/components/comments/CommentList'
import PostMutationHelpers from '~/mixins/PostMutationHelpers'
import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers'
export default {
name: 'PostSlug',
@ -87,7 +88,6 @@ export default {
HcCommentForm,
HcCommentList,
},
mixins: [PostMutationHelpers],
head() {
return {
title: this.title,
@ -209,10 +209,28 @@ export default {
this.ready = true
}, 50)
},
computed: {
menuModalsData() {
return postMenuModalsData(
// "this.post" may not always be defined at the beginning
this.post ? this.$filters.truncate(this.post.title, 30) : '',
this.deletePostCallback,
)
},
},
methods: {
isAuthor(id) {
return this.$store.getters['auth/user'].id === id
},
async deletePostCallback() {
try {
await this.$apollo.mutate(deletePostMutation(this.post.id))
this.$toast.success(this.$t('delete.contribution.success'))
this.$router.history.push('/') // Redirect to index (main) page
} catch (err) {
this.$toast.error(err.message)
}
},
},
}
</script>

View File

@ -1,8 +1,6 @@
<template>
<ds-card>
<h2 style="margin-bottom: .2em;">
Mehr Informationen
</h2>
<h2 style="margin-bottom: .2em;">Mehr Informationen</h2>
<p>Hier findest du weitere infos zum Thema.</p>
<ds-space />
<h3>
@ -42,7 +40,7 @@
:key="relatedPost.id"
:post="relatedPost"
:width="{ base: '100%', lg: 1 }"
@deletePost="post.relatedContributions.splice(index, 1)"
@removePostFromList="post.relatedContributions.splice(index, 1)"
/>
</ds-flex>
<hc-empty v-else margin="large" icon="file" message="No related Posts" />

View File

@ -1,4 +1,4 @@
import { config, mount, shallowMount, createLocalVue } from '@vue/test-utils'
import { config, mount, createLocalVue } from '@vue/test-utils'
import ProfileSlug from './_slug.vue'
import Vuex from 'vuex'
import Styleguide from '@human-connection/styleguide'
@ -50,48 +50,6 @@ describe('ProfileSlug', () => {
}
})
describe('shallowMount', () => {
Wrapper = () => {
return shallowMount(ProfileSlug, {
mocks,
localVue,
})
}
beforeEach(jest.useFakeTimers)
describe('test mixin "PostMutationHelpers"', () => {
beforeEach(() => {
wrapper = Wrapper()
})
describe('deletion of Post from List by invoking "deletePostCallback(`list`)"', () => {
beforeEach(() => {
wrapper.vm.deletePostCallback('list')
})
describe('after timeout', () => {
beforeEach(jest.runAllTimers)
it('emits "deletePost"', () => {
expect(wrapper.emitted().deletePost).toHaveLength(1)
})
it('does not go to index (main) page', () => {
expect(mocks.$router.history.push).not.toHaveBeenCalled()
})
it('does call mutation', () => {
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
})
it('mutation is successful', () => {
expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
})
})
})
})
})
describe('mount', () => {
Wrapper = () => {
return mount(ProfileSlug, {
@ -104,6 +62,7 @@ describe('ProfileSlug', () => {
beforeEach(() => {
mocks.$filters = {
removeLinks: c => c,
truncate: a => a,
}
mocks.$store = {
getters: {
@ -170,7 +129,10 @@ describe('ProfileSlug', () => {
describe('pagination returned less posts than available', () => {
beforeEach(() => {
const posts = [1, 2, 3, 4, 5].map(id => {
return { ...aPost, id }
return {
...aPost,
id,
}
})
wrapper.setData({
@ -200,7 +162,10 @@ describe('ProfileSlug', () => {
describe('pagination returned as many posts as available', () => {
beforeEach(() => {
const posts = [1, 2, 3, 4, 5, 6].map(id => {
return { ...aPost, id }
return {
...aPost,
id,
}
})
wrapper.setData({

View File

@ -12,12 +12,12 @@
>
<hc-upload v-if="myProfile" :user="user" />
<hc-avatar v-else :user="user" class="profile-avatar" size="x-large" />
<!-- Menu -->
<no-ssr>
<content-menu
placement="bottom-end"
resource-type="user"
:resource="user"
:callbacks="{ confirm: deletePostCallback, cancel: null }"
:is-owner="myProfile"
class="user-content-menu"
/>
@ -204,7 +204,7 @@
:key="post.id"
:post="post"
:width="{ base: '100%', md: '100%', xl: '50%' }"
@deletePost="Post.splice(index, 1)"
@removePostFromList="activePosts.splice(index, 1)"
/>
</template>
<template v-else-if="$apollo.loading">
@ -238,9 +238,9 @@ import HcEmpty from '~/components/Empty.vue'
import ContentMenu from '~/components/ContentMenu'
import HcUpload from '~/components/Upload'
import HcAvatar from '~/components/Avatar/Avatar.vue'
import PostMutationHelpers from '~/mixins/PostMutationHelpers'
import PostQuery from '~/graphql/UserProfile/Post.js'
import UserQuery from '~/graphql/UserProfile/User.js'
const tabToFilterMapping = ({ tab, id }) => {
return {
post: { author: { id } },
@ -248,7 +248,9 @@ const tabToFilterMapping = ({ tab, id }) => {
shout: { shoutedBy_some: { id } },
}[tab]
}
export default {
name: 'HcUserProfile',
components: {
User,
HcPostCard,
@ -261,7 +263,6 @@ export default {
ContentMenu,
HcUpload,
},
mixins: [PostMutationHelpers],
transition: {
name: 'slide-up',
mode: 'out-in',
@ -271,6 +272,7 @@ export default {
return {
User: [],
Post: [],
activePosts: [],
voted: false,
page: 1,
pageSize: 6,
@ -300,12 +302,6 @@ export default {
offset() {
return (this.page - 1) * this.pageSize
},
activePosts() {
if (!this.Post) {
return []
}
return this.uniq(this.Post.filter(post => !post.deleted))
},
socialMediaLinks() {
const { socialMedia = [] } = this.user
return socialMedia.map(socialMedia => {
@ -328,6 +324,9 @@ export default {
throw new Error('User not found!')
}
},
Post(val) {
this.activePosts = this.setActivePosts()
},
},
methods: {
handleTab(tab) {
@ -361,6 +360,12 @@ export default {
fetchPolicy: 'cache-and-network',
})
},
setActivePosts() {
if (!this.Post) {
return []
}
return this.uniq(this.Post.filter(post => !post.deleted))
},
},
apollo: {
Post: {