Extract TeaserImage component, refactor tests

This commit is contained in:
Matt Rider 2019-06-25 20:49:09 -03:00
parent 6d8e981043
commit 2baaf85b21
5 changed files with 290 additions and 205 deletions

View File

@ -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'] = '<span><slot /></span>'
@ -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 = {
@ -97,9 +104,6 @@ describe('ContributionForm.vue', () => {
})
describe('valid form submission', () => {
const imageUpload = [
{ file: { filename: 'avataar.svg', previewElement: '' }, url: 'someUrlToImage' },
]
beforeEach(async () => {
expectedParams = {
mutation: PostMutations().CreatePost,
@ -134,8 +138,8 @@ describe('ContributionForm.vue', () => {
})
it('supports adding a teaser image', async () => {
expectedParams.variables.imageUpload = imageUpload[0]
wrapper.vm.addTeaserImage(imageUpload)
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))
})
@ -173,25 +177,6 @@ describe('ContributionForm.vue', () => {
await mocks.$apollo.mutate
expect(mocks.$toast.error).toHaveBeenCalledWith('Not Authorised!')
})
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)
})
})
})
@ -204,6 +189,7 @@ describe('ContributionForm.vue', () => {
title: 'dies ist ein Post',
content: 'auf Deutsch geschrieben',
language: 'de',
imageUpload,
},
}
wrapper = Wrapper()
@ -225,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,
@ -237,6 +219,7 @@ describe('ContributionForm.vue', () => {
content: postContent,
language: propsData.contribution.language,
id: propsData.contribution.id,
imageUpload,
},
}
postTitleInput = wrapper.find('.ds-input')

View File

