Merge branch 'master' of github.com:Human-Connection/Human-Connection into 781-language-of-contribution

This commit is contained in:
Matt Rider 2019-06-14 13:31:39 -03:00
commit 1c10e0863f
26 changed files with 270 additions and 224 deletions

View File

@ -29,7 +29,7 @@ script:
- docker-compose exec backend yarn run test:jest --ci --verbose=false --coverage
- docker-compose exec backend yarn run db:reset
- docker-compose exec backend yarn run db:seed
- docker-compose exec backend yarn run test:cucumber
- docker-compose exec backend yarn run test:cucumber --tags "not @wip"
- docker-compose exec backend yarn run db:reset
- docker-compose exec backend yarn run db:seed
# Frontend

View File

@ -47,12 +47,12 @@
"apollo-client": "~2.6.2",
"apollo-link-context": "~1.0.14",
"apollo-link-http": "~1.5.14",
"apollo-server": "~2.6.2",
"apollo-server": "~2.6.3",
"bcryptjs": "~2.4.3",
"cheerio": "~1.0.0-rc.3",
"cors": "~2.8.5",
"cross-env": "~5.2.0",
"date-fns": "2.0.0-alpha.31",
"date-fns": "2.0.0-alpha.33",
"debug": "~4.1.1",
"dotenv": "~8.0.0",
"express": "~4.17.1",
@ -87,7 +87,7 @@
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
"@babel/preset-env": "~7.4.5",
"@babel/register": "~7.4.4",
"apollo-server-testing": "~2.6.2",
"apollo-server-testing": "~2.6.3",
"babel-core": "~7.0.0-0",
"babel-eslint": "~10.0.1",
"babel-jest": "~24.8.0",

View File

