mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' into fix-deploy-branded
This commit is contained in:
commit
9075798027
@ -150,6 +150,19 @@ export const changeGroupMemberRoleMutation = () => {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const removeUserFromGroupMutation = () => {
|
||||||
|
return gql`
|
||||||
|
mutation ($groupId: ID!, $userId: ID!) {
|
||||||
|
RemoveUserFromGroup(groupId: $groupId, userId: $userId) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
myRoleInGroup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
// ------ queries
|
// ------ queries
|
||||||
|
|
||||||
export const groupQuery = () => {
|
export const groupQuery = () => {
|
||||||
|
|||||||
@ -253,6 +253,42 @@ const isMemberOfGroup = rule({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const canRemoveUserFromGroup = rule({
|
||||||
|
cache: 'no_cache',
|
||||||
|
})(async (_parent, args, { user, driver }) => {
|
||||||
|
if (!(user && user.id)) return false
|
||||||
|
const { groupId, userId } = args
|
||||||
|
const currentUserId = user.id
|
||||||
|
if (currentUserId === userId) return false
|
||||||
|
const session = driver.session()
|
||||||
|
const readTxPromise = session.readTransaction(async (transaction) => {
|
||||||
|
const transactionResponse = await transaction.run(
|
||||||
|
`
|
||||||
|
MATCH (User {id: $currentUserId})-[currentUserMembership:MEMBER_OF]->(group:Group {id: $groupId})
|
||||||
|
OPTIONAL MATCH (group)<-[userMembership:MEMBER_OF]-(user:User { id: $userId })
|
||||||
|
RETURN currentUserMembership.role AS currentUserRole, userMembership.role AS userRole
|
||||||
|
`,
|
||||||
|
{ currentUserId, groupId, userId },
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
currentUserRole: transactionResponse.records.map((record) =>
|
||||||
|
record.get('currentUserRole'),
|
||||||
|
)[0],
|
||||||
|
userRole: transactionResponse.records.map((record) => record.get('userRole'))[0],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const { currentUserRole, userRole } = await readTxPromise
|
||||||
|
return (
|
||||||
|
currentUserRole && ['owner'].includes(currentUserRole) && userRole && userRole !== 'owner'
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error)
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const canCommentPost = rule({
|
const canCommentPost = rule({
|
||||||
cache: 'no_cache',
|
cache: 'no_cache',
|
||||||
})(async (_parent, args, { user, driver }) => {
|
})(async (_parent, args, { user, driver }) => {
|
||||||
@ -382,6 +418,7 @@ export default shield(
|
|||||||
JoinGroup: isAllowedToJoinGroup,
|
JoinGroup: isAllowedToJoinGroup,
|
||||||
LeaveGroup: isAllowedToLeaveGroup,
|
LeaveGroup: isAllowedToLeaveGroup,
|
||||||
ChangeGroupMemberRole: isAllowedToChangeGroupMemberRole,
|
ChangeGroupMemberRole: isAllowedToChangeGroupMemberRole,
|
||||||
|
RemoveUserFromGroup: canRemoveUserFromGroup,
|
||||||
CreatePost: and(isAuthenticated, isMemberOfGroup),
|
CreatePost: and(isAuthenticated, isMemberOfGroup),
|
||||||
UpdatePost: isAuthor,
|
UpdatePost: isAuthor,
|
||||||
DeletePost: isAuthor,
|
DeletePost: isAuthor,
|
||||||
|
|||||||
@ -295,25 +295,8 @@ export default {
|
|||||||
LeaveGroup: async (_parent, params, context, _resolveInfo) => {
|
LeaveGroup: async (_parent, params, context, _resolveInfo) => {
|
||||||
const { groupId, userId } = params
|
const { groupId, userId } = params
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
|
||||||
const leaveGroupCypher = `
|
|
||||||
MATCH (member:User {id: $userId})-[membership:MEMBER_OF]->(group:Group {id: $groupId})
|
|
||||||
DELETE membership
|
|
||||||
WITH member, group
|
|
||||||
OPTIONAL MATCH (p:Post)-[:IN]->(group)
|
|
||||||
WHERE NOT group.groupType = 'public'
|
|
||||||
WITH member, group, collect(p) AS posts
|
|
||||||
FOREACH (post IN posts |
|
|
||||||
MERGE (member)-[:CANNOT_SEE]->(post))
|
|
||||||
RETURN member {.*, myRoleInGroup: NULL}
|
|
||||||
`
|
|
||||||
|
|
||||||
const transactionResponse = await transaction.run(leaveGroupCypher, { groupId, userId })
|
|
||||||
const [member] = await transactionResponse.records.map((record) => record.get('member'))
|
|
||||||
return member
|
|
||||||
})
|
|
||||||
try {
|
try {
|
||||||
return await writeTxResultPromise
|
return await removeUserFromGroupWriteTxResultPromise(session, groupId, userId)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(error)
|
throw new Error(error)
|
||||||
} finally {
|
} finally {
|
||||||
@ -368,6 +351,17 @@ export default {
|
|||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
RemoveUserFromGroup: async (_parent, params, context, _resolveInfo) => {
|
||||||
|
const { groupId, userId } = params
|
||||||
|
const session = context.driver.session()
|
||||||
|
try {
|
||||||
|
return await removeUserFromGroupWriteTxResultPromise(session, groupId, userId)
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error)
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Group: {
|
Group: {
|
||||||
...Resolver('Group', {
|
...Resolver('Group', {
|
||||||
@ -383,3 +377,27 @@ export default {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const removeUserFromGroupWriteTxResultPromise = async (session, groupId, userId) => {
|
||||||
|
return session.writeTransaction(async (transaction) => {
|
||||||
|
const removeUserFromGroupCypher = `
|
||||||
|
MATCH (user:User {id: $userId})-[membership:MEMBER_OF]->(group:Group {id: $groupId})
|
||||||
|
DELETE membership
|
||||||
|
WITH user, group
|
||||||
|
OPTIONAL MATCH (author:User)-[:WROTE]->(p:Post)-[:IN]->(group)
|
||||||
|
WHERE NOT group.groupType = 'public'
|
||||||
|
AND NOT author.id = $userId
|
||||||
|
WITH user, collect(p) AS posts
|
||||||
|
FOREACH (post IN posts |
|
||||||
|
MERGE (user)-[:CANNOT_SEE]->(post))
|
||||||
|
RETURN user {.*, myRoleInGroup: NULL}
|
||||||
|
`
|
||||||
|
|
||||||
|
const transactionResponse = await transaction.run(removeUserFromGroupCypher, {
|
||||||
|
groupId,
|
||||||
|
userId,
|
||||||
|
})
|
||||||
|
const [user] = await transactionResponse.records.map((record) => record.get('user'))
|
||||||
|
return user
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
joinGroupMutation,
|
joinGroupMutation,
|
||||||
leaveGroupMutation,
|
leaveGroupMutation,
|
||||||
changeGroupMemberRoleMutation,
|
changeGroupMemberRoleMutation,
|
||||||
|
removeUserFromGroupMutation,
|
||||||
groupMembersQuery,
|
groupMembersQuery,
|
||||||
groupQuery,
|
groupQuery,
|
||||||
} from '../../graphql/groups'
|
} from '../../graphql/groups'
|
||||||
@ -196,7 +197,6 @@ const seedComplexScenarioAndClearAuthentication = async () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
// hidden-group
|
// hidden-group
|
||||||
authenticatedUser = await adminMemberUser.toJson()
|
|
||||||
await mutate({
|
await mutate({
|
||||||
mutation: createGroupMutation(),
|
mutation: createGroupMutation(),
|
||||||
variables: {
|
variables: {
|
||||||
@ -214,32 +214,17 @@ const seedComplexScenarioAndClearAuthentication = async () => {
|
|||||||
mutation: changeGroupMemberRoleMutation(),
|
mutation: changeGroupMemberRoleMutation(),
|
||||||
variables: {
|
variables: {
|
||||||
groupId: 'hidden-group',
|
groupId: 'hidden-group',
|
||||||
userId: 'admin-member-user',
|
userId: 'usual-member-user',
|
||||||
roleInGroup: 'usual',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
await mutate({
|
|
||||||
mutation: changeGroupMemberRoleMutation(),
|
|
||||||
variables: {
|
|
||||||
groupId: 'hidden-group',
|
|
||||||
userId: 'second-owner-member-user',
|
|
||||||
roleInGroup: 'usual',
|
roleInGroup: 'usual',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await mutate({
|
await mutate({
|
||||||
mutation: changeGroupMemberRoleMutation(),
|
mutation: changeGroupMemberRoleMutation(),
|
||||||
variables: {
|
variables: {
|
||||||
groupId: 'hidden-group',
|
groupId: 'hidden-group',
|
||||||
userId: 'admin-member-user',
|
userId: 'admin-member-user',
|
||||||
roleInGroup: 'usual',
|
roleInGroup: 'admin',
|
||||||
},
|
|
||||||
})
|
|
||||||
await mutate({
|
|
||||||
mutation: changeGroupMemberRoleMutation(),
|
|
||||||
variables: {
|
|
||||||
groupId: 'hidden-group',
|
|
||||||
userId: 'second-owner-member-user',
|
|
||||||
roleInGroup: 'usual',
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -2982,4 +2967,192 @@ describe('in mode', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('RemoveUserFromGroup', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await seedComplexScenarioAndClearAuthentication()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDatabase()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('unauthenticated', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: removeUserFromGroupMutation(),
|
||||||
|
variables: {
|
||||||
|
groupId: 'hidden-group',
|
||||||
|
userId: 'usual-member-user',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
errors: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
message: 'Not Authorized!',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('authenticated', () => {
|
||||||
|
describe('as usual member', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
authenticatedUser = await usualMemberUser.toJson()
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: removeUserFromGroupMutation(),
|
||||||
|
variables: {
|
||||||
|
groupId: 'hidden-group',
|
||||||
|
userId: 'admin-member-user',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
errors: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
message: 'Not Authorized!',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('as owner', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
authenticatedUser = await ownerMemberUser.toJson()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('removes the user from the group', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: removeUserFromGroupMutation(),
|
||||||
|
variables: {
|
||||||
|
groupId: 'hidden-group',
|
||||||
|
userId: 'usual-member-user',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
|
RemoveUserFromGroup: expect.objectContaining({
|
||||||
|
id: 'usual-member-user',
|
||||||
|
myRoleInGroup: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cannot remove self', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: removeUserFromGroupMutation(),
|
||||||
|
variables: {
|
||||||
|
groupId: 'hidden-group',
|
||||||
|
userId: 'owner-member-user',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
errors: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
message: 'Not Authorized!',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('as admin', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
authenticatedUser = await adminMemberUser.toJson()
|
||||||
|
await mutate({
|
||||||
|
mutation: changeGroupMemberRoleMutation(),
|
||||||
|
variables: {
|
||||||
|
groupId: 'hidden-group',
|
||||||
|
userId: 'usual-member-user',
|
||||||
|
roleInGroup: 'usual',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error', async () => {
|
||||||
|
authenticatedUser = await usualMemberUser.toJson()
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: removeUserFromGroupMutation(),
|
||||||
|
variables: {
|
||||||
|
groupId: 'hidden-group',
|
||||||
|
userId: 'admin-member-user',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
errors: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
message: 'Not Authorized!',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
it('removes the user from the group', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: removeUserFromGroupMutation(),
|
||||||
|
variables: {
|
||||||
|
groupId: 'hidden-group',
|
||||||
|
userId: 'usual-member-user',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
|
RemoveUserFromGroup: expect.objectContaining({
|
||||||
|
id: 'usual-member-user',
|
||||||
|
myRoleInGroup: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cannot remove self', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: removeUserFromGroupMutation(),
|
||||||
|
variables: {
|
||||||
|
groupId: 'hidden-group',
|
||||||
|
userId: 'admin-member-user',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
errors: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
message: 'Not Authorized!',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cannot remove owner', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: removeUserFromGroupMutation(),
|
||||||
|
variables: {
|
||||||
|
groupId: 'hidden-group',
|
||||||
|
userId: 'owner-member-user',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
errors: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
message: 'Not Authorized!',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1524,9 +1524,9 @@ describe('Posts in Groups', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not show the posts of the closed group anymore', async () => {
|
it('stil shows the posts of the closed group', async () => {
|
||||||
const result = await query({ query: filterPosts(), variables: {} })
|
const result = await query({ query: filterPosts(), variables: {} })
|
||||||
expect(result.data.Post).toHaveLength(3)
|
expect(result.data.Post).toHaveLength(4)
|
||||||
expect(result).toMatchObject({
|
expect(result).toMatchObject({
|
||||||
data: {
|
data: {
|
||||||
Post: expect.arrayContaining([
|
Post: expect.arrayContaining([
|
||||||
@ -1540,6 +1540,11 @@ describe('Posts in Groups', () => {
|
|||||||
title: 'A post without a group',
|
title: 'A post without a group',
|
||||||
content: 'I am a user who does not belong to a group yet.',
|
content: 'I am a user who does not belong to a group yet.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'post-to-closed-group',
|
||||||
|
title: 'A post to a closed group',
|
||||||
|
content: 'I am posting into a closed group as a member of the group',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'post-to-hidden-group',
|
id: 'post-to-hidden-group',
|
||||||
title: 'A post to a hidden group',
|
title: 'A post to a hidden group',
|
||||||
@ -1564,9 +1569,9 @@ describe('Posts in Groups', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does only show the public posts', async () => {
|
it('still shows the post of the hidden group', async () => {
|
||||||
const result = await query({ query: filterPosts(), variables: {} })
|
const result = await query({ query: filterPosts(), variables: {} })
|
||||||
expect(result.data.Post).toHaveLength(2)
|
expect(result.data.Post).toHaveLength(4)
|
||||||
expect(result).toMatchObject({
|
expect(result).toMatchObject({
|
||||||
data: {
|
data: {
|
||||||
Post: expect.arrayContaining([
|
Post: expect.arrayContaining([
|
||||||
@ -1580,6 +1585,16 @@ describe('Posts in Groups', () => {
|
|||||||
title: 'A post without a group',
|
title: 'A post without a group',
|
||||||
content: 'I am a user who does not belong to a group yet.',
|
content: 'I am a user who does not belong to a group yet.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'post-to-closed-group',
|
||||||
|
title: 'A post to a closed group',
|
||||||
|
content: 'I am posting into a closed group as a member of the group',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'post-to-hidden-group',
|
||||||
|
title: 'A post to a hidden group',
|
||||||
|
content: 'I am posting into a hidden group as a member of the group',
|
||||||
|
},
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
errors: undefined,
|
errors: undefined,
|
||||||
@ -1603,9 +1618,9 @@ describe('Posts in Groups', () => {
|
|||||||
authenticatedUser = await allGroupsUser.toJson()
|
authenticatedUser = await allGroupsUser.toJson()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not show the posts of the closed group', async () => {
|
it('shows the posts of the closed group', async () => {
|
||||||
const result = await query({ query: filterPosts(), variables: {} })
|
const result = await query({ query: filterPosts(), variables: {} })
|
||||||
expect(result.data.Post).toHaveLength(3)
|
expect(result.data.Post).toHaveLength(4)
|
||||||
expect(result).toMatchObject({
|
expect(result).toMatchObject({
|
||||||
data: {
|
data: {
|
||||||
Post: expect.arrayContaining([
|
Post: expect.arrayContaining([
|
||||||
@ -1624,6 +1639,11 @@ describe('Posts in Groups', () => {
|
|||||||
title: 'A post to a closed group',
|
title: 'A post to a closed group',
|
||||||
content: 'I am posting into a closed group as a member of the group',
|
content: 'I am posting into a closed group as a member of the group',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'post-to-hidden-group',
|
||||||
|
title: 'A post to a hidden group',
|
||||||
|
content: 'I am posting into a hidden group as a member of the group',
|
||||||
|
},
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
errors: undefined,
|
errors: undefined,
|
||||||
|
|||||||
@ -132,4 +132,9 @@ type Mutation {
|
|||||||
userId: ID!
|
userId: ID!
|
||||||
roleInGroup: GroupMemberRole!
|
roleInGroup: GroupMemberRole!
|
||||||
): User
|
): User
|
||||||
|
|
||||||
|
RemoveUserFromGroup(
|
||||||
|
groupId: ID!
|
||||||
|
userId: ID!
|
||||||
|
): User
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +1,65 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import GroupMember from './GroupMember.vue'
|
import GroupMember from './GroupMember.vue'
|
||||||
|
import { changeGroupMemberRoleMutation, removeUserFromGroupMutation } from '~/graphql/groups.js'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
const propsData = {
|
const propsData = {
|
||||||
groupId: '',
|
groupId: 'group-id',
|
||||||
groupMembers: [],
|
groupMembers: [
|
||||||
|
{
|
||||||
|
slug: 'owner',
|
||||||
|
id: 'owner',
|
||||||
|
myRoleInGroup: 'owner',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'user',
|
||||||
|
id: 'user',
|
||||||
|
myRoleInGroup: 'usual',
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stubs = {
|
||||||
|
'nuxt-link': true,
|
||||||
|
}
|
||||||
|
|
||||||
|
const apolloMock = jest
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValueOnce({ message: 'Oh no!' })
|
||||||
|
.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
ChangeGroupMemberRole: {
|
||||||
|
slug: 'user',
|
||||||
|
id: 'user',
|
||||||
|
myRoleInGroup: 'admin',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const toastErrorMock = jest.fn()
|
||||||
|
const toastSuccessMock = jest.fn()
|
||||||
|
|
||||||
describe('GroupMember', () => {
|
describe('GroupMember', () => {
|
||||||
let wrapper
|
let wrapper
|
||||||
let mocks
|
let mocks
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mocks = {
|
mocks = {
|
||||||
$t: jest.fn(),
|
$t: jest.fn((t) => t),
|
||||||
|
$apollo: {
|
||||||
|
mutate: apolloMock,
|
||||||
|
},
|
||||||
|
$toast: {
|
||||||
|
error: toastErrorMock,
|
||||||
|
success: toastSuccessMock,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('mount', () => {
|
describe('mount', () => {
|
||||||
const Wrapper = () => {
|
const Wrapper = () => {
|
||||||
return mount(GroupMember, { propsData, mocks, localVue })
|
return mount(GroupMember, { propsData, mocks, localVue, stubs })
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -30,5 +69,120 @@ describe('GroupMember', () => {
|
|||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
expect(wrapper.findAll('.group-member')).toHaveLength(1)
|
expect(wrapper.findAll('.group-member')).toHaveLength(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('has two users in table', () => {
|
||||||
|
expect(wrapper.find('tbody').findAll('tr')).toHaveLength(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has no modal', () => {
|
||||||
|
expect(wrapper.find('div.ds-modal-wrapper').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('change user role', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
wrapper
|
||||||
|
.find('tbody')
|
||||||
|
.findAll('tr')
|
||||||
|
.at(1)
|
||||||
|
.find('select')
|
||||||
|
.findAll('option')
|
||||||
|
.at(2)
|
||||||
|
.setSelected()
|
||||||
|
wrapper.find('tbody').findAll('tr').at(1).find('select').trigger('change')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with server error', () => {
|
||||||
|
it('toasts an error message', () => {
|
||||||
|
expect(toastErrorMock).toBeCalledWith('Oh no!')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with server success', () => {
|
||||||
|
it('calls the API', () => {
|
||||||
|
expect(apolloMock).toBeCalledWith({
|
||||||
|
mutation: changeGroupMemberRoleMutation(),
|
||||||
|
variables: { groupId: 'group-id', userId: 'user', roleInGroup: 'admin' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toasts a success message', () => {
|
||||||
|
expect(toastSuccessMock).toBeCalledWith('group.changeMemberRole')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('click remove user', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
apolloMock.mockRejectedValueOnce({ message: 'Oh no!!' }).mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
RemoveUserFromGroup: {
|
||||||
|
slug: 'user',
|
||||||
|
id: 'user',
|
||||||
|
myRoleInGroup: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
wrapper.find('tbody').findAll('tr').at(1).find('button').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('opens the modal', () => {
|
||||||
|
expect(wrapper.find('div.ds-modal-wrapper').isVisible()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('click on cancel', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper.find('div.ds-modal-wrapper').find('button.ds-button-ghost').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('closes the modal', () => {
|
||||||
|
expect(wrapper.find('div.ds-modal-wrapper').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('click on confirm with server error', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper.find('div.ds-modal-wrapper').find('button.ds-button-primary').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toasts an error message', () => {
|
||||||
|
expect(toastErrorMock).toBeCalledWith('Oh no!!')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('closes the modal', () => {
|
||||||
|
expect(wrapper.find('div.ds-modal-wrapper').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('click on confirm with success', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
wrapper.find('div.ds-modal-wrapper').find('button.ds-button-primary').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the API', () => {
|
||||||
|
expect(apolloMock).toBeCalledWith({
|
||||||
|
mutation: removeUserFromGroupMutation(),
|
||||||
|
variables: { groupId: 'group-id', userId: 'user' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits load group members', () => {
|
||||||
|
expect(wrapper.emitted('loadGroupMembers')).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toasts a success message', () => {
|
||||||
|
expect(toastSuccessMock).toBeCalledWith('group.memberRemoved')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('closes the modal', () => {
|
||||||
|
expect(wrapper.find('div.ds-modal-wrapper').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -53,30 +53,33 @@
|
|||||||
</ds-chip>
|
</ds-chip>
|
||||||
</template>
|
</template>
|
||||||
<template #edit="scope">
|
<template #edit="scope">
|
||||||
<ds-button v-if="scope.row.myRoleInGroup !== 'owner'" size="small" primary disabled>
|
<base-button
|
||||||
<!-- TODO: implement removal of group members -->
|
v-if="scope.row.myRoleInGroup !== 'owner'"
|
||||||
<!-- :disabled="scope.row.myRoleInGroup === 'owner'"
|
size="small"
|
||||||
-->
|
primary
|
||||||
|
@click="
|
||||||
|
isOpen = true
|
||||||
|
userId = scope.row.id
|
||||||
|
"
|
||||||
|
>
|
||||||
{{ $t('group.removeMemberButton') }}
|
{{ $t('group.removeMemberButton') }}
|
||||||
</ds-button>
|
</base-button>
|
||||||
</template>
|
</template>
|
||||||
</ds-table>
|
</ds-table>
|
||||||
<!-- TODO: implement removal of group members -->
|
<ds-modal
|
||||||
<!-- TODO: change to ocelot.social modal -->
|
v-if="isOpen"
|
||||||
<!-- <ds-modal
|
v-model="isOpen"
|
||||||
v-if="isOpen"
|
:title="`${$t('group.removeMember')}`"
|
||||||
v-model="isOpen"
|
force
|
||||||
:title="`${$t('group.removeMember')}`"
|
extended
|
||||||
force
|
:confirm-label="$t('group.removeMember')"
|
||||||
extended
|
:cancel-label="$t('actions.cancel')"
|
||||||
:confirm-label="$t('group.removeMember')"
|
@confirm="removeUser()"
|
||||||
:cancel-label="$t('actions.cancel')"
|
/>
|
||||||
@confirm="deleteMember(memberId)"
|
|
||||||
/> -->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { changeGroupMemberRoleMutation } from '~/graphql/groups.js'
|
import { changeGroupMemberRoleMutation, removeUserFromGroupMutation } from '~/graphql/groups.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'GroupMember',
|
name: 'GroupMember',
|
||||||
@ -96,6 +99,8 @@ export default {
|
|||||||
query: '',
|
query: '',
|
||||||
searchProcess: null,
|
searchProcess: null,
|
||||||
user: {},
|
user: {},
|
||||||
|
isOpen: false,
|
||||||
|
userId: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -139,6 +144,25 @@ export default {
|
|||||||
this.$toast.error(error.message)
|
this.$toast.error(error.message)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
removeUser() {
|
||||||
|
this.$apollo
|
||||||
|
.mutate({
|
||||||
|
mutation: removeUserFromGroupMutation(),
|
||||||
|
variables: { groupId: this.groupId, userId: this.userId },
|
||||||
|
})
|
||||||
|
.then(({ data }) => {
|
||||||
|
this.$emit('loadGroupMembers')
|
||||||
|
this.$toast.success(
|
||||||
|
this.$t('group.memberRemoved', { name: data.RemoveUserFromGroup.slug }),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$toast.error(error.message)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.userId = null
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -143,6 +143,19 @@ export const changeGroupMemberRoleMutation = () => {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const removeUserFromGroupMutation = () => {
|
||||||
|
return gql`
|
||||||
|
mutation ($groupId: ID!, $userId: ID!) {
|
||||||
|
RemoveUserFromGroup(groupId: $groupId, userId: $userId) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
myRoleInGroup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
// ------ queries
|
// ------ queries
|
||||||
|
|
||||||
export const groupQuery = (i18n) => {
|
export const groupQuery = (i18n) => {
|
||||||
|
|||||||
@ -455,6 +455,7 @@
|
|||||||
"message": "Eine Gruppe zu verlassen ist möglicherweise nicht rückgängig zu machen!<br>Gruppe <b>„{name}“</b> verlassen!",
|
"message": "Eine Gruppe zu verlassen ist möglicherweise nicht rückgängig zu machen!<br>Gruppe <b>„{name}“</b> verlassen!",
|
||||||
"title": "Möchtest du wirklich die Gruppe verlassen?"
|
"title": "Möchtest du wirklich die Gruppe verlassen?"
|
||||||
},
|
},
|
||||||
|
"memberRemoved": "Nutzer „{name}“ wurde aus der Gruppe entfernt!",
|
||||||
"members": "Mitglieder",
|
"members": "Mitglieder",
|
||||||
"membersAdministrationList": {
|
"membersAdministrationList": {
|
||||||
"avatar": "Avatar",
|
"avatar": "Avatar",
|
||||||
|
|||||||
@ -455,6 +455,7 @@
|
|||||||
"message": "Leaving a group may be irreversible!<br>Leave group <b>“{name}”</b>!",
|
"message": "Leaving a group may be irreversible!<br>Leave group <b>“{name}”</b>!",
|
||||||
"title": "Do you really want to leave the group?"
|
"title": "Do you really want to leave the group?"
|
||||||
},
|
},
|
||||||
|
"memberRemoved": "User “{name}” was removed from group!",
|
||||||
"members": "Members",
|
"members": "Members",
|
||||||
"membersAdministrationList": {
|
"membersAdministrationList": {
|
||||||
"avatar": "Avatar",
|
"avatar": "Avatar",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user