@ -2,33 +2,13 @@
<ds-form ref="contributionForm" v-model="form" :schema="formSchema" @submit="submit">
<template slot-scope="{ errors }">
<ds-card>
<vue-dropzone
:options="dropzoneOptions"
ref="el"
id="postdropzone"
:use-custom-slot="true"
@vdropzone-thumbnail="thumbnail"
@vdropzone-error="verror"
>
<div class="dz-message">
<div
:class="{
'hc-attachments-upload-area-post': createAndUpdate,
'hc-attachments-upload-area-update-post': contribution,
}"
>
<slot></slot>
<div
:class="{
'hc-drag-marker-post': createAndUpdate,
'hc-drag-marker-update-post': contribution,
}"
>
<ds-icon name="image" size="xxx-large" />
</div>
</div>
</div>
</vue-dropzone>
<hc-teaser-image :contribution="contribution" @addTeaserImage="addTeaserImage">
<img
v-if="contribution"
class="contribution-image"
:src="contribution.image | proxyApiUrl"
/>
</hc-teaser-image>
<ds-input model="title" class="post-title" placeholder="Title" name="title" autofocus />
<no-ssr>
<hc-editor :users="users" :value="form.content" @input="updateEditorContent" />
@ -74,16 +54,16 @@
<script>
import gql from 'graphql-tag'
import vueDropzone from 'nuxt-dropzone'
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,
vueDropzone,
HcTeaserImage,
},
props: {
contribution: { type: Object, default: () => {} },
@ -106,13 +86,6 @@ export default {
disabled: false,
slug: null,
users: [],
dropzoneOptions: {
url: this.addTeaserImage,
maxFilesize: 5.0,
previewTemplate: this.template(),
},
error: false,
createAndUpdate: true,
}
},
watch: {
@ -127,15 +100,8 @@ export default {
this.form.content = contribution.content
this.form.title = contribution.title
this.form.teaserImage = contribution.imageUpload
this.form.language = { value: contribution.language }
},
},
error() {
let that = this
setTimeout(function() {
that.error = false
}, 2000)
},
},
computed: {
locale() {
@ -151,7 +117,15 @@ export default {
},
methods: {
submit() {
const { title, content, language, teaserImage } = this.form
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({
@ -160,7 +134,7 @@ export default {
id: this.id,
title,
content,
language: language ? language.value : this.$i18n.locale(),
language,
imageUpload: teaserImage,
},
})
@ -186,41 +160,13 @@ export default {
// this.form.content = value
this.$refs.contributionForm.update('content', value)
},
template() {
return `<div class="dz-preview dz-file-preview">
<div class="dz-image">
<div data-dz-thumbnail-bg></div>
</div>
</div>
`
},
verror(file, message) {
this.error = true
this.$toast.error(file.status, message)
},
availableLocales() {
orderBy(locales, 'name').map(locale => {
this.form.languageOptions.push({ label: locale.name, value: locale.code })
})
},
addTeaserImage(file) {
this.form.teaserImage = file[0]
return ''
},
thumbnail: (file, dataUrl) => {
let thumbnailElement, contributionImage, uploadArea
if (file.previewElement) {
thumbnailElement = document.querySelectorAll('#postdropzone')[0]
contributionImage = document.querySelectorAll('.contribution-image')[0]
if (contributionImage) {
uploadArea = document.querySelectorAll('.hc-attachments-upload-area-update-post')[0]
uploadArea.removeChild(contributionImage)
uploadArea.classList.remove('hc-attachments-upload-area-update-post')
}
thumbnailElement.classList.add('image-preview')
thumbnailElement.alt = file.name
thumbnailElement.style.backgroundImage = 'url("' + dataUrl + '")'
}
this.form.teaserImage = file
},
},
apollo: {
@ -254,104 +200,4 @@ export default {
padding-right: 0;
}
}
#postdropzone {
width: 100%;
min-height: 300px;
background-color: $background-color-softest;
}
#postdropzone.image-preview {
background-repeat: no-repeat;
background-size: cover;
background-position: center;
height: auto;
transition: all 0.2s ease-out;
width: 100%;
}
@media only screen and (max-width: 400px) {
#postdropzone.image-preview {
height: 200px;
}
}
@media only screen and (min-width: 401px) and (max-width: 960px) {
#postdropzone.image-preview {
height: 300px;
}
}
.hc-attachments-upload-area-post {
position: relative;
display: flex;
justify-content: center;
cursor: pointer;
}
.hc-attachments-upload-area-update-post {
background-color: $background-color-base;
align-items: center;
}
.hc-attachments-upload-area-update-post:hover {
opacity: 0.7;
}
.hc-drag-marker-post {
position: absolute;
width: 122px;
height: 122px;
border-radius: 100%;
display: flex;
align-items: center;
justify-content: center;
color: hsl(0, 0%, 25%);
transition: all 0.2s ease-out;
font-size: 60px;
margin: 80px 5px;
background-color: $background-color-softest;
opacity: 0.65;
&:before {
position: absolute;
content: '';
top: 0;
left: 0;
bottom: 0;
right: 0;
border-radius: 100%;
border: 20px solid $text-color-base;
visibility: hidden;
}
&:after {
position: absolute;
content: '';
top: 10px;
left: 10px;
bottom: 10px;
right: 10px;
border-radius: 100%;
border: $border-size-base dashed $text-color-base;
}
.hc-attachments-upload-area-post:hover & {
opacity: 1;
}
}
.hc-drag-marker-update-post {
opacity: 0;
}
.contribution-form-footer {
border-top: $border-size-base solid $border-color-softest;
}
.contribution-image {
max-height: 300px;
}
</style>

View File

@ -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)
})
})
})
})

View File

