mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-12 23:35:58 +00:00
Merge pull request #605 from Human-Connection/399-user-profile-image-uploads
User profile image uploads
This commit is contained in:
commit
a21fb27351
BIN
cypress/fixtures/onourjourney.png
Normal file
BIN
cypress/fixtures/onourjourney.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
34
cypress/integration/common/profile.js
Normal file
34
cypress/integration/common/profile.js
Normal file
@ -0,0 +1,34 @@
|
||||
import { When, Then } from 'cypress-cucumber-preprocessor/steps'
|
||||
|
||||
/* global cy */
|
||||
|
||||
When('I visit my profile page', () => {
|
||||
cy.openPage('profile/peter-pan')
|
||||
})
|
||||
|
||||
Then('I should be able to change my profile picture', () => {
|
||||
const avatarUpload = 'onourjourney.png'
|
||||
|
||||
cy.fixture(avatarUpload, 'base64').then(fileContent => {
|
||||
cy.get('#customdropzone').upload(
|
||||
{ fileContent, fileName: avatarUpload, mimeType: 'image/png' },
|
||||
{ subjectType: 'drag-n-drop' },
|
||||
)
|
||||
})
|
||||
cy.get('#customdropzone')
|
||||
.should('have.attr', 'style')
|
||||
.and('contains', 'onourjourney')
|
||||
cy.contains('.iziToast-message', 'Upload successful')
|
||||
.should('have.length', 1)
|
||||
})
|
||||
|
||||
When("I visit another user's profile page", () => {
|
||||
cy.openPage('profile/peter-pan')
|
||||
})
|
||||
|
||||
Then('I cannot upload a picture', () => {
|
||||
cy.get('.ds-card-content')
|
||||
.children()
|
||||
.should('not.have.id', 'customdropzone')
|
||||
.should('have.class', 'ds-avatar')
|
||||
})
|
||||
@ -45,6 +45,7 @@ When('people visit my profile page', url => {
|
||||
cy.openPage('/profile/peter-pan')
|
||||
})
|
||||
|
||||
|
||||
When('they can see the text in the info box below my avatar', () => {
|
||||
cy.contains(aboutMeText)
|
||||
})
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
Feature: Upload UserProfile Image
|
||||
As a user
|
||||
I would like to be able to add an avatar/profile pic to my profile
|
||||
So that I can personalize my profile
|
||||
|
||||
|
||||
Background:
|
||||
Given I have a user account
|
||||
|
||||
Scenario: Change my UserProfile Image
|
||||
Given I am logged in
|
||||
And I visit my profile page
|
||||
Then I should be able to change my profile picture
|
||||
|
||||
Scenario: Unable to change another user's avatar
|
||||
Given I am logged in with a "user" role
|
||||
And I visit another user's profile page
|
||||
Then I cannot upload a picture
|
||||
@ -13,7 +13,7 @@
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
|
||||
/* globals Cypress cy */
|
||||
|
||||
import 'cypress-file-upload'
|
||||
import { getLangByName } from './helpers'
|
||||
import users from '../fixtures/users.json'
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
"cross-env": "^5.2.0",
|
||||
"cypress": "^3.3.1",
|
||||
"cypress-cucumber-preprocessor": "^1.11.2",
|
||||
"cypress-file-upload": "^3.1.2",
|
||||
"cypress-plugin-retries": "^1.2.2",
|
||||
"dotenv": "^8.0.0",
|
||||
"faker": "^4.1.0",
|
||||
@ -30,4 +31,4 @@
|
||||
"neo4j-driver": "^1.7.4",
|
||||
"npm-run-all": "^4.1.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
153
webapp/components/Upload/index.vue
Normal file
153
webapp/components/Upload/index.vue
Normal file
@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<div>
|
||||
<vue-dropzone
|
||||
id="customdropzone"
|
||||
:key="user.avatar"
|
||||
ref="el"
|
||||
:options="dropzoneOptions"
|
||||
:include-styling="false"
|
||||
:style="backgroundImage"
|
||||
@vdropzone-thumbnail="thumbnail"
|
||||
@vdropzone-drop="vddrop"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import vueDropzone from 'nuxt-dropzone'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
vueDropzone,
|
||||
},
|
||||
props: {
|
||||
user: { type: Object, default: null },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dropzoneOptions: {
|
||||
url: this.vddrop,
|
||||
maxFilesize: 0.5,
|
||||
previewTemplate: this.template(),
|
||||
dictDefaultMessage: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
backgroundImage() {
|
||||
const { avatar } = this.user || {}
|
||||
const userAvatar = avatar.startsWith('/') ? avatar.replace('/', '/api/') : avatar
|
||||
return {
|
||||
backgroundImage: `url(${userAvatar})`,
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
template() {
|
||||
return `<div class="dz-preview dz-file-preview">
|
||||
<div class="dz-image">
|
||||
<div data-dz-thumbnail-bg></div>
|
||||
</div>
|
||||
<div class="dz-details">
|
||||
<div class="dz-size"><span data-dz-size></span></div>
|
||||
<div class="dz-filename"><span data-dz-name></span></div>
|
||||
</div>
|
||||
<div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>
|
||||
<div class="dz-error-message"><span data-dz-errormessage></span></div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
},
|
||||
thumbnail(file, dataUrl) {
|
||||
let j, len, ref, thumbnailElement
|
||||
if (file.previewElement) {
|
||||
this.$refs.el.$el.style.backgroundImage = ''
|
||||
file.previewElement.classList.remove('dz-file-preview')
|
||||
ref = file.previewElement.querySelectorAll('[data-dz-thumbnail-bg]')
|
||||
for (j = 0, len = ref.length; j < len; j++) {
|
||||
thumbnailElement = ref[j]
|
||||
thumbnailElement.alt = file.name
|
||||
thumbnailElement.style.backgroundImage = 'url("' + dataUrl + '")'
|
||||
}
|
||||
file.previewElement.classList.add('dz-image-preview')
|
||||
}
|
||||
},
|
||||
vddrop(file) {
|
||||
const avatarUpload = file[0]
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: gql`
|
||||
mutation($id: ID!, $avatarUpload: Upload) {
|
||||
UpdateUser(id: $id, avatarUpload: $avatarUpload) {
|
||||
id
|
||||
avatar
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
avatarUpload,
|
||||
id: this.user.id,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.$toast.success(this.$t('user.avatar.submitted'))
|
||||
})
|
||||
.catch(error => this.$toast.error(error.message))
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
#customdropzone {
|
||||
margin: -60px auto auto;
|
||||
width: 122px;
|
||||
min-height: 122px;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 50%;
|
||||
font-family: 'Arial', sans-serif;
|
||||
letter-spacing: 0.2px;
|
||||
color: #777;
|
||||
transition: background-color 0.2s linear;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
#customdropzone:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#customdropzone .dz-preview {
|
||||
width: 160px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#customdropzone .dz-preview .dz-image {
|
||||
position: relative;
|
||||
width: 122px;
|
||||
height: 122px;
|
||||
margin: -35px;
|
||||
}
|
||||
|
||||
#customdropzone .dz-preview .dz-image > div {
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
border-radius: 50%;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
#customdropzone .dz-preview .dz-image > img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#customdropzone .dz-preview .dz-details {
|
||||
color: white;
|
||||
transition: opacity 0.2s linear;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#customdropzone .dz-success-mark,
|
||||
.dz-error-mark,
|
||||
.dz-remove {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
71
webapp/components/Upload/spec.js
Normal file
71
webapp/components/Upload/spec.js
Normal file
@ -0,0 +1,71 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils'
|
||||
import Upload from '.'
|
||||
import Vuex from 'vuex'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Styleguide)
|
||||
|
||||
describe('Upload', () => {
|
||||
let wrapper
|
||||
|
||||
const mocks = {
|
||||
$apollo: {
|
||||
mutate: jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
data: { UpdateUser: { id: 'upload1', avatar: '/upload/avatar.jpg' } },
|
||||
})
|
||||
.mockRejectedValue({
|
||||
message: 'File upload unsuccessful! Whatcha gonna do?',
|
||||
}),
|
||||
},
|
||||
$toast: {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
},
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
user: {
|
||||
avatar: '/api/generic.jpg',
|
||||
},
|
||||
}
|
||||
|
||||
const file = {
|
||||
filename: 'avatar.jpg',
|
||||
previewElement: {
|
||||
classList: {
|
||||
remove: jest.fn(),
|
||||
add: jest.fn(),
|
||||
},
|
||||
querySelectorAll: jest.fn().mockReturnValue([
|
||||
{
|
||||
alt: '',
|
||||
style: {
|
||||
'background-image': '/api/generic.jpg',
|
||||
},
|
||||
},
|
||||
]),
|
||||
},
|
||||
}
|
||||
|
||||
const dataUrl = 'avatar.jpg'
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers()
|
||||
wrapper = shallowMount(Upload, { localVue, propsData, mocks })
|
||||
})
|
||||
|
||||
it('sends a the UpdateUser mutation when vddrop is called', () => {
|
||||
wrapper.vm.vddrop([{ filename: 'avatar.jpg' }])
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('thumbnail', () => {
|
||||
wrapper.vm.thumbnail(file, dataUrl)
|
||||
expect(file.previewElement.classList.add).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
@ -247,5 +247,10 @@
|
||||
},
|
||||
"shoutButton": {
|
||||
"shouted": "shouted"
|
||||
},
|
||||
"user": {
|
||||
"avatar": {
|
||||
"submitted": "Upload successful"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -67,6 +67,7 @@
|
||||
"jsonwebtoken": "~8.5.1",
|
||||
"linkify-it": "~2.1.0",
|
||||
"nuxt": "~2.7.1",
|
||||
"nuxt-dropzone": "^1.0.2",
|
||||
"nuxt-env": "~0.1.0",
|
||||
"stack-utils": "^1.0.2",
|
||||
"string-hash": "^1.1.3",
|
||||
@ -76,6 +77,7 @@
|
||||
"vue-count-to": "~1.0.13",
|
||||
"vue-izitoast": "1.1.2",
|
||||
"vue-sweetalert-icons": "~3.2.0",
|
||||
"vue2-dropzone": "^3.5.9",
|
||||
"vuex-i18n": "~1.11.0",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
@ -111,4 +113,4 @@
|
||||
"vue-jest": "~3.0.4",
|
||||
"vue-svg-loader": "~0.12.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,12 @@
|
||||
:class="{'disabled-content': user.disabled}"
|
||||
style="position: relative; height: auto;"
|
||||
>
|
||||
<hc-upload
|
||||
v-if="myProfile"
|
||||
:user="user"
|
||||
/>
|
||||
<ds-avatar
|
||||
v-else
|
||||
:image="user.avatar"
|
||||
:name="userName"
|
||||
class="profile-avatar"
|
||||
@ -223,7 +228,7 @@
|
||||
>
|
||||
<a :href="link.url">
|
||||
<ds-avatar :image="link.favicon" />
|
||||
{{ link.username }}
|
||||
{{ 'link.username' }}
|
||||
</a>
|
||||
</ds-space>
|
||||
</template>
|
||||
@ -327,6 +332,7 @@ import HcBadges from '~/components/Badges.vue'
|
||||
import HcLoadMore from '~/components/LoadMore.vue'
|
||||
import HcEmpty from '~/components/Empty.vue'
|
||||
import ContentMenu from '~/components/ContentMenu'
|
||||
import HcUpload from '~/components/Upload'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -338,6 +344,7 @@ export default {
|
||||
HcLoadMore,
|
||||
HcEmpty,
|
||||
ContentMenu,
|
||||
HcUpload,
|
||||
},
|
||||
transition: {
|
||||
name: 'slide-up',
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export default ({ app }) => {
|
||||
const backendUrl = process.env.GRAPHQL_URI || 'http://localhost:4000'
|
||||
|
||||
return {
|
||||
httpEndpoint: process.server ? backendUrl : '/api',
|
||||
httpLinkOptions: {
|
||||
|
||||
@ -3968,6 +3968,11 @@ dotenv@^6.0.0:
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064"
|
||||
integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==
|
||||
|
||||
dropzone@^5.5.1:
|
||||
version "5.5.1"
|
||||
resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-5.5.1.tgz#06e2f513e61d6aa363d4b556f18574f47cf7ba26"
|
||||
integrity sha512-3VduRWLxx9hbVr42QieQN25mx/I61/mRdUSuxAmDGdDqZIN8qtP7tcKMa3KfpJjuGjOJGYYUzzeq6eGDnkzesA==
|
||||
|
||||
duplexer3@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
|
||||
@ -7579,6 +7584,13 @@ number-is-nan@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
|
||||
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
|
||||
|
||||
nuxt-dropzone@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/nuxt-dropzone/-/nuxt-dropzone-1.0.2.tgz#7b39014ebf4c2084ea5c976f8d9f7b3cead2c7af"
|
||||
integrity sha512-Oj6YrQxNH5KhCyFSFz2O809u23+cFAevBTdcld88qakbR2l5stTQjrv8VJ9beaqfenT9kKEkhYQT0mXc3nUdKw==
|
||||
dependencies:
|
||||
vue2-dropzone "3.5.8"
|
||||
|
||||
nuxt-env@~0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/nuxt-env/-/nuxt-env-0.1.0.tgz#8ac50b9ff45391ad3044ea932cbd05f06a585f87"
|
||||
@ -11155,6 +11167,20 @@ vue-template-es2015-compiler@^1.6.0, vue-template-es2015-compiler@^1.9.0:
|
||||
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
|
||||
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
|
||||
|
||||
vue2-dropzone@3.5.8:
|
||||
version "3.5.8"
|
||||
resolved "https://registry.yarnpkg.com/vue2-dropzone/-/vue2-dropzone-3.5.8.tgz#cbe92d5424b5cc62c4d4ad62814d0cf6f3bb6cda"
|
||||
integrity sha512-32rLGSx+mLKhyzxRz4CdeNT9JmbO6NsYX8m83WYqrf2ilRbm6KSZmUqZ8EIT+2dwq8EzY9jdrWlWuZJRBFPUGw==
|
||||
dependencies:
|
||||
dropzone "^5.5.1"
|
||||
|
||||
vue2-dropzone@^3.5.9:
|
||||
version "3.5.9"
|
||||
resolved "https://registry.yarnpkg.com/vue2-dropzone/-/vue2-dropzone-3.5.9.tgz#a63999a45a7aad24d4c21e3d35be409b4e6bdce8"
|
||||
integrity sha512-nJz6teulVKlZIAeKgvPU7wBI/gzfIgqDOrEp1okSkQIkdprDVCoM0U7XWM0NOM4AAVX+4XGRtMoocYWdTYb3bQ==
|
||||
dependencies:
|
||||
dropzone "^5.5.1"
|
||||
|
||||
vue@^2.6.10, vue@^2.6.6:
|
||||
version "2.6.10"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
|
||||
|
||||
@ -1815,6 +1815,11 @@ cypress-cucumber-preprocessor@^1.11.2:
|
||||
glob "^7.1.2"
|
||||
through "^2.3.8"
|
||||
|
||||
cypress-file-upload@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-3.1.2.tgz#4a0024f99ca157565bf2b20c110e6e6874da28cb"
|
||||
integrity sha512-gZE2G7ZTD2Y8APrcgs+ATRMKs/IgH2rafCmi+8o99q5sDoNRLR+XKxOcoyWLehj9raGnO98YDYO8DY7k1VMGBw==
|
||||
|
||||
cypress-plugin-retries@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/cypress-plugin-retries/-/cypress-plugin-retries-1.2.2.tgz#7235371ca575ad9e16f883169e7f1588379f80f2"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user