Merge pull request #977 from Human-Connection/806-HcContributionForm-submit-is-not-disabled-by-default

HcContributionForm submit is disabled by default
This commit is contained in:
Wolfgang Huß 2019-07-31 15:20:03 +02:00 committed by GitHub
commit c1a13f3e0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 126 additions and 73 deletions

View File

@ -127,4 +127,4 @@
"prettier": "~1.18.2",
"supertest": "~4.0.2"
}
}
}

View File

@ -1583,32 +1583,6 @@ apollo-server-caching@0.5.0:
dependencies:
lru-cache "^5.0.0"
apollo-server-core@2.7.2:
version "2.7.2"
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.7.2.tgz#4acd9f4d0d235bef0e596e2a821326dfc07ae7b2"
integrity sha512-Dv6ZMMf8Y+ovkj1ioMtcYvjbcsSMqnZblbPPzOWo29vvKEjMXAL1OTSL1WBYxGA/WSBSCTnxAzipn71XZkYoCw==
dependencies:
"@apollographql/apollo-tools" "^0.4.0"
"@apollographql/graphql-playground-html" "1.6.24"
"@types/ws" "^6.0.0"
apollo-cache-control "0.8.1"
apollo-datasource "0.6.1"
apollo-engine-reporting "1.4.2"
apollo-server-caching "0.5.0"
apollo-server-env "2.4.1"
apollo-server-errors "2.3.1"
apollo-server-plugin-base "0.6.1"
apollo-server-types "0.2.1"
apollo-tracing "0.8.1"
fast-json-stable-stringify "^2.0.0"
graphql-extensions "0.8.2"
graphql-tag "^2.9.2"
graphql-tools "^4.0.0"
graphql-upload "^8.0.2"
sha.js "^2.4.11"
subscriptions-transport-ws "^0.9.11"
ws "^6.0.0"
apollo-server-core@2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.8.0.tgz#0bfba3d5eb557c6ffa68ad60e77f69e2634e211d"

View File

