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 6af69d632..e54919cbd 100644 --- a/webapp/components/Comment.vue +++ b/webapp/components/Comment.vue @@ -13,26 +13,29 @@ + + + + - - - -
-
@@ -66,7 +69,7 @@ import { mapGetters } from 'vuex' import HcUser from '~/components/User/User' import ContentMenu from '~/components/ContentMenu' import ContentViewer from '~/components/Editor/ContentViewer' -import HcEditCommentForm from '~/components/EditCommentForm/EditCommentForm' +import HcCommentForm from '~/components/CommentForm/CommentForm' import CommentMutations from '~/graphql/CommentMutations' export default { @@ -80,7 +83,7 @@ export default { HcUser, ContentMenu, ContentViewer, - HcEditCommentForm, + HcCommentForm, }, props: { post: { type: Object, default: () => {} }, @@ -136,6 +139,9 @@ export default { editCommentMenu(showMenu) { this.openEditCommentMenu = showMenu }, + updateComment(comment) { + this.$emit('updateComment', comment) + }, async deleteCommentCallback() { try { const { diff --git a/webapp/components/CommentForm/CommentForm.spec.js b/webapp/components/CommentForm/CommentForm.spec.js index fc20abffa..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,79 +21,180 @@ 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(), }, - } - propsData = { - post: { - id: 1, + $filters: { + removeHtml: a => a, }, } }) 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) + }) }) }) }) diff --git a/webapp/components/CommentForm/CommentForm.vue b/webapp/components/CommentForm/CommentForm.vue index 935784c28..6f1c7d931 100644 --- a/webapp/components/CommentForm/CommentForm.vue +++ b/webapp/components/CommentForm/CommentForm.vue @@ -2,18 +2,18 @@ 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/graphql/User.js b/webapp/graphql/User.js index 37551364e..3bf106991 100644 --- a/webapp/graphql/User.js +++ b/webapp/graphql/User.js @@ -77,6 +77,19 @@ export default i18n => { ` } +export const minimisedUserQuery = () => { + return gql` + query { + User(orderBy: slug_asc) { + id + slug + name + avatar + } + } + ` +} + export const notificationQuery = i18n => { const lang = i18n.locale().toUpperCase() return gql` 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: { 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()