diff --git a/backend/package.json b/backend/package.json
index 565973a67..4036430ea 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -61,7 +61,7 @@
"graphql-custom-directives": "~0.2.14",
"graphql-iso-date": "~3.6.1",
"graphql-middleware": "~3.0.2",
- "graphql-shield": "~5.7.1",
+ "graphql-shield": "~5.6.1",
"graphql-tag": "~2.10.1",
"graphql-yoga": "~1.18.0",
"helmet": "~3.18.0",
diff --git a/backend/src/schema/resolvers/fileUpload/index.js b/backend/src/schema/resolvers/fileUpload/index.js
index c37d87e39..fa78238c3 100644
--- a/backend/src/schema/resolvers/fileUpload/index.js
+++ b/backend/src/schema/resolvers/fileUpload/index.js
@@ -12,7 +12,6 @@ const storeUpload = ({ createReadStream, fileLocation }) =>
export default async function fileUpload(params, { file, url }, uploadCallback = storeUpload) {
const upload = params[file]
-
if (upload) {
const { createReadStream, filename } = await upload
const { name } = path.parse(filename)
diff --git a/backend/src/schema/types/scalar/Upload.gql b/backend/src/schema/types/scalar/Upload.gql
index fca9ea1fc..cf3965846 100644
--- a/backend/src/schema/types/scalar/Upload.gql
+++ b/backend/src/schema/types/scalar/Upload.gql
@@ -1 +1 @@
-scalar Upload
\ No newline at end of file
+scalar Upload
diff --git a/backend/yarn.lock b/backend/yarn.lock
index 14cec1a81..b66143b5e 100644
--- a/backend/yarn.lock
+++ b/backend/yarn.lock
@@ -1110,10 +1110,10 @@
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0"
integrity sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA==
-"@types/yup@0.26.17":
- version "0.26.17"
- resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.17.tgz#5cb7cfc211d8e985b21d88289542591c92cad9dc"
- integrity sha512-MN7VHlPsZQ2MTBxLE2Gl+Qfg2WyKsoz+vIr8xN0OSZ4AvJDrrKBlxc8b59UXCCIG9tPn9XhxTXh3j/htHbzC2Q==
+"@types/yup@0.26.16":
+ version "0.26.16"
+ resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.16.tgz#75c428236207c48d9f8062dd1495cda8c5485a15"
+ integrity sha512-E2RNc7DSeQ+2EIJ1H3+yFjYu6YiyQBUJ7yNpIxomrYJ3oFizLZ5yDS3T1JTUNBC2OCRkgnhLS0smob5UuCHfNA==
"@types/zen-observable@^0.5.3":
version "0.5.4"
@@ -3788,12 +3788,12 @@ graphql-request@~1.8.2:
dependencies:
cross-fetch "2.2.2"
-graphql-shield@~5.7.1:
- version "5.7.1"
- resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.7.1.tgz#04095fb8148a463997f7c509d4aeb2a6abf79f98"
- integrity sha512-UZ0K1uAqRAoGA1U2DsUu4vIZX2Vents4Xim99GFEUBTgvSDkejiE+k/Dywqfu76lJFEE8qu3vG5fhJN3SmnKbA==
+graphql-shield@~5.6.1:
+ version "5.6.2"
+ resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.6.2.tgz#27eaad2ce2591ed81b1203e8915df99b28fb5ad5"
+ integrity sha512-DlS6r39s7AaP07yMM6i7GI87UkfL65O1tUPW4kNqp67fD1BU71Ekl7Kt/1L3rxS/gcQdGufuKka5oKUa5GKo2A==
dependencies:
- "@types/yup" "0.26.17"
+ "@types/yup" "0.26.16"
lightercollective "^0.3.0"
object-hash "^1.3.1"
yup "^0.27.0"
diff --git a/webapp/components/ContributionForm/ContributionForm.spec.js b/webapp/components/ContributionForm/ContributionForm.spec.js
index 6856a64b2..caeeafdf6 100644
--- a/webapp/components/ContributionForm/ContributionForm.spec.js
+++ b/webapp/components/ContributionForm/ContributionForm.spec.js
@@ -3,11 +3,14 @@ import ContributionForm from './index.vue'
import Styleguide from '@human-connection/styleguide'
import Vuex from 'vuex'
import PostMutations from '~/graphql/PostMutations.js'
+import Filters from '~/plugins/vue-filters'
+import TeaserImage from '~/components/TeaserImage/TeaserImage'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Styleguide)
+localVue.use(Filters)
config.stubs['no-ssr'] = ''
@@ -21,6 +24,10 @@ describe('ContributionForm.vue', () => {
let propsData
const postTitle = 'this is a title for a post'
const postContent = 'this is a post'
+ const imageUpload = {
+ file: { filename: 'avataar.svg', previewElement: '' },
+ url: 'someUrlToImage',
+ }
beforeEach(() => {
mocks = {
@@ -100,7 +107,13 @@ describe('ContributionForm.vue', () => {
beforeEach(async () => {
expectedParams = {
mutation: PostMutations().CreatePost,
- variables: { title: postTitle, content: postContent, language: 'en', id: null },
+ variables: {
+ title: postTitle,
+ content: postContent,
+ language: 'en',
+ id: null,
+ imageUpload: null,
+ },
}
postTitleInput = wrapper.find('.ds-input')
postTitleInput.setValue(postTitle)
@@ -124,6 +137,13 @@ describe('ContributionForm.vue', () => {
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')
+ expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expect.objectContaining(expectedParams))
+ })
+
it("pushes the user to the post's page", async () => {
expect(mocks.$router.push).toHaveBeenCalledTimes(1)
})
@@ -143,6 +163,7 @@ describe('ContributionForm.vue', () => {
describe('handles errors', () => {
beforeEach(async () => {
+ jest.useFakeTimers()
wrapper = Wrapper()
postTitleInput = wrapper.find('.ds-input')
postTitleInput.setValue(postTitle)
@@ -150,6 +171,7 @@ describe('ContributionForm.vue', () => {
// second submission causes mutation to reject
await wrapper.find('form').trigger('submit')
})
+
it('shows an error toaster when apollo mutation rejects', async () => {
await wrapper.find('form').trigger('submit')
await mocks.$apollo.mutate
@@ -167,6 +189,7 @@ describe('ContributionForm.vue', () => {
title: 'dies ist ein Post',
content: 'auf Deutsch geschrieben',
language: 'de',
+ imageUpload,
},
}
wrapper = Wrapper()
@@ -188,10 +211,6 @@ describe('ContributionForm.vue', () => {
expect(wrapper.vm.form.content).toEqual(propsData.contribution.content)
})
- it('sets language equal to contribution language', () => {
- expect(wrapper.vm.form.language).toEqual({ value: propsData.contribution.language })
- })
-
it('calls the UpdatePost apollo mutation', async () => {
expectedParams = {
mutation: PostMutations().UpdatePost,
@@ -200,6 +219,7 @@ describe('ContributionForm.vue', () => {
content: postContent,
language: propsData.contribution.language,
id: propsData.contribution.id,
+ imageUpload,
},
}
postTitleInput = wrapper.find('.ds-input')
diff --git a/webapp/components/ContributionForm/index.vue b/webapp/components/ContributionForm/index.vue
index c925a6dca..57eb105be 100644
--- a/webapp/components/ContributionForm/index.vue
+++ b/webapp/components/ContributionForm/index.vue
@@ -2,6 +2,13 @@
+
+
+
@@ -39,6 +46,7 @@
{{ $t('actions.save') }}
+
@@ -50,10 +58,12 @@ import HcEditor from '~/components/Editor'
import orderBy from 'lodash/orderBy'
import locales from '~/locales'
import PostMutations from '~/graphql/PostMutations.js'
+import HcTeaserImage from '~/components/TeaserImage/TeaserImage'
export default {
components: {
HcEditor,
+ HcTeaserImage,
},
props: {
contribution: { type: Object, default: () => {} },
@@ -63,6 +73,7 @@ export default {
form: {
title: '',
content: '',
+ teaserImage: null,
language: null,
languageOptions: [],
},
@@ -88,7 +99,7 @@ export default {
this.slug = contribution.slug
this.form.content = contribution.content
this.form.title = contribution.title
- this.form.language = { value: contribution.language }
+ this.form.teaserImage = contribution.imageUpload
},
},
},
@@ -106,15 +117,25 @@ export default {
},
methods: {
submit() {
+ const { title, content, teaserImage } = this.form
+ let language
+ if (this.form.language) {
+ language = this.form.language.value
+ } else if (this.contribution && this.contribution.language) {
+ language = this.contribution.language
+ } else {
+ language = this.$i18n.locale()
+ }
this.loading = true
this.$apollo
.mutate({
mutation: this.id ? PostMutations().UpdatePost : PostMutations().CreatePost,
variables: {
id: this.id,
- title: this.form.title,
- content: this.form.content,
- language: this.form.language ? this.form.language.value : this.$i18n.locale(),
+ title,
+ content,
+ language,
+ imageUpload: teaserImage,
},
})
.then(res => {
@@ -144,6 +165,9 @@ export default {
this.form.languageOptions.push({ label: locale.name, value: locale.code })
})
},
+ addTeaserImage(file) {
+ this.form.teaserImage = file
+ },
},
apollo: {
User: {
@@ -176,8 +200,4 @@ export default {
padding-right: 0;
}
}
-
-.contribution-form-footer {
- border-top: $border-size-base solid $border-color-softest;
-}
diff --git a/webapp/components/TeaserImage/TeaserImage.spec.js b/webapp/components/TeaserImage/TeaserImage.spec.js
new file mode 100644
index 000000000..07b17e16b
--- /dev/null
+++ b/webapp/components/TeaserImage/TeaserImage.spec.js
@@ -0,0 +1,61 @@
+import { mount, createLocalVue } from '@vue/test-utils'
+import TeaserImage from './TeaserImage.vue'
+import Styleguide from '@human-connection/styleguide'
+
+const localVue = createLocalVue()
+
+localVue.use(Styleguide)
+
+describe('TeaserImage.vue', () => {
+ let wrapper
+ let mocks
+
+ beforeEach(() => {
+ mocks = {
+ $toast: {
+ error: jest.fn(),
+ },
+ }
+ })
+ describe('mount', () => {
+ const Wrapper = () => {
+ return mount(TeaserImage, { mocks, localVue })
+ }
+ beforeEach(() => {
+ wrapper = Wrapper()
+ })
+
+ describe('File upload', () => {
+ const imageUpload = [
+ { file: { filename: 'avataar.svg', previewElement: '' }, url: 'someUrlToImage' },
+ ]
+
+ it('supports adding a teaser image', () => {
+ wrapper.vm.addTeaserImage(imageUpload)
+ expect(wrapper.emitted().addTeaserImage[0]).toEqual(imageUpload)
+ })
+ })
+
+ describe('handles errors', () => {
+ beforeEach(() => jest.useFakeTimers())
+ const message = 'File upload failed'
+ const fileError = { status: 'error' }
+
+ it('defaults to error false', () => {
+ expect(wrapper.vm.error).toEqual(false)
+ })
+
+ it('shows an error toaster when verror is called', () => {
+ wrapper.vm.verror(fileError, message)
+ expect(mocks.$toast.error).toHaveBeenCalledWith(fileError.status, message)
+ })
+
+ it('changes error status from false to true to false', () => {
+ wrapper.vm.verror(fileError, message)
+ expect(wrapper.vm.error).toEqual(true)
+ jest.runAllTimers()
+ expect(wrapper.vm.error).toEqual(false)
+ })
+ })
+ })
+})
diff --git a/webapp/components/TeaserImage/TeaserImage.vue b/webapp/components/TeaserImage/TeaserImage.vue
new file mode 100644
index 000000000..cb657fe9a
--- /dev/null
+++ b/webapp/components/TeaserImage/TeaserImage.vue
@@ -0,0 +1,197 @@
+
+
+
+
+
+
+
+
diff --git a/webapp/components/Upload/index.vue b/webapp/components/Upload/index.vue
index f7f730632..3f84f8a7c 100644
--- a/webapp/components/Upload/index.vue
+++ b/webapp/components/Upload/index.vue
@@ -9,7 +9,7 @@
@vdropzone-error="verror"
>