diff --git a/cypress/fixtures/onourjourney.png b/cypress/fixtures/onourjourney.png new file mode 100644 index 000000000..8e606fabd Binary files /dev/null and b/cypress/fixtures/onourjourney.png differ diff --git a/cypress/integration/common/profile.js b/cypress/integration/common/profile.js new file mode 100644 index 000000000..cb5689f63 --- /dev/null +++ b/cypress/integration/common/profile.js @@ -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') +}) \ No newline at end of file diff --git a/cypress/integration/common/settings.js b/cypress/integration/common/settings.js index b6621ec87..664ffcff8 100644 --- a/cypress/integration/common/settings.js +++ b/cypress/integration/common/settings.js @@ -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) }) diff --git a/cypress/integration/user_profile/UploadUserProfileImage.feature b/cypress/integration/user_profile/UploadUserProfileImage.feature new file mode 100644 index 000000000..b46a31de8 --- /dev/null +++ b/cypress/integration/user_profile/UploadUserProfileImage.feature @@ -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 \ No newline at end of file diff --git a/cypress/support/commands.js b/cypress/support/commands.js index a7cb76a27..f6253af20 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -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' diff --git a/package.json b/package.json index 0c2e47271..a37c40327 100644 --- a/package.json +++ b/package.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" } -} +} \ No newline at end of file diff --git a/webapp/components/Upload/index.vue b/webapp/components/Upload/index.vue new file mode 100644 index 000000000..831edd2f8 --- /dev/null +++ b/webapp/components/Upload/index.vue @@ -0,0 +1,153 @@ + + + diff --git a/webapp/components/Upload/spec.js b/webapp/components/Upload/spec.js new file mode 100644 index 000000000..85215ea59 --- /dev/null +++ b/webapp/components/Upload/spec.js @@ -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) + }) +}) diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 186b31d41..c4d21b6e4 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -247,5 +247,10 @@ }, "shoutButton": { "shouted": "shouted" + }, + "user": { + "avatar": { + "submitted": "Upload successful" + } } } \ No newline at end of file diff --git a/webapp/package.json b/webapp/package.json index 8018ea667..e6f73f880 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -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" } -} \ No newline at end of file +} diff --git a/webapp/pages/profile/_id/_slug.vue b/webapp/pages/profile/_id/_slug.vue index 3dfb2c751..b38458a16 100644 --- a/webapp/pages/profile/_id/_slug.vue +++ b/webapp/pages/profile/_id/_slug.vue @@ -14,7 +14,12 @@ :class="{'disabled-content': user.disabled}" style="position: relative; height: auto;" > + - {{ link.username }} + {{ 'link.username' }} @@ -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', diff --git a/webapp/plugins/apollo-config.js b/webapp/plugins/apollo-config.js index 83ec452e3..e095aaba2 100644 --- a/webapp/plugins/apollo-config.js +++ b/webapp/plugins/apollo-config.js @@ -1,5 +1,6 @@ export default ({ app }) => { const backendUrl = process.env.GRAPHQL_URI || 'http://localhost:4000' + return { httpEndpoint: process.server ? backendUrl : '/api', httpLinkOptions: { diff --git a/webapp/yarn.lock b/webapp/yarn.lock index e244c215e..819186689 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -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" diff --git a/yarn.lock b/yarn.lock index 16acc6407..1ed66cb51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"