@ -26,9 +26,22 @@ describe('ContributionForm.vue', () => {
let mocks
let propsData
const postTitle = 'this is a title for a post'
const postTitleTooShort = 'xx'
let postTitleTooLong = ''
for (let i = 0; i < 65; i++) {
postTitleTooLong += 'x'
}
const postContent = 'this is a post'
const postContentTooShort = 'xx'
let postContentTooLong = ''
for (let i = 0; i < 2001; i++) {
postContentTooLong += 'x'
}
const imageUpload = {
file: { filename: 'avataar.svg', previewElement: '' },
file: {
filename: 'avataar.svg',
previewElement: '',
},
url: 'someUrlToImage',
}
const image = '/uploads/1562010976466-avataaars'
@ -36,22 +49,17 @@ describe('ContributionForm.vue', () => {
mocks = {
$t: jest.fn(),
$apollo: {
mutate: jest
.fn()
.mockResolvedValueOnce({
data: {
CreatePost: {
title: postTitle,
slug: 'this-is-a-title-for-a-post',
content: postContent,
contentExcerpt: postContent,
language: 'en',
},
mutate: jest.fn().mockResolvedValueOnce({
data: {
CreatePost: {
title: postTitle,
slug: 'this-is-a-title-for-a-post',
content: postContent,
contentExcerpt: postContent,
language: 'en',
},
})
.mockRejectedValue({
message: 'Not Authorised!',
}),
},
}),
},
$toast: {
error: jest.fn(),
@ -115,16 +123,53 @@ describe('ContributionForm.vue', () => {
})
describe('invalid form submission', () => {
it('title required for form submission', async () => {
postTitleInput = wrapper.find('.ds-input')
postTitleInput.setValue(postTitle)
await wrapper.find('form').trigger('submit')
it('title and content should not be empty ', async () => {
wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
})
it('content required for form submission', async () => {
wrapper.vm.updateEditorContent(postContent)
await wrapper.find('form').trigger('submit')
it('title should not be empty', async () => {
await wrapper.vm.updateEditorContent(postContent)
wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
})
it('title should not be too long', async () => {
postTitleInput = wrapper.find('.ds-input')
postTitleInput.setValue(postTitleTooLong)
await wrapper.vm.updateEditorContent(postContent)
wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
})
it('title should not be too short', async () => {
postTitleInput = wrapper.find('.ds-input')
postTitleInput.setValue(postTitleTooShort)
await wrapper.vm.updateEditorContent(postContent)
wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
})
it('content should not be empty', async () => {
postTitleInput = wrapper.find('.ds-input')
postTitleInput.setValue(postTitle)
await wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
})
it('content should not be too short', async () => {
postTitleInput = wrapper.find('.ds-input')
postTitleInput.setValue(postTitle)
await wrapper.vm.updateEditorContent(postContentTooShort)
wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
})
it('content should not be too long', async () => {
postTitleInput = wrapper.find('.ds-input')
postTitleInput.setValue(postTitle)
await wrapper.vm.updateEditorContent(postContentTooLong)
wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
})
})
@ -145,15 +190,16 @@ describe('ContributionForm.vue', () => {
}
postTitleInput = wrapper.find('.ds-input')
postTitleInput.setValue(postTitle)
wrapper.vm.updateEditorContent(postContent)
await wrapper.find('form').trigger('submit')
await wrapper.vm.updateEditorContent(postContent)
})
it('with title and content', () => {
wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
})
it("sends a fallback language based on a user's locale", () => {
wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
})
@ -161,7 +207,7 @@ describe('ContributionForm.vue', () => {
expectedParams.variables.language = 'de'
deutschOption = wrapper.findAll('li').at(0)
deutschOption.trigger('click')
await wrapper.find('form').trigger('submit')
wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
})
@ -169,22 +215,26 @@ describe('ContributionForm.vue', () => {
const categoryIds = ['cat12', 'cat15', 'cat37']
expectedParams.variables.categoryIds = categoryIds
wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds)
await wrapper.find('form').trigger('submit')
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)
await wrapper.find('form').trigger('submit')
await wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
})
it("pushes the user to the post's page", async () => {
wrapper.find('.submit-button-for-test').trigger('click')
await mocks.$apollo.mutate
expect(mocks.$router.push).toHaveBeenCalledTimes(1)
})
it('shows a success toaster', () => {
it('shows a success toaster', async () => {
wrapper.find('.submit-button-for-test').trigger('click')
await mocks.$apollo.mutate
expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
})
})
@ -200,18 +250,19 @@ describe('ContributionForm.vue', () => {
describe('handles errors', () => {
beforeEach(async () => {
jest.useFakeTimers()
mocks.$apollo.mutate = jest.fn().mockRejectedValueOnce({
message: 'Not Authorised!',
})
wrapper = Wrapper()
postTitleInput = wrapper.find('.ds-input')
postTitleInput.setValue(postTitle)
wrapper.vm.updateEditorContent(postContent)
// second submission causes mutation to reject
await wrapper.find('form').trigger('submit')
await wrapper.vm.updateEditorContent(postContent)
})
it('shows an error toaster when apollo mutation rejects', async () => {
await wrapper.find('form').trigger('submit')
await wrapper.find('.submit-button-for-test').trigger('click')
await mocks.$apollo.mutate
expect(mocks.$toast.error).toHaveBeenCalledWith('Not Authorised!')
await expect(mocks.$toast.error).toHaveBeenCalledWith('Not Authorised!')
})
})
})
@ -226,7 +277,12 @@ describe('ContributionForm.vue', () => {
content: 'auf Deutsch geschrieben',
language: 'de',
image,
categories: [{ id: 'cat12', name: 'Democracy & Politics' }],
categories: [
{
id: 'cat12',
name: 'Democracy & Politics',
},
],
},
}
wrapper = Wrapper()
@ -264,7 +320,7 @@ describe('ContributionForm.vue', () => {
postTitleInput = wrapper.find('.ds-input')
postTitleInput.setValue(postTitle)
wrapper.vm.updateEditorContent(postContent)
await wrapper.find('form').trigger('submit')
await wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
})
@ -275,7 +331,7 @@ describe('ContributionForm.vue', () => {
wrapper.vm.updateEditorContent(postContent)
expectedParams.variables.categoryIds = categoryIds
wrapper.find(CategoriesSelect).vm.$emit('updateCategories', categoryIds)
await wrapper.find('form').trigger('submit')
await wrapper.find('.submit-button-for-test').trigger('click')
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
})
})

View File

@ -1,5 +1,5 @@
<template>
<ds-form ref="contributionForm" v-model="form" :schema="formSchema" @submit="submit">
<ds-form ref="contributionForm" v-model="form" :schema="formSchema">
<template slot-scope="{ errors }">
<ds-card>
<hc-teaser-image :contribution="contribution" @addTeaserImage="addTeaserImage">
@ -13,6 +13,7 @@
<hc-user :user="currentUser" :trunc="35" />
<ds-space />
<ds-input model="title" class="post-title" placeholder="Title" name="title" autofocus />
<small class="smallTag">{{ form.title.length }}/{{ formSchema.title.max }}</small>
<no-ssr>
<hc-editor
:users="users"
@ -20,6 +21,7 @@
:value="form.content"
@input="updateEditorContent"
/>
<small class="smallTag">{{ form.contentLength }}/{{ contentMax }}</small>
</no-ssr>
<ds-space margin-bottom="xxx-large" />
<hc-categories-select
@ -44,18 +46,20 @@
<div slot="footer" style="text-align: right">
<ds-button
class="cancel-button"
:disabled="loading || disabled"
:disabled="loading"
ghost
@click.prevent="$router.back()"
>
{{ $t('actions.cancel') }}
</ds-button>
<ds-button
class="submit-button-for-test"
type="submit"
icon="check"
:loading="loading"
:disabled="disabled || errors"
:disabled="disabledByContent || errors"
primary
@click.prevent="submit"
>
{{ $t('actions.save') }}
</ds-button>
@ -92,6 +96,7 @@ export default {
form: {
title: '',
content: '',
contentLength: 0,
teaserImage: null,
image: null,
language: null,
@ -100,13 +105,16 @@ export default {
},
formSchema: {
title: { required: true, min: 3, max: 64 },
content: { required: true, min: 3 },
content: [{ required: true }],
},
id: null,
loading: false,
disabled: false,
disabledByContent: true,
slug: null,
users: [],
contentMin: 3,
contentMax: 2000,
hashtags: [],
}
},
@ -119,8 +127,9 @@ export default {
}
this.id = contribution.id
this.slug = contribution.slug
this.form.content = contribution.content
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)
},
@ -169,7 +178,7 @@ export default {
.then(res => {
this.loading = false
this.$toast.success(this.$t('contribution.success'))
this.disabled = true
this.disabledByContent = true
const result = res.data[this.id ? 'UpdatePost' : 'CreatePost']
this.$router.push({
@ -180,12 +189,21 @@ export default {
.catch(err => {
this.$toast.error(err.message)
this.loading = false
this.disabled = false
this.disabledByContent = false
})
},
updateEditorContent(value) {
// this.form.content = value
// TODO: Do smth????? what is happening
this.$refs.contributionForm.update('content', value)
this.manageContent(value)
},
manageContent(content) {
// filter HTML out of content value
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)
},
availableLocales() {
orderBy(locales, 'name').map(locale => {
@ -242,6 +260,11 @@ export default {
</script>
<style lang="scss">
.smallTag {
width: 100%;
position: relative;
left: 90%;
}
.post-title {
margin-top: $space-x-small;
margin-bottom: $space-xx-small;