diff --git a/webapp/components/ContributionForm/ContributionForm.spec.js b/webapp/components/ContributionForm/ContributionForm.spec.js index 199d6ab18..e1f293c77 100644 --- a/webapp/components/ContributionForm/ContributionForm.spec.js +++ b/webapp/components/ContributionForm/ContributionForm.spec.js @@ -28,6 +28,7 @@ describe('ContributionForm.vue', () => { let cancelBtn let mocks let propsData + let categoryIds const postTitle = 'this is a title for a post' const postTitleTooShort = 'xx' let postTitleTooLong = '' @@ -60,6 +61,7 @@ describe('ContributionForm.vue', () => { content: postContent, contentExcerpt: postContent, language: 'en', + categoryIds, }, }, }), @@ -175,6 +177,23 @@ describe('ContributionForm.vue', () => { wrapper.find('.submit-button-for-test').trigger('click') expect(mocks.$apollo.mutate).not.toHaveBeenCalled() }) + + it('should have at least one category', async () => { + postTitleInput = wrapper.find('.ds-input') + postTitleInput.setValue(postTitle) + await wrapper.vm.updateEditorContent(postContent) + wrapper.find('.submit-button-for-test').trigger('click') + expect(mocks.$apollo.mutate).not.toHaveBeenCalled() + }) + + it('should have not have more than three categories', async () => { + postTitleInput = wrapper.find('.ds-input') + postTitleInput.setValue(postTitle) + await wrapper.vm.updateEditorContent(postContent) + wrapper.vm.form.categoryIds = ['cat4', 'cat9', 'cat15', 'cat27'] + wrapper.find('.submit-button-for-test').trigger('click') + expect(mocks.$apollo.mutate).not.toHaveBeenCalled() + }) }) describe('valid form submission', () => { @@ -186,7 +205,7 @@ describe('ContributionForm.vue', () => { content: postContent, language: 'en', id: null, - categoryIds: null, + categoryIds: ['cat12'], imageUpload: null, image: null, }, @@ -194,11 +213,13 @@ describe('ContributionForm.vue', () => { postTitleInput = wrapper.find('.ds-input') postTitleInput.setValue(postTitle) await wrapper.vm.updateEditorContent(postContent) + categoryIds = ['cat12'] + wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds) }) - it('with title and content', () => { - wrapper.find('.submit-button-for-test').trigger('click') - expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1) + it('creates a post with valid title, content, and at least one category', async () => { + await wrapper.find('.submit-button-for-test').trigger('click') + expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams)) }) it("sends a fallback language based on a user's locale", () => { @@ -214,14 +235,6 @@ describe('ContributionForm.vue', () => { expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams)) }) - it('supports adding categories', async () => { - const categoryIds = ['cat12', 'cat15', 'cat37'] - expectedParams.variables.categoryIds = categoryIds - wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds) - await wrapper.find('.submit-button-for-test').trigger('click') - expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams)) - }) - it('supports adding a teaser image', async () => { expectedParams.variables.imageUpload = imageUpload wrapper.find(TeaserImage).vm.$emit('addTeaserImage', imageUpload) @@ -260,6 +273,8 @@ describe('ContributionForm.vue', () => { postTitleInput = wrapper.find('.ds-input') postTitleInput.setValue(postTitle) await wrapper.vm.updateEditorContent(postContent) + categoryIds = ['cat12'] + wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds) }) it('shows an error toaster when apollo mutation rejects', async () => { @@ -307,35 +322,54 @@ describe('ContributionForm.vue', () => { expect(wrapper.vm.form.content).toEqual(propsData.contribution.content) }) - it('calls the UpdatePost apollo mutation', async () => { - expectedParams = { - mutation: PostMutations().UpdatePost, - variables: { - title: postTitle, - content: postContent, - language: propsData.contribution.language, - id: propsData.contribution.id, - categoryIds: ['cat12'], - image, - imageUpload: null, - }, - } - postTitleInput = wrapper.find('.ds-input') - postTitleInput.setValue(postTitle) - wrapper.vm.updateEditorContent(postContent) - await wrapper.find('.submit-button-for-test').trigger('click') - expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams)) - }) + describe('valid update', () => { + beforeEach(() => { + mocks.$apollo.mutate = jest.fn().mockResolvedValueOnce({ + data: { + UpdatePost: { + title: postTitle, + slug: 'this-is-a-title-for-a-post', + content: postContent, + contentExcerpt: postContent, + language: 'en', + categoryIds, + }, + }, + }) + wrapper = Wrapper() + expectedParams = { + mutation: PostMutations().UpdatePost, + variables: { + title: postTitle, + content: postContent, + language: propsData.contribution.language, + id: propsData.contribution.id, + categoryIds, + image, + imageUpload: null, + }, + } + }) - it('supports updating categories', async () => { - const categoryIds = ['cat3', 'cat51', 'cat37'] - postTitleInput = wrapper.find('.ds-input') - postTitleInput.setValue(postTitle) - wrapper.vm.updateEditorContent(postContent) - expectedParams.variables.categoryIds = categoryIds - wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds) - await wrapper.find('.submit-button-for-test').trigger('click') - expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams)) + it('calls the UpdatePost apollo mutation', async () => { + postTitleInput = wrapper.find('.ds-input') + postTitleInput.setValue(postTitle) + wrapper.vm.updateEditorContent(postContent) + wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds) + wrapper.find('.submit-button-for-test').trigger('click') + expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams)) + }) + + it('supports updating categories', async () => { + const categoryIds = ['cat3', 'cat51', 'cat37'] + postTitleInput = wrapper.find('.ds-input') + postTitleInput.setValue(postTitle) + wrapper.vm.updateEditorContent(postContent) + expectedParams.variables.categoryIds = categoryIds + wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds) + await wrapper.find('.submit-button-for-test').trigger('click') + expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams)) + }) }) }) }) diff --git a/webapp/components/ContributionForm/ContributionForm.vue b/webapp/components/ContributionForm/ContributionForm.vue index 9d1c92cc9..fcea30c59 100644 --- a/webapp/components/ContributionForm/ContributionForm.vue +++ b/webapp/components/ContributionForm/ContributionForm.vue @@ -57,7 +57,7 @@ type="submit" icon="check" :loading="loading" - :disabled="disabledByContent || errors" + :disabled="failsValidations || errors" primary @click.prevent="submit" > @@ -101,7 +101,7 @@ export default { image: null, language: null, languageOptions: [], - categoryIds: null, + categoryIds: [], }, formSchema: { title: { required: true, min: 3, max: 64 }, @@ -109,12 +109,11 @@ export default { }, id: null, loading: false, - disabledByContent: true, slug: null, users: [], contentMin: 3, contentMax: 2000, - + failsValidations: true, hashtags: [], } }, @@ -129,9 +128,9 @@ export default { this.slug = contribution.slug this.form.title = contribution.title this.form.content = contribution.content - this.manageContent(this.form.content) this.form.image = contribution.image this.form.categoryIds = this.categoryIds(contribution.categories) + this.manageContent(this.form.content) }, }, }, @@ -175,11 +174,11 @@ export default { imageUpload: teaserImage, }, }) - .then(res => { + .then(({ data }) => { this.loading = false this.$toast.success(this.$t('contribution.success')) - this.disabledByContent = true - const result = res.data[this.id ? 'UpdatePost' : 'CreatePost'] + this.failedValidations = true + const result = data[this.id ? 'UpdatePost' : 'CreatePost'] this.$router.push({ name: 'post-id-slug', @@ -189,7 +188,7 @@ export default { .catch(err => { this.$toast.error(err.message) this.loading = false - this.disabledByContent = false + this.failedValidations = false }) }, updateEditorContent(value) { @@ -202,8 +201,7 @@ export default { const str = content.replace(/<\/?[^>]+(>|$)/gm, '') // Set counter length of text this.form.contentLength = str.length - // Enable save button if requirements are met - this.disabledByContent = !(this.contentMin <= str.length && str.length <= this.contentMax) + this.validatePost() }, availableLocales() { orderBy(locales, 'name').map(locale => { @@ -212,6 +210,7 @@ export default { }, updateCategories(ids) { this.form.categoryIds = ids + this.validatePost() }, addTeaserImage(file) { this.form.teaserImage = file @@ -223,6 +222,13 @@ export default { }) return categoryIds }, + validatePost() { + const passesContentValidations = + this.form.contentLength >= this.contentMin && this.form.contentLength <= this.contentMax + const passesCategoryValidations = + this.form.categoryIds.length > 0 && this.form.categoryIds.length <= 3 + this.failsValidations = !(passesContentValidations && passesCategoryValidations) + }, }, apollo: { User: {