diff --git a/.travis.yml b/.travis.yml index c858fc186..cd43b771f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ script: - docker-compose exec webapp yarn run lint - docker-compose exec webapp yarn run test --ci --verbose=false - docker-compose exec -d backend yarn run test:before:seeder - - yarn run cypress:run --record --key $CYPRESS_TOKEN + - yarn run cypress:run after_success: - wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh diff --git a/backend/package.json b/backend/package.json index a3f03f1fb..234a38614 100644 --- a/backend/package.json +++ b/backend/package.json @@ -10,8 +10,8 @@ "dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/index.js -e js,graphql", "lint": "eslint src --config .eslintrc.js", "test": "run-s test:jest test:cucumber", - "test:before:server": "cross-env GRAPHQL_URI=http://localhost:4123 GRAPHQL_PORT=4123 yarn run dev", - "test:before:seeder": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 DISABLED_MIDDLEWARES=permissions,activityPub yarn run dev", + "test:before:server": "cross-env GRAPHQL_URI=http://localhost:4123 GRAPHQL_PORT=4123 yarn run dev 2> /dev/null", + "test:before:seeder": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 DISABLED_MIDDLEWARES=permissions,activityPub yarn run dev 2> /dev/null", "test:jest:cmd": "wait-on tcp:4001 tcp:4123 && jest --forceExit --detectOpenHandles --runInBand", "test:cucumber:cmd": "wait-on tcp:4001 tcp:4123 && cucumber-js --require-module @babel/register --exit test/", "test:jest:cmd:debug": "wait-on tcp:4001 tcp:4123 && node --inspect-brk ./node_modules/.bin/jest -i --forceExit --detectOpenHandles --runInBand", @@ -50,7 +50,7 @@ "graphql-custom-directives": "~0.2.14", "graphql-iso-date": "~3.6.1", "graphql-middleware": "~3.0.2", - "graphql-shield": "~5.3.2", + "graphql-shield": "~5.3.3", "graphql-tag": "~2.10.1", "graphql-yoga": "~1.17.4", "helmet": "~3.16.0", @@ -84,7 +84,7 @@ "cucumber": "~5.1.0", "eslint": "~5.16.0", "eslint-config-standard": "~12.0.0", - "eslint-plugin-import": "~2.16.0", + "eslint-plugin-import": "~2.17.1", "eslint-plugin-jest": "~22.4.1", "eslint-plugin-node": "~8.0.1", "eslint-plugin-promise": "~4.1.1", @@ -94,4 +94,4 @@ "nodemon": "~1.18.11", "supertest": "~4.0.2" } -} \ No newline at end of file +} diff --git a/backend/src/graphql-schema.js b/backend/src/graphql-schema.js index c17b967d2..1e13c95f4 100644 --- a/backend/src/graphql-schema.js +++ b/backend/src/graphql-schema.js @@ -7,6 +7,7 @@ import reports from './resolvers/reports.js' import posts from './resolvers/posts.js' import moderation from './resolvers/moderation.js' import rewards from './resolvers/rewards.js' +import socialMedia from './resolvers/socialMedia.js' import notifications from './resolvers/notifications' export const typeDefs = fs @@ -27,6 +28,7 @@ export const resolvers = { ...posts.Mutation, ...moderation.Mutation, ...rewards.Mutation, + ...socialMedia.Mutation, ...notifications.Mutation } } diff --git a/backend/src/middleware/index.js b/backend/src/middleware/index.js index 8d893a78b..e6759e8ff 100644 --- a/backend/src/middleware/index.js +++ b/backend/src/middleware/index.js @@ -10,12 +10,14 @@ import permissionsMiddleware from './permissionsMiddleware' import userMiddleware from './userMiddleware' import includedFieldsMiddleware from './includedFieldsMiddleware' import orderByMiddleware from './orderByMiddleware' +import validUrlMiddleware from './validUrlMiddleware' import notificationsMiddleware from './notificationsMiddleware' export default schema => { let middleware = [ passwordMiddleware, dateTimeMiddleware, + validUrlMiddleware, sluggifyMiddleware, excerptMiddleware, xssMiddleware, diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 549499dcd..3ac43a6e2 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -74,6 +74,7 @@ const permissions = shield({ UpdateBadge: isAdmin, DeleteBadge: isAdmin, AddUserBadges: isAdmin, + CreateSocialMedia: isAuthenticated, // AddBadgeRewarded: isAdmin, // RemoveBadgeRewarded: isAdmin, reward: isAdmin, diff --git a/backend/src/middleware/validUrlMiddleware.js b/backend/src/middleware/validUrlMiddleware.js new file mode 100644 index 000000000..37f06199c --- /dev/null +++ b/backend/src/middleware/validUrlMiddleware.js @@ -0,0 +1,18 @@ +const validURL = str => { + const isValid = str.match(/^(?:https?:\/\/)(?:[^@\n])?(?:www\.)?([^:/\n?]+)/g) + return !!isValid +} + +export default { + Mutation: { + CreateSocialMedia: async (resolve, root, args, context, info) => { + let socialMedia + if (validURL(args.url)) { + socialMedia = await resolve(root, args, context, info) + } else { + throw Error('Input is not a URL') + } + return socialMedia + } + } +} diff --git a/backend/src/resolvers/socialMedia.js b/backend/src/resolvers/socialMedia.js new file mode 100644 index 000000000..3adf0e2d0 --- /dev/null +++ b/backend/src/resolvers/socialMedia.js @@ -0,0 +1,21 @@ +import { neo4jgraphql } from 'neo4j-graphql-js' + +export default { + Mutation: { + CreateSocialMedia: async (object, params, context, resolveInfo) => { + const socialMedia = await neo4jgraphql(object, params, context, resolveInfo, true) + const session = context.driver.session() + await session.run( + `MATCH (owner:User {id: $userId}), (socialMedia:SocialMedia {id: $socialMediaId}) + MERGE (socialMedia)<-[:OWNED]-(owner) + RETURN owner`, { + userId: context.user.id, + socialMediaId: socialMedia.id + } + ) + session.close() + + return socialMedia + } + } +} diff --git a/backend/src/resolvers/socialMedia.spec.js b/backend/src/resolvers/socialMedia.spec.js new file mode 100644 index 000000000..b97316543 --- /dev/null +++ b/backend/src/resolvers/socialMedia.spec.js @@ -0,0 +1,49 @@ +import Factory from '../seed/factories' +import { GraphQLClient } from 'graphql-request' +import { host, login } from '../jest/helpers' + +const factory = Factory() + +describe('CreateSocialMedia', () => { + let client + let headers + const mutation = ` + mutation($url: String!) { + CreateSocialMedia(url: $url) { + url + } + } + ` + beforeEach(async () => { + await factory.create('User', { + avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/jimmuirhead/128.jpg', + id: 'acb2d923-f3af-479e-9f00-61b12e864666', + name: 'Matilde Hermiston', + slug: 'matilde-hermiston', + role: 'user', + email: 'test@example.org', + password: '1234' + }) + }) + + afterEach(async () => { + await factory.cleanDatabase() + }) + + describe('authenticated', () => { + beforeEach(async () => { + headers = await login({ email: 'test@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + + it('rejects empty string', async () => { + const variables = { url: '' } + await expect(client.request(mutation, variables)).rejects.toThrow('Input is not a URL') + }) + + it('validates URLs', async () => { + const variables = { url: 'not-a-url' } + await expect(client.request(mutation, variables)).rejects.toThrow('Input is not a URL') + }) + }) +}) diff --git a/backend/src/schema.graphql b/backend/src/schema.graphql index db468471a..94e28d0d7 100644 --- a/backend/src/schema.graphql +++ b/backend/src/schema.graphql @@ -128,6 +128,7 @@ type User { location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l") locationName: String about: String + socialMedia: [SocialMedia]! @relation(name: "OWNED", direction: "OUT") createdAt: String updatedAt: String @@ -318,3 +319,10 @@ type SharedInboxEndpoint { id: ID! uri: String } + +type SocialMedia { + id: ID! + url: String + ownedBy: [User]! @relation(name: "OWNED", direction: "IN") +} + diff --git a/backend/yarn.lock b/backend/yarn.lock index 29de5d201..f547530dc 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -1104,10 +1104,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.9": - version "0.26.9" - resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.9.tgz#8a619ac4d2b8dcacb0d81345746018303b479919" - integrity sha512-C7HdLLs1ZNPbYeNsSX++fMosxWAwzVeUs9wc76XlKJrKvLEyNwXMDUjag75EVAPxlZ36YiRJ6iTy4zc5Dbtndw== +"@types/yup@0.26.12": + version "0.26.12" + resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.12.tgz#60fc1a485923a929699d2107fac46e6769707c4a" + integrity sha512-lWCsvLer6G84Gj7yh+oFGRuGHsqZd1Dwu47CVVL0ATw+bOnGDgMNHbTn80p1onT66fvLfN8FnRA3eRANsnnbbQ== "@types/zen-observable@^0.5.3": version "0.5.4" @@ -1609,6 +1609,14 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= +array-includes@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" + integrity sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0= + dependencies: + define-properties "^1.1.2" + es-abstract "^1.7.0" + array-map@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" @@ -2892,7 +2900,7 @@ es-abstract@^1.4.3: is-callable "^1.1.3" is-regex "^1.0.4" -es-abstract@^1.5.1: +es-abstract@^1.5.1, es-abstract@^1.7.0: version "1.13.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== @@ -2989,10 +2997,10 @@ eslint-import-resolver-node@^0.3.2: debug "^2.6.9" resolve "^1.5.0" -eslint-module-utils@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.3.0.tgz#546178dab5e046c8b562bbb50705e2456d7bda49" - integrity sha512-lmDJgeOOjk8hObTysjqH7wyMi+nsHwwvfBykwfhjR1LNdd7C2uFJBvx4OpWYpXOw4df1yE1cDEVd1yLHitk34w== +eslint-module-utils@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz#8b93499e9b00eab80ccb6614e69f03678e84e09a" + integrity sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw== dependencies: debug "^2.6.8" pkg-dir "^2.0.0" @@ -3005,21 +3013,22 @@ eslint-plugin-es@^1.3.1: eslint-utils "^1.3.0" regexpp "^2.0.1" -eslint-plugin-import@~2.16.0: - version "2.16.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.16.0.tgz#97ac3e75d0791c4fac0e15ef388510217be7f66f" - integrity sha512-z6oqWlf1x5GkHIFgrSvtmudnqM6Q60KM4KvpWi5ubonMjycLjndvd5+8VAZIsTlHC03djdgJuyKG6XO577px6A== +eslint-plugin-import@~2.17.1: + version "2.17.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.17.1.tgz#b888feb4d9b3ee155113c8dccdd4bec5db33bdf4" + integrity sha512-lzD9uvRvW4MsHzIOMJEDSb5MOV9LzgxRPBaovvOhJqzgxRHYfGy9QOrMuwHIh5ehKFJ7Z3DcrcGKDQ0IbP0EdQ== dependencies: + array-includes "^3.0.3" contains-path "^0.1.0" debug "^2.6.9" doctrine "1.5.0" eslint-import-resolver-node "^0.3.2" - eslint-module-utils "^2.3.0" + eslint-module-utils "^2.4.0" has "^1.0.3" lodash "^4.17.11" minimatch "^3.0.4" read-pkg-up "^2.0.0" - resolve "^1.9.0" + resolve "^1.10.0" eslint-plugin-jest@~22.4.1: version "22.4.1" @@ -3743,12 +3752,12 @@ graphql-request@~1.8.2: dependencies: cross-fetch "2.2.2" -graphql-shield@~5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.2.tgz#2d47907ed9882a0636cb8ade6087123309d215ef" - integrity sha512-fib7rSr5aS/WHL3+Aa5LXhcCuPGEIDXmzfGtFjUXkUiZ6E5u+bDSL+9KRXo/p14A28GkJF+1Vu1hlg9H/QFG1w== +graphql-shield@~5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.3.tgz#e3fbdb2a5f927fe1bb660ccf60c614defcc3aed4" + integrity sha512-9Hdmp71ewi9w7Tj1x8CSl3arWvtQOYKpZrsSBid2Vpr6BISAKe/2edEfgP4xYIKAkmpclG0Gl7ID5+qt1RJu7A== dependencies: - "@types/yup" "0.26.9" + "@types/yup" "0.26.12" lightercollective "^0.2.0" object-hash "^1.3.1" yup "^0.27.0" @@ -6677,7 +6686,7 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.3.2, resolve@^1.3.3, resolve@^1.5.0, resolve@^1.8.1, resolve@^1.9.0: +resolve@^1.10.0, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.5.0, resolve@^1.8.1: version "1.10.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg== diff --git a/cypress/integration/common/settings.js b/cypress/integration/common/settings.js index 3aa6022a8..e1f3cc5a8 100644 --- a/cypress/integration/common/settings.js +++ b/cypress/integration/common/settings.js @@ -61,3 +61,52 @@ Then( 'I can see my new name {string} when I click on my profile picture in the top right', name => matchNameInUserMenu(name) ) + +When('I click on the {string} link', link => { + cy.get('a') + .contains(link) + .click() +}) + +Then('I should be on the {string} page', page => { + cy.location() + .should(loc => { + expect(loc.pathname).to.eq(page) + }) + .get('h3') + .should('contain', 'Social media') +}) + +Then('I add a social media link', () => { + cy.get("input[name='social-media']") + .type('https://freeradical.zone/peter-pan') + .get('button') + .contains('Add link') + .click() +}) + +Then('it gets saved successfully', () => { + cy.get('.iziToast-message') + .should('contain', 'Updated user') +}) + +Then('the new social media link shows up on the page', () => { + cy.get('a[href="https://freeradical.zone/peter-pan"]') + .should('have.length', 1) +}) + +Given('I have added a social media link', () => { + cy.openPage('/settings/my-social-media') + .get("input[name='social-media']") + .type('https://freeradical.zone/peter-pan') + .get('button') + .contains('Add link') + .click() +}) + +Then('they should be able to see my social media links', () => { + cy.get('.ds-card-content') + .contains('Where else can I find Peter Pan?') + .get('a[href="https://freeradical.zone/peter-pan"]') + .should('have.length', 1) +}) diff --git a/cypress/integration/user_profile/SocialMedia.feature b/cypress/integration/user_profile/SocialMedia.feature new file mode 100644 index 000000000..988923c17 --- /dev/null +++ b/cypress/integration/user_profile/SocialMedia.feature @@ -0,0 +1,21 @@ +Feature: List Social Media Accounts + As a User + I'd like to enter my social media + So I can show them to other users to get in contact + + Background: + Given I have a user account + And I am logged in + + Scenario: Adding Social Media + Given I am on the "settings" page + And I click on the "Social media" link + Then I should be on the "/settings/my-social-media" page + When I add a social media link + Then it gets saved successfully + And the new social media link shows up on the page + + Scenario: Other user's viewing my Social Media + Given I have added a social media link + When people visit my profile page + Then they should be able to see my social media links diff --git a/webapp/graphql/UserProfileQuery.js b/webapp/graphql/UserProfileQuery.js index f0d7720ae..16e7e1440 100644 --- a/webapp/graphql/UserProfileQuery.js +++ b/webapp/graphql/UserProfileQuery.js @@ -98,6 +98,10 @@ export default app => { } } } + socialMedia { + id + url + } } } `) diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 6e47d7122..94ca1ac1b 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -15,7 +15,8 @@ "followers": "Folgen", "following": "Folgt", "shouted": "Empfohlen", - "commented": "Kommentiert" + "commented": "Kommentiert", + "socialMedia": "Wo sonst finde ich" }, "search": { "placeholder": "Suchen", @@ -51,6 +52,11 @@ }, "languages": { "name": "Sprachen" + }, + "social-media": { + "name": "Soziale Medien", + "submit": "Link hinzufügen", + "success": "Profil aktualisiert" } }, "admin": { diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 62c8f3e19..40cc766d0 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -15,7 +15,8 @@ "followers": "Followers", "following": "Following", "shouted": "Shouted", - "commented": "Commented" + "commented": "Commented", + "socialMedia": "Where else can I find" }, "search": { "placeholder": "Search", @@ -51,6 +52,11 @@ }, "languages": { "name": "Languages" + }, + "social-media": { + "name": "Social media", + "submit": "Add link", + "success": "Updated user profile" } }, "admin": { diff --git a/webapp/package.json b/webapp/package.json index 02347edee..3033970b0 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -53,7 +53,7 @@ "string-hash": "^1.1.3", "tiptap": "^1.14.0", "tiptap-extensions": "^1.14.0", - "v-tooltip": "~2.0.0", + "v-tooltip": "~2.0.1", "vue-count-to": "~1.0.13", "vue-izitoast": "1.1.2", "vue-sweetalert-icons": "~3.2.0", @@ -62,7 +62,7 @@ "devDependencies": { "@babel/core": "~7.4.3", "@babel/preset-env": "~7.4.3", - "@vue/cli-shared-utils": "~3.5.1", + "@vue/cli-shared-utils": "~3.6.0", "@vue/eslint-config-prettier": "~4.0.1", "@vue/server-test-utils": "~1.0.0-beta.29", "@vue/test-utils": "~1.0.0-beta.29", diff --git a/webapp/pages/profile/_id/_slug.vue b/webapp/pages/profile/_id/_slug.vue index 2eed6ac04..a70058b06 100644 --- a/webapp/pages/profile/_id/_slug.vue +++ b/webapp/pages/profile/_id/_slug.vue @@ -202,6 +202,37 @@

