Merge pull request #535 from Human-Connection/463-delete-socialMedia

Delete SocialMedia
This commit is contained in:
Wolfgang Huß 2019-05-09 14:25:30 +02:00 committed by GitHub
commit a9860c49fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 203 additions and 29 deletions

View File

@ -75,6 +75,7 @@ const permissions = shield({
DeleteBadge: isAdmin, DeleteBadge: isAdmin,
AddUserBadges: isAdmin, AddUserBadges: isAdmin,
CreateSocialMedia: isAuthenticated, CreateSocialMedia: isAuthenticated,
DeleteSocialMedia: isAuthenticated,
// AddBadgeRewarded: isAdmin, // AddBadgeRewarded: isAdmin,
// RemoveBadgeRewarded: isAdmin, // RemoveBadgeRewarded: isAdmin,
reward: isAdmin, reward: isAdmin,

View File

@ -3,6 +3,9 @@ import { neo4jgraphql } from 'neo4j-graphql-js'
export default { export default {
Mutation: { Mutation: {
CreateSocialMedia: async (object, params, context, resolveInfo) => { CreateSocialMedia: async (object, params, context, resolveInfo) => {
/**
* TODO?: Creates double Nodes!
*/
const socialMedia = await neo4jgraphql(object, params, context, resolveInfo, false) const socialMedia = await neo4jgraphql(object, params, context, resolveInfo, false)
const session = context.driver.session() const session = context.driver.session()
await session.run( await session.run(
@ -15,6 +18,11 @@ export default {
) )
session.close() session.close()
return socialMedia
},
DeleteSocialMedia: async (object, params, context, resolveInfo) => {
const socialMedia = await neo4jgraphql(object, params, context, resolveInfo, false)
return socialMedia return socialMedia
} }
} }

View File

@ -7,9 +7,18 @@ const factory = Factory()
describe('CreateSocialMedia', () => { describe('CreateSocialMedia', () => {
let client let client
let headers let headers
const mutation = ` const mutationC = `
mutation($url: String!) { mutation($url: String!) {
CreateSocialMedia(url: $url) { CreateSocialMedia(url: $url) {
id
url
}
}
`
const mutationD = `
mutation($id: ID!) {
DeleteSocialMedia(id: $id) {
id
url url
} }
} }
@ -30,20 +39,63 @@ describe('CreateSocialMedia', () => {
await factory.cleanDatabase() await factory.cleanDatabase()
}) })
describe('unauthenticated', () => {
it('throws authorization error', async () => {
client = new GraphQLClient(host)
const variables = { url: 'http://nsosp.org' }
await expect(
client.request(mutationC, variables)
).rejects.toThrow('Not Authorised')
})
})
describe('authenticated', () => { describe('authenticated', () => {
beforeEach(async () => { beforeEach(async () => {
headers = await login({ email: 'test@example.org', password: '1234' }) headers = await login({ email: 'test@example.org', password: '1234' })
client = new GraphQLClient(host, { headers }) client = new GraphQLClient(host, { headers })
}) })
it('creates social media with correct URL', async () => {
const variables = { url: 'http://nsosp.org' }
await expect(
client.request(mutationC, variables)
).resolves.toEqual(expect.objectContaining({
CreateSocialMedia: {
id: expect.any(String),
url: 'http://nsosp.org'
}
}))
})
it('deletes social media', async () => {
const creationVariables = { url: 'http://nsosp.org' }
const { CreateSocialMedia } = await client.request(mutationC, creationVariables)
const { id } = CreateSocialMedia
const deletionVariables = { id }
const expected = {
DeleteSocialMedia: {
id: id,
url: 'http://nsosp.org'
}
}
await expect(
client.request(mutationD, deletionVariables)
).resolves.toEqual(expected)
})
it('rejects empty string', async () => { it('rejects empty string', async () => {
const variables = { url: '' } const variables = { url: '' }
await expect(client.request(mutation, variables)).rejects.toThrow('Input is not a URL') await expect(
client.request(mutationC, variables)
).rejects.toThrow('Input is not a URL')
}) })
it('validates URLs', async () => { it('validates URLs', async () => {
const variables = { url: 'not-a-url' } const variables = { url: 'not-a-url' }
await expect(client.request(mutation, variables)).rejects.toThrow('Input is not a URL') await expect(
client.request(mutationC, variables)
).rejects.toThrow('Input is not a URL')
}) })
}) })
}) })

View File

@ -77,7 +77,7 @@ Then('I should be on the {string} page', page => {
.should('contain', 'Social media') .should('contain', 'Social media')
}) })
Then('I add a social media link', () => { When('I add a social media link', () => {
cy.get("input[name='social-media']") cy.get("input[name='social-media']")
.type('https://freeradical.zone/peter-pan') .type('https://freeradical.zone/peter-pan')
.get('button') .get('button')
@ -87,7 +87,7 @@ Then('I add a social media link', () => {
Then('it gets saved successfully', () => { Then('it gets saved successfully', () => {
cy.get('.iziToast-message') cy.get('.iziToast-message')
.should('contain', 'Updated user') .should('contain', 'Added social media')
}) })
Then('the new social media link shows up on the page', () => { Then('the new social media link shows up on the page', () => {
@ -110,3 +110,13 @@ Then('they should be able to see my social media links', () => {
.get('a[href="https://freeradical.zone/peter-pan"]') .get('a[href="https://freeradical.zone/peter-pan"]')
.should('have.length', 1) .should('have.length', 1)
}) })
When('I delete a social media link', () => {
cy.get("a[name='delete']")
.click()
})
Then('it gets deleted successfully', () => {
cy.get('.iziToast-message')
.should('contain', 'Deleted social media')
})

View File

@ -19,3 +19,11 @@ Feature: List Social Media Accounts
Given I have added a social media link Given I have added a social media link
When people visit my profile page When people visit my profile page
Then they should be able to see my social media links Then they should be able to see my social media links
Scenario: Deleting 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
Given I have added a social media link
When I delete a social media link
Then it gets deleted successfully

View File

@ -65,8 +65,10 @@
}, },
"social-media": { "social-media": {
"name": "Soziale Medien", "name": "Soziale Medien",
"placeholder": "Füge eine Social-Media URL hinzu",
"submit": "Link hinzufügen", "submit": "Link hinzufügen",
"success": "Profil aktualisiert" "successAdd": "Social-Media hinzugefügt. Profil aktualisiert!",
"successDelete": "Social-Media gelöscht. Profil aktualisiert!"
} }
}, },
"admin": { "admin": {

View File

@ -65,8 +65,10 @@
}, },
"social-media": { "social-media": {
"name": "Social media", "name": "Social media",
"placeholder": "Add social media url",
"submit": "Add link", "submit": "Add link",
"success": "Updated user profile" "successAdd": "Added social media. Updated user profile!",
"successDelete": "Deleted social media. Updated user profile!"
} }
}, },
"admin": { "admin": {

View File

@ -71,6 +71,40 @@ describe('my-social-media.vue', () => {
const socialMediaLink = wrapper.find('a').attributes().href const socialMediaLink = wrapper.find('a').attributes().href
expect(socialMediaLink).toBe(socialMediaUrl) expect(socialMediaLink).toBe(socialMediaUrl)
}) })
beforeEach(() => {
mocks = {
$t: jest.fn(),
$apollo: {
mutate: jest
.fn()
.mockRejectedValue({ message: 'Ouch!' })
.mockResolvedValueOnce({
data: { DeleteSocialMeda: { id: 's1', url: socialMediaUrl } }
})
},
$toast: {
error: jest.fn(),
success: jest.fn()
}
}
getters = {
'auth/user': () => {
return {
socialMedia: [{ id: 's1', url: socialMediaUrl }]
}
}
}
})
it('displays a trash sympol after a social media and allows the user to delete it', () => {
wrapper = Wrapper()
const deleteSelector = wrapper.find({ name: 'delete' })
expect(deleteSelector).toEqual({ selector: 'Component' })
const icon = wrapper.find({ name: 'trash' })
icon.trigger('click')
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
})
}) })
describe('currentUser does not have a social media account linked', () => { describe('currentUser does not have a social media account linked', () => {

View File

@ -8,9 +8,12 @@
<ds-list> <ds-list>
<ds-list-item <ds-list-item
v-for="link in socialMediaLinks" v-for="link in socialMediaLinks"
:key="link.url" :key="link.id"
> >
<a :href="link.url"> <a
:href="link.url"
target="_blank"
>
<img <img
:src="link.favicon" :src="link.favicon"
alt="Social Media link" alt="Social Media link"
@ -19,26 +22,39 @@
> >
{{ link.url }} {{ link.url }}
</a> </a>
&nbsp;&nbsp; <span class="layout-leave-active">|</span> &nbsp;&nbsp;
<ds-icon
name="edit"
class="layout-leave-active"
/>
<a
name="delete"
@click="handleDeleteSocialMedia(link)"
>
<ds-icon name="trash" />
</a>
</ds-list-item> </ds-list-item>
</ds-list> </ds-list>
</ds-space> </ds-space>
<div>
<ds-input
v-model="value"
placeholder="Add social media url"
name="social-media"
:schema="{type: 'url'}"
/>
</div>
<ds-space margin-top="base"> <ds-space margin-top="base">
<div> <div>
<ds-button <ds-input
primary v-model="value"
@click="handleAddSocialMedia" :placeholder="$t('settings.social-media.placeholder')"
> name="social-media"
{{ $t('settings.social-media.submit') }} :schema="{type: 'url'}"
</ds-button> />
</div> </div>
<ds-space margin-top="base">
<div>
<ds-button
primary
@click="handleAddSocialMedia"
>
{{ $t('settings.social-media.submit') }}
</ds-button>
</div>
</ds-space>
</ds-space> </ds-space>
</ds-card> </ds-card>
</template> </template>
@ -59,13 +75,13 @@ export default {
socialMediaLinks() { socialMediaLinks() {
const { socialMedia = [] } = this.currentUser const { socialMedia = [] } = this.currentUser
return socialMedia.map(socialMedia => { return socialMedia.map(socialMedia => {
const { url } = socialMedia const { id, url } = socialMedia
const matches = url.match( const matches = url.match(
/^(?:https?:\/\/)?(?:[^@\n])?(?:www\.)?([^:\/\n?]+)/g /^(?:https?:\/\/)?(?:[^@\n])?(?:www\.)?([^:\/\n?]+)/g
) )
const [domain] = matches || [] const [domain] = matches || []
const favicon = domain ? `${domain}/favicon.ico` : null const favicon = domain ? `${domain}/favicon.ico` : null
return { url, favicon } return { id, url, favicon }
}) })
} }
}, },
@ -79,6 +95,7 @@ export default {
mutation: gql` mutation: gql`
mutation($url: String!) { mutation($url: String!) {
CreateSocialMedia(url: $url) { CreateSocialMedia(url: $url) {
id
url url
} }
} }
@ -97,11 +114,51 @@ export default {
}) })
} }
}) })
.then( .then(() => {
this.$toast.success(this.$t('settings.social-media.success')), this.$toast.success(this.$t('settings.social-media.successAdd')),
(this.value = '') (this.value = '')
) })
.catch(error => {
this.$toast.error(error.message)
})
},
handleDeleteSocialMedia(link) {
this.$apollo
.mutate({
mutation: gql`
mutation($id: ID!) {
DeleteSocialMedia(id: $id) {
id
url
}
}
`,
variables: {
id: link.id
},
update: (store, { data }) => {
const socialMedia = this.currentUser.socialMedia.filter(
element => element.id !== link.id
)
this.setCurrentUser({
...this.currentUser,
socialMedia
})
}
})
.then(() => {
this.$toast.success(this.$t('settings.social-media.successDelete'))
})
.catch(error => {
this.$toast.error(error.message)
})
} }
} }
} }
</script> </script>
<style lang="scss">
.layout-leave-active {
opacity: 0.4;
}
</style>