From e544725e873a89299c91f981ffa562ff85532dcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 9 Sep 2019 10:27:21 +0200 Subject: [PATCH 1/6] Remove `editPending` and `SET_EDIT_PENDING` from the store --- webapp/components/Comment.vue | 7 ++---- .../CommentForm/CommentForm.spec.js | 1 - webapp/components/CommentForm/CommentForm.vue | 8 +------ .../EditCommentForm/EditCommentForm.vue | 24 +++++++++---------- webapp/store/editor.js | 7 ------ 5 files changed, 15 insertions(+), 32 deletions(-) diff --git a/webapp/components/Comment.vue b/webapp/components/Comment.vue index 7547927eb..fbb39c094 100644 --- a/webapp/components/Comment.vue +++ b/webapp/components/Comment.vue @@ -58,7 +58,7 @@ diff --git a/webapp/constants/comment.js b/webapp/constants/comment.js new file mode 100644 index 000000000..bab79ad37 --- /dev/null +++ b/webapp/constants/comment.js @@ -0,0 +1 @@ +export const COMMENT_MIN_LENGTH = 1 diff --git a/webapp/plugins/vue-filters.js b/webapp/plugins/vue-filters.js index 2001ee67b..f0b40de93 100644 --- a/webapp/plugins/vue-filters.js +++ b/webapp/plugins/vue-filters.js @@ -83,10 +83,13 @@ export default ({ app = {} }) => { return excerpt }, - removeHtml: content => { + removeHtml: (content, replaceLinebreaks = true) => { if (!content) return '' - // replace linebreaks with spaces first - let contentExcerpt = content.replace(/
/gim, ' ').trim() + let contentExcerpt = content + if (replaceLinebreaks) { + // replace linebreaks with spaces first + contentExcerpt = contentExcerpt.replace(/
/gim, ' ').trim() + } // remove the rest of the HTML contentExcerpt = contentExcerpt.replace(/<(?:.|\n)*?>/gm, '').trim() From 2c732bfbfb17809928959d8fef82350d4a5698db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 9 Sep 2019 15:53:50 +0200 Subject: [PATCH 4/6] Refactor create and update comment to emit events --- webapp/components/Comment.vue | 5 ++++- webapp/components/CommentForm/CommentForm.vue | 19 +++++++++---------- webapp/components/CommentList/CommentList.vue | 7 ++++--- webapp/pages/post/_id/_slug/index.vue | 5 ++++- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/webapp/components/Comment.vue b/webapp/components/Comment.vue index 4d71025f9..2ce4d9933 100644 --- a/webapp/components/Comment.vue +++ b/webapp/components/Comment.vue @@ -34,6 +34,7 @@ :post="post" :comment="comment" @showEditCommentMenu="editCommentMenu" + @updateComment="updateComment" />
@@ -131,9 +132,11 @@ export default { return this.user.id === id }, editCommentMenu(showMenu) { - console.log('editCommentMenu, showMenu: ', showMenu) this.openEditCommentMenu = showMenu }, + async updateComment(comment) { + this.$emit('updateComment', comment) + }, async deleteCommentCallback() { try { const { diff --git a/webapp/components/CommentForm/CommentForm.vue b/webapp/components/CommentForm/CommentForm.vue index 5712536a6..6f1c7d931 100644 --- a/webapp/components/CommentForm/CommentForm.vue +++ b/webapp/components/CommentForm/CommentForm.vue @@ -32,7 +32,6 @@ import HcEditor from '~/components/Editor/Editor' import { COMMENT_MIN_LENGTH } from '../../constants/comment' import { minimisedUserQuery } from '~/graphql/User' -import PostQuery from '~/graphql/PostQuery' import CommentMutations from '~/graphql/CommentMutations' export default { @@ -94,21 +93,13 @@ export default { postId: this.post.id, content: this.form.content, }, - update: async (store, { data: { CreateComment } }) => { - const data = await store.readQuery({ - query: PostQuery(this.$i18n), - variables: { id: this.post.id }, - }) - data.Post[0].comments.push(CreateComment) - await store.writeQuery({ query: PostQuery(this.$i18n), data }) - }, } } else { mutateParams = { mutation: CommentMutations(this.$i18n).UpdateComment, variables: { - content: this.form.content, id: this.comment.id, + content: this.form.content, }, } } @@ -120,10 +111,18 @@ export default { .then(res => { this.loading = false if (!this.update) { + const { + data: { CreateComment }, + } = res + this.$emit('createComment', CreateComment) this.clear() this.$toast.success(this.$t('post.comment.submitted')) this.disabled = false } else { + const { + data: { UpdateComment }, + } = res + this.$emit('updateComment', UpdateComment) this.$toast.success(this.$t('post.comment.updated')) this.disabled = false this.closeEditWindow() diff --git a/webapp/components/CommentList/CommentList.vue b/webapp/components/CommentList/CommentList.vue index 710607b94..17da8dda4 100644 --- a/webapp/components/CommentList/CommentList.vue +++ b/webapp/components/CommentList/CommentList.vue @@ -22,7 +22,8 @@ :key="comment.id" :comment="comment" :post="post" - @deleteComment="deleteComment" + @deleteComment="updateCommentList" + @updateComment="updateCommentList" />
@@ -41,9 +42,9 @@ export default { post: { type: Object, default: () => {} }, }, methods: { - deleteComment(deleted) { + updateCommentList(updatedComment) { this.post.comments = this.post.comments.map(comment => { - return comment.id === deleted.id ? deleted : comment + return comment.id === updatedComment.id ? updatedComment : comment }) }, }, diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index 5e4ce758e..e888d4f9d 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -68,7 +68,7 @@ - + @@ -151,6 +151,9 @@ export default { this.$toast.error(err.message) } }, + async createComment(comment) { + this.post.comments.push(comment) + }, }, apollo: { Post: { From 7dc5856845d061febfc32e1da9abf7e5e5f365ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 11 Sep 2019 15:13:31 +0200 Subject: [PATCH 5/6] write tests and fix minor appearing and position problems with context menu --- webapp/components/Comment.spec.js | 36 ++- webapp/components/Comment.vue | 25 ++- .../CommentForm/CommentForm.spec.js | 212 +++++++++++++----- 3 files changed, 202 insertions(+), 71 deletions(-) diff --git a/webapp/components/Comment.spec.js b/webapp/components/Comment.spec.js index 375280cea..81972e501 100644 --- a/webapp/components/Comment.spec.js +++ b/webapp/components/Comment.spec.js @@ -33,7 +33,11 @@ describe('Comment.vue', () => { }, $apollo: { mutate: jest.fn().mockResolvedValue({ - data: { DeleteComment: { id: 'it-is-the-deleted-comment' } }, + data: { + DeleteComment: { + id: 'it-is-the-deleted-comment', + }, + }, }), }, } @@ -125,7 +129,11 @@ describe('Comment.vue', () => { it('emits "deleteComment"', () => { expect(wrapper.emitted('deleteComment')).toEqual([ - [{ id: 'it-is-the-deleted-comment' }], + [ + { + id: 'it-is-the-deleted-comment', + }, + ], ]) }) @@ -138,6 +146,30 @@ describe('Comment.vue', () => { }) }) }) + + describe('test update comment', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + describe('with a given comment', () => { + beforeEach(async () => { + await wrapper.vm.updateComment({ + id: 'it-is-the-updated-comment', + }) + }) + + it('emits "updateComment"', () => { + expect(wrapper.emitted('updateComment')).toEqual([ + [ + { + id: 'it-is-the-updated-comment', + }, + ], + ]) + }) + }) + }) }) }) }) diff --git a/webapp/components/Comment.vue b/webapp/components/Comment.vue index 2ce4d9933..b7c73b84d 100644 --- a/webapp/components/Comment.vue +++ b/webapp/components/Comment.vue @@ -13,19 +13,20 @@ + + + + - - - -
diff --git a/webapp/components/CommentForm/CommentForm.spec.js b/webapp/components/CommentForm/CommentForm.spec.js index 1ab778859..94ce5aa66 100644 --- a/webapp/components/CommentForm/CommentForm.spec.js +++ b/webapp/components/CommentForm/CommentForm.spec.js @@ -12,8 +12,8 @@ describe('CommentForm.vue', () => { let mocks let wrapper let propsData - let cancelBtn let cancelMethodSpy + let closeMethodSpy beforeEach(() => { mocks = { @@ -21,20 +21,6 @@ describe('CommentForm.vue', () => { $i18n: { locale: () => 'en', }, - $apollo: { - mutate: jest - .fn() - .mockResolvedValueOnce({ - data: { - CreateComment: { - contentExcerpt: 'this is a comment', - }, - }, - }) - .mockRejectedValue({ - message: 'Ouch!', - }), - }, $toast: { error: jest.fn(), success: jest.fn(), @@ -43,60 +29,172 @@ describe('CommentForm.vue', () => { removeHtml: a => a, }, } - propsData = { - post: { - id: 1, - }, - } }) describe('mount', () => { - const Wrapper = () => { - return mount(CommentForm, { - mocks, - localVue, - propsData, + describe('create comment', () => { + beforeEach(() => { + mocks = { + ...mocks, + $apollo: { + mutate: jest + .fn() + .mockResolvedValueOnce({ + data: { + CreateComment: { + contentExcerpt: 'this is a comment', + }, + }, + }) + .mockRejectedValue({ + message: 'Ouch!', + }), + }, + } + propsData = { + post: { + id: 'p001', + }, + } + const Wrapper = () => { + return mount(CommentForm, { + mocks, + localVue, + propsData, + }) + } + wrapper = Wrapper() + cancelMethodSpy = jest.spyOn(wrapper.vm, 'clear') }) - } - beforeEach(() => { - wrapper = Wrapper() - cancelMethodSpy = jest.spyOn(wrapper.vm, 'clear') - }) - - it('calls the apollo mutation when form is submitted', async () => { - wrapper.vm.updateEditorContent('this is a comment') - await wrapper.find('form').trigger('submit') - expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1) - }) - - it('calls clear method when the cancel button is clicked', () => { - wrapper.vm.updateEditorContent('ok') - cancelBtn = wrapper.find('.cancelBtn') - cancelBtn.trigger('click') - expect(cancelMethodSpy).toHaveBeenCalledTimes(1) - }) - - describe('mutation resolves', () => { - beforeEach(async () => { + it('calls the apollo mutation when form is submitted', async () => { wrapper.vm.updateEditorContent('this is a comment') - wrapper.find('form').trigger('submit') + await wrapper.find('form').trigger('submit') + expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1) }) - it('shows a success toaster', async () => { - await mocks.$apollo.mutate - expect(mocks.$toast.success).toHaveBeenCalledTimes(1) - }) - - it('clears the editor', () => { + it('calls `clear` method when the cancel button is clicked', async () => { + wrapper.vm.updateEditorContent('ok') + await wrapper.find('.cancelBtn').trigger('submit') expect(cancelMethodSpy).toHaveBeenCalledTimes(1) }) - describe('mutation fails', () => { - it('shows the error toaster', async () => { - await wrapper.find('form').trigger('submit') + describe('mutation resolves', () => { + beforeEach(async () => { + wrapper.vm.updateEditorContent('this is a comment') + wrapper.find('form').trigger('submit') + }) + + it('shows a success toaster', async () => { await mocks.$apollo.mutate - expect(mocks.$toast.error).toHaveBeenCalledTimes(1) + expect(mocks.$toast.success).toHaveBeenCalledTimes(1) + }) + + it('clears the editor', () => { + expect(cancelMethodSpy).toHaveBeenCalledTimes(1) + }) + + describe('mutation fails', () => { + it('shows the error toaster', async () => { + await wrapper.find('form').trigger('submit') + await mocks.$apollo.mutate + expect(mocks.$toast.error).toHaveBeenCalledTimes(1) + }) + }) + }) + }) + + describe('update comment', () => { + beforeEach(() => { + mocks = { + ...mocks, + $apollo: { + mutate: jest + .fn() + .mockResolvedValueOnce({ + data: { + UpdateComment: { + contentExcerpt: 'this is a comment', + }, + }, + }) + .mockRejectedValue({ + message: 'Ouch!', + }), + }, + } + propsData = { + update: true, + comment: { + id: 'c001', + }, + } + const Wrapper = () => { + return mount(CommentForm, { + mocks, + localVue, + propsData, + }) + } + wrapper = Wrapper() + closeMethodSpy = jest.spyOn(wrapper.vm, 'closeEditWindow') + }) + + describe('form submitted', () => { + it('calls the apollo mutation', async () => { + wrapper.vm.updateEditorContent('this is a comment') + await wrapper.find('form').trigger('submit') + expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1) + }) + + it('calls `closeEditWindow` method', async () => { + wrapper.vm.updateEditorContent('ok') + await wrapper.find('form').trigger('submit') + expect(closeMethodSpy).toHaveBeenCalledTimes(1) + }) + + it('emits `showEditCommentMenu` event', async () => { + wrapper.vm.updateEditorContent('ok') + await wrapper.find('form').trigger('submit') + expect(wrapper.emitted('showEditCommentMenu')).toEqual([[false]]) + }) + }) + + describe('cancel button is clicked', () => { + it('calls `closeEditWindow` method', async () => { + wrapper.vm.updateEditorContent('ok') + await wrapper.find('.cancelBtn').trigger('submit') + expect(closeMethodSpy).toHaveBeenCalledTimes(1) + }) + + it('emits `showEditCommentMenu` event', async () => { + wrapper.vm.updateEditorContent('ok') + await wrapper.find('.cancelBtn').trigger('submit') + expect(wrapper.emitted('showEditCommentMenu')).toEqual([[false]]) + }) + }) + + describe('mutation resolves', () => { + beforeEach(async () => { + wrapper.vm.updateEditorContent('this is a comment') + wrapper.find('form').trigger('submit') + }) + + it('shows a success toaster', async () => { + await mocks.$apollo.mutate + expect(mocks.$toast.success).toHaveBeenCalledTimes(1) + }) + + it('closes the editor', () => { + expect(closeMethodSpy).toHaveBeenCalledTimes(1) + }) + + describe('mutation fails', () => { + it('shows the error toaster', async () => { + await wrapper.find('form').trigger('submit') + await mocks.$apollo.mutate + expect(mocks.$toast.error).toHaveBeenCalledTimes(1) + }) }) }) }) From 36a0844fdd79c44091b0a209b3729249e8f1ac93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 12 Sep 2019 09:30:47 +0200 Subject: [PATCH 6/6] Followed @mattwrd18 suggestion --- webapp/components/Comment.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/components/Comment.vue b/webapp/components/Comment.vue index b7c73b84d..ca96c8d59 100644 --- a/webapp/components/Comment.vue +++ b/webapp/components/Comment.vue @@ -135,7 +135,7 @@ export default { editCommentMenu(showMenu) { this.openEditCommentMenu = showMenu }, - async updateComment(comment) { + updateComment(comment) { this.$emit('updateComment', comment) }, async deleteCommentCallback() {