+ + + + + {{ $t('profile.socialMedia') }} {{ user.name | truncate(15) }}? + + + + + !post.deleted)) + }, + socialMediaLinks() { + const { socialMedia = [] } = this.user + return socialMedia.map(socialMedia => { + const { url } = socialMedia + const matches = url.match( + /^(?:https?:\/\/)?(?:[^@\n])?(?:www\.)?([^:\/\n?]+)/g + ) + const [domain] = matches || [] + const favicon = domain ? `${domain}/favicon.ico` : null + const username = url.split('/').pop() + return { url, username, favicon } + }) } }, watch: { diff --git a/webapp/pages/settings.vue b/webapp/pages/settings.vue index 33e29bde0..68b7c6d31 100644 --- a/webapp/pages/settings.vue +++ b/webapp/pages/settings.vue @@ -34,6 +34,10 @@ export default { { name: this.$t('settings.security.name'), path: `/settings/security` + }, + { + name: this.$t('settings.social-media.name'), + path: `/settings/my-social-media` } // TODO implement /* { @@ -59,6 +63,7 @@ export default { /* { name: this.$t('settings.languages.name'), path: `/settings/languages` + }, } */ ] } diff --git a/webapp/pages/settings/my-social-media.spec.js b/webapp/pages/settings/my-social-media.spec.js new file mode 100644 index 000000000..4f48a2835 --- /dev/null +++ b/webapp/pages/settings/my-social-media.spec.js @@ -0,0 +1,88 @@ +import { mount, createLocalVue } from '@vue/test-utils' +import MySocialMedia from './my-social-media.vue' +import Vue from 'vue' +import Vuex from 'vuex' +import Styleguide from '@human-connection/styleguide' + +const localVue = createLocalVue() + +localVue.use(Vuex) +localVue.use(Styleguide) + +describe('my-social-media.vue', () => { + let wrapper + let Wrapper + let store + let mocks + let getters + let input + let submitBtn + const socialMediaUrl = 'https://freeradical.zone/@mattwr18' + + beforeEach(() => { + mocks = { + $t: jest.fn(), + $apollo: { + mutate: jest + .fn() + .mockRejectedValue({ message: 'Ouch!' }) + .mockResolvedValueOnce({ + data: { CreateSocialMeda: { id: 's1', url: socialMediaUrl } } + }) + }, + $toast: { + error: jest.fn(), + success: jest.fn() + } + } + getters = { + 'auth/user': () => { + return {} + } + } + }) + + describe('mount', () => { + const Wrapper = () => { + store = new Vuex.Store({ + getters + }) + return mount(MySocialMedia, { store, mocks, localVue }) + } + + it('renders', () => { + wrapper = Wrapper() + expect(wrapper.contains('div')).toBe(true) + }) + + describe('given currentUser has a social media account linked', () => { + beforeEach(() => { + getters = { + 'auth/user': () => { + return { + socialMedia: [{ id: 's1', url: socialMediaUrl }] + } + } + } + }) + + it("displays a link to the currentUser's social media", () => { + wrapper = Wrapper() + const socialMediaLink = wrapper.find('a').attributes().href + expect(socialMediaLink).toBe(socialMediaUrl) + }) + }) + + describe('currentUser does not have a social media account linked', () => { + it('allows a user to add a social media link', () => { + wrapper = Wrapper() + input = wrapper.find({ name: 'social-media' }) + input.element.value = socialMediaUrl + input.trigger('input') + submitBtn = wrapper.find('.ds-button') + submitBtn.trigger('click') + expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1) + }) + }) + }) +}) diff --git a/webapp/pages/settings/my-social-media.vue b/webapp/pages/settings/my-social-media.vue new file mode 100644 index 000000000..c031f54a4 --- /dev/null +++ b/webapp/pages/settings/my-social-media.vue @@ -0,0 +1,107 @@ + + diff --git a/webapp/store/auth.js b/webapp/store/auth.js index 4785ff0c0..7b7fd2601 100644 --- a/webapp/store/auth.js +++ b/webapp/store/auth.js @@ -83,6 +83,10 @@ export const actions = { role about locationName + socialMedia { + id + url + } } }`) }) diff --git a/webapp/yarn.lock b/webapp/yarn.lock index eac609862..36b048d53 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -1473,10 +1473,10 @@ "@vue/babel-plugin-transform-vue-jsx" "^1.0.0-beta.3" camelcase "^5.0.0" -"@vue/cli-shared-utils@~3.5.1": - version "3.5.1" - resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-3.5.1.tgz#71d66f06fc619ba28df279bd7d37ba1ba29c9066" - integrity sha512-hCB7UbKeeC41w2Q8+Q7jmw3gHdq+ltRqp80S3uDRRGxwiOhxrSmdBHMzKUjh01L8bXOBRgvLey+BERi1Nj9n6Q== +"@vue/cli-shared-utils@~3.6.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-3.6.0.tgz#43937a2ea42b809dcd35d9348edf318ebc28b0d9" + integrity sha512-C8nTiJ7o+dncNLyOIOZF8P4bMJdOVXhWOuwyZKqn8k3CcsQVzuLyCKUHHezWc+sI+PJi4wIg2ZffCiueeIXZ+w== dependencies: chalk "^2.4.1" execa "^1.0.0" @@ -1485,10 +1485,10 @@ lru-cache "^5.1.1" node-ipc "^9.1.1" opn "^5.3.0" - ora "^3.1.0" + ora "^3.4.0" request "^2.87.0" request-promise-native "^1.0.7" - semver "^5.5.0" + semver "^6.0.0" string.prototype.padstart "^3.0.0" "@vue/component-compiler-utils@^2.5.1": @@ -1816,6 +1816,11 @@ ansi-regex@^4.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9" integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w== +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -6758,11 +6763,6 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= -lodash.merge@^4.6.1: - version "4.6.1" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.1.tgz#adc25d9cb99b9391c59624f379fbba60d7111d54" - integrity sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ== - lodash.mergewith@^4.6.0: version "4.6.1" resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927" @@ -7676,16 +7676,16 @@ optionator@^0.8.1, optionator@^0.8.2: type-check "~0.3.2" wordwrap "~1.0.0" -ora@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-3.2.0.tgz#67e98a7e11f7f0ac95deaaaf11bb04de3d09e481" - integrity sha512-XHMZA5WieCbtg+tu0uPF8CjvwQdNzKCX6BVh3N6GFsEXH40mTk5dsw/ya1lBTUGJslcEFJFQ8cBhOgkkZXQtMA== +ora@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318" + integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg== dependencies: chalk "^2.4.2" cli-cursor "^2.1.0" cli-spinners "^2.0.0" log-symbols "^2.2.0" - strip-ansi "^5.0.0" + strip-ansi "^5.2.0" wcwidth "^1.0.1" orderedmap@^1.0.0: @@ -10047,6 +10047,13 @@ strip-ansi@^5.0.0: dependencies: ansi-regex "^4.0.0" +strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" @@ -10807,12 +10814,12 @@ uuid@^3.1.0, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== -v-tooltip@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/v-tooltip/-/v-tooltip-2.0.0.tgz#022c9019198119cd735f9f09d7208043c651bdae" - integrity sha512-AXUUA7G06AiGmmxh+Om0/kgw52OTNzNxAXxomgH+ehPVr5GRJKp9b8lxJBWwQkUcdULYOE5u8Nf2IQ1gIJeP3g== +v-tooltip@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/v-tooltip/-/v-tooltip-2.0.1.tgz#ba959552651dfa33210170ac9cfcce3525abe797" + integrity sha512-Ifby9zx34MtzZRSYlr+tFQjXxHd8NZG9YoEHdeSQb8qwFGRQZhc8JVnxKBKUDGFowLUEzfX4e3mVmqrLw7b5oQ== dependencies: - lodash.merge "^4.6.1" + lodash "^4.17.11" popper.js "^1.15.0" vue-resize "^0.4.5"