@ -0,0 +1,197 @@
<template>
<vue-dropzone
:options="dropzoneOptions"
ref="el"
id="postdropzone"
:use-custom-slot="true"
@vdropzone-thumbnail="thumbnail"
@vdropzone-error="verror"
>
<div class="dz-message">
<div
:class="{
'hc-attachments-upload-area-post': createAndUpdate,
'hc-attachments-upload-area-update-post': contribution,
}"
>
<slot></slot>
<div
:class="{
'hc-drag-marker-post': createAndUpdate,
'hc-drag-marker-update-post': contribution,
}"
>
<ds-icon name="image" size="xxx-large" />
</div>
</div>
</div>
</vue-dropzone>
</template>
<script>
import vueDropzone from 'nuxt-dropzone'
export default {
components: {
vueDropzone,
},
props: {
contribution: { type: Object, default: () => {} },
},
data() {
return {
dropzoneOptions: {
url: this.addTeaserImage,
maxFilesize: 5.0,
previewTemplate: this.template(),
},
error: false,
createAndUpdate: true,
}
},
watch: {
error() {
let that = this
setTimeout(function() {
that.error = false
}, 2000)
},
},
methods: {
template() {
return `<div class="dz-preview dz-file-preview">
<div class="dz-image">
<div data-dz-thumbnail-bg></div>
</div>
</div>
`
},
verror(file, message) {
this.error = true
this.$toast.error(file.status, message)
},
addTeaserImage(file) {
this.$emit('addTeaserImage', file[0])
return ''
},
thumbnail: (file, dataUrl) => {
let thumbnailElement, contributionImage, uploadArea
if (file.previewElement) {
thumbnailElement = document.querySelectorAll('#postdropzone')[0]
contributionImage = document.querySelectorAll('.contribution-image')[0]
if (contributionImage) {
uploadArea = document.querySelectorAll('.hc-attachments-upload-area-update-post')[0]
uploadArea.removeChild(contributionImage)
uploadArea.classList.remove('hc-attachments-upload-area-update-post')
}
thumbnailElement.classList.add('image-preview')
thumbnailElement.alt = file.name
thumbnailElement.style.backgroundImage = 'url("' + dataUrl + '")'
}
},
},
}
</script>
<style lang="scss">
#postdropzone {
width: 100%;
min-height: 300px;
background-color: $background-color-softest;
}
#postdropzone.image-preview {
background-repeat: no-repeat;
background-size: cover;
background-position: center;
height: auto;
transition: all 0.2s ease-out;
width: 100%;
}
@media only screen and (max-width: 400px) {
#postdropzone.image-preview {
height: 200px;
}
}
@media only screen and (min-width: 401px) and (max-width: 960px) {
#postdropzone.image-preview {
height: 300px;
}
}
.hc-attachments-upload-area-post {
position: relative;
display: flex;
justify-content: center;
cursor: pointer;
}
.hc-attachments-upload-area-update-post img {
object-fit: cover;
object-position: center;
display: block;
width: 100%;
}
.hc-attachments-upload-area-update-post:hover {
opacity: 0.7;
}
.hc-drag-marker-post {
position: absolute;
width: 122px;
height: 122px;
border-radius: 100%;
display: flex;
align-items: center;
justify-content: center;
color: hsl(0, 0%, 25%);
transition: all 0.2s ease-out;
font-size: 60px;
margin: 80px 5px;
background-color: $background-color-softest;
opacity: 0.65;
&:before {
position: absolute;
content: '';
top: 0;
left: 0;
bottom: 0;
right: 0;
border-radius: 100%;
border: 20px solid $text-color-base;
visibility: hidden;
}
&:after {
position: absolute;
content: '';
top: 10px;
left: 10px;
bottom: 10px;
right: 10px;
border-radius: 100%;
border: $border-size-base dashed $text-color-base;
}
.hc-attachments-upload-area-post:hover & {
opacity: 1;
}
}
.hc-drag-marker-update-post {
opacity: 0;
}
.contribution-form-footer {
border-top: $border-size-base solid $border-color-softest;
}
.contribution-image {
max-height: 300px;
}
</style>

View File

@ -1,9 +1,7 @@
<template>
<ds-flex :width="{ base: '100%' }" gutter="base">
<ds-flex-item :width="{ base: '100%', md: 3 }">
<hc-contribution-form :contribution="contribution">
<img class="contribution-image" :src="contribution.image | proxyApiUrl" />
</hc-contribution-form>
<hc-contribution-form :contribution="contribution" />
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', md: 1 }">&nbsp;</ds-flex-item>
</ds-flex>