@ -1,4 +1,4 @@
import { rule, shield, allow, or } from 'graphql-shield'
import { rule, shield, deny, allow, or } from 'graphql-shield'
/*
* TODO: implement
@ -16,6 +16,12 @@ const isAdmin = rule()(async (parent, args, { user }, info) => {
return user && user.role === 'admin'
})
const onlyYourself = rule({
cache: 'no_cache',
})(async (parent, args, context, info) => {
return context.user.id === args.id
})
const isMyOwn = rule({
cache: 'no_cache',
})(async (parent, args, context, info) => {
@ -48,6 +54,13 @@ const belongsToMe = rule({
return Boolean(notification)
})
/* TODO: decide if we want to remove this check: the check
* `onlyEnabledContent` throws authorization errors only if you have
* arguments for `disabled` or `deleted` assuming these are filter
* parameters. Soft-delete middleware obfuscates data on its way out
* anyways. Furthermore, `neo4j-graphql-js` offers many ways to filter for
* data so I believe, this is not a good check anyways.
*/
const onlyEnabledContent = rule({
cache: 'strict',
})(async (parent, args, ctx, info) => {
@ -81,46 +94,62 @@ const isAuthor = rule({
})
// Permissions
const permissions = shield({
Query: {
Notification: isAdmin,
statistics: allow,
currentUser: allow,
Post: or(onlyEnabledContent, isModerator),
const permissions = shield(
{
Query: {
'*': deny,
findPosts: allow,
Category: isAdmin,
Tag: isAdmin,
Report: isModerator,
Notification: isAdmin,
statistics: allow,
currentUser: allow,
Post: or(onlyEnabledContent, isModerator),
Comment: allow,
User: allow,
isLoggedIn: allow,
},
Mutation: {
'*': deny,
login: allow,
UpdateNotification: belongsToMe,
CreateUser: isAdmin,
UpdateUser: onlyYourself,
CreatePost: isAuthenticated,
UpdatePost: isAuthor,
DeletePost: isAuthor,
report: isAuthenticated,
CreateBadge: isAdmin,
UpdateBadge: isAdmin,
DeleteBadge: isAdmin,
AddUserBadges: isAdmin,
CreateSocialMedia: isAuthenticated,
DeleteSocialMedia: isAuthenticated,
// AddBadgeRewarded: isAdmin,
// RemoveBadgeRewarded: isAdmin,
reward: isAdmin,
unreward: isAdmin,
// addFruitToBasket: isAuthenticated
follow: isAuthenticated,
unfollow: isAuthenticated,
shout: isAuthenticated,
unshout: isAuthenticated,
changePassword: isAuthenticated,
enable: isModerator,
disable: isModerator,
CreateComment: isAuthenticated,
DeleteComment: isAuthor,
},
User: {
email: isMyOwn,
password: isMyOwn,
privateKey: isMyOwn,
},
},
Mutation: {
UpdateNotification: belongsToMe,
CreatePost: isAuthenticated,
UpdatePost: isAuthor,
DeletePost: isAuthor,
report: isAuthenticated,
CreateBadge: isAdmin,
UpdateBadge: isAdmin,
DeleteBadge: isAdmin,
AddUserBadges: isAdmin,
CreateSocialMedia: isAuthenticated,
DeleteSocialMedia: isAuthenticated,
// AddBadgeRewarded: isAdmin,
// RemoveBadgeRewarded: isAdmin,
reward: isAdmin,
unreward: isAdmin,
// addFruitToBasket: isAuthenticated
follow: isAuthenticated,
unfollow: isAuthenticated,
shout: isAuthenticated,
unshout: isAuthenticated,
changePassword: isAuthenticated,
enable: isModerator,
disable: isModerator,
CreateComment: isAuthenticated,
DeleteComment: isAuthor,
// CreateUser: allow,
{
fallbackRule: allow,
},
User: {
email: isMyOwn,
password: isMyOwn,
privateKey: isMyOwn,
},
})
)
export default permissions

View File

@ -7,12 +7,14 @@ let headers
const factory = Factory()
beforeEach(async () => {
await factory.create('User', { email: 'user@example.org', password: '1234' })
const adminParams = { role: 'admin', email: 'admin@example.org', password: '1234' }
await factory.create('User', adminParams)
await factory.create('User', {
email: 'someone@example.org',
password: '1234',
})
headers = await login({ email: 'user@example.org', password: '1234' })
// we need to be an admin, otherwise we're not authorized to create a user
headers = await login(adminParams)
authenticatedClient = new GraphQLClient(host, { headers })
})

View File

@ -315,6 +315,8 @@ describe('change password', () => {
describe('do not expose private RSA key', () => {
let headers
let client
let authenticatedClient
const queryUserPuplicKey = gql`
query($queriedUserSlug: String) {
User(slug: $queriedUserSlug) {
@ -332,7 +334,7 @@ describe('do not expose private RSA key', () => {
}
`
const actionGenUserWithKeys = async () => {
const generateUserWithKeys = async authenticatedClient => {
// Generate user with "privateKey" via 'CreateUser' mutation instead of using the factories "factory.create('User', {...})", see above.
const variables = {
id: 'bcb2d923-f3af-479e-9f00-61b12e864667',
@ -341,7 +343,7 @@ describe('do not expose private RSA key', () => {
name: 'Apfel Strudel',
email: 'apfel-strudel@test.org',
}
await client.request(
await authenticatedClient.request(
gql`
mutation($id: ID, $password: String!, $slug: String, $name: String, $email: String!) {
CreateUser(id: $id, password: $password, slug: $slug, name: $name, email: $email) {
@ -353,14 +355,23 @@ describe('do not expose private RSA key', () => {
)
}
// not authenticate
beforeEach(async () => {
const adminParams = {
role: 'admin',
email: 'admin@example.org',
password: '1234',
}
// create an admin user who has enough permissions to create other users
await factory.create('User', adminParams)
const headers = await login(adminParams)
authenticatedClient = new GraphQLClient(host, { headers })
// but also create an unauthenticated client to issue the `User` query
client = new GraphQLClient(host)
})
describe('unauthenticated query of "publicKey" (does the RSA key pair get generated at all?)', () => {
it('returns publicKey', async () => {
await actionGenUserWithKeys()
await generateUserWithKeys(authenticatedClient)
await expect(
await client.request(queryUserPuplicKey, { queriedUserSlug: 'apfel-strudel' }),
).toEqual(
@ -378,7 +389,7 @@ describe('do not expose private RSA key', () => {
describe('unauthenticated query of "privateKey"', () => {
it('throws "Not Authorised!"', async () => {
await actionGenUserWithKeys()
await generateUserWithKeys(authenticatedClient)
await expect(
client.request(queryUserPrivateKey, { queriedUserSlug: 'apfel-strudel' }),
).rejects.toThrow('Not Authorised')
@ -393,7 +404,7 @@ describe('do not expose private RSA key', () => {
describe('authenticated query of "publicKey"', () => {
it('returns publicKey', async () => {
await actionGenUserWithKeys()
await generateUserWithKeys(authenticatedClient)
await expect(
await client.request(queryUserPuplicKey, { queriedUserSlug: 'apfel-strudel' }),
).toEqual(
@ -411,7 +422,7 @@ describe('do not expose private RSA key', () => {
describe('authenticated query of "privateKey"', () => {
it('throws "Not Authorised!"', async () => {
await actionGenUserWithKeys()
await generateUserWithKeys(authenticatedClient)
await expect(
client.request(queryUserPrivateKey, { queriedUserSlug: 'apfel-strudel' }),
).rejects.toThrow('Not Authorised')

View File

@ -1,5 +1,5 @@
import { GraphQLClient } from 'graphql-request'
import { host } from '../../jest/helpers'
import { login, host } from '../../jest/helpers'
import Factory from '../../seed/factories'
const factory = Factory()
@ -18,27 +18,58 @@ describe('users', () => {
}
}
`
client = new GraphQLClient(host)
it('with password and email', async () => {
describe('given valid password and email', () => {
const variables = {
name: 'John Doe',
password: '123',
email: '123@123.de',
}
const expected = {
CreateUser: {
id: expect.any(String),
},
}
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
describe('unauthenticated', () => {
beforeEach(async () => {
client = new GraphQLClient(host)
})
it('is not allowed to create users', async () => {
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
})
})
describe('authenticated admin', () => {
beforeEach(async () => {
const adminParams = {
role: 'admin',
email: 'admin@example.org',
password: '1234',
}
await factory.create('User', adminParams)
const headers = await login(adminParams)
client = new GraphQLClient(host, { headers })
})
it('is allowed to create new users', async () => {
const expected = {
CreateUser: {
id: expect.any(String),
},
}
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
})
})
})
})
describe('UpdateUser', () => {
beforeEach(async () => {
await factory.create('User', { id: 'u47', name: 'John Doe' })
})
const userParams = {
email: 'user@example.org',
password: '1234',
id: 'u47',
name: 'John Doe',
}
const variables = {
id: 'u47',
name: 'John Doughnut',
}
const mutation = `
mutation($id: ID!, $name: String) {
@ -48,38 +79,62 @@ describe('users', () => {
}
}
`
client = new GraphQLClient(host)
it('name within specifications', async () => {
const variables = {
id: 'u47',
name: 'James Doe',
}
const expected = {
UpdateUser: {
id: 'u47',
beforeEach(async () => {
await factory.create('User', userParams)
})
describe('as another user', () => {
beforeEach(async () => {
const someoneElseParams = {
email: 'someoneElse@example.org',
password: '1234',
name: 'James Doe',
},
}
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
}
await factory.create('User', someoneElseParams)
const headers = await login(someoneElseParams)
client = new GraphQLClient(host, { headers })
})
it('is not allowed to change other user accounts', async () => {
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
})
})
it('with no name', async () => {
const variables = {
id: 'u47',
name: null,
}
const expected = 'Username must be at least 3 characters long!'
await expect(client.request(mutation, variables)).rejects.toThrow(expected)
})
describe('as the same user', () => {
beforeEach(async () => {
const headers = await login(userParams)
client = new GraphQLClient(host, { headers })
})
it('with too short name', async () => {
const variables = {
id: 'u47',
name: ' ',
}
const expected = 'Username must be at least 3 characters long!'
await expect(client.request(mutation, variables)).rejects.toThrow(expected)
it('name within specifications', async () => {
const expected = {
UpdateUser: {
id: 'u47',
name: 'John Doughnut',
},
}
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
})
it('with no name', async () => {
const variables = {
id: 'u47',
name: null,
}
const expected = 'Username must be at least 3 characters long!'
await expect(client.request(mutation, variables)).rejects.toThrow(expected)
})
it('with too short name', async () => {
const variables = {
id: 'u47',
name: ' ',
}
const expected = 'Username must be at least 3 characters long!'
await expect(client.request(mutation, variables)).rejects.toThrow(expected)
})
})
})
})

View File

@ -10,6 +10,7 @@ Feature: Follow a user
| stuart-little |
| tero-vota |
@wip
Scenario: Send a follow to a user inbox and make sure it's added to the right followers collection
When I send a POST request with the following activity to "/activitypub/users/tero-vota/inbox":
"""

View File

@ -27,6 +27,7 @@ Feature: Like an object like an article or note
}
"""
@wip
Scenario: Send a like of a person to an users inbox and make sure it's added to the likes collection
When I send a POST request with the following activity to "/activitypub/users/karl-heinz/inbox":
"""

View File

@ -1029,10 +1029,10 @@
"@types/express-serve-static-core" "*"
"@types/serve-static" "*"
"@types/express@4.16.1":
version "4.16.1"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.16.1.tgz#d756bd1a85c34d87eaf44c888bad27ba8a4b7cf0"
integrity sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg==
"@types/express@4.17.0":
version "4.17.0"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.0.tgz#49eaedb209582a86f12ed9b725160f12d04ef287"
integrity sha512-CjaMu57cjgjuZbh9DpkloeGxV45CnMGlVd+XpG7Gm9QgVrd7KFq+X4HY0vM+2v0bczS48Wg7bvnMY5TN+Xmcfw==
dependencies:
"@types/body-parser" "*"
"@types/express-serve-static-core" "*"
@ -1351,14 +1351,14 @@ apollo-engine-reporting-protobuf@0.3.1:
dependencies:
protobufjs "^6.8.6"
apollo-engine-reporting@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.3.0.tgz#50151811a0f5e70f4a73e7092a61fec422d8e722"
integrity sha512-xP+Z+wdQH4ee7xfuP3WsJcIe30AH68gpp2lQm2+rnW5JfjIqD5YehSoO2Svi2jK3CSv8Y561i3QMW9i34P7hEQ==
apollo-engine-reporting@1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.3.1.tgz#f2c2c63f865871a57c15cdbb2a3bcd4b4af28115"
integrity sha512-e0Xp+0yite8DH/xm9fnJt42CxfWAcY6waiq3icCMAgO9T7saXzVOPpl84SkuA+hIJUBtfaKrTnC+7Jxi/I7OrQ==
dependencies:
apollo-engine-reporting-protobuf "0.3.1"
apollo-graphql "^0.3.0"
apollo-server-core "2.6.2"
apollo-server-core "2.6.3"
apollo-server-env "2.4.0"
async-retry "^1.2.1"
graphql-extensions "0.7.2"
@ -1431,17 +1431,17 @@ apollo-server-caching@0.4.0:
dependencies:
lru-cache "^5.0.0"
apollo-server-core@2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.6.2.tgz#a792b50d4df9e26ec03759a31fbcbce38361b218"
integrity sha512-AbAnfoQ26NPsNIyBa/BVKBtA/wRsNL/E6eEem1VIhzitfgO25bVXFbEZDLxbgz6wvJ+veyRFpse7Qi1bvRpxOw==
apollo-server-core@2.6.3:
version "2.6.3"
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.6.3.tgz#786c8251c82cf29acb5cae9635a321f0644332ae"
integrity sha512-tfC0QO1NbJW3ShkB5pRCnUaYEkW2AwnswaTeedkfv//EO3yiC/9LeouCK5F22T8stQG+vGjvCqf0C8ldI/XsIA==
dependencies:
"@apollographql/apollo-tools" "^0.3.6"
"@apollographql/graphql-playground-html" "1.6.20"
"@types/ws" "^6.0.0"
apollo-cache-control "0.7.2"
apollo-datasource "0.5.0"
apollo-engine-reporting "1.3.0"
apollo-engine-reporting "1.3.1"
apollo-server-caching "0.4.0"
apollo-server-env "2.4.0"
apollo-server-errors "2.3.0"
@ -1479,18 +1479,18 @@ apollo-server-errors@2.3.0:
resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.3.0.tgz#700622b66a16dffcad3b017e4796749814edc061"
integrity sha512-rUvzwMo2ZQgzzPh2kcJyfbRSfVKRMhfIlhY7BzUfM4x6ZT0aijlgsf714Ll3Mbf5Fxii32kD0A/DmKsTecpccw==
apollo-server-express@2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.6.2.tgz#526297c01a7a32fe9215566f9fd7ff92e82f1fa0"
integrity sha512-nbL3noJ5KxKGg+hT8UsAA7++oHWq/KNSevfdCluWTfUNqH1vYRTvAnARx/6JM06S9zcPTfOLcqwHnDnY9zYFxA==
apollo-server-express@2.6.3:
version "2.6.3"
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.6.3.tgz#62034c978f84207615c0430fb37ab006f71146fe"
integrity sha512-8ca+VpKArgNzFar0D3DesWnn0g9YDtFLhO56TQprHh2Spxu9WxTnYNjsYs2MCCNf+iV/uy7vTvEknErvnIcZaQ==
dependencies:
"@apollographql/graphql-playground-html" "1.6.20"
"@types/accepts" "^1.3.5"
"@types/body-parser" "1.17.0"
"@types/cors" "^2.8.4"
"@types/express" "4.16.1"
"@types/express" "4.17.0"
accepts "^1.3.5"
apollo-server-core "2.6.2"
apollo-server-core "2.6.3"
body-parser "^1.18.3"
cors "^2.8.4"
graphql-subscriptions "^1.0.0"
@ -1523,20 +1523,20 @@ apollo-server-plugin-base@0.5.2:
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.5.2.tgz#f97ba983f1e825fec49cba8ff6a23d00e1901819"
integrity sha512-j81CpadRLhxikBYHMh91X4aTxfzFnmmebEiIR9rruS6dywWCxV2aLW87l9ocD1MiueNam0ysdwZkX4F3D4csNw==
apollo-server-testing@~2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.6.2.tgz#e0ecddd565fce1c38a346f9fbe6118f543ccf6a6"
integrity sha512-I9QLFk4I/z9oOIXfnLc8RPBYAKih6Olrg3RDeRvWhDjLQ8gfALXVhCO+7WuvM35wNZcZVn7aXBeZ8Y3mlgkj8w==
apollo-server-testing@~2.6.3:
version "2.6.3"
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.6.3.tgz#a0199a5d42000e60ecf0dea44b851f5f581e280e"
integrity sha512-LTkegcGVSkM+pA0FINDSYVl3TiFYKZyfjlKrEr/LN6wLiL6gbRgy6LMtk2j+qli/bnTDqqQREX8OEqmV8FKUoQ==
dependencies:
apollo-server-core "2.6.2"
apollo-server-core "2.6.3"
apollo-server@~2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.6.2.tgz#33fe894b740588f059a7679346516ffce50377d5"
integrity sha512-fMXaAKIb0dX0lzcZ4zlu7ay1L596d9HTNkdn8cKuM7zmTpugZSAL966COguJUDSjUS9CaB1Kh5hl1yRuRqHXSA==
apollo-server@~2.6.3:
version "2.6.3"
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.6.3.tgz#71235325449c6d3881a5143975ca44c07a07d2d7"
integrity sha512-pTIXE5xEMAikKLTIBIqLNvimMETiZbzmiqDb6BGzIUicAz4Rxa1/+bDi1ZeJWrZQjE/TfBLd2Si3qam7dZGrjw==
dependencies:
apollo-server-core "2.6.2"
apollo-server-express "2.6.2"
apollo-server-core "2.6.3"
apollo-server-express "2.6.3"
express "^4.0.0"
graphql-subscriptions "^1.0.0"
graphql-tools "^4.0.0"
@ -2586,10 +2586,10 @@ data-urls@^1.0.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"
date-fns@2.0.0-alpha.31:
version "2.0.0-alpha.31"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.31.tgz#51bcfdca25dfc9bea334a556ab33dfc0bb00421c"
integrity sha512-S19PwMqnbYsqcbCg02Yj9gv4veVNZ0OX7v2+zcd+Mq0RI7LoDKJipJjnMrTZ3Cc6blDuTce5G/pHXcVIGRwJWQ==
date-fns@2.0.0-alpha.33:
version "2.0.0-alpha.33"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.33.tgz#c2f73c3cc50ac301c9217eb93603c9bc40e891bf"
integrity sha512-tqUVEk3oxnJuNIvwAMKHAMo4uFRG0zXvjxZQll+BonoPt+m4NMcUgO14NDxbHuy7uYcrVErd2GdSsw02EDZQ7w==
debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
version "2.6.9"

View File

@ -22,8 +22,8 @@
"codecov": "^3.5.0",
"cross-env": "^5.2.0",
"cypress": "^3.3.1",
"cypress-cucumber-preprocessor": "^1.11.2",
"cypress-file-upload": "^3.1.2",
"cypress-cucumber-preprocessor": "^1.12.0",
"cypress-file-upload": "^3.1.4",
"cypress-plugin-retries": "^1.2.2",
"dotenv": "^8.0.0",
"faker": "^4.1.0",

View File

@ -1,9 +1,11 @@
import { mount, createLocalVue } from '@vue/test-utils'
import Styleguide from '@human-connection/styleguide'
import Avatar from './Avatar.vue'
import Filters from '~/plugins/vue-filters'
const localVue = createLocalVue()
localVue.use(Styleguide)
localVue.use(Filters)
describe('Avatar.vue', () => {
let propsData = {}

View File

@ -1,5 +1,10 @@
<template>
<ds-avatar :image="avatarUrl" :name="userName" class="avatar" :size="size" />
<ds-avatar
:image="user && user.avatar | proxyApiUrl"
:name="userName"
class="avatar"
:size="size"
/>
</template>
<script>
@ -10,11 +15,6 @@ export default {
size: { type: String, default: 'small' },
},
computed: {
avatarUrl() {
const { avatar: imageSrc } = this.user || {}
if (!imageSrc) return imageSrc
return imageSrc.startsWith('/') ? imageSrc.replace('/', '/api/') : imageSrc
},
userName() {
const { name } = this.user || {}
// The name is used to display the initials in case

View File

@ -1,17 +1,13 @@
<template>
<div :class="[badges.length === 2 && 'hc-badges-dual']" class="hc-badges">
<div v-for="badge in badges" :key="badge.key" class="hc-badge-container">
<hc-image :title="badge.key" :image-props="{ src: badge.icon }" class="hc-badge" />
<img :title="badge.key" :src="badge.icon | proxyApiUrl" class="hc-badge" />
</div>
</div>
</template>
<script>
import HcImage from './Image'
export default {
components: {
HcImage,
},
props: {
badges: {
type: Array,

View File

@ -1,20 +0,0 @@
<template>
<img v-bind="imageProps" :src="imageSrc" />
</template>
<script>
export default {
props: {
imageProps: {
type: Object,
required: true,
},
},
computed: {
imageSrc() {
const src = this.imageProps.src
return src.startsWith('/') ? src.replace('/', '/api/') : src
},
},
}
</script>

View File

@ -1,39 +0,0 @@
import { shallowMount } from '@vue/test-utils'
import Image from '.'
describe('Image', () => {
let propsData = { imageProps: { class: 'hc-badge', src: '' } }
const Wrapper = () => {
return shallowMount(Image, { propsData })
}
it('renders', () => {
expect(Wrapper().is('img')).toBe(true)
})
it('passes properties down to `img`', () => {
expect(Wrapper().classes()).toEqual(['hc-badge'])
})
describe('given a relative `src`', () => {
beforeEach(() => {
propsData.imageProps.src = '/img/badges/fundraisingbox_de_airship.svg'
})
it('adds a prefix to load the image from the backend', () => {
expect(Wrapper().attributes('src')).toBe('/api/img/badges/fundraisingbox_de_airship.svg')
})
})
describe('given an absolute `src`', () => {
beforeEach(() => {
propsData.imageProps.src = 'http://lorempixel.com/640/480/animals'
})
it('keeps the URL as is', () => {
// e.g. our seeds have absolute image URLs
expect(Wrapper().attributes('src')).toBe('http://lorempixel.com/640/480/animals')
})
})
})

View File

@ -1,10 +1,12 @@
import { mount, createLocalVue } from '@vue/test-utils'
import ChangePassword from './Change.vue'
import Styleguide from '@human-connection/styleguide'
import Filters from '~/plugins/vue-filters'
const localVue = createLocalVue()
localVue.use(Styleguide)
localVue.use(Filters)
describe('ChangePassword.vue', () => {
let mocks

View File

@ -1,6 +1,9 @@
<template>
<ds-flex-item :width="width">
<ds-card :image="post.image" :class="{ 'post-card': true, 'disabled-content': post.disabled }">
<ds-card
:image="post.image | proxyApiUrl"
:class="{ 'post-card': true, 'disabled-content': post.disabled }"
>
<!-- Post Link Target -->
<nuxt-link
class="post-link"

View File

@ -2,6 +2,7 @@ import { mount, createLocalVue, RouterLinkStub } from '@vue/test-utils'
import User from './index'
import Vuex from 'vuex'
import VTooltip from 'v-tooltip'
import Filters from '~/plugins/vue-filters'
import Styleguide from '@human-connection/styleguide'
@ -11,6 +12,7 @@ const filter = jest.fn(str => str)
localVue.use(Vuex)
localVue.use(VTooltip)
localVue.use(Styleguide)
localVue.use(Filters)
localVue.filter('truncate', filter)

View File

@ -3,11 +3,13 @@ import CommentList from '.'
import Empty from '~/components/Empty'
import Vuex from 'vuex'
import Styleguide from '@human-connection/styleguide'
import Filters from '~/plugins/vue-filters'
const localVue = createLocalVue()
localVue.use(Styleguide)
localVue.use(Vuex)
localVue.use(Filters)
localVue.filter('truncate', string => string)
config.stubs['v-popover'] = '<span><slot /></span>'

View File

@ -59,7 +59,7 @@
"apollo-client": "~2.6.2",
"cookie-universal-nuxt": "~2.0.16",
"cross-env": "~5.2.0",
"date-fns": "2.0.0-alpha.31",
"date-fns": "2.0.0-alpha.33",
"express": "~4.17.1",
"graphql": "~14.3.1",
"jsonwebtoken": "~8.5.1",

View File

@ -2,11 +2,13 @@ import { config, mount, shallowMount, createLocalVue } from '@vue/test-utils'
import ProfileSlug from './_slug.vue'
import Vuex from 'vuex'
import Styleguide from '@human-connection/styleguide'
import Filters from '~/plugins/vue-filters'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Styleguide)
localVue.use(Filters)
localVue.filter('date', d => d)
config.stubs['no-ssr'] = '<span><slot /></span>'

View File

@ -2,11 +2,13 @@ import { mount, createLocalVue } from '@vue/test-utils'
import MySocialMedia from './my-social-media.vue'
import Vuex from 'vuex'
import Styleguide from '@human-connection/styleguide'
import Filters from '~/plugins/vue-filters'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Styleguide)
localVue.use(Filters)
describe('my-social-media.vue', () => {
let wrapper

View File

@ -4,12 +4,7 @@
<ds-list>
<ds-list-item v-for="link in socialMediaLinks" :key="link.id">
<a :href="link.url" target="_blank">
<hc-image
:image-props="{ src: link.favicon }"
alt="Social Media link"
width="16"
height="16"
/>
<img :src="link.favicon | proxyApiUrl" alt="Social Media link" width="16" height="16" />
{{ link.url }}
</a>
&nbsp;&nbsp;
@ -44,12 +39,8 @@
<script>
import gql from 'graphql-tag'
import { mapGetters, mapMutations } from 'vuex'
import HcImage from '~/components/Image'
export default {
components: {
HcImage,
},
data() {
return {
value: '',

View File

@ -93,6 +93,10 @@ export default ({ app = {} }) => {
return excerpt
},
proxyApiUrl: url => {
if (!url) return url
return url.startsWith('/') ? url.replace('/', '/api/') : url
},
})
// add all methods as filters on each vue component

View File

@ -3754,10 +3754,10 @@ data-urls@^1.0.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"
date-fns@2.0.0-alpha.31:
version "2.0.0-alpha.31"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.31.tgz#51bcfdca25dfc9bea334a556ab33dfc0bb00421c"
integrity sha512-S19PwMqnbYsqcbCg02Yj9gv4veVNZ0OX7v2+zcd+Mq0RI7LoDKJipJjnMrTZ3Cc6blDuTce5G/pHXcVIGRwJWQ==
date-fns@2.0.0-alpha.33:
version "2.0.0-alpha.33"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.33.tgz#c2f73c3cc50ac301c9217eb93603c9bc40e891bf"
integrity sha512-tqUVEk3oxnJuNIvwAMKHAMo4uFRG0zXvjxZQll+BonoPt+m4NMcUgO14NDxbHuy7uYcrVErd2GdSsw02EDZQ7w==
date-now@^0.1.4:
version "0.1.4"

View File

@ -1805,10 +1805,10 @@ cucumber@^4.2.1:
util-arity "^1.0.2"
verror "^1.9.0"
cypress-cucumber-preprocessor@^1.11.2:
version "1.11.2"
resolved "https://registry.yarnpkg.com/cypress-cucumber-preprocessor/-/cypress-cucumber-preprocessor-1.11.2.tgz#daa86805e25a39cea1cf2278f3b7cee204478853"
integrity sha512-Cret/EmqGdC6QLUQrszDdzDt+y4aL0ViaOWfZ1PgM4GpAay4gHQ+j0mtTIBvRg8Y86w6NOfzaflcHKGk54v2XQ==
cypress-cucumber-preprocessor@^1.12.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/cypress-cucumber-preprocessor/-/cypress-cucumber-preprocessor-1.12.0.tgz#092428ba267331e3d2cc6e1309c331d17632b8b1"
integrity sha512-uKrWbs51hGeHiLgcSZcjFvvVEW9UdStsLVpD1snuPuik9WE61kbZv7xumlPjRmkMF81zTUGnNLwZuAk3CV9dEw==
dependencies:
"@cypress/browserify-preprocessor" "^1.1.2"
chai "^4.1.2"
@ -1822,10 +1822,10 @@ 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-file-upload@^3.1.4:
version "3.1.4"
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-3.1.4.tgz#cc208cb937a3abb136b52309eaf4637d5676c5bd"
integrity sha512-4aZeJOYFhYiP+nk9Mo5YHWqComsT24J9OBQVJzvkEzw7g1v2ogGe7nLT/U7Fsm/Xjl1Tyxsc0xxECa254WfQqg==
cypress-plugin-retries@^1.2.2:
version "1.2.2"