mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-12 23:35:52 +00:00
feat(webapp): notification settings frontend (#8320)
Implements detailed settings for email notifications. Issues relates 🚀 [Feature] Drei neue Interaktions-Benachrichtigungen #8280
This commit is contained in:
parent
fe7bab4675
commit
1b07b06ca7
@ -72,7 +72,6 @@ Factory.define('basicUser')
|
||||
termsAndConditionsAgreedAt: '2019-08-01T10:47:19.212Z',
|
||||
allowEmbedIframes: false,
|
||||
showShoutsPublicly: false,
|
||||
sendNotificationEmails: true,
|
||||
locale: 'en',
|
||||
})
|
||||
.attr('slug', ['slug', 'name'], (slug, name) => {
|
||||
|
||||
@ -56,19 +56,18 @@ const createDefaultAdminUser = async (session) => {
|
||||
`MERGE (e:EmailAddress {
|
||||
email: "${defaultAdmin.email}",
|
||||
createdAt: toString(datetime())
|
||||
})-[:BELONGS_TO]->(u:User {
|
||||
name: "${defaultAdmin.name}",
|
||||
encryptedPassword: "${defaultAdmin.password}",
|
||||
role: "admin",
|
||||
id: "${defaultAdmin.id}",
|
||||
slug: "${defaultAdmin.slug}",
|
||||
createdAt: toString(datetime()),
|
||||
allowEmbedIframes: false,
|
||||
showShoutsPublicly: false,
|
||||
sendNotificationEmails: true,
|
||||
deleted: false,
|
||||
disabled: false
|
||||
})-[:PRIMARY_EMAIL]->(e)`,
|
||||
})-[:BELONGS_TO]->(u:User {
|
||||
name: "${defaultAdmin.name}",
|
||||
encryptedPassword: "${defaultAdmin.password}",
|
||||
role: "admin",
|
||||
id: "${defaultAdmin.id}",
|
||||
slug: "${defaultAdmin.slug}",
|
||||
createdAt: toString(datetime()),
|
||||
allowEmbedIframes: false,
|
||||
showShoutsPublicly: false,
|
||||
deleted: false,
|
||||
disabled: false
|
||||
})-[:PRIMARY_EMAIL]->(e)`,
|
||||
)
|
||||
})
|
||||
try {
|
||||
|
||||
@ -0,0 +1,68 @@
|
||||
import { getDriver } from '@db/neo4j'
|
||||
|
||||
export const description =
|
||||
'Transforms the `sendNotificationEmails` property on User to a multi value system'
|
||||
|
||||
export async function up(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
try {
|
||||
// Implement your migration here.
|
||||
await transaction.run(`
|
||||
MATCH (user:User)
|
||||
SET user.emailNotificationsCommentOnObservedPost = user.sendNotificationEmails
|
||||
SET user.emailNotificationsMention = user.sendNotificationEmails
|
||||
SET user.emailNotificationsChatMessage = user.sendNotificationEmails
|
||||
SET user.emailNotificationsGroupMemberJoined = user.sendNotificationEmails
|
||||
SET user.emailNotificationsGroupMemberLeft = user.sendNotificationEmails
|
||||
SET user.emailNotificationsGroupMemberRemoved = user.sendNotificationEmails
|
||||
SET user.emailNotificationsGroupMemberRoleChanged = user.sendNotificationEmails
|
||||
REMOVE user.sendNotificationEmails
|
||||
`)
|
||||
await transaction.commit()
|
||||
next()
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
await transaction.rollback()
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
try {
|
||||
// Implement your migration here.
|
||||
await transaction.run(`
|
||||
MATCH (user:User)
|
||||
SET user.sendNotificationEmails = true
|
||||
REMOVE user.emailNotificationsCommentOnObservedPost
|
||||
REMOVE user.emailNotificationsMention
|
||||
REMOVE user.emailNotificationsChatMessage
|
||||
REMOVE user.emailNotificationsGroupMemberJoined
|
||||
REMOVE user.emailNotificationsGroupMemberLeft
|
||||
REMOVE user.emailNotificationsGroupMemberRemoved
|
||||
REMOVE user.emailNotificationsGroupMemberRoleChanged
|
||||
`)
|
||||
await transaction.commit()
|
||||
next()
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
await transaction.rollback()
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
@ -20,8 +20,10 @@ jest.mock('../helpers/email/sendMail', () => ({
|
||||
}))
|
||||
|
||||
const chatMessageTemplateMock = jest.fn()
|
||||
const notificationTemplateMock = jest.fn()
|
||||
jest.mock('../helpers/email/templateBuilder', () => ({
|
||||
chatMessageTemplate: () => chatMessageTemplateMock(),
|
||||
notificationTemplate: () => notificationTemplateMock(),
|
||||
}))
|
||||
|
||||
let isUserOnlineMock = jest.fn()
|
||||
@ -86,8 +88,8 @@ afterAll(async () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
publishSpy.mockClear()
|
||||
notifiedUser = await neode.create(
|
||||
'User',
|
||||
notifiedUser = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'you',
|
||||
name: 'Al Capone',
|
||||
@ -187,6 +189,7 @@ describe('notifications', () => {
|
||||
|
||||
describe('commenter is not me', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
commentContent = 'Commenters comment.'
|
||||
commentAuthor = await neode.create(
|
||||
'User',
|
||||
@ -202,25 +205,8 @@ describe('notifications', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('sends me a notification', async () => {
|
||||
it('sends me a notification and email', async () => {
|
||||
await createCommentOnPostAction()
|
||||
const expected = expect.objectContaining({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
createdAt: expect.any(String),
|
||||
reason: 'commented_on_post',
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c47',
|
||||
content: commentContent,
|
||||
},
|
||||
relatedUser: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
@ -228,24 +214,85 @@ describe('notifications', () => {
|
||||
read: false,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(expected)
|
||||
).resolves.toMatchObject(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
createdAt: expect.any(String),
|
||||
reason: 'commented_on_post',
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c47',
|
||||
content: commentContent,
|
||||
},
|
||||
relatedUser: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('sends me no notification if I have blocked the comment author', async () => {
|
||||
await notifiedUser.relateTo(commentAuthor, 'blocked')
|
||||
await createCommentOnPostAction()
|
||||
const expected = expect.objectContaining({
|
||||
data: { notifications: [] },
|
||||
})
|
||||
describe('if I have disabled `emailNotificationsCommentOnObservedPost`', () => {
|
||||
it('sends me a notification but no email', async () => {
|
||||
await notifiedUser.update({ emailNotificationsCommentOnObservedPost: false })
|
||||
await createCommentOnPostAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
variables: {
|
||||
read: false,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
createdAt: expect.any(String),
|
||||
reason: 'commented_on_post',
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c47',
|
||||
content: commentContent,
|
||||
},
|
||||
relatedUser: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
variables: {
|
||||
read: false,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(expected)
|
||||
// No Mail
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(notificationTemplateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('if I have blocked the comment author', () => {
|
||||
it('sends me no notification', async () => {
|
||||
await notifiedUser.relateTo(commentAuthor, 'blocked')
|
||||
await createCommentOnPostAction()
|
||||
const expected = expect.objectContaining({
|
||||
data: { notifications: [] },
|
||||
})
|
||||
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
variables: {
|
||||
read: false,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -274,6 +321,7 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
postAuthor = await neode.create(
|
||||
'User',
|
||||
{
|
||||
@ -296,7 +344,7 @@ describe('notifications', () => {
|
||||
'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone">@al-capone</a> how do you do?'
|
||||
})
|
||||
|
||||
it('sends me a notification', async () => {
|
||||
it('sends me a notification and email', async () => {
|
||||
await createPostAction()
|
||||
const expectedContent =
|
||||
'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone" target="_blank">@al-capone</a> how do you do?'
|
||||
@ -324,6 +372,47 @@ describe('notifications', () => {
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe('if I have disabled `emailNotificationsMention`', () => {
|
||||
it('sends me a notification but no email', async () => {
|
||||
await notifiedUser.update({ emailNotificationsMention: false })
|
||||
await createPostAction()
|
||||
const expectedContent =
|
||||
'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone" target="_blank">@al-capone</a> how do you do?'
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
variables: {
|
||||
read: false,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: undefined,
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
createdAt: expect.any(String),
|
||||
reason: 'mentioned_in_post',
|
||||
from: {
|
||||
__typename: 'Post',
|
||||
id: 'p47',
|
||||
content: expectedContent,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(notificationTemplateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('publishes `NOTIFICATION_ADDED` to me', async () => {
|
||||
@ -689,7 +778,7 @@ describe('notifications', () => {
|
||||
roomId = room.data.CreateRoom.id
|
||||
})
|
||||
|
||||
describe('chatReceiver is online', () => {
|
||||
describe('if the chatReceiver is online', () => {
|
||||
it('sends no email', async () => {
|
||||
isUserOnlineMock = jest.fn().mockReturnValue(true)
|
||||
|
||||
@ -706,7 +795,7 @@ describe('notifications', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('chatReceiver is offline', () => {
|
||||
describe('if the chatReceiver is offline', () => {
|
||||
it('sends an email', async () => {
|
||||
isUserOnlineMock = jest.fn().mockReturnValue(false)
|
||||
|
||||
@ -723,7 +812,7 @@ describe('notifications', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('chatReceiver has blocked chatSender', () => {
|
||||
describe('if the chatReceiver has blocked chatSender', () => {
|
||||
it('sends no email', async () => {
|
||||
isUserOnlineMock = jest.fn().mockReturnValue(false)
|
||||
await chatReceiver.relateTo(chatSender, 'blocked')
|
||||
@ -741,10 +830,10 @@ describe('notifications', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('chatReceiver has disabled email notifications', () => {
|
||||
describe('if the chatReceiver has disabled `emailNotificationsChatMessage`', () => {
|
||||
it('sends no email', async () => {
|
||||
isUserOnlineMock = jest.fn().mockReturnValue(false)
|
||||
await chatReceiver.update({ sendNotificationEmails: false })
|
||||
await chatReceiver.update({ emailNotificationsChatMessage: false })
|
||||
|
||||
await mutate({
|
||||
mutation: createMessageMutation(),
|
||||
@ -764,8 +853,8 @@ describe('notifications', () => {
|
||||
let groupOwner
|
||||
|
||||
beforeEach(async () => {
|
||||
groupOwner = await neode.create(
|
||||
'User',
|
||||
groupOwner = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'group-owner',
|
||||
name: 'Group Owner',
|
||||
@ -792,7 +881,7 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
describe('user joins group', () => {
|
||||
beforeEach(async () => {
|
||||
const joinGroupAction = async () => {
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
@ -802,9 +891,14 @@ describe('notifications', () => {
|
||||
},
|
||||
})
|
||||
authenticatedUser = await groupOwner.toJson()
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('has the notification in database', async () => {
|
||||
it('sends the group owner a notification and email', async () => {
|
||||
await joinGroupAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
@ -828,19 +922,50 @@ describe('notifications', () => {
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe('if the group owner has disabled `emailNotificationsGroupMemberJoined`', () => {
|
||||
it('sends the group owner a notification but no email', async () => {
|
||||
await groupOwner.update({ emailNotificationsGroupMemberJoined: false })
|
||||
await joinGroupAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
reason: 'user_joined_group',
|
||||
createdAt: expect.any(String),
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'closed-group',
|
||||
},
|
||||
relatedUser: {
|
||||
id: 'you',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(notificationTemplateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('user leaves group', () => {
|
||||
beforeEach(async () => {
|
||||
describe('user joins and leaves group', () => {
|
||||
const leaveGroupAction = async () => {
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: authenticatedUser.id,
|
||||
},
|
||||
})
|
||||
await mutate({
|
||||
mutation: leaveGroupMutation(),
|
||||
variables: {
|
||||
@ -849,9 +974,22 @@ describe('notifications', () => {
|
||||
},
|
||||
})
|
||||
authenticatedUser = await groupOwner.toJson()
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: authenticatedUser.id,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('has two the notification in database', async () => {
|
||||
it('sends the group owner two notifications and emails', async () => {
|
||||
await leaveGroupAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
@ -887,19 +1025,61 @@ describe('notifications', () => {
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(2)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
describe('if the group owner has disabled `emailNotificationsGroupMemberLeft`', () => {
|
||||
it('sends the group owner two notification but only only one email', async () => {
|
||||
await groupOwner.update({ emailNotificationsGroupMemberLeft: false })
|
||||
await leaveGroupAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
reason: 'user_left_group',
|
||||
createdAt: expect.any(String),
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'closed-group',
|
||||
},
|
||||
relatedUser: {
|
||||
id: 'you',
|
||||
},
|
||||
},
|
||||
{
|
||||
read: false,
|
||||
reason: 'user_joined_group',
|
||||
createdAt: expect.any(String),
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'closed-group',
|
||||
},
|
||||
relatedUser: {
|
||||
id: 'you',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('user role in group changes', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: authenticatedUser.id,
|
||||
},
|
||||
})
|
||||
const changeGroupMemberRoleAction = async () => {
|
||||
authenticatedUser = await groupOwner.toJson()
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
@ -910,9 +1090,23 @@ describe('notifications', () => {
|
||||
},
|
||||
})
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: authenticatedUser.id,
|
||||
},
|
||||
})
|
||||
// Clear after because the above generates a notification not related
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('has notification in database', async () => {
|
||||
it('sends the group member a notification and email', async () => {
|
||||
await changeGroupMemberRoleAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
@ -936,19 +1130,49 @@ describe('notifications', () => {
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe('if the group member has disabled `emailNotificationsGroupMemberRoleChanged`', () => {
|
||||
it('sends the group member a notification but no email', async () => {
|
||||
notifiedUser.update({ emailNotificationsGroupMemberRoleChanged: false })
|
||||
await changeGroupMemberRoleAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
reason: 'changed_group_member_role',
|
||||
createdAt: expect.any(String),
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'closed-group',
|
||||
},
|
||||
relatedUser: {
|
||||
id: 'group-owner',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(notificationTemplateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('user is removed from group', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: authenticatedUser.id,
|
||||
},
|
||||
})
|
||||
const removeUserFromGroupAction = async () => {
|
||||
authenticatedUser = await groupOwner.toJson()
|
||||
await mutate({
|
||||
mutation: removeUserFromGroupMutation(),
|
||||
@ -958,9 +1182,23 @@ describe('notifications', () => {
|
||||
},
|
||||
})
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
groupId: 'closed-group',
|
||||
userId: authenticatedUser.id,
|
||||
},
|
||||
})
|
||||
// Clear after because the above generates a notification not related
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('has notification in database', async () => {
|
||||
it('sends the previous group member a notification and email', async () => {
|
||||
await removeUserFromGroupAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
@ -984,6 +1222,44 @@ describe('notifications', () => {
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).toHaveBeenCalledTimes(1)
|
||||
expect(notificationTemplateMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe('if the previous group member has disabled `emailNotificationsGroupMemberRemoved`', () => {
|
||||
it('sends the previous group member a notification but no email', async () => {
|
||||
notifiedUser.update({ emailNotificationsGroupMemberRemoved: false })
|
||||
await removeUserFromGroupAction()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
read: false,
|
||||
reason: 'removed_user_from_group',
|
||||
createdAt: expect.any(String),
|
||||
from: {
|
||||
__typename: 'Group',
|
||||
id: 'closed-group',
|
||||
},
|
||||
relatedUser: {
|
||||
id: 'group-owner',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
|
||||
// Mail
|
||||
expect(sendMailMock).not.toHaveBeenCalled()
|
||||
expect(notificationTemplateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -38,7 +38,7 @@ const queryNotificationEmails = async (context, notificationUserIds) => {
|
||||
}
|
||||
}
|
||||
|
||||
const publishNotifications = async (context, promises) => {
|
||||
const publishNotifications = async (context, promises, emailNotificationSetting: string) => {
|
||||
let notifications = await Promise.all(promises)
|
||||
notifications = notifications.flat()
|
||||
const notificationsEmailAddresses = await queryNotificationEmails(
|
||||
@ -47,7 +47,7 @@ const publishNotifications = async (context, promises) => {
|
||||
)
|
||||
notifications.forEach((notificationAdded, index) => {
|
||||
pubsub.publish(NOTIFICATION_ADDED, { notificationAdded })
|
||||
if (notificationAdded.to.sendNotificationEmails) {
|
||||
if (notificationAdded.to[emailNotificationSetting] ?? true) {
|
||||
sendMail(
|
||||
notificationTemplate({
|
||||
email: notificationsEmailAddresses[index].email,
|
||||
@ -62,9 +62,11 @@ const handleJoinGroup = async (resolve, root, args, context, resolveInfo) => {
|
||||
const { groupId, userId } = args
|
||||
const user = await resolve(root, args, context, resolveInfo)
|
||||
if (user) {
|
||||
await publishNotifications(context, [
|
||||
notifyOwnersOfGroup(groupId, userId, 'user_joined_group', context),
|
||||
])
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyOwnersOfGroup(groupId, userId, 'user_joined_group', context)],
|
||||
'emailNotificationsGroupMemberJoined',
|
||||
)
|
||||
}
|
||||
return user
|
||||
}
|
||||
@ -73,9 +75,11 @@ const handleLeaveGroup = async (resolve, root, args, context, resolveInfo) => {
|
||||
const { groupId, userId } = args
|
||||
const user = await resolve(root, args, context, resolveInfo)
|
||||
if (user) {
|
||||
await publishNotifications(context, [
|
||||
notifyOwnersOfGroup(groupId, userId, 'user_left_group', context),
|
||||
])
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyOwnersOfGroup(groupId, userId, 'user_left_group', context)],
|
||||
'emailNotificationsGroupMemberLeft',
|
||||
)
|
||||
}
|
||||
return user
|
||||
}
|
||||
@ -84,9 +88,11 @@ const handleChangeGroupMemberRole = async (resolve, root, args, context, resolve
|
||||
const { groupId, userId } = args
|
||||
const user = await resolve(root, args, context, resolveInfo)
|
||||
if (user) {
|
||||
await publishNotifications(context, [
|
||||
notifyMemberOfGroup(groupId, userId, 'changed_group_member_role', context),
|
||||
])
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyMemberOfGroup(groupId, userId, 'changed_group_member_role', context)],
|
||||
'emailNotificationsGroupMemberRoleChanged',
|
||||
)
|
||||
}
|
||||
return user
|
||||
}
|
||||
@ -95,9 +101,11 @@ const handleRemoveUserFromGroup = async (resolve, root, args, context, resolveIn
|
||||
const { groupId, userId } = args
|
||||
const user = await resolve(root, args, context, resolveInfo)
|
||||
if (user) {
|
||||
await publishNotifications(context, [
|
||||
notifyMemberOfGroup(groupId, userId, 'removed_user_from_group', context),
|
||||
])
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyMemberOfGroup(groupId, userId, 'removed_user_from_group', context)],
|
||||
'emailNotificationsGroupMemberRemoved',
|
||||
)
|
||||
}
|
||||
return user
|
||||
}
|
||||
@ -106,9 +114,11 @@ const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo
|
||||
const idsOfUsers = extractMentionedUsers(args.content)
|
||||
const post = await resolve(root, args, context, resolveInfo)
|
||||
if (post) {
|
||||
await publishNotifications(context, [
|
||||
notifyUsersOfMention('Post', post.id, idsOfUsers, 'mentioned_in_post', context),
|
||||
])
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyUsersOfMention('Post', post.id, idsOfUsers, 'mentioned_in_post', context)],
|
||||
'emailNotificationsMention',
|
||||
)
|
||||
}
|
||||
return post
|
||||
}
|
||||
@ -119,16 +129,26 @@ const handleContentDataOfComment = async (resolve, root, args, context, resolveI
|
||||
const comment = await resolve(root, args, context, resolveInfo)
|
||||
const [postAuthor] = await postAuthorOfComment(comment.id, { context })
|
||||
idsOfMentionedUsers = idsOfMentionedUsers.filter((id) => id !== postAuthor.id)
|
||||
await publishNotifications(context, [
|
||||
notifyUsersOfMention(
|
||||
'Comment',
|
||||
comment.id,
|
||||
idsOfMentionedUsers,
|
||||
'mentioned_in_comment',
|
||||
context,
|
||||
),
|
||||
notifyUsersOfComment('Comment', comment.id, 'commented_on_post', context),
|
||||
])
|
||||
await publishNotifications(
|
||||
context,
|
||||
[
|
||||
notifyUsersOfMention(
|
||||
'Comment',
|
||||
comment.id,
|
||||
idsOfMentionedUsers,
|
||||
'mentioned_in_comment',
|
||||
context,
|
||||
),
|
||||
],
|
||||
'emailNotificationsMention',
|
||||
)
|
||||
|
||||
await publishNotifications(
|
||||
context,
|
||||
[notifyUsersOfComment('Comment', comment.id, 'commented_on_post', context)],
|
||||
'emailNotificationsCommentOnObservedPost',
|
||||
)
|
||||
|
||||
return comment
|
||||
}
|
||||
|
||||
@ -339,7 +359,7 @@ const handleCreateMessage = async (resolve, root, args, context, resolveInfo) =>
|
||||
MATCH (room)<-[:CHATS_IN]-(recipientUser:User)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
|
||||
WHERE NOT recipientUser.id = $currentUserId
|
||||
AND NOT (recipientUser)-[:BLOCKED]-(currentUser)
|
||||
AND recipientUser.sendNotificationEmails = true
|
||||
AND NOT recipientUser.emailNotificationsChatMessage = false
|
||||
RETURN recipientUser, emailAddress {.email}
|
||||
`
|
||||
const txResponse = await transaction.run(messageRecipientCypher, {
|
||||
|
||||
@ -471,6 +471,7 @@ export default shield(
|
||||
},
|
||||
User: {
|
||||
email: or(isMyOwn, isAdmin),
|
||||
emailNotificationSettings: isMyOwn,
|
||||
},
|
||||
Report: isModerator,
|
||||
},
|
||||
|
||||
@ -155,10 +155,37 @@ export default {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
sendNotificationEmails: {
|
||||
|
||||
// emailNotifications
|
||||
emailNotificationsCommentOnObservedPost: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
emailNotificationsMention: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
emailNotificationsChatMessage: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
emailNotificationsGroupMemberJoined: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
emailNotificationsGroupMemberLeft: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
emailNotificationsGroupMemberRemoved: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
emailNotificationsGroupMemberRoleChanged: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
|
||||
locale: {
|
||||
type: 'string',
|
||||
allow: [null],
|
||||
|
||||
@ -11,6 +11,8 @@ export default makeAugmentedSchema({
|
||||
exclude: [
|
||||
'Badge',
|
||||
'Embed',
|
||||
'EmailNotificationSettings',
|
||||
'EmailNotificationSettingsOption',
|
||||
'EmailAddress',
|
||||
'Notification',
|
||||
'Statistics',
|
||||
|
||||
@ -100,7 +100,6 @@ const signupCypher = (inviteCode) => {
|
||||
SET user.updatedAt = toString(datetime())
|
||||
SET user.allowEmbedIframes = false
|
||||
SET user.showShoutsPublicly = false
|
||||
SET user.sendNotificationEmails = true
|
||||
SET email.verifiedAt = toString(datetime())
|
||||
WITH user
|
||||
OPTIONAL MATCH (post:Post)-[:IN]->(group:Group)
|
||||
|
||||
@ -593,6 +593,220 @@ describe('switch user role', () => {
|
||||
})
|
||||
})
|
||||
|
||||
let anotherUser
|
||||
const emailNotificationSettingsQuery = gql`
|
||||
query ($id: ID!) {
|
||||
User(id: $id) {
|
||||
emailNotificationSettings {
|
||||
type
|
||||
settings {
|
||||
name
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const emailNotificationSettingsMutation = gql`
|
||||
mutation ($id: ID!, $emailNotificationSettings: [EmailNotificationSettingsInput]!) {
|
||||
UpdateUser(id: $id, emailNotificationSettings: $emailNotificationSettings) {
|
||||
emailNotificationSettings {
|
||||
type
|
||||
settings {
|
||||
name
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
describe('emailNotificationSettings', () => {
|
||||
beforeEach(async () => {
|
||||
user = await Factory.build('user', {
|
||||
id: 'user',
|
||||
role: 'user',
|
||||
})
|
||||
anotherUser = await Factory.build('user', {
|
||||
id: 'anotherUser',
|
||||
role: 'anotherUser',
|
||||
})
|
||||
})
|
||||
|
||||
describe('query the field', () => {
|
||||
describe('as another user', () => {
|
||||
it('throws an error', async () => {
|
||||
authenticatedUser = await anotherUser.toJson()
|
||||
const targetUser = await user.toJson()
|
||||
await expect(
|
||||
query({ query: emailNotificationSettingsQuery, variables: { id: targetUser.id } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
expect.objectContaining({
|
||||
message: 'Not Authorized!',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('as self', () => {
|
||||
it('returns the emailNotificationSettings', async () => {
|
||||
authenticatedUser = await user.toJson()
|
||||
await expect(
|
||||
query({ query: emailNotificationSettingsQuery, variables: { id: authenticatedUser.id } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
User: [
|
||||
{
|
||||
emailNotificationSettings: [
|
||||
{
|
||||
type: 'post',
|
||||
settings: [
|
||||
{
|
||||
name: 'commentOnObservedPost',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'mention',
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'chat',
|
||||
settings: [
|
||||
{
|
||||
name: 'chatMessage',
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
settings: [
|
||||
{
|
||||
name: 'groupMemberJoined',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'groupMemberLeft',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'groupMemberRemoved',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'groupMemberRoleChanged',
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('mutate the field', () => {
|
||||
const emailNotificationSettings = [{ name: 'mention', value: false }]
|
||||
|
||||
describe('as another user', () => {
|
||||
it('throws an error', async () => {
|
||||
authenticatedUser = await anotherUser.toJson()
|
||||
const targetUser = await user.toJson()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: emailNotificationSettingsMutation,
|
||||
variables: { id: targetUser.id, emailNotificationSettings },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
expect.objectContaining({
|
||||
message: 'Not Authorized!',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('as self', () => {
|
||||
it('updates the emailNotificationSettings', async () => {
|
||||
authenticatedUser = await user.toJson()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: emailNotificationSettingsMutation,
|
||||
variables: { id: authenticatedUser.id, emailNotificationSettings },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
UpdateUser: {
|
||||
emailNotificationSettings: [
|
||||
{
|
||||
type: 'post',
|
||||
settings: [
|
||||
{
|
||||
name: 'commentOnObservedPost',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'mention',
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'chat',
|
||||
settings: [
|
||||
{
|
||||
name: 'chatMessage',
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
settings: [
|
||||
{
|
||||
name: 'groupMemberJoined',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'groupMemberLeft',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'groupMemberRemoved',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'groupMemberRoleChanged',
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('save category settings', () => {
|
||||
beforeEach(async () => {
|
||||
await Promise.all(
|
||||
|
||||
@ -152,6 +152,19 @@ export default {
|
||||
}
|
||||
params.termsAndConditionsAgreedAt = new Date().toISOString()
|
||||
}
|
||||
|
||||
const {
|
||||
emailNotificationSettings,
|
||||
}: { emailNotificationSettings: { name: string; value: boolean }[] | undefined } = params
|
||||
delete params.emailNotificationSettings
|
||||
if (emailNotificationSettings) {
|
||||
emailNotificationSettings.forEach((setting) => {
|
||||
params[
|
||||
'emailNotifications' + setting.name.charAt(0).toUpperCase() + setting.name.slice(1)
|
||||
] = setting.value
|
||||
})
|
||||
}
|
||||
|
||||
const session = context.driver.session()
|
||||
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
@ -357,6 +370,53 @@ export default {
|
||||
const [{ email }] = result.records.map((r) => r.get('e').properties)
|
||||
return email
|
||||
},
|
||||
emailNotificationSettings: async (parent, params, context, resolveInfo) => {
|
||||
return [
|
||||
{
|
||||
type: 'post',
|
||||
settings: [
|
||||
{
|
||||
name: 'commentOnObservedPost',
|
||||
value: parent.emailNotificationsCommentOnObservedPost ?? true,
|
||||
},
|
||||
{
|
||||
name: 'mention',
|
||||
value: parent.emailNotificationsMention ?? true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'chat',
|
||||
settings: [
|
||||
{
|
||||
name: 'chatMessage',
|
||||
value: parent.emailNotificationsChatMessage ?? true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
settings: [
|
||||
{
|
||||
name: 'groupMemberJoined',
|
||||
value: parent.emailNotificationsGroupMemberJoined ?? true,
|
||||
},
|
||||
{
|
||||
name: 'groupMemberLeft',
|
||||
value: parent.emailNotificationsGroupMemberLeft ?? true,
|
||||
},
|
||||
{
|
||||
name: 'groupMemberRemoved',
|
||||
value: parent.emailNotificationsGroupMemberRemoved ?? true,
|
||||
},
|
||||
{
|
||||
name: 'groupMemberRoleChanged',
|
||||
value: parent.emailNotificationsGroupMemberRoleChanged ?? true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
...Resolver('User', {
|
||||
undefinedToNull: [
|
||||
'actorId',
|
||||
@ -368,7 +428,6 @@ export default {
|
||||
'termsAndConditionsAgreedAt',
|
||||
'allowEmbedIframes',
|
||||
'showShoutsPublicly',
|
||||
'sendNotificationEmails',
|
||||
'locale',
|
||||
],
|
||||
boolean: {
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
enum EmailNotificationSettingsName {
|
||||
commentOnObservedPost
|
||||
mention
|
||||
chatMessage
|
||||
groupMemberJoined
|
||||
groupMemberLeft
|
||||
groupMemberRemoved
|
||||
groupMemberRoleChanged
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
enum EmailNotificationSettingsType {
|
||||
post
|
||||
chat
|
||||
group
|
||||
}
|
||||
@ -19,6 +19,21 @@ enum _UserOrdering {
|
||||
locale_desc
|
||||
}
|
||||
|
||||
input EmailNotificationSettingsInput {
|
||||
name: EmailNotificationSettingsName
|
||||
value: Boolean
|
||||
}
|
||||
|
||||
type EmailNotificationSettings {
|
||||
type: EmailNotificationSettingsType
|
||||
settings: [EmailNotificationSettingsOption] @neo4j_ignore
|
||||
}
|
||||
|
||||
type EmailNotificationSettingsOption {
|
||||
name: EmailNotificationSettingsName
|
||||
value: Boolean
|
||||
}
|
||||
|
||||
type User {
|
||||
id: ID!
|
||||
actorId: String
|
||||
@ -46,7 +61,7 @@ type User {
|
||||
|
||||
allowEmbedIframes: Boolean
|
||||
showShoutsPublicly: Boolean
|
||||
sendNotificationEmails: Boolean
|
||||
emailNotificationSettings: [EmailNotificationSettings]! @neo4j_ignore
|
||||
locale: String
|
||||
friends: [User]! @relation(name: "FRIENDS", direction: "BOTH")
|
||||
friendsCount: Int! @cypher(statement: "MATCH (this)<-[:FRIENDS]->(r:User) RETURN COUNT(DISTINCT r)")
|
||||
@ -206,7 +221,7 @@ type Mutation {
|
||||
termsAndConditionsAgreedAt: String
|
||||
allowEmbedIframes: Boolean
|
||||
showShoutsPublicly: Boolean
|
||||
sendNotificationEmails: Boolean
|
||||
emailNotificationSettings: [EmailNotificationSettingsInput]
|
||||
locale: String
|
||||
): User
|
||||
|
||||
|
||||
@ -46,7 +46,6 @@ export const profileUserQuery = (i18n) => {
|
||||
url
|
||||
}
|
||||
showShoutsPublicly
|
||||
sendNotificationEmails
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -335,7 +334,7 @@ export const updateUserMutation = () => {
|
||||
$about: String
|
||||
$allowEmbedIframes: Boolean
|
||||
$showShoutsPublicly: Boolean
|
||||
$sendNotificationEmails: Boolean
|
||||
$emailNotificationSettings: [EmailNotificationSettingsInput]
|
||||
$termsAndConditionsAgreedVersion: String
|
||||
$avatar: ImageInput
|
||||
$locationName: String # empty string '' sets it to null
|
||||
@ -347,7 +346,7 @@ export const updateUserMutation = () => {
|
||||
about: $about
|
||||
allowEmbedIframes: $allowEmbedIframes
|
||||
showShoutsPublicly: $showShoutsPublicly
|
||||
sendNotificationEmails: $sendNotificationEmails
|
||||
emailNotificationSettings: $emailNotificationSettings
|
||||
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
|
||||
avatar: $avatar
|
||||
locationName: $locationName
|
||||
@ -359,7 +358,13 @@ export const updateUserMutation = () => {
|
||||
about
|
||||
allowEmbedIframes
|
||||
showShoutsPublicly
|
||||
sendNotificationEmails
|
||||
emailNotificationSettings {
|
||||
type
|
||||
settings {
|
||||
name
|
||||
value
|
||||
}
|
||||
}
|
||||
locale
|
||||
termsAndConditionsAgreedVersion
|
||||
avatar {
|
||||
@ -390,7 +395,13 @@ export const currentUserQuery = gql`
|
||||
locale
|
||||
allowEmbedIframes
|
||||
showShoutsPublicly
|
||||
sendNotificationEmails
|
||||
emailNotificationSettings {
|
||||
type
|
||||
settings {
|
||||
name
|
||||
value
|
||||
}
|
||||
}
|
||||
termsAndConditionsAgreedVersion
|
||||
socialMedia {
|
||||
id
|
||||
|
||||
@ -1039,9 +1039,23 @@
|
||||
},
|
||||
"name": "Einstellungen",
|
||||
"notifications": {
|
||||
"name": "Benachrichtigungen",
|
||||
"chat": "Chat",
|
||||
"chatMessage": "Nachricht erhalten während Abwesenheit",
|
||||
"checkAll": "Alle auswählen",
|
||||
"commentOnObservedPost": "Kommentare zu beobachteten Beiträgen",
|
||||
"group": "Gruppen",
|
||||
"groupMemberJoined": "Ein Mitglied ist deiner Gruppe beigetreten",
|
||||
"groupMemberLeft": "Ein Mitglied hat deine Gruppe verlassen",
|
||||
"groupMemberRemoved": "Du wurdest aus einer Gruppe entfernt",
|
||||
"groupMemberRoleChanged": "Deine Rolle in einer Gruppe wurde geändert",
|
||||
"mention": "Ich wurde erwähnt",
|
||||
"name": "Benachrichtigungen per Email",
|
||||
"post": "Beiträge und Kommentare",
|
||||
"postByFollowedUser": "Beitrag von einem Nutzer, dem ich folge",
|
||||
"postInGroup": "Beitrag in einer Gruppe, die ich beobachte",
|
||||
"send-email-notifications": "Sende E-Mail-Benachrichtigungen",
|
||||
"success-update": "Benachrichtigungs-Einstellungen gespeichert!"
|
||||
"success-update": "Benachrichtigungs-Einstellungen gespeichert!",
|
||||
"uncheckAll": "Alle abwählen"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Meine Organisationen"
|
||||
|
||||
@ -1039,9 +1039,23 @@
|
||||
},
|
||||
"name": "Settings",
|
||||
"notifications": {
|
||||
"name": "Notifications",
|
||||
"chat": "Chat",
|
||||
"chatMessage": "Message received while absent",
|
||||
"checkAll": "Check all",
|
||||
"commentOnObservedPost": "Comments on observed posts",
|
||||
"group": "Groups",
|
||||
"groupMemberJoined": "Member joined a group I own",
|
||||
"groupMemberLeft": "Member left a group I own",
|
||||
"groupMemberRemoved": "I was removed from a group",
|
||||
"groupMemberRoleChanged": "My role in a group was changed",
|
||||
"mention": "I was mentioned",
|
||||
"name": "Email Notifications",
|
||||
"post": "Posts and comments",
|
||||
"postByFollowedUser": "Posts by users I follow",
|
||||
"postInGroup": "Post in a group I am a member of",
|
||||
"send-email-notifications": "Send e-mail notifications",
|
||||
"success-update": "Notifications settings saved!"
|
||||
"success-update": "Notifications settings saved!",
|
||||
"uncheckAll": "Uncheck all"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "My Organizations"
|
||||
|
||||
@ -1039,9 +1039,23 @@
|
||||
},
|
||||
"name": "Configuración",
|
||||
"notifications": {
|
||||
"name": null,
|
||||
"send-email-notifications": null,
|
||||
"success-update": null
|
||||
"chat": "Chat",
|
||||
"chatMessage": "Mensaje recibido mientras estaba ausente",
|
||||
"checkAll": "Seleccionar todo",
|
||||
"commentOnObservedPost": "Comentario en una contribución que estoy observando",
|
||||
"group": "Grupos",
|
||||
"groupMemberJoined": "Un nuevo miembro se unió a un grupo mio",
|
||||
"groupMemberLeft": "Un miembro dejó un grupo mio",
|
||||
"groupMemberRemoved": "Fui eliminado de un grupo",
|
||||
"groupMemberRoleChanged": "Mi rol en un grupo ha cambiado",
|
||||
"mention": "Mencionado en una contribución",
|
||||
"name": "Notificaciones por correo electrónico",
|
||||
"post": "Entradas y comentarios",
|
||||
"postByFollowedUser": "Posts by users I follow",
|
||||
"postInGroup": "Post en un grupo del que soy miembro",
|
||||
"send-email-notifications": "Enviar notificaciones por correo electrónico",
|
||||
"success-update": "¡Configuración de notificaciones guardada!",
|
||||
"uncheckAll": "Deseleccionar todo"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Mis organizaciones"
|
||||
|
||||
@ -1039,9 +1039,23 @@
|
||||
},
|
||||
"name": "Paramètres",
|
||||
"notifications": {
|
||||
"name": null,
|
||||
"send-email-notifications": null,
|
||||
"success-update": null
|
||||
"chat": "Chat",
|
||||
"chatMessage": "Message reçu pendant l'absence",
|
||||
"checkAll": "Tout cocher",
|
||||
"commentOnObservedPost": "Commentez une contribution que je suis",
|
||||
"group": "Groups",
|
||||
"groupMemberJoined": "Un nouveau membre a rejoint un de mes groupes",
|
||||
"groupMemberLeft": "Un membre a quitté un de mes groupes",
|
||||
"groupMemberRemoved": "J'ai été retiré d'un groupe",
|
||||
"groupMemberRoleChanged": "Mon rôle au sein d'un groupe a changé",
|
||||
"mention": "Mentionné dans une contribution",
|
||||
"name": "Notifications par mail",
|
||||
"post": "Messages et commentaires",
|
||||
"postByFollowedUser": "Messages des utilisateurs que je suis",
|
||||
"postInGroup": "Message dans un groupe dont je suis membre",
|
||||
"send-email-notifications": "Envoyer des notifications par courrier électronique",
|
||||
"success-update": "Paramètres de notification sauvegardés ! ",
|
||||
"uncheckAll": "Tout décocher"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Mes organisations"
|
||||
|
||||
@ -1039,9 +1039,23 @@
|
||||
},
|
||||
"name": "Impostazioni",
|
||||
"notifications": {
|
||||
"name": null,
|
||||
"send-email-notifications": null,
|
||||
"success-update": null
|
||||
"chat": "Chat",
|
||||
"chatMessage": "Messaggio ricevuto durante l'assenza",
|
||||
"checkAll": "Seleziona tutto",
|
||||
"commentOnObservedPost": "Commenta un contributo che sto guardando",
|
||||
"group": "Gruppi",
|
||||
"groupMemberJoined": "Un nuovo membro si è unito a un mio gruppo",
|
||||
"groupMemberLeft": "Un membro ha lasciato un mio gruppo",
|
||||
"groupMemberRemoved": "Sono stato rimosso da un gruppo",
|
||||
"groupMemberRoleChanged": "Il mio ruolo in un gruppo è cambiato",
|
||||
"mention": "Menzionato in un contributo",
|
||||
"name": "Notifiche via e-mail",
|
||||
"post": "Messaggi e commenti",
|
||||
"postByFollowedUser": "Messaggi di utenti che seguo",
|
||||
"postInGroup": "Post in un gruppo di cui sono membro",
|
||||
"send-email-notifications": "Invia notifiche via e-mail",
|
||||
"success-update": "Impostazioni di notifica salvate! ",
|
||||
"uncheckAll": "Deseleziona tutto"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Mie organizzazioni"
|
||||
|
||||
@ -1039,9 +1039,23 @@
|
||||
},
|
||||
"name": "Instellingen",
|
||||
"notifications": {
|
||||
"name": null,
|
||||
"send-email-notifications": null,
|
||||
"success-update": null
|
||||
"chat": "Chat",
|
||||
"chatMessage": "Bericht ontvangen tijdens afwezigheid",
|
||||
"checkAll": "Vink alles aan",
|
||||
"commentOnObservedPost": "Geef commentaar op een bijdrage die ik volg",
|
||||
"group": "Groepen",
|
||||
"groupMemberJoined": "Een nieuw lid is lid geworden van een groep van mij",
|
||||
"groupMemberLeft": "Een lid heeft een groep van mij verlaten",
|
||||
"groupMemberRemoved": "Ik ben verwijderd uit een groep",
|
||||
"groupMemberRoleChanged": "Mijn rol in een groep is veranderd",
|
||||
"mention": "Genoemd in een bijdrage",
|
||||
"name": "Email Meldingen",
|
||||
"post": "Berichten en reacties",
|
||||
"postByFollowedUser": "Berichten van gebruikers die ik volg",
|
||||
"postInGroup": "Bericht in een groep waar ik lid van ben",
|
||||
"send-email-notifications": "E-mailmeldingen verzenden",
|
||||
"success-update": "Meldingsinstellingen opgeslagen! ",
|
||||
"uncheckAll": "Vink alles uit"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Mijn Organisaties"
|
||||
|
||||
@ -1039,9 +1039,23 @@
|
||||
},
|
||||
"name": "Ustawienia",
|
||||
"notifications": {
|
||||
"name": null,
|
||||
"send-email-notifications": null,
|
||||
"success-update": null
|
||||
"chat": "Chat",
|
||||
"chatMessage": "Wiadomość otrzymana podczas nieobecności",
|
||||
"checkAll": "Wybierz wszystko",
|
||||
"commentOnObservedPost": "Skomentuj wpis, który obserwuję",
|
||||
"group": "Grupy",
|
||||
"groupMemberJoined": "Nowy członek dołączył do mojej grupy",
|
||||
"groupMemberLeft": "Członek opuścił moją grupę",
|
||||
"groupMemberRemoved": "Zostałem usunięty z grupy",
|
||||
"groupMemberRoleChanged": "Moja rola w grupie uległa zmianie",
|
||||
"mention": "Mentioned in a contribution",
|
||||
"name": "Powiadomienia e-mail",
|
||||
"post": "Posty",
|
||||
"postByFollowedUser": "Posty użytkowników, których obserwuję",
|
||||
"postInGroup": "Posty w grupie, której jestem członkiem",
|
||||
"send-email-notifications": "Wyślij powiadomienia e-mail",
|
||||
"success-update": "Ustawienia powiadomień zapisane! ",
|
||||
"uncheckAll": "Odznacz wszystko"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "My Organizations"
|
||||
|
||||
@ -1039,9 +1039,23 @@
|
||||
},
|
||||
"name": "Configurações",
|
||||
"notifications": {
|
||||
"name": null,
|
||||
"send-email-notifications": null,
|
||||
"success-update": null
|
||||
"chat": "Chat",
|
||||
"chatMessage": "Mensagem recebida durante a ausência",
|
||||
"checkAll": "Marcar tudo",
|
||||
"commentOnObservedPost": "Comentários sobre as mensagens observadas",
|
||||
"group": "Grupos",
|
||||
"groupMemberJoined": "Member joined a group I own",
|
||||
"groupMemberLeft": "Membro saiu de um grupo de que sou proprietário",
|
||||
"groupMemberRemoved": "Fui removido de um grupo",
|
||||
"groupMemberRoleChanged": "O meu papel num grupo foi alterado",
|
||||
"mention": "Fui mencionado",
|
||||
"name": "Notificações por correio eletrónico",
|
||||
"post": "Posts e comentários",
|
||||
"postByFollowedUser": "Publicações de utilizadores que sigo",
|
||||
"postInGroup": "Postar num grupo de que sou membro",
|
||||
"send-email-notifications": "Enviar notificações por correio eletrónico",
|
||||
"success-update": "Definições de notificações guardadas!",
|
||||
"uncheckAll": "Desmarcar tudo"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Minhas Organizações"
|
||||
|
||||
@ -1039,9 +1039,23 @@
|
||||
},
|
||||
"name": "Настройки",
|
||||
"notifications": {
|
||||
"name": null,
|
||||
"send-email-notifications": null,
|
||||
"success-update": null
|
||||
"chat": "Чат",
|
||||
"chatMessage": "Сообщение, полученное во время отсутствия",
|
||||
"checkAll": "Отметить все",
|
||||
"commentOnObservedPost": "Комментарии по поводу замеченных сообщений",
|
||||
"group": "Группы",
|
||||
"groupMemberJoined": "Участник присоединился к группе, которой я владею",
|
||||
"groupMemberLeft": "Участник вышел из группы, которой владею",
|
||||
"groupMemberRemoved": "Был удален из группы",
|
||||
"groupMemberRoleChanged": "Моя роль в группе была изменена",
|
||||
"mention": "Упоминание в вкладе",
|
||||
"name": "Уведомления",
|
||||
"post": "Сообщения и комментарии",
|
||||
"postByFollowedUser": "Сообщения пользователей, за которыми я слежу",
|
||||
"postInGroup": "Сообщение в группе, членом которой я являюсь",
|
||||
"send-email-notifications": "Отправлять уведомления по электронной почте",
|
||||
"success-update": "Настройки уведомлений сохранены! ",
|
||||
"uncheckAll": "Снимите все флажки"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Мои организации"
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
"locales:normalize": "../scripts/translations/normalize.sh",
|
||||
"precommit": "yarn lint",
|
||||
"test": "cross-env NODE_ENV=test jest --coverage --forceExit --detectOpenHandles",
|
||||
"test:unit:update": "yarn test -- --updateSnapshot",
|
||||
"test:unit:debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --no-cache --runInBand"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -78,6 +79,7 @@
|
||||
"@storybook/addon-actions": "^5.3.21",
|
||||
"@storybook/addon-notes": "^5.3.18",
|
||||
"@storybook/vue": "~7.4.0",
|
||||
"@testing-library/vue": "5",
|
||||
"@vue/cli-shared-utils": "~4.3.1",
|
||||
"@vue/eslint-config-prettier": "~6.0.0",
|
||||
"@vue/server-test-utils": "~1.0.0-beta.31",
|
||||
|
||||
197
webapp/pages/settings/__snapshots__/notifications.spec.js.snap
Normal file
197
webapp/pages/settings/__snapshots__/notifications.spec.js.snap
Normal file
@ -0,0 +1,197 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`notifications.vue mount renders 1`] = `
|
||||
<article
|
||||
class="base-card"
|
||||
>
|
||||
<h2
|
||||
class="title"
|
||||
>
|
||||
settings.notifications.name
|
||||
</h2>
|
||||
|
||||
<div
|
||||
class="ds-space"
|
||||
style="margin-top: 24px; margin-bottom: 32px;"
|
||||
>
|
||||
<div
|
||||
class="ds-space"
|
||||
style="margin-bottom: 16px;"
|
||||
>
|
||||
<h4>
|
||||
settings.notifications.post
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="notifcation-settings-section"
|
||||
>
|
||||
<div
|
||||
class="ds-space"
|
||||
style="margin-bottom: 8px;"
|
||||
>
|
||||
<input
|
||||
id="commentOnObservedPost"
|
||||
type="checkbox"
|
||||
/>
|
||||
|
||||
<label
|
||||
class="label"
|
||||
for="commentOnObservedPost"
|
||||
>
|
||||
|
||||
settings.notifications.commentOnObservedPost
|
||||
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ds-space"
|
||||
style="margin-bottom: 8px;"
|
||||
>
|
||||
<input
|
||||
id="mention"
|
||||
type="checkbox"
|
||||
/>
|
||||
|
||||
<label
|
||||
class="label"
|
||||
for="mention"
|
||||
>
|
||||
|
||||
settings.notifications.mention
|
||||
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ds-space"
|
||||
style="margin-top: 24px; margin-bottom: 32px;"
|
||||
>
|
||||
<div
|
||||
class="ds-space"
|
||||
style="margin-bottom: 16px;"
|
||||
>
|
||||
<h4>
|
||||
settings.notifications.group
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="notifcation-settings-section"
|
||||
>
|
||||
<div
|
||||
class="ds-space"
|
||||
style="margin-bottom: 8px;"
|
||||
>
|
||||
<input
|
||||
id="groupMemberJoined"
|
||||
type="checkbox"
|
||||
/>
|
||||
|
||||
<label
|
||||
class="label"
|
||||
for="groupMemberJoined"
|
||||
>
|
||||
|
||||
settings.notifications.groupMemberJoined
|
||||
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ds-space"
|
||||
style="margin-bottom: 8px;"
|
||||
>
|
||||
<input
|
||||
id="groupMemberLeft"
|
||||
type="checkbox"
|
||||
/>
|
||||
|
||||
<label
|
||||
class="label"
|
||||
for="groupMemberLeft"
|
||||
>
|
||||
|
||||
settings.notifications.groupMemberLeft
|
||||
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ds-space"
|
||||
style="margin-bottom: 8px;"
|
||||
>
|
||||
<input
|
||||
id="groupMemberRemoved"
|
||||
type="checkbox"
|
||||
/>
|
||||
|
||||
<label
|
||||
class="label"
|
||||
for="groupMemberRemoved"
|
||||
>
|
||||
|
||||
settings.notifications.groupMemberRemoved
|
||||
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ds-space"
|
||||
style="margin-bottom: 8px;"
|
||||
>
|
||||
<input
|
||||
id="groupMemberRoleChanged"
|
||||
type="checkbox"
|
||||
/>
|
||||
|
||||
<label
|
||||
class="label"
|
||||
for="groupMemberRoleChanged"
|
||||
>
|
||||
|
||||
settings.notifications.groupMemberRoleChanged
|
||||
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="base-button"
|
||||
type="button"
|
||||
>
|
||||
<!---->
|
||||
|
||||
<!---->
|
||||
|
||||
settings.notifications.checkAll
|
||||
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="base-button"
|
||||
type="button"
|
||||
>
|
||||
<!---->
|
||||
|
||||
<!---->
|
||||
|
||||
settings.notifications.uncheckAll
|
||||
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="save-button base-button --filled"
|
||||
disabled="disabled"
|
||||
type="button"
|
||||
>
|
||||
<!---->
|
||||
|
||||
<!---->
|
||||
|
||||
actions.save
|
||||
|
||||
</button>
|
||||
|
||||
<!---->
|
||||
</article>
|
||||
`;
|
||||
@ -1,5 +1,6 @@
|
||||
import Vuex from 'vuex'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { render, fireEvent, screen } from '@testing-library/vue'
|
||||
import Notifications from './notifications.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
@ -11,7 +12,7 @@ describe('notifications.vue', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
$t: jest.fn((v) => v),
|
||||
$apollo: {
|
||||
mutate: jest.fn(),
|
||||
},
|
||||
@ -26,7 +27,42 @@ describe('notifications.vue', () => {
|
||||
return {
|
||||
id: 'u343',
|
||||
name: 'MyAccount',
|
||||
sendNotificationEmails: true,
|
||||
emailNotificationSettings: [
|
||||
{
|
||||
type: 'post',
|
||||
settings: [
|
||||
{
|
||||
name: 'commentOnObservedPost',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'mention',
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
settings: [
|
||||
{
|
||||
name: 'groupMemberJoined',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'groupMemberLeft',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'groupMemberRemoved',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'groupMemberRoleChanged',
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -47,21 +83,116 @@ describe('notifications.vue', () => {
|
||||
})
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.classes('base-card')).toBe(true)
|
||||
expect(wrapper.element).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Notifications', () => {
|
||||
beforeEach(() => {
|
||||
render(Notifications, {
|
||||
store,
|
||||
mocks,
|
||||
localVue,
|
||||
})
|
||||
})
|
||||
|
||||
it('clicking on submit changes notifyByEmail to false', async () => {
|
||||
await wrapper.find('#send-email').setChecked(false)
|
||||
await wrapper.find('.base-button').trigger('click')
|
||||
expect(wrapper.vm.notifyByEmail).toBe(false)
|
||||
it('check all button works', async () => {
|
||||
const button = screen.getByText('settings.notifications.checkAll')
|
||||
await fireEvent.click(button)
|
||||
|
||||
const checkboxes = screen.getAllByRole('checkbox')
|
||||
for (const checkbox of checkboxes) {
|
||||
expect(checkbox.checked).toEqual(true)
|
||||
}
|
||||
|
||||
// Check that the button is disabled
|
||||
expect(button.disabled).toBe(true)
|
||||
})
|
||||
|
||||
it('clicking on submit with a server error shows a toast and notifyByEmail is still true', async () => {
|
||||
it('uncheck all button works', async () => {
|
||||
const button = screen.getByText('settings.notifications.uncheckAll')
|
||||
await fireEvent.click(button)
|
||||
|
||||
const checkboxes = screen.getAllByRole('checkbox')
|
||||
for (const checkbox of checkboxes) {
|
||||
expect(checkbox.checked).toEqual(false)
|
||||
}
|
||||
|
||||
// Check that the button is disabled
|
||||
expect(button.disabled).toBe(true)
|
||||
})
|
||||
|
||||
it('clicking on submit keeps set values and shows success message', async () => {
|
||||
mocks.$apollo.mutate = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
UpdateUser: {
|
||||
emailNotificationSettings: [
|
||||
{
|
||||
type: 'post',
|
||||
settings: [
|
||||
{
|
||||
name: 'commentOnObservedPost',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'mention',
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
settings: [
|
||||
{
|
||||
name: 'groupMemberJoined',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'groupMemberLeft',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'groupMemberRemoved',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
name: 'groupMemberRoleChanged',
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Change some value to enable save button
|
||||
const checkbox = screen.getAllByRole('checkbox')[0]
|
||||
await fireEvent.click(checkbox)
|
||||
|
||||
const newValue = checkbox.checked
|
||||
|
||||
// Click save button
|
||||
const button = screen.getByText('actions.save')
|
||||
await fireEvent.click(button)
|
||||
|
||||
expect(checkbox.checked).toEqual(newValue)
|
||||
|
||||
expect(mocks.$toast.success).toHaveBeenCalledWith('settings.notifications.success-update')
|
||||
})
|
||||
|
||||
it('clicking on submit with a server error shows a toast', async () => {
|
||||
mocks.$apollo.mutate = jest.fn().mockRejectedValue({ message: 'Ouch!' })
|
||||
await wrapper.find('#send-email').setChecked(false)
|
||||
await wrapper.find('.base-button').trigger('click')
|
||||
|
||||
// Change some value to enable save button
|
||||
const checkbox = screen.getAllByRole('checkbox')[0]
|
||||
await fireEvent.click(checkbox)
|
||||
|
||||
// Click save button
|
||||
const button = screen.getByText('actions.save')
|
||||
await fireEvent.click(button)
|
||||
|
||||
expect(mocks.$toast.error).toHaveBeenCalledWith('Ouch!')
|
||||
expect(wrapper.vm.notifyByEmail).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,11 +1,26 @@
|
||||
<template>
|
||||
<base-card>
|
||||
<h2 class="title">{{ $t('settings.notifications.name') }}</h2>
|
||||
<ds-space margin-bottom="small">
|
||||
<input id="send-email" type="checkbox" v-model="notifyByEmail" />
|
||||
<label for="send-email">{{ $t('settings.notifications.send-email-notifications') }}</label>
|
||||
<ds-space margin-top="base" v-for="topic in emailNotificationSettings" :key="topic.type">
|
||||
<ds-space margin-bottom="small">
|
||||
<h4>{{ $t(`settings.notifications.${topic.type}`) }}</h4>
|
||||
</ds-space>
|
||||
<div class="notifcation-settings-section">
|
||||
<ds-space margin-bottom="x-small" v-for="setting in topic.settings" :key="setting.name">
|
||||
<input :id="setting.name" type="checkbox" v-model="setting.value" />
|
||||
<label :for="setting.name" class="label">
|
||||
{{ $t(`settings.notifications.${setting.name}`) }}
|
||||
</label>
|
||||
</ds-space>
|
||||
</div>
|
||||
</ds-space>
|
||||
<base-button class="save-button" filled @click="submit" :disabled="disabled">
|
||||
<base-button @click="checkAll" :disabled="isCheckAllDisabled">
|
||||
{{ $t('settings.notifications.checkAll') }}
|
||||
</base-button>
|
||||
<base-button @click="uncheckAll" :disabled="isUncheckAllDisabled">
|
||||
{{ $t('settings.notifications.uncheckAll') }}
|
||||
</base-button>
|
||||
<base-button class="save-button" filled @click="submit" :disabled="isSubmitDisabled">
|
||||
{{ $t('actions.save') }}
|
||||
</base-button>
|
||||
</base-card>
|
||||
@ -18,46 +33,107 @@ import { updateUserMutation } from '~/graphql/User'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
notifyByEmail: false,
|
||||
emailNotificationSettings: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentUser: 'auth/user',
|
||||
}),
|
||||
disabled() {
|
||||
return this.notifyByEmail === this.currentUser.sendNotificationEmails
|
||||
isSubmitDisabled() {
|
||||
return this.emailNotificationSettings.every((topic) =>
|
||||
topic.settings.every(
|
||||
(setting) =>
|
||||
setting.value ===
|
||||
this.currentUser.emailNotificationSettings
|
||||
.find((t) => t.type === topic.type)
|
||||
.settings.find((s) => s.name === setting.name).value,
|
||||
),
|
||||
)
|
||||
},
|
||||
isCheckAllDisabled() {
|
||||
return this.emailNotificationSettings.every((topic) =>
|
||||
topic.settings.every((setting) => setting.value),
|
||||
)
|
||||
},
|
||||
isUncheckAllDisabled() {
|
||||
return this.emailNotificationSettings.every((topic) =>
|
||||
topic.settings.every((setting) => !setting.value),
|
||||
)
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.notifyByEmail = this.currentUser.sendNotificationEmails || false
|
||||
this.emailNotificationSettings = [
|
||||
...this.currentUser.emailNotificationSettings.map((topic) => ({
|
||||
type: topic.type,
|
||||
settings: topic.settings.map((setting) => ({
|
||||
name: setting.name,
|
||||
value: setting.value,
|
||||
})),
|
||||
})),
|
||||
]
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({
|
||||
setCurrentUser: 'auth/SET_USER',
|
||||
}),
|
||||
setAll(value) {
|
||||
for (const topic of this.emailNotificationSettings) {
|
||||
for (const setting of topic.settings) {
|
||||
setting.value = value
|
||||
}
|
||||
}
|
||||
},
|
||||
checkAll() {
|
||||
this.setAll(true)
|
||||
},
|
||||
uncheckAll() {
|
||||
this.setAll(false)
|
||||
},
|
||||
transformToEmailSettingsInput(emailSettings) {
|
||||
const emailSettingsInput = []
|
||||
for (const topic of emailSettings) {
|
||||
for (const setting of topic.settings) {
|
||||
emailSettingsInput.push({
|
||||
name: setting.name,
|
||||
value: setting.value,
|
||||
})
|
||||
}
|
||||
}
|
||||
return emailSettingsInput
|
||||
},
|
||||
async submit() {
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: updateUserMutation(),
|
||||
variables: {
|
||||
id: this.currentUser.id,
|
||||
sendNotificationEmails: this.notifyByEmail,
|
||||
emailNotificationSettings: this.transformToEmailSettingsInput(
|
||||
this.emailNotificationSettings,
|
||||
),
|
||||
},
|
||||
update: (_, { data: { UpdateUser } }) => {
|
||||
const { sendNotificationEmails } = UpdateUser
|
||||
const { emailNotificationSettings } = UpdateUser
|
||||
this.setCurrentUser({
|
||||
...this.currentUser,
|
||||
sendNotificationEmails,
|
||||
emailNotificationSettings,
|
||||
})
|
||||
this.$toast.success(this.$t('settings.notifications.success-update'))
|
||||
},
|
||||
})
|
||||
this.$toast.success(this.$t('settings.notifications.success-update'))
|
||||
} catch (error) {
|
||||
this.notifyByEmail = !this.notifyByEmail
|
||||
this.$toast.error(error.message)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.notifcation-settings-section {
|
||||
margin-left: $space-x-small;
|
||||
}
|
||||
.label {
|
||||
margin-left: $space-xx-small;
|
||||
}
|
||||
</style>
|
||||
|
||||
316
webapp/yarn.lock
316
webapp/yarn.lock
@ -115,15 +115,7 @@
|
||||
"@babel/highlight" "^7.24.2"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
"@babel/code-frame@^7.25.7":
|
||||
version "7.25.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7"
|
||||
integrity sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==
|
||||
dependencies:
|
||||
"@babel/highlight" "^7.25.7"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
"@babel/code-frame@^7.26.2":
|
||||
"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.26.2":
|
||||
version "7.26.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
|
||||
integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
|
||||
@ -132,6 +124,14 @@
|
||||
js-tokens "^4.0.0"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
"@babel/code-frame@^7.25.7":
|
||||
version "7.25.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7"
|
||||
integrity sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==
|
||||
dependencies:
|
||||
"@babel/highlight" "^7.25.7"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.7", "@babel/compat-data@^7.25.8":
|
||||
version "7.25.8"
|
||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.8.tgz#0376e83df5ab0eb0da18885c0140041f0747a402"
|
||||
@ -1966,6 +1966,13 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.12.5", "@babel/runtime@^7.21.0":
|
||||
version "7.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762"
|
||||
integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/template@^7.22.15", "@babel/template@^7.24.0", "@babel/template@^7.3.3", "@babel/template@^7.8.3":
|
||||
version "7.24.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50"
|
||||
@ -4321,6 +4328,29 @@
|
||||
dependencies:
|
||||
defer-to-connect "^2.0.0"
|
||||
|
||||
"@testing-library/dom@^9.0.0":
|
||||
version "9.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.4.tgz#50696ec28376926fec0a1bf87d9dbac5e27f60ce"
|
||||
integrity sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.10.4"
|
||||
"@babel/runtime" "^7.12.5"
|
||||
"@types/aria-query" "^5.0.1"
|
||||
aria-query "5.1.3"
|
||||
chalk "^4.1.0"
|
||||
dom-accessibility-api "^0.5.9"
|
||||
lz-string "^1.5.0"
|
||||
pretty-format "^27.0.2"
|
||||
|
||||
"@testing-library/vue@5":
|
||||
version "5.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/vue/-/vue-5.9.0.tgz#d33c52ae89e076808abe622f70dcbccb1b5d080c"
|
||||
integrity sha512-HWvI4s6FayFLmiqGcEMAMfTSO1SV12NukdoyllYMBobFqfO0TalQmfofMtiO+eRz+Amej8Z26dx4/WYIROzfVw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.21.0"
|
||||
"@testing-library/dom" "^9.0.0"
|
||||
"@vue/test-utils" "^1.3.0"
|
||||
|
||||
"@tootallnate/once@2":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
|
||||
@ -4333,6 +4363,11 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/aria-query@^5.0.1":
|
||||
version "5.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708"
|
||||
integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==
|
||||
|
||||
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14":
|
||||
version "7.20.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.1.tgz#916ecea274b0c776fec721e333e55762d3a9614b"
|
||||
@ -5071,6 +5106,15 @@
|
||||
lodash "^4.17.15"
|
||||
pretty "^2.0.0"
|
||||
|
||||
"@vue/test-utils@^1.3.0":
|
||||
version "1.3.6"
|
||||
resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.3.6.tgz#6656bd8fa44dd088b4ad80ff1ee28abe7e5ddf87"
|
||||
integrity sha512-udMmmF1ts3zwxUJEIAj5ziioR900reDrt6C9H3XpWPsLBx2lpHKoA4BTdd9HNIYbkGltWw+JjWJ+5O6QBwiyEw==
|
||||
dependencies:
|
||||
dom-event-types "^1.0.0"
|
||||
lodash "^4.17.15"
|
||||
pretty "^2.0.0"
|
||||
|
||||
"@vue/vue2-jest@29":
|
||||
version "29.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@vue/vue2-jest/-/vue2-jest-29.2.6.tgz#b827c14fbdfca6e20aa807b00f309866fcf99f47"
|
||||
@ -6096,6 +6140,13 @@ argparse@^1.0.7:
|
||||
dependencies:
|
||||
sprintf-js "~1.0.2"
|
||||
|
||||
aria-query@5.1.3:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e"
|
||||
integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==
|
||||
dependencies:
|
||||
deep-equal "^2.0.5"
|
||||
|
||||
arr-diff@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
|
||||
@ -6111,6 +6162,14 @@ arr-union@^3.1.0:
|
||||
resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
|
||||
integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==
|
||||
|
||||
array-buffer-byte-length@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b"
|
||||
integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==
|
||||
dependencies:
|
||||
call-bound "^1.0.3"
|
||||
is-array-buffer "^3.0.5"
|
||||
|
||||
array-buffer-byte-length@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f"
|
||||
@ -7091,7 +7150,7 @@ cacheable-request@^7.0.2:
|
||||
normalize-url "^6.0.1"
|
||||
responselike "^2.0.0"
|
||||
|
||||
call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
|
||||
call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
|
||||
integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
|
||||
@ -7118,7 +7177,17 @@ call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7:
|
||||
get-intrinsic "^1.2.4"
|
||||
set-function-length "^1.2.1"
|
||||
|
||||
call-bound@^1.0.2:
|
||||
call-bind@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c"
|
||||
integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==
|
||||
dependencies:
|
||||
call-bind-apply-helpers "^1.0.0"
|
||||
es-define-property "^1.0.0"
|
||||
get-intrinsic "^1.2.4"
|
||||
set-function-length "^1.2.2"
|
||||
|
||||
call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a"
|
||||
integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==
|
||||
@ -8567,6 +8636,30 @@ dedent@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a"
|
||||
integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==
|
||||
|
||||
deep-equal@^2.0.5:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.3.tgz#af89dafb23a396c7da3e862abc0be27cf51d56e1"
|
||||
integrity sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==
|
||||
dependencies:
|
||||
array-buffer-byte-length "^1.0.0"
|
||||
call-bind "^1.0.5"
|
||||
es-get-iterator "^1.1.3"
|
||||
get-intrinsic "^1.2.2"
|
||||
is-arguments "^1.1.1"
|
||||
is-array-buffer "^3.0.2"
|
||||
is-date-object "^1.0.5"
|
||||
is-regex "^1.1.4"
|
||||
is-shared-array-buffer "^1.0.2"
|
||||
isarray "^2.0.5"
|
||||
object-is "^1.1.5"
|
||||
object-keys "^1.1.1"
|
||||
object.assign "^4.1.4"
|
||||
regexp.prototype.flags "^1.5.1"
|
||||
side-channel "^1.0.4"
|
||||
which-boxed-primitive "^1.0.2"
|
||||
which-collection "^1.0.1"
|
||||
which-typed-array "^1.1.13"
|
||||
|
||||
deep-extend@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
||||
@ -8798,6 +8891,11 @@ doctrine@^3.0.0:
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
dom-accessibility-api@^0.5.9:
|
||||
version "0.5.16"
|
||||
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453"
|
||||
integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==
|
||||
|
||||
dom-converter@^0.2:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
|
||||
@ -9341,6 +9439,21 @@ es-errors@^1.2.1, es-errors@^1.3.0:
|
||||
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
|
||||
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
|
||||
|
||||
es-get-iterator@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6"
|
||||
integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
get-intrinsic "^1.1.3"
|
||||
has-symbols "^1.0.3"
|
||||
is-arguments "^1.1.1"
|
||||
is-map "^2.0.2"
|
||||
is-set "^2.0.2"
|
||||
is-string "^1.0.7"
|
||||
isarray "^2.0.5"
|
||||
stop-iteration-iterator "^1.0.0"
|
||||
|
||||
es-object-atoms@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941"
|
||||
@ -10414,6 +10527,13 @@ for-each@^0.3.3:
|
||||
dependencies:
|
||||
is-callable "^1.1.3"
|
||||
|
||||
for-each@^0.3.5:
|
||||
version "0.3.5"
|
||||
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47"
|
||||
integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==
|
||||
dependencies:
|
||||
is-callable "^1.2.7"
|
||||
|
||||
for-in@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||
@ -10698,7 +10818,7 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@
|
||||
has-symbols "^1.0.3"
|
||||
hasown "^2.0.0"
|
||||
|
||||
get-intrinsic@^1.2.5, get-intrinsic@^1.3.0:
|
||||
get-intrinsic@^1.2.2, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
|
||||
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
|
||||
@ -11823,6 +11943,15 @@ internal-slot@^1.0.7:
|
||||
hasown "^2.0.0"
|
||||
side-channel "^1.0.4"
|
||||
|
||||
internal-slot@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961"
|
||||
integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==
|
||||
dependencies:
|
||||
es-errors "^1.3.0"
|
||||
hasown "^2.0.2"
|
||||
side-channel "^1.1.0"
|
||||
|
||||
interpret@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296"
|
||||
@ -11892,6 +12021,23 @@ is-arguments@^1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3"
|
||||
integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==
|
||||
|
||||
is-arguments@^1.1.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.2.0.tgz#ad58c6aecf563b78ef2bf04df540da8f5d7d8e1b"
|
||||
integrity sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==
|
||||
dependencies:
|
||||
call-bound "^1.0.2"
|
||||
has-tostringtag "^1.0.2"
|
||||
|
||||
is-array-buffer@^3.0.2, is-array-buffer@^3.0.5:
|
||||
version "3.0.5"
|
||||
resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280"
|
||||
integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==
|
||||
dependencies:
|
||||
call-bind "^1.0.8"
|
||||
call-bound "^1.0.3"
|
||||
get-intrinsic "^1.2.6"
|
||||
|
||||
is-array-buffer@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98"
|
||||
@ -12011,6 +12157,14 @@ is-date-object@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
|
||||
integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
|
||||
|
||||
is-date-object@^1.0.5:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7"
|
||||
integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==
|
||||
dependencies:
|
||||
call-bound "^1.0.2"
|
||||
has-tostringtag "^1.0.2"
|
||||
|
||||
is-decimal@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.3.tgz#381068759b9dc807d8c0dc0bfbae2b68e1da48b7"
|
||||
@ -12130,6 +12284,11 @@ is-lower-case@^1.1.0:
|
||||
dependencies:
|
||||
lower-case "^1.1.0"
|
||||
|
||||
is-map@^2.0.2, is-map@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e"
|
||||
integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==
|
||||
|
||||
is-nan@^1.2.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.0.tgz#85d1f5482f7051c2019f5673ccebdb06f3b0db03"
|
||||
@ -12266,6 +12425,11 @@ is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
|
||||
integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=
|
||||
|
||||
is-set@^2.0.2, is-set@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d"
|
||||
integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==
|
||||
|
||||
is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688"
|
||||
@ -12328,6 +12492,11 @@ is-utf8@^0.2.0:
|
||||
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
|
||||
integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
|
||||
|
||||
is-weakmap@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd"
|
||||
integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==
|
||||
|
||||
is-weakref@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2"
|
||||
@ -12335,6 +12504,14 @@ is-weakref@^1.0.2:
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
|
||||
is-weakset@^2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca"
|
||||
integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==
|
||||
dependencies:
|
||||
call-bound "^1.0.3"
|
||||
get-intrinsic "^1.2.6"
|
||||
|
||||
is-whitespace@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/is-whitespace/-/is-whitespace-0.3.0.tgz#1639ecb1be036aec69a54cbb401cfbed7114ab7f"
|
||||
@ -13648,6 +13825,11 @@ lru_map@^0.3.3:
|
||||
resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd"
|
||||
integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=
|
||||
|
||||
lz-string@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
|
||||
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
|
||||
|
||||
magic-string@^0.25.7:
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
|
||||
@ -14911,6 +15093,14 @@ object-is@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.2.tgz#6b80eb84fe451498f65007982f035a5b445edec4"
|
||||
integrity sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==
|
||||
|
||||
object-is@^1.1.5:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07"
|
||||
integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==
|
||||
dependencies:
|
||||
call-bind "^1.0.7"
|
||||
define-properties "^1.2.1"
|
||||
|
||||
object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
|
||||
@ -14948,6 +15138,18 @@ object.assign@^4.1.1:
|
||||
has-symbols "^1.0.1"
|
||||
object-keys "^1.1.1"
|
||||
|
||||
object.assign@^4.1.4:
|
||||
version "4.1.7"
|
||||
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d"
|
||||
integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==
|
||||
dependencies:
|
||||
call-bind "^1.0.8"
|
||||
call-bound "^1.0.3"
|
||||
define-properties "^1.2.1"
|
||||
es-object-atoms "^1.0.0"
|
||||
has-symbols "^1.1.0"
|
||||
object-keys "^1.1.1"
|
||||
|
||||
object.assign@^4.1.5:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0"
|
||||
@ -16327,6 +16529,15 @@ pretty-error@^2.0.2:
|
||||
renderkid "^2.0.1"
|
||||
utila "~0.4"
|
||||
|
||||
pretty-format@^27.0.2:
|
||||
version "27.5.1"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
|
||||
integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
ansi-styles "^5.0.0"
|
||||
react-is "^17.0.1"
|
||||
|
||||
pretty-format@^29.7.0:
|
||||
version "29.7.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812"
|
||||
@ -16875,6 +17086,11 @@ react-is@^16.8.1:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-is@^17.0.1:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||
|
||||
react-is@^18.0.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
|
||||
@ -17169,6 +17385,18 @@ regex-not@^1.0.0, regex-not@^1.0.2:
|
||||
extend-shallow "^3.0.2"
|
||||
safe-regex "^1.1.0"
|
||||
|
||||
regexp.prototype.flags@^1.5.1:
|
||||
version "1.5.4"
|
||||
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19"
|
||||
integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==
|
||||
dependencies:
|
||||
call-bind "^1.0.8"
|
||||
define-properties "^1.2.1"
|
||||
es-errors "^1.3.0"
|
||||
get-proto "^1.0.1"
|
||||
gopd "^1.2.0"
|
||||
set-function-name "^2.0.2"
|
||||
|
||||
regexp.prototype.flags@^1.5.2:
|
||||
version "1.5.3"
|
||||
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz#b3ae40b1d2499b8350ab2c3fe6ef3845d3a96f42"
|
||||
@ -17818,7 +18046,7 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
|
||||
|
||||
set-function-length@^1.2.1:
|
||||
set-function-length@^1.2.1, set-function-length@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
|
||||
integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
|
||||
@ -18296,6 +18524,14 @@ stealthy-require@^1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
|
||||
integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=
|
||||
|
||||
stop-iteration-iterator@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad"
|
||||
integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==
|
||||
dependencies:
|
||||
es-errors "^1.3.0"
|
||||
internal-slot "^1.1.0"
|
||||
|
||||
store2@^2.7.1:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/store2/-/store2-2.8.0.tgz#032d5dcbd185a5d74049d67a1765ff1e75faa04b"
|
||||
@ -18401,7 +18637,7 @@ string-length@^4.0.1:
|
||||
char-regex "^1.0.2"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@ -18454,15 +18690,6 @@ string-width@^4.2.0:
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^5.0.1, string-width@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
||||
@ -18522,7 +18749,7 @@ string_decoder@~1.1.1:
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@ -18557,13 +18784,6 @@ strip-ansi@^6.0.0:
|
||||
dependencies:
|
||||
ansi-regex "^5.0.0"
|
||||
|
||||
strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.1:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||
@ -20536,11 +20756,34 @@ which-boxed-primitive@^1.0.2:
|
||||
is-string "^1.0.5"
|
||||
is-symbol "^1.0.3"
|
||||
|
||||
which-collection@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0"
|
||||
integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==
|
||||
dependencies:
|
||||
is-map "^2.0.3"
|
||||
is-set "^2.0.3"
|
||||
is-weakmap "^2.0.2"
|
||||
is-weakset "^2.0.3"
|
||||
|
||||
which-module@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
|
||||
integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=
|
||||
|
||||
which-typed-array@^1.1.13:
|
||||
version "1.1.19"
|
||||
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956"
|
||||
integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==
|
||||
dependencies:
|
||||
available-typed-arrays "^1.0.7"
|
||||
call-bind "^1.0.8"
|
||||
call-bound "^1.0.4"
|
||||
for-each "^0.3.5"
|
||||
get-proto "^1.0.1"
|
||||
gopd "^1.2.0"
|
||||
has-tostringtag "^1.0.2"
|
||||
|
||||
which-typed-array@^1.1.14, which-typed-array@^1.1.15:
|
||||
version "1.1.15"
|
||||
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d"
|
||||
@ -20609,7 +20852,7 @@ worker-farm@^1.7.0:
|
||||
dependencies:
|
||||
errno "~0.1.7"
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
@ -20661,15 +20904,6 @@ wrap-ansi@^6.2.0:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user