Refactored and created the tests of DeletePostCallback

Created a folder `utils` for PostHelpers.js.
Fixed a new incoming problem of the master with deleting Posts on the User Profile.

Co-Authored-By: mattwr18 <mattwr18@gmail.com>
This commit is contained in:
Wolfgang Huß 2019-06-13 19:23:13 +02:00
parent cd0074ec86
commit 482ac1572b
15 changed files with 233 additions and 150 deletions

View File

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

View File

@ -15,7 +15,6 @@
placement="bottom-end" placement="bottom-end"
resource-type="comment" resource-type="comment"
:resource="comment" :resource="comment"
:callbacks="{ confirm: deleteCommentCallback, cancel: null }"
:modalsData="menuModalsData" :modalsData="menuModalsData"
style="float-right" style="float-right"
:is-owner="isAuthor(author.id)" :is-owner="isAuthor(author.id)"
@ -49,9 +48,20 @@ export default {
}, },
dateTime: { type: [Date, String], default: null }, dateTime: { type: [Date, String], default: null },
}, },
data() { computed: {
return { ...mapGetters({
menuModalsData: { user: 'auth/user',
isModerator: 'auth/isModerator',
}),
displaysComment() {
return !this.unavailable || this.isModerator
},
author() {
if (this.deleted) return {}
return this.comment.author || {}
},
menuModalsData() {
return {
delete: { delete: {
titleIdent: 'delete.comment.title', titleIdent: 'delete.comment.title',
messageIdent: 'delete.comment.message', messageIdent: 'delete.comment.message',
@ -72,20 +82,7 @@ export default {
}, },
}, },
}, },
}, }
}
},
computed: {
...mapGetters({
user: 'auth/user',
isModerator: 'auth/isModerator',
}),
displaysComment() {
return !this.unavailable || this.isModerator
},
author() {
if (this.deleted) return {}
return this.comment.author || {}
}, },
}, },
methods: { methods: {

View File

@ -2,7 +2,7 @@ import { shallowMount, mount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex' import Vuex from 'vuex'
import Styleguide from '@human-connection/styleguide' import Styleguide from '@human-connection/styleguide'
import ConfirmModal from './ConfirmModal.vue' import ConfirmModal from './ConfirmModal.vue'
import PostHelpers from '~/components/PostHelpers' import { postMenuModalsData } from '~/components/utils/PostHelpers'
const localVue = createLocalVue() const localVue = createLocalVue()
@ -23,7 +23,7 @@ describe('ConfirmModal.vue', () => {
type: 'contribution', type: 'contribution',
id: 'p23', id: 'p23',
name: postName, name: postName,
modalData: PostHelpers.postMenuModalsData(postName, confirmCallback, cancelCallback).delete, modalData: postMenuModalsData(postName, confirmCallback, cancelCallback).delete,
} }
mocks = { mocks = {
$t: jest.fn(), $t: jest.fn(),
@ -143,7 +143,8 @@ describe('ConfirmModal.vue', () => {
it('does call the confirm callback', () => { it('does call the confirm callback', () => {
expect(confirmCallback).toHaveBeenCalledTimes(1) expect(confirmCallback).toHaveBeenCalledTimes(1)
}) })
it('emits close', () => {
it('emits "close"', () => {
expect(wrapper.emitted().close).toHaveLength(1) expect(wrapper.emitted().close).toHaveLength(1)
}) })

View File

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

View File

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

View File

@ -66,9 +66,9 @@ export default {
}, },
async confirm() { async confirm() {
this.loading = true this.loading = true
// 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()
try { 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({ await this.$apollo.mutate({
mutation: gql` mutation: gql`
mutation($id: ID!) { 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

@ -70,7 +70,7 @@ import HcCategory from '~/components/Category'
import HcRibbon from '~/components/Ribbon' import HcRibbon from '~/components/Ribbon'
// import { randomBytes } from 'crypto' // import { randomBytes } from 'crypto'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import PostHelpers from '~/components/PostHelpers' import { postMenuModalsData, deletePostMutationData } from '~/components/utils/PostHelpers'
export default { export default {
name: 'HcPostCard', name: 'HcPostCard',
@ -103,7 +103,7 @@ export default {
return this.user.id === this.post.author.id return this.user.id === this.post.author.id
}, },
menuModalsData() { menuModalsData() {
return PostHelpers.postMenuModalsData( return postMenuModalsData(
// "this.post" may not always be defined at the beginning // "this.post" may not always be defined at the beginning
this.post ? this.$filters.truncate(this.post.title, 30) : '', this.post ? this.$filters.truncate(this.post.title, 30) : '',
this.deletePostCallback, this.deletePostCallback,
@ -113,7 +113,7 @@ export default {
methods: { methods: {
async deletePostCallback() { async deletePostCallback() {
try { try {
await this.$apollo.mutate(PostHelpers.deletePostMutationData(this.post.id)) await this.$apollo.mutate(deletePostMutationData(this.post.id))
this.$toast.success(this.$t('delete.contribution.success')) this.$toast.success(this.$t('delete.contribution.success'))
this.$emit('removePostFromList') this.$emit('removePostFromList')
} catch (err) { } catch (err) {

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

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

View File

@ -56,7 +56,7 @@ describe('PostSlug', () => {
beforeEach(jest.useFakeTimers) beforeEach(jest.useFakeTimers)
describe('test "PostHelpers"', () => { describe('test Post callbacks', () => {
beforeEach(() => { beforeEach(() => {
wrapper = Wrapper() wrapper = Wrapper()
wrapper.setData({ 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(() => { beforeEach(() => {
wrapper.vm.deletePostCallback('page') wrapper.vm.deletePostCallback()
}) })
describe('after timeout', () => { describe('after timeout', () => {
beforeEach(jest.runAllTimers) 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', () => { it('does call mutation', () => {
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1) expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
}) })
@ -93,6 +85,10 @@ describe('PostSlug', () => {
it('mutation is successful', () => { it('mutation is successful', () => {
expect(mocks.$toast.success).toHaveBeenCalledTimes(1) expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
}) })
it('does go to index (main) page', () => {
expect(mocks.$router.history.push).toHaveBeenCalledTimes(1)
})
}) })
}) })
}) })

View File

@ -71,7 +71,7 @@ import HcUser from '~/components/User'
import HcShoutButton from '~/components/ShoutButton.vue' import HcShoutButton from '~/components/ShoutButton.vue'
import HcCommentForm from '~/components/comments/CommentForm' import HcCommentForm from '~/components/comments/CommentForm'
import HcCommentList from '~/components/comments/CommentList' import HcCommentList from '~/components/comments/CommentList'
import PostHelpers from '~/components/PostHelpers' import { postMenuModalsData, deletePostMutationData } from '~/components/utils/PostHelpers'
export default { export default {
name: 'PostSlug', name: 'PostSlug',
@ -211,7 +211,7 @@ export default {
}, },
computed: { computed: {
menuModalsData() { menuModalsData() {
return PostHelpers.postMenuModalsData( return postMenuModalsData(
// "this.post" may not always be defined at the beginning // "this.post" may not always be defined at the beginning
this.post ? this.$filters.truncate(this.post.title, 30) : '', this.post ? this.$filters.truncate(this.post.title, 30) : '',
this.deletePostCallback, this.deletePostCallback,
@ -224,7 +224,7 @@ export default {
}, },
async deletePostCallback() { async deletePostCallback() {
try { try {
await this.$apollo.mutate(PostHelpers.deletePostMutationData(this.post.id)) await this.$apollo.mutate(deletePostMutationData(this.post.id))
this.$toast.success(this.$t('delete.contribution.success')) this.$toast.success(this.$t('delete.contribution.success'))
this.$router.history.push('/') // Redirect to index (main) page this.$router.history.push('/') // Redirect to index (main) page
} catch (err) { } catch (err) {

View File

@ -1,17 +1,17 @@
import { shallowMount, createLocalVue } from '@vue/test-utils' import { config, mount } from '@vue/test-utils'
import ProfileSlug from './_slug.vue' import HcUserProfile from './_slug.vue'
import Vuex from 'vuex' import Vuex from 'vuex'
import Styleguide from '@human-connection/styleguide' import Vue from 'vue'
const localVue = createLocalVue() config.stubs['ds-space'] = '<span><div>Hello, Wolle</div></span>'
localVue.use(Vuex) Vue.use(Vuex)
localVue.use(Styleguide)
describe('ProfileSlug', () => { describe('ProfileSlug', () => {
let wrapper let wrapper
let Wrapper let Wrapper
let mocks let mocks
let getters
beforeEach(() => { beforeEach(() => {
mocks = { mocks = {
@ -21,6 +21,12 @@ describe('ProfileSlug', () => {
}, },
$t: jest.fn(), $t: jest.fn(),
// If you mocking router, than don't use VueRouter with lacalVue: https://vue-test-utils.vuejs.org/guides/using-with-vue-router.html // If you mocking router, than don't use VueRouter with lacalVue: https://vue-test-utils.vuejs.org/guides/using-with-vue-router.html
$route: {
params: {
id: '4711',
slug: 'john-doe',
},
},
$router: { $router: {
history: { history: {
push: jest.fn(), push: jest.fn(),
@ -34,13 +40,24 @@ describe('ProfileSlug', () => {
mutate: jest.fn().mockResolvedValue(), mutate: jest.fn().mockResolvedValue(),
}, },
} }
getters = {
'auth/user': () => {
return {
slug: 'john-doe',
id: '4711',
}
},
}
}) })
describe('shallowMount', () => { describe('mount', () => {
Wrapper = () => { Wrapper = () => {
return shallowMount(ProfileSlug, { const store = new Vuex.Store({
getters,
})
return mount(HcUserProfile, {
store,
mocks, mocks,
localVue,
}) })
} }
@ -52,27 +69,13 @@ describe('ProfileSlug', () => {
}) })
describe('deletion of Post from List by invoking "deletePostCallback(`list`)"', () => { describe('deletion of Post from List by invoking "deletePostCallback(`list`)"', () => {
beforeEach(() => { beforeEach(() => {})
wrapper.vm.deletePostCallback('list')
})
describe('after timeout', () => { describe('after timeout', () => {
beforeEach(jest.runAllTimers) beforeEach(jest.runAllTimers)
it.skip('emits "deletePost"', () => { it('fetches the user', () => {
expect(wrapper.emitted().deletePost).toHaveLength(1) expect(wrapper.is('div')).toBe(true)
})
it.skip('does not go to index (main) page', () => {
expect(mocks.$router.history.push).not.toHaveBeenCalled()
})
it.skip('does call mutation', () => {
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
})
it.skip('mutation is successful', () => {
expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
}) })
}) })
}) })

View File

@ -204,7 +204,7 @@
:key="post.id" :key="post.id"
:post="post" :post="post"
:width="{ base: '100%', md: '100%', xl: '50%' }" :width="{ base: '100%', md: '100%', xl: '50%' }"
@removePostFromList="user.contributions.splice(index, 1)" @removePostFromList="activePosts.splice(index, 1)"
/> />
</template> </template>
<template v-else-if="$apollo.loading"> <template v-else-if="$apollo.loading">
@ -252,6 +252,7 @@ const tabToFilterMapping = ({ tab, id }) => {
} }
export default { export default {
name: 'HcUserProfile',
components: { components: {
User, User,
HcPostCard, HcPostCard,
@ -273,6 +274,7 @@ export default {
return { return {
User: [], User: [],
Post: [], Post: [],
activePosts: [],
voted: false, voted: false,
page: 1, page: 1,
pageSize: 6, pageSize: 6,
@ -302,12 +304,6 @@ export default {
offset() { offset() {
return (this.page - 1) * this.pageSize return (this.page - 1) * this.pageSize
}, },
activePosts() {
if (!this.Post) {
return []
}
return this.uniq(this.Post.filter(post => !post.deleted))
},
socialMediaLinks() { socialMediaLinks() {
const { socialMedia = [] } = this.user const { socialMedia = [] } = this.user
return socialMedia.map(socialMedia => { return socialMedia.map(socialMedia => {
@ -330,6 +326,9 @@ export default {
throw new Error('User not found!') throw new Error('User not found!')
} }
}, },
Post(val) {
this.activePosts = this.setActivePosts()
},
}, },
methods: { methods: {
handleTab(tab) { handleTab(tab) {
@ -363,6 +362,12 @@ export default {
fetchPolicy: 'cache-and-network', fetchPolicy: 'cache-and-network',
}) })
}, },
setActivePosts() {
if (!this.Post) {
return []
}
return this.uniq(this.Post.filter(post => !post.deleted))
},
}, },
apollo: { apollo: {
Post: { Post: {