[MM-60218] Resettting to default doesnt reset to user's global notification setting in a channel's setting (#28046)

This commit is contained in:
M-ZubairAhmed 2024-09-27 18:17:23 +00:00 committed by GitHub
parent bb4d7de2b5
commit 4c6ac0a432
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 2839 additions and 1517 deletions

View File

@ -142,82 +142,97 @@ describe('CRT Desktop notifications', () => {
it('MM-T4417_2 Click on sameMobileSettingsDesktop and check if additional settings still appears', () => {
cy.visit(testChannelUrl);
// # Open channel's notification preferences
cy.uiOpenChannelMenu('Notification Preferences');
cy.get('#desktopNotification-mention').scrollIntoView().should('be.visible').click().then(() => {
cy.get('[data-testid="desktopReplyThreads"]').scrollIntoView().should('be.visible').click();
});
cy.get('.channel-notifications-settings-modal__body').scrollTo('center').get('[data-testid="desktopReplyThreads"]').should('be.visible').click();
cy.get('.channel-notifications-settings-modal__body').get('[data-testid="sameMobileSettingsDesktop"]').scrollIntoView().click().should('be.checked').then(() => {
cy.findByText('Notify me about…').should('not.be.visible');
// * As per previous conditions the mobile and desktop settings should be the same
cy.get('[data-testid="sameMobileSettingsDesktop"]').scrollIntoView().should('be.checked');
// * Verify that Notify me about section of mobile settings is not visible
cy.get('[data-testid="mobile-notify-me-radio-section"]').should('not.exist');
// # Now uncheck the sameMobileSettingsDesktop so that mobile and desktop settings are different
cy.get('[data-testid="sameMobileSettingsDesktop"]').scrollIntoView().should('be.visible').click();
// * Verify that Notify me about section of mobile settings is visible
cy.get('[data-testid="mobile-notify-me-radio-section"]').should('be.visible').scrollIntoView().within(() => {
cy.findByText('Notify me about…').should('be.visible');
// # Click on mentions option
cy.get('[data-testid="MobileNotification-mention"]').should('be.visible').click();
});
// check the box to see if the additional settings appears
cy.get('.channel-notifications-settings-modal__body').get('[data-testid="sameMobileSettingsDesktop"]').scrollIntoView().click();
cy.get('.mm-modal-generic-section-item__title').should('be.visible').and('contain', 'Notify me about');
cy.get('#MobileNotification-all').should('be.visible').click();
cy.get('#MobileNotification-mention').should('be.visible').click().then(() => {
cy.get('[data-testid="mobileReplyThreads"]').should('be.visible').click();
// * Verify that Thread reply notifications section of mobile settings is visible
cy.get('[data-testid="mobile-reply-threads-checkbox-section"]').should('be.visible').scrollIntoView().within(() => {
cy.findByText('Notify me about replies to threads Im following').should('be.visible');
});
cy.get('#MobileNotification-none').should('be.visible').click();
cy.get('[data-testid="autoFollowThreads"]').should('be.visible').click();
// # Save the changes
cy.findByText('Save').should('be.visible').click();
// # Close the modal
cy.get('body').type('{esc}');
});
it('MM-T4417_3 Trigger notifications only on mention replies when channel setting is unchecked', () => {
// # Visit the test channel
cy.visit(testChannelUrl);
// Setup notification spy
spyNotificationAs('notifySpy', 'granted');
// # Setup notification spy
spyNotificationAs('notifySpy1', 'granted');
// # Open channel's notification preferences for test channel
cy.uiOpenChannelMenu('Notification Preferences');
// # Select "Mentions, direct messages, and keywords only" as notify me about option
cy.get('#desktopNotification-mention').scrollIntoView().should('be.visible').click();
// # Unselect "Notify me about replies to threads Im following"
cy.get('[data-testid="desktopReplyThreads"]').scrollIntoView().should('be.visible').then(($el) => {
if ($el.is(':checked')) {
cy.wrap($el).click();
}
});
// # Select notification checkbox active
cy.get('[data-testid="desktopNotificationSoundsCheckbox"]').scrollIntoView().should('be.visible').then(($el) => {
if (!$el.is(':checked')) {
cy.wrap($el).click();
}
});
// # Select a notification sound from dropdown
cy.get('#desktopNotificationSoundsSelect').scrollIntoView().should('be.visible').click();
cy.findByText('Crackle').should('be.visible').click();
// # Save the changes
cy.findByText('Save').should('be.visible').click();
// # Post a root message as other user
cy.postMessageAs({sender, message: 'This is a not followed root message', channelId: testChannelId, rootId: ''}).then(({id: postId}) => {
// # Switch to town-square so that unread notifications in test channel may be triggered
cy.uiClickSidebarItem('town-square');
// # Go to the town-square channel
cy.visit(`/${testTeam.name}/channels/town-square`);
// # Post a message in unfollowed thread as another user
cy.postMessageAs({sender, message: 'This is a reply to the unfollowed thread', channelId: testChannelId, rootId: postId});
// # Post a root message as other user in the test channel
cy.postMessageAs({sender, message: 'This is the root message which will not have a at-mention in thread', channelId: testChannelId, rootId: ''}).then(({id: postId}) => {
// # Post a message in the thread without at-mention
cy.postMessageAs({sender, message: 'Reply without at-mention', channelId: testChannelId, rootId: postId}).then(() => {
// * Verify Notification stub was not called for threads which does not have at-mention as per channel settings
cy.get('@notifySpy1').should('not.be.called');
});
// * Verify stub was not called for unfollowed thread
cy.get('@notifySpy').should('not.be.called');
// # Cleanup
cy.apiDeletePost(postId);
});
// # Visit channel
cy.visit(testChannelUrl);
// Setup another notification spy
spyNotificationAs('notifySpy2', 'granted');
// Setup notification spy
spyNotificationAs('notifySpy', 'granted');
// # Post a message
cy.postMessage('Hi there, this is a root message');
// # Get post id of message
cy.getLastPostId().then((postId) => {
// # Switch to town-square so that unread notifications in test channel may be triggered
cy.uiClickSidebarItem('town-square');
// # Post a message in original thread as another user
cy.postMessageAs({sender, message: 'This is a reply to the root post', channelId: testChannelId, rootId: postId});
// * Verify stub was not called
cy.get('@notifySpy').should('not.be.called');
// # Post a mention message in original thread as another user
const message = `@${receiver.username} this is a mention to receiver`;
// # Post another message in the test channel
cy.postMessageAs({sender, message: 'This is another root message which will have a at-mention in thread', channelId: testChannelId, rootId: ''}).then(({id: postId}) => {
const message = `Reply with at-mention @${receiver.username}`;
// # Post a message in the thread with at-mention
cy.postMessageAs({sender, message, channelId: testChannelId, rootId: postId});
// * Verify stub was called with correct title and body
cy.get('@notifySpy').should('have.been.calledWithMatch', `Reply in ${testChannelName}`, (args) => {
// * Verify Notification stub was called with correct title and body with at-mention as per channel settings
cy.get('@notifySpy2').should('have.been.calledWithMatch', `Reply in ${testChannelName}`, (args) => {
expect(args.body, `Notification body: "${args.body}" should match: "${message}"`).to.equal(`@${sender.username}: ${message}`);
return true;
});

View File

@ -18,11 +18,11 @@ import {getChannelURL, getPermalinkURL} from 'selectors/urls';
import {isThreadOpen} from 'selectors/views/threads';
import {getHistory} from 'utils/browser_history';
import Constants, {NotificationLevels, UserStatuses, IgnoreChannelMentions} from 'utils/constants';
import Constants, {NotificationLevels, UserStatuses, IgnoreChannelMentions, DesktopSound} from 'utils/constants';
import DesktopApp from 'utils/desktop_api';
import {stripMarkdown, formatWithRenderer} from 'utils/markdown';
import MentionableRenderer from 'utils/markdown/mentionable_renderer';
import * as NotificationSounds from 'utils/notification_sounds';
import {DesktopNotificationSounds, ding} from 'utils/notification_sounds';
import {showNotification} from 'utils/notifications';
import {cjkrPattern, escapeRegex} from 'utils/text_formatting';
import {isDesktopApp, isMobileApp} from 'utils/user_agent';
@ -30,21 +30,52 @@ import * as Utils from 'utils/utils';
import {runDesktopNotificationHooks} from './hooks';
const getSoundFromChannelMemberAndUser = (member, user) => {
if (member?.notify_props?.desktop_sound) {
return member.notify_props.desktop_sound === 'on';
/**
* This function is used to determine if the desktop sound is enabled.
* It checks if desktop sound is defined in the channel member and if not, it checks if it's defined in the user preferences.
*/
export function isDesktopSoundEnabled(channelMember, user) {
const soundInChannelMemberNotifyProps = channelMember?.notify_props?.desktop_sound;
const soundInUserNotifyProps = user?.notify_props?.desktop_sound;
if (soundInChannelMemberNotifyProps === DesktopSound.ON) {
return true;
}
return !user.notify_props || user.notify_props.desktop_sound === 'true';
};
const getNotificationSoundFromChannelMemberAndUser = (member, user) => {
if (member?.notify_props?.desktop_notification_sound) {
return member.notify_props.desktop_notification_sound;
if (soundInChannelMemberNotifyProps === DesktopSound.OFF) {
return false;
}
return user.notify_props?.desktop_notification_sound ? user.notify_props.desktop_notification_sound : 'Bing';
};
if (soundInChannelMemberNotifyProps === DesktopSound.DEFAULT) {
return soundInUserNotifyProps ? soundInUserNotifyProps === 'true' : true;
}
if (soundInUserNotifyProps) {
return soundInUserNotifyProps === 'true';
}
return true;
}
/**
* This function returns the desktop notification sound from the channel member and user.
* It checks if desktop notification sound is defined in the channel member and if not, it checks if it's defined in the user preferences.
* If neither is defined, it returns the default sound 'BING'.
*/
export function getDesktopNotificationSound(channelMember, user) {
const notificationSoundInChannelMember = channelMember?.notify_props?.desktop_notification_sound;
const notificationSoundInUser = user?.notify_props?.desktop_notification_sound;
if (notificationSoundInChannelMember && notificationSoundInChannelMember !== DesktopNotificationSounds.DEFAULT) {
return notificationSoundInChannelMember;
}
if (notificationSoundInUser && notificationSoundInUser !== DesktopNotificationSounds.DEFAULT) {
return notificationSoundInUser;
}
return DesktopNotificationSounds.BING;
}
/**
* @returns {import('mattermost-redux/types/actions').ThunkActionFunc<Promise<import('utils/notifications').NotificationResult>, GlobalState>}
@ -259,7 +290,7 @@ export function sendDesktopNotification(post, msgProps) {
}
//Play a sound if explicitly set in settings
const sound = getSoundFromChannelMemberAndUser(member, user);
const desktopSoundEnabled = isDesktopSoundEnabled(member, user);
// Notify if you're not looking in the right channel or when
// the window itself is not active
@ -285,7 +316,7 @@ export function sendDesktopNotification(post, msgProps) {
notify = true;
}
let soundName = getNotificationSoundFromChannelMemberAndUser(member, user);
let soundName = getDesktopNotificationSound(member, user);
const updatedState = getState();
let url = getChannelURL(updatedState, channel, teamId);
@ -295,7 +326,7 @@ export function sendDesktopNotification(post, msgProps) {
}
// Allow plugins to change the notification, or re-enable a notification
const args = {title, body, silent: !sound, soundName, url, notify};
const args = {title, body, silent: !desktopSoundEnabled, soundName, url, notify};
const hookResult = await dispatch(runDesktopNotificationHooks(post, msgProps, channel, teamId, args));
if (hookResult.error) {
dispatch(logError(hookResult.error));
@ -309,8 +340,8 @@ export function sendDesktopNotification(post, msgProps) {
const result = dispatch(notifyMe(title, body, channel, teamId, silent, soundName, url));
//Don't add extra sounds on native desktop clients
if (sound && !isDesktopApp() && !isMobileApp()) {
NotificationSounds.ding(soundName);
if (desktopSoundEnabled && !isDesktopApp() && !isMobileApp()) {
ding(soundName);
}
return result;

View File

@ -7,7 +7,7 @@ import Constants, {NotificationLevels, UserStatuses} from 'utils/constants';
import * as NotificationSounds from 'utils/notification_sounds';
import * as utils from 'utils/notifications';
import {sendDesktopNotification} from './notification_actions';
import {sendDesktopNotification, isDesktopSoundEnabled, getDesktopNotificationSound} from './notification_actions';
describe('notification_actions', () => {
describe('sendDesktopNotification', () => {
@ -195,7 +195,7 @@ describe('notification_actions', () => {
expect(spy).toHaveBeenCalledWith({
body: '@username: Where is Jessica Hyde?',
requireInteraction: false,
silent: true,
silent: false,
title: 'Utopia',
onClick: expect.any(Function),
});
@ -415,7 +415,7 @@ describe('notification_actions', () => {
expect(spy).toHaveBeenCalledWith({
body: '@username: Where is Jessica Hyde?',
requireInteraction: false,
silent: true,
silent: false,
title: 'Reply in Utopia',
onClick: expect.any(Function),
});
@ -509,3 +509,163 @@ describe('notification_actions', () => {
});
});
});
describe('isDesktopSoundEnabled', () => {
test('should return channel member sound if it exists', () => {
const channelMember1 = {
notify_props: {
desktop_sound: 'on',
},
};
const user1 = {
notify_props: {
desktop_sound: 'false',
},
};
expect(isDesktopSoundEnabled(channelMember1, user1)).toBe(true);
const channelMember2 = {
notify_props: {
desktop_sound: 'off',
},
};
const user2 = {
notify_props: {
desktop_sound: 'false',
},
};
expect(isDesktopSoundEnabled(channelMember2, user2)).toBe(false);
const channelMember3 = {
notify_props: {
desktop_sound: 'default',
},
};
const user3 = {
notify_props: {
desktop_sound: 'false',
},
};
expect(isDesktopSoundEnabled(channelMember3, user3)).toBe(false);
const channelMember4 = {
notify_props: {
desktop_sound: 'default',
},
};
const user4 = {
notify_props: {
desktop_sound: 'true',
},
};
expect(isDesktopSoundEnabled(channelMember4, user4)).toBe(true);
const channelMember5 = {
notify_props: {
desktop_sound: 'on',
},
};
const user5 = {
notify_props: {
desktop_sound: '',
},
};
expect(isDesktopSoundEnabled(channelMember5, user5)).toBe(true);
});
test('should return user sound if channel member sound is not defined', () => {
const channelMember1 = {
notify_props: {
desktop_sound: '',
},
};
const user1 = {
notify_props: {
desktop_sound: 'true',
},
};
expect(isDesktopSoundEnabled(channelMember1, user1)).toBe(true);
const channelMember2 = {
notify_props: {
desktop_sound: '',
},
};
const user2 = {
notify_props: {
desktop_sound: 'false',
},
};
expect(isDesktopSoundEnabled(channelMember2, user2)).toBe(false);
const channelMember3 = {
notify_props: {},
};
const user3 = {
notify_props: {
desktop_sound: 'false',
},
};
expect(isDesktopSoundEnabled(channelMember3, user3)).toBe(false);
});
test('should return default if both channel member and user are not defined', () => {
const channelMember = {};
const user = {};
expect(isDesktopSoundEnabled(channelMember, user)).toBe(true);
});
});
describe('getDesktopNotificationSound', () => {
test('should return channel member notification sound if it exists', () => {
const channelMember1 = {
notify_props: {
desktop_notification_sound: 'default',
},
};
const user1 = {
notify_props: {
desktop_notification_sound: 'Crackle',
},
};
expect(getDesktopNotificationSound(channelMember1, user1)).toBe('Crackle');
const channelMember2 = {
notify_props: {
desktop_notification_sound: 'default',
},
};
const user2 = {
notify_props: {
desktop_notification_sound: '',
},
};
expect(getDesktopNotificationSound(channelMember2, user2)).toBe('Bing');
const channelMember3 = {
notify_props: {
desktop_notification_sound: 'Crackle',
},
};
const user3 = {
notify_props: {
desktop_notification_sound: 'Bing',
},
};
expect(getDesktopNotificationSound(channelMember3, user3)).toBe('Crackle');
});
test('should return user notification sound if channel member sound is not defined', () => {
const channelMember1 = {};
const user1 = {
notify_props: {
desktop_notification_sound: 'Crackle',
},
};
expect(getDesktopNotificationSound(channelMember1, user1)).toBe('Crackle');
const channelMember2 = {};
const user2 = {};
expect(getDesktopNotificationSound(channelMember2, user2)).toBe('Bing');
});
});

View File

@ -50,6 +50,7 @@
&__reset-btn {
display: flex;
align-items: center;
padding: 5px 8px;
border: none;
border-radius: 4px;
@ -58,8 +59,10 @@
font-size: 12px;
font-weight: 600;
gap: 4px;
line-height: 9.5px;
place-items: center;
i {
font-size: 16px;
}
&:hover,
&:active {

View File

@ -2,54 +2,57 @@
// See LICENSE.txt for license information.
import {screen, fireEvent, waitFor} from '@testing-library/react';
import type {ComponentProps} from 'react';
import React from 'react';
import type {ChannelMembership} from '@mattermost/types/channels';
import type {UserNotifyProps} from '@mattermost/types/users';
import ChannelNotificationsModal from 'components/channel_notifications_modal/channel_notifications_modal';
import ChannelNotificationsModal, {createChannelNotifyPropsFromSelectedSettings, getInitialValuesOfChannelNotifyProps, areDesktopAndMobileSettingsDifferent} from 'components/channel_notifications_modal/channel_notifications_modal';
import type {Props} from 'components/channel_notifications_modal/channel_notifications_modal';
import {renderWithContext} from 'tests/react_testing_utils';
import {IgnoreChannelMentions, NotificationLevels} from 'utils/constants';
import {DesktopSound, IgnoreChannelMentions, NotificationLevels} from 'utils/constants';
import {DesktopNotificationSounds, convertDesktopSoundNotifyPropFromUserToDesktop} from 'utils/notification_sounds';
import {TestHelper} from 'utils/test_helper';
describe('components/channel_notifications_modal/ChannelNotificationsModal', () => {
const baseProps: ComponentProps<typeof ChannelNotificationsModal> = {
describe('ChannelNotificationsModal', () => {
const baseProps: Props = {
onExited: jest.fn(),
channel: TestHelper.getChannelMock({
id: 'channel_id',
display_name: 'channel_display_name',
}),
channelMember: {
notify_props: {
desktop: NotificationLevels.ALL,
desktop_sound: 'on',
desktop_notification_sound: 'Bing',
mark_unread: NotificationLevels.ALL,
push: NotificationLevels.DEFAULT,
ignore_channel_mentions: IgnoreChannelMentions.DEFAULT,
desktop_threads: NotificationLevels.ALL,
push_threads: NotificationLevels.DEFAULT,
},
} as unknown as ChannelMembership,
currentUser: TestHelper.getUserMock({
id: 'current_user_id',
notify_props: {
desktop: NotificationLevels.ALL,
desktop_threads: NotificationLevels.MENTION,
desktop_sound: 'true',
desktop_notification_sound: 'Bing',
desktop_threads: NotificationLevels.ALL,
push: NotificationLevels.MENTION,
push_threads: NotificationLevels.MENTION,
} as UserNotifyProps,
}),
channelMember: {
notify_props: {
desktop: NotificationLevels.ALL,
desktop_threads: NotificationLevels.ALL,
desktop_sound: 'on',
desktop_notification_sound: 'Bing',
push: NotificationLevels.ALL,
push_threads: NotificationLevels.ALL,
mark_unread: NotificationLevels.ALL,
ignore_channel_mentions: IgnoreChannelMentions.DEFAULT,
},
} as unknown as ChannelMembership,
sendPushNotifications: true,
actions: {
updateChannelNotifyProps: jest.fn().mockImplementation(() => Promise.resolve({data: true})),
},
collapsedReplyThreads: false,
collapsedReplyThreads: true,
};
it('should not show other settings if channel is mute', async () => {
test('should not show other settings if channel is mute', async () => {
const wrapper = renderWithContext(
<ChannelNotificationsModal {...baseProps}/>,
);
@ -64,7 +67,7 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
expect(screen.queryByText('Desktop Notifications')).toBeNull();
expect(screen.queryByText('Mobile Notifications')).toBeNull();
expect(screen.queryByText('Follow all threads in this channel')).toBeNull();
expect(screen.queryByText('Follow all threads in this channel')).not.toBeNull();
fireEvent.click(screen.getByRole('button', {name: /Save/i}));
@ -73,19 +76,22 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
'current_user_id',
'channel_id',
{
desktop: baseProps.channelMember?.notify_props.desktop,
ignore_channel_mentions: 'off',
mark_unread: 'mention',
desktop: 'default',
desktop_threads: 'all',
desktop_sound: 'default',
desktop_notification_sound: 'default',
push: 'all',
desktop_sound: 'on',
desktop_notification_sound: 'Bing',
push_threads: 'all',
mark_unread: 'mention',
ignore_channel_mentions: 'off',
channel_auto_follow_threads: 'off',
},
),
);
expect(wrapper).toMatchSnapshot();
});
test('should Ignore mentions for @channel, @here and @all', async () => {
test('should save correctly for \'Ignore mentions for @channel, @here and @all\'', async () => {
const wrapper = renderWithContext(
<ChannelNotificationsModal {...baseProps}/>,
);
@ -99,13 +105,15 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
'current_user_id',
'channel_id',
{
desktop: 'all',
ignore_channel_mentions: 'on',
mark_unread:
baseProps.channelMember?.notify_props.mark_unread,
desktop: 'default',
desktop_threads: 'all',
desktop_sound: 'default',
desktop_notification_sound: 'default',
push: 'all',
desktop_sound: 'on',
desktop_notification_sound: 'Bing',
push_threads: 'all',
mark_unread: 'all',
ignore_channel_mentions: 'on',
channel_auto_follow_threads: 'off',
},
),
);
@ -144,11 +152,14 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
'channel_id',
{
desktop: 'none',
channel_auto_follow_threads: 'off',
desktop_threads: 'all',
push_threads: 'all',
desktop_notification_sound: 'default',
desktop_sound: 'default',
ignore_channel_mentions: 'off',
mark_unread: 'all',
push: 'all',
desktop_sound: 'on',
desktop_notification_sound: 'Bing',
push: 'none',
},
),
);
@ -158,7 +169,7 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
test('should disable message notification sound if option is unchecked', async () => {
renderWithContext(<ChannelNotificationsModal {...baseProps}/>);
// Since the default value is on, we will uncheck the checkbox
// Since the default value is checked, we will uncheck the checkbox
fireEvent.click(screen.getByTestId('desktopNotificationSoundsCheckbox'));
expect(screen.getByTestId('desktopNotificationSoundsCheckbox')).not.toBeChecked();
@ -168,12 +179,15 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
'current_user_id',
'channel_id',
{
desktop: 'all',
ignore_channel_mentions: 'off',
mark_unread: 'all',
push: 'all',
desktop: 'default',
desktop_threads: 'all',
desktop_sound: 'off',
desktop_notification_sound: 'Bing',
desktop_notification_sound: 'default',
push: 'all',
push_threads: 'all',
mark_unread: 'all',
ignore_channel_mentions: 'off',
channel_auto_follow_threads: 'off',
},
);
});
@ -203,7 +217,6 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
const sameAsDesktop: HTMLInputElement = screen.getByTestId(
'sameMobileSettingsDesktop',
);
fireEvent.click(sameAsDesktop);
expect(sameAsDesktop.checked).toEqual(true);
expect(screen.queryByText('All new messages')).toBeNull();
@ -214,12 +227,15 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
'current_user_id',
'channel_id',
{
desktop: 'all',
ignore_channel_mentions: 'off',
mark_unread: 'all',
desktop: 'default',
desktop_threads: 'all',
desktop_sound: 'default',
desktop_notification_sound: 'default',
push: 'all',
desktop_sound: 'on',
desktop_notification_sound: 'Bing',
push_threads: 'all',
mark_unread: 'all',
ignore_channel_mentions: 'off',
channel_auto_follow_threads: 'off',
},
),
);
@ -227,27 +243,27 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
});
test('should check the options in the mobile notifications', async () => {
const props = {
...baseProps,
channelMember: {
notify_props: {
...baseProps.channelMember?.notify_props,
push: NotificationLevels.MENTION,
},
} as unknown as ChannelMembership,
};
const wrapper = renderWithContext(
<ChannelNotificationsModal {...baseProps}/>,
<ChannelNotificationsModal {...props}/>,
);
const AlllabelRadio: HTMLInputElement = screen.getByTestId(
'MobileNotification-all',
);
fireEvent.click(AlllabelRadio);
expect(AlllabelRadio.checked).toEqual(true);
fireEvent.click(screen.getByTestId('MobileNotification-all'));
expect(screen.getByTestId('MobileNotification-all')).toBeChecked();
const MentionslabelRadio: HTMLInputElement = screen.getByTestId(
'MobileNotification-mention',
);
fireEvent.click(MentionslabelRadio);
expect(MentionslabelRadio.checked).toEqual(true);
fireEvent.click(screen.getByTestId('MobileNotification-mention'));
expect(screen.getByTestId('MobileNotification-mention')).toBeChecked();
const NothinglabelRadio: HTMLInputElement = screen.getByTestId(
'MobileNotification-none',
);
fireEvent.click(NothinglabelRadio);
expect(NothinglabelRadio.checked).toEqual(true);
fireEvent.click(screen.getByTestId('MobileNotification-none'));
expect(screen.getByTestId('MobileNotification-none')).toBeChecked();
fireEvent.click(screen.getByRole('button', {name: /Save/i}));
await waitFor(() =>
@ -255,28 +271,31 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
'current_user_id',
'channel_id',
{
desktop: 'all',
desktop: 'default',
channel_auto_follow_threads: 'off',
desktop_threads: 'all',
push_threads: 'all',
desktop_notification_sound: 'default',
desktop_sound: 'default',
ignore_channel_mentions: 'off',
mark_unread: 'all',
push: 'none',
desktop_sound: 'on',
desktop_notification_sound: 'Bing',
},
),
);
expect(wrapper).toMatchSnapshot();
});
it('should show auto follow, desktop threads and mobile threads settings if collapsed reply threads is enabled', async () => {
test('should show not auto follow, desktop threads and mobile threads settings if collapsed reply threads is enabled', async () => {
const props = {
...baseProps,
collapsedReplyThreads: true,
collapsedReplyThreads: false,
};
const wrapper = renderWithContext(
<ChannelNotificationsModal {...props}/>,
);
expect(screen.queryByText('Follow all threads in this channel')).toBeVisible();
expect(screen.queryByText('Follow all threads in this channel')).toBeNull();
fireEvent.click(screen.getByRole('button', {name: /Save/i}));
@ -285,18 +304,660 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
'current_user_id',
'channel_id',
{
desktop: baseProps.channelMember?.notify_props.desktop,
channel_auto_follow_threads: 'off',
desktop: 'default',
desktop_notification_sound: 'default',
desktop_sound: 'default',
desktop_threads: 'all',
ignore_channel_mentions: 'off',
mark_unread: 'all',
channel_auto_follow_threads: 'off',
push: 'all',
push_threads: 'default',
desktop_threads: 'all',
desktop_sound: 'on',
desktop_notification_sound: 'Bing',
push_threads: 'all',
},
),
);
expect(wrapper).toMatchSnapshot();
});
});
describe('createChannelNotifyPropsFromSelectedSettings', () => {
test('should return passed in mark_unread, ignore_channel_mentions, channel_auto_follow_threads', () => {
const userNotifyProps = TestHelper.getUserMock().notify_props;
const savedChannelNotifyProps = TestHelper.getChannelMembershipMock({
notify_props: {
mark_unread: 'all',
ignore_channel_mentions: 'off',
channel_auto_follow_threads: 'off',
},
}).notify_props;
const channelNotifyProps = createChannelNotifyPropsFromSelectedSettings(userNotifyProps, savedChannelNotifyProps, true);
expect(channelNotifyProps.mark_unread).toEqual('all');
expect(channelNotifyProps.ignore_channel_mentions).toEqual('off');
expect(channelNotifyProps.channel_auto_follow_threads).toEqual('off');
});
test('should return default if channel\'s desktop is same as user\'s desktop value', () => {
const userNotifyProps1 = TestHelper.getUserMock({
notify_props: {
desktop: 'all',
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps1 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop: 'all',
},
}).notify_props;
const channelNotifyProps1 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps1, savedChannelNotifyProps1, true);
expect(channelNotifyProps1.desktop).toEqual('default');
const userNotifyProps2 = TestHelper.getUserMock({
notify_props: {
desktop: 'mention',
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps2 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop: 'mention',
},
}).notify_props;
const channelNotifyProps2 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps2, savedChannelNotifyProps2, true);
expect(channelNotifyProps2.desktop).toEqual('default');
});
test('should return desktop value if channel\'s desktop is different from user\'s desktop value', () => {
const userNotifyProps = TestHelper.getUserMock({
notify_props: {
desktop: 'mention',
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps = TestHelper.getChannelMembershipMock({
notify_props: {
desktop: 'all',
},
}).notify_props;
const channelNotifyProps = createChannelNotifyPropsFromSelectedSettings(userNotifyProps, savedChannelNotifyProps, true);
expect(channelNotifyProps.desktop).toEqual('all');
});
test('should return correct desktop_threads when user\'s desktop_threads is defined', () => {
const userNotifyProps1 = TestHelper.getUserMock({
notify_props: {
desktop_threads: 'mention',
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps1 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_threads: 'mention',
},
}).notify_props;
const channelNotifyProps1 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps1, savedChannelNotifyProps1, true);
expect(channelNotifyProps1.desktop_threads).toEqual('default');
const userNotifyProps2 = TestHelper.getUserMock({
notify_props: {
desktop_threads: 'all',
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps2 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_threads: 'mention',
},
}).notify_props;
const channelNotifyProps2 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps2, savedChannelNotifyProps2, true);
expect(channelNotifyProps2.desktop_threads).toEqual('mention');
});
test('should return correct desktop_threads when user\'s desktop_threads is not defined', () => {
const userNotifyProps1 = TestHelper.getUserMock({
notify_props: {} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps1 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_threads: 'mention',
},
}).notify_props;
const channelNotifyProps1 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps1, savedChannelNotifyProps1, true);
expect(channelNotifyProps1.desktop_threads).toEqual('default');
const userNotifyProps2 = TestHelper.getUserMock({
notify_props: {} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps2 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_threads: 'default',
},
}).notify_props;
const channelNotifyProps2 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps2, savedChannelNotifyProps2, true);
expect(channelNotifyProps2.desktop_threads).toEqual('default');
const userNotifyProps3 = TestHelper.getUserMock({
notify_props: {} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps3 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_threads: 'none',
},
}).notify_props;
const channelNotifyProps3 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps3, savedChannelNotifyProps3, true);
expect(channelNotifyProps3.desktop_threads).toEqual('none');
const userNotifyProps4 = TestHelper.getUserMock({
notify_props: {
desktop_threads: '' as any,
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps4 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_threads: 'none',
},
}).notify_props;
const channelNotifyProps4 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps4, savedChannelNotifyProps4, true);
expect(channelNotifyProps4.desktop_threads).toEqual('none');
});
test('should return correct desktop_sound value', () => {
const userNotifyProps1 = TestHelper.getUserMock({
notify_props: {
desktop_sound: 'true',
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps1 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_sound: 'on',
},
}).notify_props;
const channelNotifyProps1 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps1, savedChannelNotifyProps1, true);
expect(channelNotifyProps1.desktop_sound).toEqual('default');
const userNotifyProps2 = {
desktop_sound: 'false',
} as UserNotifyProps;
const savedChannelNotifyProps2 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_sound: 'off',
},
}).notify_props;
const channelNotifyProps2 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps2, savedChannelNotifyProps2, true);
expect(channelNotifyProps2.desktop_sound).toEqual('default');
const userNotifyProps3 = {
desktop_sound: '' as any,
} as UserNotifyProps;
const savedChannelNotifyProps3 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_sound: 'on',
},
}).notify_props;
const channelNotifyProps3 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps3, savedChannelNotifyProps3, true);
expect(channelNotifyProps3.desktop_sound).toEqual('default');
const userNotifyProps4 = {
desktop_sound: '' as any,
} as UserNotifyProps;
const savedChannelNotifyProps4 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_sound: 'off',
},
}).notify_props;
const channelNotifyProps4 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps4, savedChannelNotifyProps4, true);
expect(channelNotifyProps4.desktop_sound).toEqual('off');
const userNotifyProps5 = {} as UserNotifyProps;
const savedChannelNotifyProps5 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_sound: 'off',
},
}).notify_props;
const channelNotifyProps5 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps5, savedChannelNotifyProps5, true);
expect(channelNotifyProps5.desktop_sound).toEqual('off');
});
test('should return correct desktop_notification_sound when user\'s desktop_notification_sound is defined', () => {
const userNotifyProps = TestHelper.getUserMock({
notify_props: {
desktop_notification_sound: 'Bing',
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_notification_sound: 'Bing',
},
}).notify_props;
const channelNotifyProps = createChannelNotifyPropsFromSelectedSettings(userNotifyProps, savedChannelNotifyProps, true);
expect(channelNotifyProps.desktop_notification_sound).toEqual('default');
const userNotifyProps2 = TestHelper.getUserMock({
notify_props: {
desktop_notification_sound: 'Crackle',
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps2 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_notification_sound: 'Bing',
},
}).notify_props;
const channelNotifyProps2 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps2, savedChannelNotifyProps2, true);
expect(channelNotifyProps2.desktop_notification_sound).toEqual('Bing');
});
test('should return correct desktop_notification_sound when user\'s desktop_notification_sound is not defined', () => {
const userNotifyProps = TestHelper.getUserMock({
notify_props: {} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_notification_sound: 'Bing',
},
}).notify_props;
const channelNotifyProps = createChannelNotifyPropsFromSelectedSettings(userNotifyProps, savedChannelNotifyProps, true);
expect(channelNotifyProps.desktop_notification_sound).toEqual('default');
const userNotifyProps2 = TestHelper.getUserMock({
notify_props: {} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps2 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_notification_sound: 'default',
},
}).notify_props;
const channelNotifyProps2 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps2, savedChannelNotifyProps2, true);
expect(channelNotifyProps2.desktop_notification_sound).toEqual('default');
const userNotifyProps3 = TestHelper.getUserMock({
notify_props: {} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps3 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_notification_sound: 'Crackle',
},
}).notify_props;
const channelNotifyProps3 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps3, savedChannelNotifyProps3, true);
expect(channelNotifyProps3.desktop_notification_sound).toEqual('Crackle');
});
test('should return correct push value', () => {
const userNotifyProps1 = TestHelper.getUserMock({
notify_props: {
push: 'mention',
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps1 = TestHelper.getChannelMembershipMock({
notify_props: {
push: 'all',
},
}).notify_props;
const channelNotifyProps1 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps1, savedChannelNotifyProps1, true);
expect(channelNotifyProps1.push).toEqual('all');
const userNotifyProps2 = TestHelper.getUserMock({
notify_props: {
push: 'all',
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps2 = TestHelper.getChannelMembershipMock({
notify_props: {
push: 'all',
},
}).notify_props;
const channelNotifyProps2 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps2, savedChannelNotifyProps2, true);
expect(channelNotifyProps2.push).toEqual('default');
});
test('should return correct push value when desktop and mobile settings are the same', () => {
const userNotifyProps1 = TestHelper.getUserMock({
notify_props: {
desktop: 'all',
push: 'mention',
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps1 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop: 'all',
push: 'all',
},
}).notify_props;
const channelNotifyProps1 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps1, savedChannelNotifyProps1, false);
expect(channelNotifyProps1.push).toEqual('all');
const userNotifyProps2 = TestHelper.getUserMock({
notify_props: {
desktop: 'all',
push: 'all',
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps2 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop: 'mention',
push: 'all',
},
}).notify_props;
const channelNotifyProps2 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps2, savedChannelNotifyProps2, false);
expect(channelNotifyProps2.push).toEqual('mention');
});
test('should return correct push_threads value', () => {
const userNotifyProps1 = TestHelper.getUserMock({
notify_props: {
push: 'mention',
push_threads: 'mention',
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps1 = TestHelper.getChannelMembershipMock({
notify_props: {
push: 'all',
push_threads: 'all',
},
}).notify_props;
const channelNotifyProps1 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps1, savedChannelNotifyProps1, true);
expect(channelNotifyProps1.push).toEqual('all');
expect(channelNotifyProps1.push_threads).toEqual('all');
const userNotifyProps2 = TestHelper.getUserMock({
notify_props: {
push: 'mention',
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps2 = TestHelper.getChannelMembershipMock({
notify_props: {
push: 'all',
push_threads: 'all',
},
}).notify_props;
const channelNotifyProps2 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps2, savedChannelNotifyProps2, true);
expect(channelNotifyProps2.push).toEqual('all');
expect(channelNotifyProps2.push_threads).toEqual('all');
const userNotifyProps3 = TestHelper.getUserMock({
notify_props: {
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps3 = TestHelper.getChannelMembershipMock({
notify_props: {
push: 'all',
push_threads: 'all',
},
}).notify_props;
const channelNotifyProps3 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps3, savedChannelNotifyProps3, true);
expect(channelNotifyProps3.push).toEqual('all');
expect(channelNotifyProps3.push_threads).toEqual('all');
const userNotifyProps4 = TestHelper.getUserMock({
notify_props: {
desktop_threads: 'all',
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps4 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_threads: 'mention',
push_threads: 'none',
},
}).notify_props;
const channelNotifyProps4 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps4, savedChannelNotifyProps4, false);
expect(channelNotifyProps4.push_threads).toEqual('mention');
const userNotifyProps5 = TestHelper.getUserMock({
notify_props: {
desktop_threads: 'none',
} as UserNotifyProps,
}).notify_props;
const savedChannelNotifyProps5 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_threads: 'all',
push_threads: 'none',
},
}).notify_props;
const channelNotifyProps5 = createChannelNotifyPropsFromSelectedSettings(userNotifyProps5, savedChannelNotifyProps5, false);
expect(channelNotifyProps5.push_threads).toEqual('all');
});
});
describe('getInitialValuesOfChannelNotifyProps', () => {
test('should return correct value for desktop', () => {
const userNotifyProps1 = TestHelper.getUserMock({
notify_props: {
desktop: 'all',
} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps1 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop: 'default',
},
}).notify_props;
const desktop = getInitialValuesOfChannelNotifyProps(NotificationLevels.ALL, channelMemberNotifyProps1.desktop, userNotifyProps1.desktop);
expect(desktop).toEqual(NotificationLevels.ALL);
const userNotifyProps2 = TestHelper.getUserMock({
notify_props: {
desktop: NotificationLevels.ALL,
} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps2 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop: NotificationLevels.MENTION,
},
}).notify_props;
const desktop2 = getInitialValuesOfChannelNotifyProps(NotificationLevels.ALL, channelMemberNotifyProps2.desktop, userNotifyProps2.desktop);
expect(desktop2).toEqual(NotificationLevels.MENTION);
const userNotifyProps3 = TestHelper.getUserMock({
notify_props: {
desktop: NotificationLevels.ALL,
} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps3 = TestHelper.getChannelMembershipMock({
notify_props: {},
}).notify_props;
const desktop3 = getInitialValuesOfChannelNotifyProps(NotificationLevels.ALL, channelMemberNotifyProps3.desktop, userNotifyProps3.desktop);
expect(desktop3).toEqual(NotificationLevels.ALL);
const userNotifyProps4 = TestHelper.getUserMock({
notify_props: {} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps4 = TestHelper.getChannelMembershipMock({
notify_props: {},
}).notify_props;
const desktop4 = getInitialValuesOfChannelNotifyProps(NotificationLevels.ALL, channelMemberNotifyProps4.desktop, userNotifyProps4.desktop);
expect(desktop4).toEqual(NotificationLevels.ALL);
});
test('should return correct value for desktop_threads', () => {
const userNotifyProps1 = TestHelper.getUserMock({
notify_props: {
desktop_threads: NotificationLevels.ALL,
} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps1 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_threads: NotificationLevels.DEFAULT,
},
}).notify_props;
const desktopThreads = getInitialValuesOfChannelNotifyProps(NotificationLevels.ALL, channelMemberNotifyProps1.desktop_threads, userNotifyProps1.desktop_threads);
expect(desktopThreads).toEqual(NotificationLevels.ALL);
const userNotifyProps2 = TestHelper.getUserMock({
notify_props: {} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps2 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_threads: NotificationLevels.MENTION,
},
}).notify_props;
const desktopThreads2 = getInitialValuesOfChannelNotifyProps(NotificationLevels.ALL, channelMemberNotifyProps2.desktop_threads, userNotifyProps2.desktop_threads);
expect(desktopThreads2).toEqual(NotificationLevels.MENTION);
const userNotifyProps3 = TestHelper.getUserMock({
notify_props: {
desktop_threads: NotificationLevels.ALL,
} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps3 = TestHelper.getChannelMembershipMock({
notify_props: {},
}).notify_props;
const desktopThreads3 = getInitialValuesOfChannelNotifyProps(NotificationLevels.ALL, channelMemberNotifyProps3.desktop_threads, userNotifyProps3.desktop_threads);
expect(desktopThreads3).toEqual(NotificationLevels.ALL);
const userNotifyProps4 = TestHelper.getUserMock({
notify_props: {} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps4 = TestHelper.getChannelMembershipMock({
notify_props: {},
}).notify_props;
const desktopThreads4 = getInitialValuesOfChannelNotifyProps(NotificationLevels.ALL, channelMemberNotifyProps4.desktop_threads, userNotifyProps4.desktop_threads);
expect(desktopThreads4).toEqual(NotificationLevels.ALL);
});
test('should return correct value for desktop_sound', () => {
const userNotifyProps1 = TestHelper.getUserMock({
notify_props: {
desktop_sound: 'false',
} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps1 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_sound: DesktopSound.DEFAULT,
},
}).notify_props;
const desktopSound = getInitialValuesOfChannelNotifyProps(DesktopSound.ON, channelMemberNotifyProps1?.desktop_sound, convertDesktopSoundNotifyPropFromUserToDesktop(userNotifyProps1?.desktop_sound));
expect(desktopSound).toEqual(DesktopSound.OFF);
const userNotifyProps2 = TestHelper.getUserMock({
notify_props: {
desktop_sound: 'false',
} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps2 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_sound: DesktopSound.OFF,
},
}).notify_props;
const desktopSound2 = getInitialValuesOfChannelNotifyProps(DesktopSound.ON, channelMemberNotifyProps2?.desktop_sound, convertDesktopSoundNotifyPropFromUserToDesktop(userNotifyProps2?.desktop_sound));
expect(desktopSound2).toEqual(DesktopSound.OFF);
const userNotifyProps3 = TestHelper.getUserMock({
notify_props: {
desktop_sound: 'false',
} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps3 = TestHelper.getChannelMembershipMock({
notify_props: {},
}).notify_props;
const desktopSound3 = getInitialValuesOfChannelNotifyProps(DesktopSound.ON, channelMemberNotifyProps3?.desktop_sound, convertDesktopSoundNotifyPropFromUserToDesktop(userNotifyProps3?.desktop_sound));
expect(desktopSound3).toEqual(DesktopSound.OFF);
const userNotifyProps4 = TestHelper.getUserMock({
notify_props: {} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps4 = TestHelper.getChannelMembershipMock({
notify_props: {},
}).notify_props;
const desktopSound4 = getInitialValuesOfChannelNotifyProps(DesktopSound.ON, channelMemberNotifyProps4?.desktop_sound, convertDesktopSoundNotifyPropFromUserToDesktop(userNotifyProps4?.desktop_sound));
expect(desktopSound4).toEqual(DesktopSound.ON);
});
test('should return correct value for desktop_notification_sound', () => {
const userNotifyProps1 = TestHelper.getUserMock({
notify_props: {
desktop_notification_sound: DesktopNotificationSounds.BING,
} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps1 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_notification_sound: DesktopNotificationSounds.DEFAULT,
},
}).notify_props;
const desktopNotificationSound = getInitialValuesOfChannelNotifyProps(DesktopNotificationSounds.BING, channelMemberNotifyProps1?.desktop_notification_sound, userNotifyProps1?.desktop_notification_sound);
expect(desktopNotificationSound).toEqual(DesktopNotificationSounds.BING);
const userNotifyProps2 = TestHelper.getUserMock({
notify_props: {
desktop_notification_sound: DesktopNotificationSounds.CRACKLE,
} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps2 = TestHelper.getChannelMembershipMock({
notify_props: {
desktop_notification_sound: DesktopNotificationSounds.BING,
},
}).notify_props;
const desktopNotificationSound2 = getInitialValuesOfChannelNotifyProps(DesktopNotificationSounds.BING, channelMemberNotifyProps2?.desktop_notification_sound, userNotifyProps2?.desktop_notification_sound);
expect(desktopNotificationSound2).toEqual(DesktopNotificationSounds.BING);
const userNotifyProps3 = TestHelper.getUserMock({
notify_props: {
desktop_notification_sound: DesktopNotificationSounds.CRACKLE,
} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps3 = TestHelper.getChannelMembershipMock({
notify_props: {},
}).notify_props;
const desktopNotificationSound3 = getInitialValuesOfChannelNotifyProps(DesktopNotificationSounds.BING, channelMemberNotifyProps3?.desktop_notification_sound, userNotifyProps3?.desktop_notification_sound);
expect(desktopNotificationSound3).toEqual(DesktopNotificationSounds.CRACKLE);
const userNotifyProps4 = TestHelper.getUserMock({
notify_props: {} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps4 = TestHelper.getChannelMembershipMock({
notify_props: {},
}).notify_props;
const desktopNotificationSound4 = getInitialValuesOfChannelNotifyProps(DesktopNotificationSounds.BING, channelMemberNotifyProps4?.desktop_notification_sound, userNotifyProps4?.desktop_notification_sound);
expect(desktopNotificationSound4).toEqual(DesktopNotificationSounds.BING);
});
test('should return correct value for push', () => {
const userNotifyProps1 = TestHelper.getUserMock({
notify_props: {
push: NotificationLevels.ALL,
} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps1 = TestHelper.getChannelMembershipMock({
notify_props: {
push: NotificationLevels.DEFAULT,
},
}).notify_props;
const push = getInitialValuesOfChannelNotifyProps(NotificationLevels.ALL, channelMemberNotifyProps1.push, userNotifyProps1.push);
expect(push).toEqual(NotificationLevels.ALL);
const userNotifyProps2 = TestHelper.getUserMock({
notify_props: {} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps2 = TestHelper.getChannelMembershipMock({
notify_props: {},
}).notify_props;
const push2 = getInitialValuesOfChannelNotifyProps(NotificationLevels.ALL, channelMemberNotifyProps2.push, userNotifyProps2.push);
expect(push2).toEqual(NotificationLevels.ALL);
});
test('should return correct value for push_threads', () => {
const userNotifyProps1 = TestHelper.getUserMock({
notify_props: {
push_threads: NotificationLevels.ALL,
} as UserNotifyProps,
}).notify_props;
const channelMemberNotifyProps1 = TestHelper.getChannelMembershipMock({
notify_props: {
push_threads: NotificationLevels.DEFAULT,
},
}).notify_props;
const pushThreads = getInitialValuesOfChannelNotifyProps(NotificationLevels.ALL, channelMemberNotifyProps1.push_threads, userNotifyProps1.push_threads);
expect(pushThreads).toEqual(NotificationLevels.ALL);
});
});
describe('areDesktopAndMobileSettingsDifferent', () => {
test('should return correct value for collapsed threads', () => {
expect(areDesktopAndMobileSettingsDifferent(true, NotificationLevels.ALL, NotificationLevels.ALL, NotificationLevels.DEFAULT, NotificationLevels.DEFAULT)).toEqual(true);
expect(areDesktopAndMobileSettingsDifferent(true, NotificationLevels.ALL, NotificationLevels.ALL, NotificationLevels.ALL, NotificationLevels.ALL)).toEqual(false);
expect(areDesktopAndMobileSettingsDifferent(true, NotificationLevels.ALL, NotificationLevels.ALL)).toEqual(true);
expect(areDesktopAndMobileSettingsDifferent(true, NotificationLevels.ALL, NotificationLevels.ALL, NotificationLevels.ALL)).toEqual(true);
expect(areDesktopAndMobileSettingsDifferent(true, NotificationLevels.MENTION, NotificationLevels.ALL, NotificationLevels.MENTION, NotificationLevels.ALL)).toEqual(false);
expect(areDesktopAndMobileSettingsDifferent(true, NotificationLevels.DEFAULT, NotificationLevels.DEFAULT)).toEqual(true);
});
});

View File

@ -6,8 +6,8 @@ import {Modal} from 'react-bootstrap';
import {FormattedMessage, useIntl} from 'react-intl';
import type {ValueType} from 'react-select';
import {BellOffOutlineIcon, RefreshIcon} from '@mattermost/compass-icons/components';
import type {Channel, ChannelNotifyProps} from '@mattermost/types/channels';
import {BellOffOutlineIcon} from '@mattermost/compass-icons/components';
import type {Channel, ChannelMembership, ChannelNotifyProps} from '@mattermost/types/channels';
import type {UserNotifyProps, UserProfile} from '@mattermost/types/users';
import AlertBanner from 'components/alert_banner';
@ -18,17 +18,17 @@ import ModalSection from 'components/widgets/modals/components/modal_section';
import RadioSettingItem from 'components/widgets/modals/components/radio_setting_item';
import type {Option} from 'components/widgets/modals/components/react_select_item';
import {IgnoreChannelMentions, NotificationLevels, DesktopSound} from 'utils/constants';
import {getValueOfNotificationSoundsSelect, notificationSoundKeys, stopTryNotificationRing, tryNotificationSound} from 'utils/notification_sounds';
import {NotificationLevels, DesktopSound, IgnoreChannelMentions} from 'utils/constants';
import {convertDesktopSoundNotifyPropFromUserToDesktop, DesktopNotificationSounds, getValueOfNotificationSoundsSelect, stopTryNotificationRing, tryNotificationSound} from 'utils/notification_sounds';
import type {ChannelMemberNotifyProps} from './utils';
import utils, {convertDesktopSoundNotifyPropFromUserToDesktop} from './utils';
import ResetToDefaultButton, {SectionName} from './reset_to_default_button';
import utils from './utils';
import type {PropsFromRedux} from './index';
import './channel_notifications_modal.scss';
type Props = PropsFromRedux & {
export type Props = PropsFromRedux & {
/**
* Function that is called when the modal has been hidden and should be removed
@ -46,74 +46,21 @@ type Props = PropsFromRedux & {
currentUser: UserProfile;
};
function getUseSameDesktopSetting(currentUserNotifyProps: UserNotifyProps, channelMemberNotifyProps?: ChannelMemberNotifyProps) {
const isSameAsDesktop = channelMemberNotifyProps ? channelMemberNotifyProps?.desktop === channelMemberNotifyProps?.push : currentUserNotifyProps.push === currentUserNotifyProps.desktop;
const isSameAsDesktopThreads = channelMemberNotifyProps ? channelMemberNotifyProps?.desktop_threads === channelMemberNotifyProps?.push_threads : currentUserNotifyProps.push_threads === currentUserNotifyProps.desktop_threads;
return isSameAsDesktop && isSameAsDesktopThreads;
}
function getStateFromNotifyProps(currentUserNotifyProps: UserNotifyProps, channelMemberNotifyProps?: ChannelMemberNotifyProps) {
let ignoreChannelMentionsDefault: ChannelNotifyProps['ignore_channel_mentions'] = IgnoreChannelMentions.OFF;
if (channelMemberNotifyProps?.mark_unread === NotificationLevels.MENTION || (currentUserNotifyProps.channel && currentUserNotifyProps.channel === 'false')) {
ignoreChannelMentionsDefault = IgnoreChannelMentions.ON;
}
let ignoreChannelMentions = channelMemberNotifyProps?.ignore_channel_mentions;
if (!ignoreChannelMentions || ignoreChannelMentions === IgnoreChannelMentions.DEFAULT) {
ignoreChannelMentions = ignoreChannelMentionsDefault;
}
const desktop = channelMemberNotifyProps?.desktop === NotificationLevels.DEFAULT ? currentUserNotifyProps.desktop : (channelMemberNotifyProps?.desktop || currentUserNotifyProps.desktop);
const push = channelMemberNotifyProps?.push === NotificationLevels.DEFAULT ? currentUserNotifyProps.desktop : (channelMemberNotifyProps?.push || currentUserNotifyProps.push);
let desktopSound;
if (channelMemberNotifyProps && channelMemberNotifyProps.desktop_sound) {
desktopSound = channelMemberNotifyProps.desktop_sound;
} else {
desktopSound = convertDesktopSoundNotifyPropFromUserToDesktop(currentUserNotifyProps.desktop_sound);
}
let desktopNotificationSound;
if (channelMemberNotifyProps && channelMemberNotifyProps.desktop_notification_sound) {
desktopNotificationSound = channelMemberNotifyProps.desktop_notification_sound;
} else if (currentUserNotifyProps && currentUserNotifyProps.desktop_notification_sound) {
desktopNotificationSound = currentUserNotifyProps.desktop_notification_sound;
} else {
desktopNotificationSound = notificationSoundKeys[0] as ChannelNotifyProps['desktop_notification_sound'];
}
return {
desktop,
desktop_threads: channelMemberNotifyProps?.desktop_threads || NotificationLevels.ALL,
desktop_sound: desktopSound,
desktop_notification_sound: desktopNotificationSound,
mark_unread: channelMemberNotifyProps?.mark_unread || NotificationLevels.ALL,
push,
push_threads: channelMemberNotifyProps?.push_threads || NotificationLevels.ALL,
ignore_channel_mentions: ignoreChannelMentions,
channel_auto_follow_threads: channelMemberNotifyProps?.channel_auto_follow_threads || 'off',
};
}
type SettingsType = {
desktop: ChannelNotifyProps['desktop'];
desktop_threads: ChannelNotifyProps['desktop_threads'];
desktop_sound: ChannelNotifyProps['desktop_sound'];
desktop_notification_sound: ChannelNotifyProps['desktop_notification_sound'];
mark_unread: ChannelNotifyProps['mark_unread'];
push: ChannelNotifyProps['push'];
push_threads: ChannelNotifyProps['push_threads'];
ignore_channel_mentions: ChannelNotifyProps['ignore_channel_mentions'];
channel_auto_follow_threads: ChannelNotifyProps['channel_auto_follow_threads'];
};
export default function ChannelNotificationsModal(props: Props) {
const {formatMessage} = useIntl();
const [show, setShow] = useState(true);
const [serverError, setServerError] = useState('');
const [mobileSettingsSameAsDesktop, setMobileSettingsSameAsDesktop] = useState<boolean>(getUseSameDesktopSetting(props.currentUser.notify_props, props.channelMember?.notify_props));
const [settings, setSettings] = useState<SettingsType>(getStateFromNotifyProps(props.currentUser.notify_props, props.channelMember?.notify_props));
const [settings, setSettings] = useState(getStateFromNotifyProps(props.currentUser.notify_props, props.channelMember?.notify_props));
const [desktopAndMobileSettingsDifferent, setDesktopAndMobileSettingDifferent] = useState<boolean>(areDesktopAndMobileSettingsDifferent(
props.collapsedReplyThreads,
getInitialValuesOfChannelNotifyProps(NotificationLevels.ALL, props?.channelMember?.notify_props?.desktop, props.currentUser.notify_props.desktop),
getInitialValuesOfChannelNotifyProps(NotificationLevels.ALL, props?.channelMember?.notify_props?.desktop_threads, props.currentUser.notify_props.desktop_threads),
getInitialValuesOfChannelNotifyProps(NotificationLevels.ALL, props?.channelMember?.notify_props?.push, props.currentUser.notify_props.push),
getInitialValuesOfChannelNotifyProps(NotificationLevels.ALL, props?.channelMember?.notify_props?.push_threads, props.currentUser.notify_props.push_threads),
));
function handleHide() {
setShow(false);
@ -123,10 +70,49 @@ export default function ChannelNotificationsModal(props: Props) {
setSettings((prevSettings) => ({...prevSettings, ...values}));
}, []);
const handleMobileSettingsChange = useCallback(() => {
setMobileSettingsSameAsDesktop((prevSettings) => !prevSettings);
setSettings((prevSettings) => ({...prevSettings, push: prevSettings.desktop, push_threads: prevSettings.desktop_threads}));
}, []);
function handleUseSameMobileSettingsAsDesktopCheckboxChange(value: boolean) {
const newValueOfSettings = {...settings};
const newValueOfDesktopAndMobileSettingsDifferent = !value;
if (newValueOfDesktopAndMobileSettingsDifferent === false) {
newValueOfSettings.push = settings.desktop;
newValueOfSettings.push_threads = settings.desktop_threads;
} else {
newValueOfSettings.push = getInitialValuesOfChannelNotifyProps(NotificationLevels.ALL, props.channelMember?.notify_props?.push, props.currentUser?.notify_props?.push);
newValueOfSettings.push_threads = getInitialValuesOfChannelNotifyProps(NotificationLevels.ALL, props.channelMember?.notify_props?.push_threads, props.currentUser?.notify_props?.push_threads);
}
setSettings(newValueOfSettings);
setDesktopAndMobileSettingDifferent(newValueOfDesktopAndMobileSettingsDifferent);
}
function handleResetToDefaultClicked(channelNotifyPropsDefaultedToUserNotifyProps: ChannelMembership['notify_props'], sectionName: SectionName) {
if (sectionName === SectionName.Mobile) {
const desktopAndMobileSettingsDifferent = areDesktopAndMobileSettingsDifferent(
props.collapsedReplyThreads,
settings.desktop,
settings.desktop_threads,
channelNotifyPropsDefaultedToUserNotifyProps.push,
channelNotifyPropsDefaultedToUserNotifyProps.push_threads,
);
setDesktopAndMobileSettingDifferent(desktopAndMobileSettingsDifferent);
}
setSettings({...settings, ...channelNotifyPropsDefaultedToUserNotifyProps});
}
function handleSave() {
const channelNotifyProps = createChannelNotifyPropsFromSelectedSettings(props.currentUser.notify_props, settings, desktopAndMobileSettingsDifferent);
props.actions.updateChannelNotifyProps(props.currentUser.id, props.channel.id, channelNotifyProps).then((value) => {
const {error} = value;
if (error) {
setServerError(error.message);
} else {
handleHide();
}
});
}
const muteOrIgnoreSectionContent = (
<>
@ -179,7 +165,7 @@ export default function ChannelNotificationsModal(props: Props) {
id: 'channel_notifications.NotifyMeTitle',
defaultMessage: 'Notify me about…',
})}
inputFieldValue={settings.desktop}
inputFieldValue={settings.desktop || ''}
inputFieldData={utils.desktopNotificationInputFieldData(props.currentUser.notify_props.desktop)}
handleChange={(e) => handleChange({desktop: e.target.value})}
/>
@ -237,23 +223,25 @@ export default function ChannelNotificationsModal(props: Props) {
defaultMessage='Use the same notification settings as desktop'
/>
}
inputFieldValue={mobileSettingsSameAsDesktop}
inputFieldValue={!desktopAndMobileSettingsDifferent}
inputFieldData={utils.sameMobileSettingsDesktopInputFieldData}
handleChange={() => handleMobileSettingsChange()}
handleChange={handleUseSameMobileSettingsAsDesktopCheckboxChange}
/>
{!mobileSettingsSameAsDesktop && (
{desktopAndMobileSettingsDifferent && (
<>
<RadioSettingItem
dataTestId='mobile-notify-me-radio-section'
title={formatMessage({
id: 'channel_notifications.NotifyMeTitle',
defaultMessage: 'Notify me about…',
})}
inputFieldValue={settings.push}
inputFieldValue={settings.push || ''}
inputFieldData={utils.mobileNotificationInputFieldData(props.currentUser.notify_props.push)}
handleChange={(e) => handleChange({push: e.target.value})}
/>
{props.collapsedReplyThreads && settings.push === 'mention' &&
<CheckboxSettingItem
dataTestId='mobile-reply-threads-checkbox-section'
title={formatMessage({
id: 'channel_notifications.ThreadsReplyTitle',
defaultMessage: 'Thread reply notifications',
@ -287,75 +275,6 @@ export default function ChannelNotificationsModal(props: Props) {
/>
);
function handleSave() {
const userSettings: Partial<SettingsType> = {...settings};
if (!props.collapsedReplyThreads) {
delete userSettings.push_threads;
delete userSettings.desktop_threads;
delete userSettings.channel_auto_follow_threads;
}
props.actions.updateChannelNotifyProps(props.currentUser.id, props.channel.id, userSettings).then((value) => {
const {error} = value;
if (error) {
setServerError(error.message);
} else {
handleHide();
}
});
}
const resetToDefaultBtn = useCallback((sectionName: string) => {
const userNotifyProps = {
...props.currentUser.notify_props,
desktop_notification_sound: props.currentUser.notify_props?.desktop_notification_sound ?? notificationSoundKeys[0] as ChannelNotifyProps['desktop_notification_sound'],
};
function resetToDefault(sectionName: string) {
if (sectionName === 'desktop') {
setSettings({
...settings,
desktop: userNotifyProps.desktop,
desktop_threads: userNotifyProps.desktop_threads || settings.desktop_threads,
desktop_sound: convertDesktopSoundNotifyPropFromUserToDesktop(userNotifyProps.desktop_sound),
desktop_notification_sound: userNotifyProps?.desktop_notification_sound ?? notificationSoundKeys[0] as ChannelNotifyProps['desktop_notification_sound'],
});
}
if (sectionName === 'push') {
setSettings({...settings, push: userNotifyProps.desktop, push_threads: userNotifyProps.push_threads || settings.push_threads});
}
}
const isDesktopSameAsDefault =
userNotifyProps.desktop === settings.desktop &&
userNotifyProps.desktop_threads === settings.desktop_threads &&
userNotifyProps.desktop_notification_sound === settings.desktop_notification_sound &&
convertDesktopSoundNotifyPropFromUserToDesktop(userNotifyProps.desktop_sound) === settings.desktop_sound;
const isPushSameAsDefault = (userNotifyProps.push === settings.push && userNotifyProps.push_threads === settings.push_threads);
if ((sectionName === 'desktop' && isDesktopSameAsDefault) || (sectionName === 'push' && isPushSameAsDefault)) {
return undefined;
}
return (
<button
className='channel-notifications-settings-modal__reset-btn'
onClick={() => resetToDefault(sectionName)}
data-testid={`resetToDefaultButton-${sectionName}`}
>
<RefreshIcon
size={14}
color={'currentColor'}
/>
<FormattedMessage
id='channel_notifications.resetToDefault'
defaultMessage='Reset to default'
/>
</button>
);
}, [props.currentUser.notify_props, settings]);
const desktopAndMobileNotificationSectionContent = settings.mark_unread === 'all' ? (
<>
<div className='channel-notifications-settings-modal__divider'/>
@ -364,7 +283,14 @@ export default function ChannelNotificationsModal(props: Props) {
id: 'channel_notifications.desktopNotificationsTitle',
defaultMessage: 'Desktop Notifications',
})}
titleSuffix={resetToDefaultBtn('desktop')}
titleSuffix={
<ResetToDefaultButton
sectionName={SectionName.Desktop}
userNotifyProps={props.currentUser.notify_props}
userSelectedChannelNotifyProps={settings}
onClick={handleResetToDefaultClicked}
/>
}
description={formatMessage({
id: 'channel_notifications.desktopNotificationsDesc',
defaultMessage: 'Available on Chrome, Edge, Firefox, and the Mattermost Desktop App.',
@ -377,7 +303,14 @@ export default function ChannelNotificationsModal(props: Props) {
id: 'channel_notifications.mobileNotificationsTitle',
defaultMessage: 'Mobile Notifications',
})}
titleSuffix={resetToDefaultBtn('push')}
titleSuffix={
<ResetToDefaultButton
sectionName={SectionName.Mobile}
userNotifyProps={props.currentUser.notify_props}
userSelectedChannelNotifyProps={settings}
onClick={handleResetToDefaultClicked}
/>
}
description={formatMessage({
id: 'channel_notifications.mobileNotificationsDesc',
defaultMessage: 'Notification alerts are pushed to your mobile device when there is activity in Mattermost.',
@ -485,3 +418,176 @@ export default function ChannelNotificationsModal(props: Props) {
);
}
function getStateFromNotifyProps(currentUserNotifyProps: UserNotifyProps, channelMemberNotifyProps?: ChannelMembership['notify_props']): Required<Omit<ChannelMembership['notify_props'], 'email'>> {
return {
mark_unread: channelMemberNotifyProps?.mark_unread ?? NotificationLevels.ALL,
ignore_channel_mentions: getInitialValuesOfIgnoreChannelMentions(channelMemberNotifyProps?.mark_unread, channelMemberNotifyProps?.ignore_channel_mentions, currentUserNotifyProps?.channel),
desktop: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['desktop']>(NotificationLevels.ALL, channelMemberNotifyProps?.desktop, currentUserNotifyProps?.desktop),
desktop_threads: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['desktop_threads']>(NotificationLevels.ALL, channelMemberNotifyProps?.desktop_threads, currentUserNotifyProps?.desktop_threads),
desktop_sound: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['desktop_sound']>(DesktopSound.ON, channelMemberNotifyProps?.desktop_sound, convertDesktopSoundNotifyPropFromUserToDesktop(currentUserNotifyProps?.desktop_sound)),
desktop_notification_sound: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['desktop_notification_sound']>(DesktopNotificationSounds.BING, channelMemberNotifyProps?.desktop_notification_sound, currentUserNotifyProps?.desktop_notification_sound),
push: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['push']>(NotificationLevels.ALL, channelMemberNotifyProps?.push, currentUserNotifyProps?.push),
push_threads: getInitialValuesOfChannelNotifyProps<ChannelMembership['notify_props']['push_threads']>(NotificationLevels.ALL, channelMemberNotifyProps?.push_threads, currentUserNotifyProps?.push_threads),
channel_auto_follow_threads: channelMemberNotifyProps?.channel_auto_follow_threads ?? 'off',
};
}
export function getInitialValuesOfChannelNotifyProps<KeyInNotifyProps>(defaultValue: NonNullable<KeyInNotifyProps>, channelMemberNotifyProp: KeyInNotifyProps | undefined = undefined, userNotifyProp: KeyInNotifyProps | undefined = undefined) {
let value = defaultValue;
// Check if channel_member's notify_prop is defined for the selected notify_prop
if (channelMemberNotifyProp) {
// If channel_member's notify_prop is default and user's notify_prop is defined, we should use user's notify_prop
if (channelMemberNotifyProp === NotificationLevels.DEFAULT && userNotifyProp) {
value = userNotifyProp;
} else {
// Otherwise, we should use channel_member's notify_prop as is
value = channelMemberNotifyProp;
}
} else if (userNotifyProp) {
// If channel_member's notify_prop is not defined and user's notify_prop is defined, we should use user's notify_prop as is
value = userNotifyProp;
}
return value;
}
export function getInitialValuesOfIgnoreChannelMentions(
markUnread: ChannelMembership['notify_props']['mark_unread'],
ignoreChannelMentions: ChannelMembership['notify_props']['ignore_channel_mentions'],
userNotifyPropForChannel: UserNotifyProps['channel'],
): NonNullable<ChannelMembership['notify_props']['ignore_channel_mentions']> {
let ignoreChannelMentionsDefault: ChannelNotifyProps['ignore_channel_mentions'] = IgnoreChannelMentions.OFF;
if (markUnread === NotificationLevels.MENTION || (userNotifyPropForChannel && userNotifyPropForChannel === 'false')) {
ignoreChannelMentionsDefault = IgnoreChannelMentions.ON;
}
if (ignoreChannelMentions) {
if (ignoreChannelMentions === IgnoreChannelMentions.DEFAULT) {
return ignoreChannelMentionsDefault;
}
return ignoreChannelMentions;
}
return ignoreChannelMentionsDefault;
}
export function createChannelNotifyPropsFromSelectedSettings(
userNotifyProps: UserNotifyProps,
savedChannelNotifyProps: ChannelMembership['notify_props'],
desktopAndMobileSettingsDifferent: boolean,
) {
const channelNotifyProps: ChannelMembership['notify_props'] = {
mark_unread: savedChannelNotifyProps.mark_unread,
ignore_channel_mentions: savedChannelNotifyProps.ignore_channel_mentions,
channel_auto_follow_threads: savedChannelNotifyProps.channel_auto_follow_threads,
};
if (savedChannelNotifyProps.desktop === userNotifyProps.desktop) {
channelNotifyProps.desktop = NotificationLevels.DEFAULT;
} else {
channelNotifyProps.desktop = savedChannelNotifyProps.desktop;
}
// Check if USER's desktop_thread setting are defined
if (userNotifyProps?.desktop_threads?.length) {
// If USER's desktop_thread setting is same as CHANNEL's new desktop_threads setting, we should set it to default
if (userNotifyProps.desktop_threads === savedChannelNotifyProps.desktop_threads) {
channelNotifyProps.desktop_threads = NotificationLevels.DEFAULT;
} else {
// Otherwise, we should use the CHANNEL's new desktop_threads setting as is
channelNotifyProps.desktop_threads = savedChannelNotifyProps.desktop_threads;
}
} else if (savedChannelNotifyProps.desktop_threads === NotificationLevels.MENTION || savedChannelNotifyProps.desktop_threads === NotificationLevels.DEFAULT) {
// If USER's desktop_thread setting is not defined and CHANNEL's new desktop_threads setting is MENTION or DEFAULT, then save it as default
channelNotifyProps.desktop_threads = NotificationLevels.DEFAULT;
} else {
// Otherwise, we should use the CHANNEL's new desktop_threads setting as is
channelNotifyProps.desktop_threads = savedChannelNotifyProps.desktop_threads;
}
if (convertDesktopSoundNotifyPropFromUserToDesktop(userNotifyProps?.desktop_sound) === savedChannelNotifyProps.desktop_sound) {
channelNotifyProps.desktop_sound = DesktopSound.DEFAULT;
} else {
channelNotifyProps.desktop_sound = savedChannelNotifyProps.desktop_sound;
}
// Check if USER's desktop_notification_sound setting is defined
if (userNotifyProps?.desktop_notification_sound?.length) {
// If USER's desktop_notification_sound setting is same as CHANNEL's new desktop_notification_sound setting, we should set it to default
if (userNotifyProps.desktop_notification_sound === savedChannelNotifyProps.desktop_notification_sound) {
channelNotifyProps.desktop_notification_sound = DesktopNotificationSounds.DEFAULT;
} else {
// Otherwise, we should use the CHANNEL's new desktop_notification_sound setting as is
channelNotifyProps.desktop_notification_sound = savedChannelNotifyProps.desktop_notification_sound;
}
} else if (savedChannelNotifyProps.desktop_notification_sound === DesktopNotificationSounds.BING || savedChannelNotifyProps.desktop_notification_sound === DesktopNotificationSounds.DEFAULT) {
// If USER's desktop_notification_sound setting is not defined and CHANNEL's new desktop_notification_sound setting is either BING or DEFAULT, then save it as default
channelNotifyProps.desktop_notification_sound = DesktopNotificationSounds.DEFAULT;
} else {
// Otherwise, we should use the CHANNEL's new desktop_notification_sound setting as is
channelNotifyProps.desktop_notification_sound = savedChannelNotifyProps.desktop_notification_sound;
}
if (savedChannelNotifyProps.push === userNotifyProps.push) {
channelNotifyProps.push = NotificationLevels.DEFAULT;
} else {
channelNotifyProps.push = savedChannelNotifyProps.push;
}
// Check if USER's push_threads setting are defined
if (userNotifyProps?.push_threads?.length) {
// If USER's push_threads setting is same as CHANNEL's new push_threads setting, we should set it to default
if (userNotifyProps.push_threads === savedChannelNotifyProps.push_threads) {
channelNotifyProps.push_threads = NotificationLevels.DEFAULT;
} else {
// Otherwise, we should use the CHANNEL's new push_threads setting as is
channelNotifyProps.push_threads = savedChannelNotifyProps.push_threads;
}
} else if (savedChannelNotifyProps.push_threads === NotificationLevels.MENTION || savedChannelNotifyProps.push_threads === NotificationLevels.DEFAULT) {
// If USER's push_threads setting is not defined and CHANNEL's new push_threads setting is MENTION or DEFAULT, then save it as default
channelNotifyProps.push_threads = NotificationLevels.DEFAULT;
} else {
// Otherwise, we should use the CHANNEL's new push_threads setting as is
channelNotifyProps.push_threads = savedChannelNotifyProps.push_threads;
}
// If desktop and mobile settings are checked to be same, then same settings should be applied to push and push_threads
// as that of desktop and desktop_threads
if (desktopAndMobileSettingsDifferent === false) {
// If desktop is set to default, it means it is synced to the user's notification settings.
// Since we checked the box to use the same settings for mobile, we need to set channel's mobile to match channel's desktop.
// Setting mobile to default would sync it to the user's notification settings, which we want to avoid.
if (channelNotifyProps.desktop === NotificationLevels.DEFAULT) {
channelNotifyProps.push = userNotifyProps.desktop;
} else {
// Otherwise, we should use the CHANNEL's desktop setting as is to match mobile settings
channelNotifyProps.push = channelNotifyProps.desktop;
}
if (channelNotifyProps.desktop_threads === NotificationLevels.DEFAULT) {
channelNotifyProps.push_threads = userNotifyProps.desktop_threads;
} else {
channelNotifyProps.push_threads = channelNotifyProps.desktop_threads;
}
}
return channelNotifyProps;
}
/**
* Check's if channel's notification settings for desktop and mobile are different
*/
export function areDesktopAndMobileSettingsDifferent(
isCollapsedThreadsEnabled: boolean,
desktop: UserNotifyProps['desktop'],
desktopThreads: UserNotifyProps['desktop_threads'],
push?: UserNotifyProps['push'],
pushThreads?: UserNotifyProps['push_threads'],
): boolean {
if (push === NotificationLevels.DEFAULT || push === desktop) {
return isCollapsedThreadsEnabled && desktopThreads !== pushThreads;
}
return true;
}

View File

@ -0,0 +1,15 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ResetToDefaultButton should render if section name is valid 1`] = `
<div>
<button
class="channel-notifications-settings-modal__reset-btn"
data-testid="resetToDefaultButton-desktop"
>
<i
class="icon icon-refresh"
/>
Reset to default
</button>
</div>
`;

View File

@ -0,0 +1,173 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import type {ChannelMembership} from '@mattermost/types/channels';
import type {UserNotifyProps} from '@mattermost/types/users';
import {renderWithContext, screen} from 'tests/react_testing_utils';
import {NotificationLevels, DesktopSound} from 'utils/constants';
import {notificationSoundKeys, convertDesktopSoundNotifyPropFromUserToDesktop} from 'utils/notification_sounds';
import {TestHelper} from 'utils/test_helper';
import ResetToDefaultButton, {SectionName} from './index';
import type {Props} from './index';
describe('ResetToDefaultButton', () => {
const defaultProps: Props = {
sectionName: SectionName.Desktop,
userNotifyProps: TestHelper.getUserMock().notify_props,
userSelectedChannelNotifyProps: TestHelper.getChannelMembershipMock({}).notify_props,
onClick: jest.fn(),
};
test('should not show if section name is not valid', () => {
// Mock console.error to suppress PropTypes warning
const originalError = console.error;
console.error = jest.fn();
const props = {
...defaultProps,
sectionName: 'invalidSectionName' as any,
};
const {container} = renderWithContext(<ResetToDefaultButton {...props}/>);
expect(container).toBeEmptyDOMElement();
console.error = originalError;
});
test('should render if section name is valid', () => {
const {container} = renderWithContext(<ResetToDefaultButton {...defaultProps}/>);
expect(container).not.toBeEmptyDOMElement();
expect(container).toMatchSnapshot();
});
test('should not show if desktop notifications are same as default user notification settings', () => {
const props = {
...defaultProps,
userNotifyProps: {
...defaultProps.userNotifyProps,
desktop: NotificationLevels.ALL,
desktop_threads: NotificationLevels.ALL,
desktop_sound: 'true' as UserNotifyProps['desktop_sound'],
desktop_notification_sound: notificationSoundKeys[0] as UserNotifyProps['desktop_notification_sound'],
},
userSelectedChannelNotifyProps: {
...defaultProps.userSelectedChannelNotifyProps,
desktop: NotificationLevels.ALL,
desktop_threads: NotificationLevels.ALL,
desktop_sound: DesktopSound.ON,
desktop_notification_sound: notificationSoundKeys[0] as ChannelMembership['notify_props']['desktop_notification_sound'],
},
};
const {container} = renderWithContext(<ResetToDefaultButton {...props}/>);
expect(container).toBeEmptyDOMElement();
});
test('should show if desktop notifications are not same as user notification settings', () => {
const props1 = {
...defaultProps,
userNotifyProps: {
...defaultProps.userNotifyProps,
desktop: NotificationLevels.ALL,
desktop_threads: NotificationLevels.ALL,
desktop_sound: 'true' as UserNotifyProps['desktop_sound'],
desktop_notification_sound: notificationSoundKeys[0] as UserNotifyProps['desktop_notification_sound'],
},
userSelectedChannelNotifyProps: {
...defaultProps.userSelectedChannelNotifyProps,
desktop: NotificationLevels.MENTION,
desktop_threads: NotificationLevels.ALL,
desktop_sound: DesktopSound.ON,
},
};
const {rerender} = renderWithContext(<ResetToDefaultButton {...props1}/>);
expect(screen.getByText('Reset to default')).toBeInTheDocument();
const props2 = {
...props1,
userSelectedChannelNotifyProps: {
...defaultProps.userSelectedChannelNotifyProps,
desktop: NotificationLevels.ALL,
desktop_sound: DesktopSound.OFF,
desktop_notification_sound: notificationSoundKeys[0] as UserNotifyProps['desktop_notification_sound'],
},
};
rerender(<ResetToDefaultButton {...props2}/>);
expect(screen.getByText('Reset to default')).toBeInTheDocument();
});
test('should not show if mobile notifications are same as default user notification settings', () => {
const props = {
...defaultProps,
sectionName: SectionName.Mobile,
userNotifyProps: {
...defaultProps.userNotifyProps,
push: NotificationLevels.ALL,
push_threads: NotificationLevels.ALL,
},
userSelectedChannelNotifyProps: {
...defaultProps.userSelectedChannelNotifyProps,
push: NotificationLevels.ALL,
push_threads: NotificationLevels.ALL,
},
};
const {container} = renderWithContext(<ResetToDefaultButton {...props}/>);
expect(container).toBeEmptyDOMElement();
});
test('should show if mobile notifications are not same as user notification settings', () => {
const props = {
...defaultProps,
sectionName: SectionName.Mobile,
userNotifyProps: {
...defaultProps.userNotifyProps,
push: NotificationLevels.ALL,
push_threads: NotificationLevels.ALL,
},
userSelectedChannelNotifyProps: {
...defaultProps.userSelectedChannelNotifyProps,
push: NotificationLevels.MENTION,
push_threads: NotificationLevels.ALL,
},
};
const {rerender} = renderWithContext(<ResetToDefaultButton {...props}/>);
expect(screen.getByText('Reset to default')).toBeInTheDocument();
const props2 = {
...defaultProps,
userSelectedChannelNotifyProps: {
...defaultProps.userSelectedChannelNotifyProps,
push: NotificationLevels.ALL,
push_threads: NotificationLevels.MENTION,
},
};
rerender(<ResetToDefaultButton {...props2}/>);
expect(screen.getByText('Reset to default')).toBeInTheDocument();
});
});
describe('convertDesktopSoundNotifyPropFromUserToDesktop', () => {
test('should return ON if user desktop sound is true', () => {
const desktopSound = convertDesktopSoundNotifyPropFromUserToDesktop('true');
expect(desktopSound).toBe(DesktopSound.ON);
});
test('should return ON if user desktop sound is not present', () => {
const desktopSound = convertDesktopSoundNotifyPropFromUserToDesktop();
expect(desktopSound).toBe(DesktopSound.ON);
});
test('should return OFF if user desktop sound is false', () => {
const desktopSound = convertDesktopSoundNotifyPropFromUserToDesktop('false');
expect(desktopSound).toBe(DesktopSound.OFF);
});
});

View File

@ -0,0 +1,117 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useMemo} from 'react';
import {FormattedMessage} from 'react-intl';
import type {ChannelMembership, ChannelNotifyProps} from '@mattermost/types/channels';
import type {UserNotifyProps, UserProfile} from '@mattermost/types/users';
import {DesktopSound, NotificationLevels} from 'utils/constants';
import {notificationSoundKeys, convertDesktopSoundNotifyPropFromUserToDesktop} from 'utils/notification_sounds';
export enum SectionName {
Desktop = 'desktop',
Mobile = 'mobile',
}
const VALID_SECTION_NAMES = Object.values(SectionName);
export interface Props {
sectionName: SectionName;
userNotifyProps: UserProfile['notify_props'];
/** The user's selected channel notify props which is not yet saved */
userSelectedChannelNotifyProps: ChannelMembership['notify_props'];
onClick: (channelNotifyPropsDefaultedToUserNotifyProps: ChannelMembership['notify_props'], sectionName: SectionName) => void;
}
export default function ResetToDefaultButton(props: Props) {
const areDesktopNotificationsSameAsDefault = useMemo(() => {
const isNotifyMeAboutSame = props.userNotifyProps.desktop === props.userSelectedChannelNotifyProps.desktop;
const isThreadReplyNotificationsSame = props.userNotifyProps.desktop_threads === props.userSelectedChannelNotifyProps.desktop_threads;
const isSoundSame = convertDesktopSoundNotifyPropFromUserToDesktop(props.userNotifyProps.desktop_sound) === props.userSelectedChannelNotifyProps.desktop_sound;
let isNotificationSoundSame = false;
if (props.userNotifyProps.desktop_notification_sound) {
isNotificationSoundSame = props.userNotifyProps.desktop_notification_sound === props.userSelectedChannelNotifyProps.desktop_notification_sound;
} else {
// It could happen that the notification sound is not set in the user's notify props. That case we should assume its the Bing sound.
isNotificationSoundSame = props.userSelectedChannelNotifyProps.desktop_notification_sound === notificationSoundKeys[0] as ChannelNotifyProps['desktop_notification_sound'];
}
return isNotifyMeAboutSame && isThreadReplyNotificationsSame && isSoundSame && isNotificationSoundSame;
}, [
props.userNotifyProps.desktop,
props.userSelectedChannelNotifyProps.desktop,
props.userNotifyProps.desktop_threads,
props.userSelectedChannelNotifyProps.desktop_threads,
props.userNotifyProps.desktop_sound,
props.userSelectedChannelNotifyProps.desktop_sound,
props.userNotifyProps.desktop_notification_sound,
props.userSelectedChannelNotifyProps.desktop_notification_sound,
]);
const areMobileNotificationsSameAsDefault = useMemo(() => {
const isNotifyMeAboutSame = props.userNotifyProps.push === props.userSelectedChannelNotifyProps.push;
const isThreadReplyNotificationsSame = props.userNotifyProps.push_threads === props.userSelectedChannelNotifyProps.push_threads;
return isNotifyMeAboutSame && isThreadReplyNotificationsSame;
}, [
props.userNotifyProps.push,
props.userSelectedChannelNotifyProps.push,
props.userNotifyProps.push_threads,
props.userSelectedChannelNotifyProps.push_threads,
]);
if (!VALID_SECTION_NAMES.includes(props.sectionName)) {
return null;
}
if (props.sectionName === SectionName.Desktop && areDesktopNotificationsSameAsDefault) {
return null;
}
if (props.sectionName === SectionName.Mobile && areMobileNotificationsSameAsDefault) {
return null;
}
function handleOnClick() {
const channelNotifyPropsDefaultedToUserNotifyProps = resetChannelsNotificationToUsersDefault(props.userNotifyProps, props.sectionName);
props.onClick(channelNotifyPropsDefaultedToUserNotifyProps, props.sectionName);
}
return (
<button
className='channel-notifications-settings-modal__reset-btn'
onClick={handleOnClick}
data-testid={`resetToDefaultButton-${props.sectionName}`}
>
<i className='icon icon-refresh'/>
<FormattedMessage
id='channel_notifications.resetToDefault'
defaultMessage='Reset to default'
/>
</button>
);
}
export function resetChannelsNotificationToUsersDefault(userNotifyProps: UserNotifyProps, sectionName: SectionName): ChannelMembership['notify_props'] {
if (sectionName === SectionName.Desktop) {
return {
desktop: userNotifyProps.desktop,
desktop_threads: userNotifyProps?.desktop_threads ?? NotificationLevels.ALL,
desktop_sound: userNotifyProps && userNotifyProps.desktop_sound ? convertDesktopSoundNotifyPropFromUserToDesktop(userNotifyProps.desktop_sound) : DesktopSound.ON,
desktop_notification_sound: userNotifyProps?.desktop_notification_sound ?? notificationSoundKeys[0] as ChannelNotifyProps['desktop_notification_sound'],
};
}
if (sectionName === SectionName.Mobile) {
return {
push: userNotifyProps.push,
push_threads: userNotifyProps?.push_threads ?? NotificationLevels.ALL,
};
}
return {};
}

View File

@ -4,18 +4,13 @@
import React from 'react';
import {FormattedMessage} from 'react-intl';
import type {ChannelNotifyProps} from '@mattermost/types/channels';
import type {UserNotifyProps} from '@mattermost/types/users';
import type {FieldsetCheckbox} from 'components/widgets/modals/components/checkbox_setting_item';
import type {FieldsetRadio} from 'components/widgets/modals/components/radio_setting_item';
import type {FieldsetReactSelect} from 'components/widgets/modals/components/react_select_item';
import {DesktopSound, NotificationLevels} from 'utils/constants';
import {NotificationLevels} from 'utils/constants';
import {optionsOfMessageNotificationSoundsSelect} from 'utils/notification_sounds';
export type ChannelMemberNotifyProps = Partial<ChannelNotifyProps> & Pick<UserNotifyProps, 'desktop_threads' | 'push_threads'>
const MuteChannelInputFieldData: FieldsetCheckbox = {
name: 'mute channel',
dataTestId: 'muteChannel',
@ -53,52 +48,58 @@ export const desktopNotificationInputFieldData = (defaultOption: string): Fields
dataTestId: `desktopNotification-${NotificationLevels.ALL}`,
title: (
<FormattedMessage
id='channel_notifications.desktopNotificationAllLabel'
defaultMessage='All new messages'
id='channelNotifications.desktopNotification.allMessages'
defaultMessage='All new messages {optionalDefault}'
values={{
optionalDefault: defaultOption === NotificationLevels.ALL ? (
<FormattedMessage
id='channel_notifications.default'
defaultMessage='(default)'
/>) : undefined,
}}
/>
),
name: `desktopNotification-${NotificationLevels.ALL}`,
key: `desktopNotification-${NotificationLevels.ALL}`,
value: NotificationLevels.ALL,
suffix: defaultOption === NotificationLevels.ALL ? (
<FormattedMessage
id='channel_notifications.default'
defaultMessage='(default)'
/>) : undefined,
},
{
dataTestId: `desktopNotification-${NotificationLevels.MENTION}`,
title: (
<FormattedMessage
id='channel_notifications.desktopNotificationMentionLabel'
defaultMessage='Mentions, direct messages, and keywords only'
id='channelNotifications.desktopNotification.mention'
defaultMessage='Mentions, direct messages, and keywords only {optionalDefault}'
values={{
optionalDefault: defaultOption === NotificationLevels.MENTION ? (
<FormattedMessage
id='channel_notifications.default'
defaultMessage='(default)'
/>) : undefined,
}}
/>
),
name: `desktopNotification-${NotificationLevels.MENTION}`,
key: `desktopNotification-${NotificationLevels.MENTION}`,
value: NotificationLevels.MENTION,
suffix: defaultOption === NotificationLevels.MENTION ? (
<FormattedMessage
id='channel_notifications.default'
defaultMessage='(default)'
/>) : undefined,
},
{
dataTestId: `desktopNotification-${NotificationLevels.NONE}`,
title: (
<FormattedMessage
id='channel_notifications.desktopNotificationNothingLabel'
defaultMessage='Nothing'
id='channelNotifications.desktopNotification.nothing'
defaultMessage='Nothing {optionalDefault}'
values={{
optionalDefault: defaultOption === NotificationLevels.NONE ? (
<FormattedMessage
id='channel_notifications.default'
defaultMessage='(default)'
/>) : undefined,
}}
/>
),
name: `desktopNotification-${NotificationLevels.NONE}`,
key: `desktopNotification-${NotificationLevels.NONE}`,
value: NotificationLevels.NONE,
suffix: defaultOption === NotificationLevels.NONE ? (
<FormattedMessage
id='channel_notifications.default'
defaultMessage='(default)'
/>) : undefined,
},
],
};
@ -122,74 +123,64 @@ export const mobileNotificationInputFieldData = (defaultOption: string): Fieldse
dataTestId: `MobileNotification-${NotificationLevels.ALL}`,
title: (
<FormattedMessage
id='channel_notifications.MobileNotificationAllLabel'
defaultMessage='All new messages'
id='channelNotifications.mobileNotification.newMessages'
defaultMessage='All new messages {optionalDefault}'
values={{
optionalDefault: defaultOption === NotificationLevels.ALL ? (
<FormattedMessage
id='channel_notifications.default'
defaultMessage='(default)'
/>) : undefined,
}}
/>
),
name: `MobileNotification-${NotificationLevels.ALL}`,
key: `MobileNotification-${NotificationLevels.ALL}`,
value: NotificationLevels.ALL,
suffix: defaultOption === NotificationLevels.ALL ? (
<FormattedMessage
id='channel_notifications.default'
defaultMessage='(default)'
/>) : undefined,
},
{
dataTestId: `MobileNotification-${NotificationLevels.MENTION}`,
title: (
<FormattedMessage
id='channel_notifications.MobileNotificationMentionLabel'
defaultMessage='Mentions, direct messages, and keywords only'
id='channelNotifications.mobileNotification.mention'
defaultMessage='Mentions, direct messages, and keywords only {optionalDefault}'
values={{
optionalDefault: defaultOption === NotificationLevels.MENTION ? (
<FormattedMessage
id='channel_notifications.default'
defaultMessage='(default)'
/>) : undefined,
}}
/>
),
name: `MobileNotification-${NotificationLevels.MENTION}`,
key: `MobileNotification-${NotificationLevels.MENTION}`,
value: NotificationLevels.MENTION,
suffix: defaultOption === NotificationLevels.MENTION ? (
<FormattedMessage
id='channel_notifications.default'
defaultMessage='(default)'
/>) : undefined,
},
{
dataTestId: `MobileNotification-${NotificationLevels.NONE}`,
title: (
<FormattedMessage
id='channel_notifications.MobileNotificationNothingLabel'
defaultMessage='Nothing'
id='channelNotifications.mobileNotification.nothing'
defaultMessage='Nothing {optionalDefault}'
values={{
optionalDefault: defaultOption === NotificationLevels.NONE ? (
<FormattedMessage
id='channel_notifications.default'
defaultMessage='(default)'
/>) : undefined,
}}
/>
),
name: `MobileNotification-${NotificationLevels.NONE}`,
key: `MobileNotification-${NotificationLevels.NONE}`,
value: NotificationLevels.NONE,
suffix: defaultOption === NotificationLevels.NONE ? (
<FormattedMessage
id='channel_notifications.default'
defaultMessage='(default)'
/>) : undefined,
},
],
};
};
/**
* This conversion is needed because User's preference for desktop sound is stored as either true or false. On the other hand,
* Channel's specific desktop sound is stored as either On or Off.
*/
export function convertDesktopSoundNotifyPropFromUserToDesktop(userNotifyDesktopSound?: UserNotifyProps['desktop_sound']) {
if (!userNotifyDesktopSound) {
return DesktopSound.OFF;
}
if (userNotifyDesktopSound === 'true') {
return DesktopSound.ON;
}
return DesktopSound.ON;
}
const utils = {
export default {
desktopNotificationInputFieldData,
desktopNotificationSoundsCheckboxFieldData,
desktopNotificationSoundsSelectFieldData,
@ -201,5 +192,3 @@ const utils = {
AutoFollowThreadsInputFieldData,
sameMobileSettingsDesktopInputFieldData,
};
export default utils;

View File

@ -429,7 +429,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
}
};
handleChangeForCustomKeysWithHightlightInput = (values: ValueType<{ value: string }>) => {
handleChangeForCustomKeysWithHighlightInput = (values: ValueType<{ value: string }>) => {
if (values && Array.isArray(values) && values.length > 0) {
const customKeysWithHighlight = values.
map((value: MultiInputValue) => {
@ -682,7 +682,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
MenuList: () => null,
}}
aria-labelledby='mentionKeysWithHighlightInput'
onChange={this.handleChangeForCustomKeysWithHightlightInput}
onChange={this.handleChangeForCustomKeysWithHighlightInput}
value={this.state.customKeysWithHighlight}
inputValue={this.state.customKeysWithHighlightInputValue}
onInputChange={this.handleChangeForCustomKeysWithHighlightInputValue}
@ -1142,9 +1142,12 @@ const customKeywordsSelectorStyles: ReactSelectStyles = {
const validNotificationLevels = Object.values(NotificationLevels);
/**
* Check's if user's global notification settings for desktop and mobile are different
*/
export function areDesktopAndMobileSettingsDifferent(
desktopActivity: UserNotifyProps['desktop'],
pushActivity: UserNotifyProps['push'],
pushActivity?: UserNotifyProps['push'],
desktopThreads?: UserNotifyProps['desktop_threads'],
pushThreads?: UserNotifyProps['push_threads'],
isCollapsedThreadsEnabled?: boolean,

View File

@ -19,6 +19,7 @@ export type BaseSettingItemProps = {
title?: string;
description?: string;
error?: ExtendedMessageDescriptor;
dataTestId?: string;
};
type Props = BaseSettingItemProps & {
@ -28,7 +29,7 @@ type Props = BaseSettingItemProps & {
descriptionAboveContent?: boolean;
}
function BaseSettingItem({title, description, content, className, error, descriptionAboveContent = false, isContentInline = false}: Props): JSX.Element {
function BaseSettingItem({title, description, content, className, error, descriptionAboveContent = false, isContentInline = false, dataTestId}: Props): JSX.Element {
const {formatMessage} = useIntl();
const titleComponent = title && (
@ -60,7 +61,10 @@ function BaseSettingItem({title, description, content, className, error, descrip
);
return (
<div className={classNames('mm-modal-generic-section-item', className)}>
<div
data-testid={dataTestId}
className={classNames('mm-modal-generic-section-item', className)}
>
{titleComponent}
{descriptionAboveContent ? descriptionComponent : undefined}
<div

View File

@ -33,6 +33,7 @@ export default function CheckboxSettingItem({
inputFieldTitle,
handleChange,
className,
dataTestId,
descriptionAboveContent = false,
}: Props) {
const content = (
@ -59,6 +60,7 @@ export default function CheckboxSettingItem({
content={content}
title={title}
description={description}
dataTestId={dataTestId}
className={className}
descriptionAboveContent={descriptionAboveContent}
/>

View File

@ -14,7 +14,6 @@ export type FieldsetRadio = {
name: string;
key: string;
value: string;
suffix?: JSX.Element;
}>;
}
@ -32,6 +31,7 @@ function RadioSettingItem({
inputFieldData,
inputFieldValue,
handleChange,
dataTestId,
}: Props): JSX.Element {
const fields = inputFieldData.options.map((option) => {
return (
@ -49,7 +49,6 @@ function RadioSettingItem({
onChange={handleChange}
/>
{option.title}
{option.suffix}
</label>
);
});
@ -61,6 +60,7 @@ function RadioSettingItem({
);
return (
<BaseSettingItem
dataTestId={dataTestId}
className={className}
content={content}
title={title}

View File

@ -3202,9 +3202,6 @@
"channel_notifications.checkbox.sameMobileSettingsDesktop": "Use the same notification settings as desktop",
"channel_notifications.checkbox.threadsReplyTitle": "Notify me about replies to threads Im following",
"channel_notifications.default": "(default)",
"channel_notifications.desktopNotificationAllLabel": "All new messages",
"channel_notifications.desktopNotificationMentionLabel": "Mentions, direct messages, and keywords only",
"channel_notifications.desktopNotificationNothingLabel": "Nothing",
"channel_notifications.desktopNotifications.soundEnable": "Message notification sounds",
"channel_notifications.desktopNotifications.soundSelectPlaceholder": "Select a sound",
"channel_notifications.desktopNotifications.title": "Sounds",
@ -3213,9 +3210,6 @@
"channel_notifications.ignoreMentionsDesc": "When enabled, @channel, @here and @all will not trigger mentions or mention notifications in this channel",
"channel_notifications.ignoreMentionsTitle": "Ignore mentions for @channel, @here and @all",
"channel_notifications.levels.all": "All",
"channel_notifications.MobileNotificationAllLabel": "All new messages",
"channel_notifications.MobileNotificationMentionLabel": "Mentions, direct messages, and keywords only",
"channel_notifications.MobileNotificationNothingLabel": "Nothing",
"channel_notifications.mobileNotificationsDesc": "Notification alerts are pushed to your mobile device when there is activity in Mattermost.",
"channel_notifications.mobileNotificationsTitle": "Mobile Notifications",
"channel_notifications.muteAndIgnore": "Mute or ignore",
@ -3234,6 +3228,12 @@
"channelHeader.removeFromFavorites": "Remove from Favorites",
"channelHeader.unmute": "Unmute",
"channelHeader.viewInfo": "View Info",
"channelNotifications.desktopNotification.allMessages": "All new messages {optionalDefault}",
"channelNotifications.desktopNotification.mention": "Mentions, direct messages, and keywords only {optionalDefault}",
"channelNotifications.desktopNotification.nothing": "Nothing {optionalDefault}",
"channelNotifications.mobileNotification.mention": "Mentions, direct messages, and keywords only {optionalDefault}",
"channelNotifications.mobileNotification.newMessages": "All new messages {optionalDefault}",
"channelNotifications.mobileNotification.nothing": "Nothing {optionalDefault}",
"channelView.login.successfull": "Login Successful",
"claim.email_to_ldap.enterLdapPwd": "Enter the ID and password for your AD/LDAP account",
"claim.email_to_ldap.enterPwd": "Enter the password for your {site} email account",

View File

@ -990,6 +990,7 @@ export const NotificationLevels = {
} as const;
export const DesktopSound = {
DEFAULT: 'default',
ON: 'on',
OFF: 'off',
} as const;

View File

@ -5,6 +5,9 @@ import type {ReactNode} from 'react';
import React from 'react';
import {FormattedMessage} from 'react-intl';
import type {ChannelNotifyProps} from '@mattermost/types/channels';
import type {UserNotifyProps} from '@mattermost/types/users';
import bing from 'sounds/bing.mp3';
import calls_calm from 'sounds/calls_calm.mp3';
import calls_cheerful from 'sounds/calls_cheerful.mp3';
@ -15,21 +18,32 @@ import down from 'sounds/down.mp3';
import hello from 'sounds/hello.mp3';
import ripple from 'sounds/ripple.mp3';
import upstairs from 'sounds/upstairs.mp3';
import {DesktopSound} from 'utils/constants';
import * as UserAgent from 'utils/user_agent';
export const notificationSounds = new Map([
['Bing', bing],
['Crackle', crackle],
['Down', down],
['Hello', hello],
['Ripple', ripple],
['Upstairs', upstairs],
export const DesktopNotificationSounds = {
DEFAULT: 'default',
BING: 'Bing',
CRACKLE: 'Crackle',
DOWN: 'Down',
HELLO: 'Hello',
RIPPLE: 'Ripple',
UPSTAIRS: 'Upstairs',
} as const;
export const notificationSounds = new Map<string, string>([
[DesktopNotificationSounds.BING, bing],
[DesktopNotificationSounds.CRACKLE, crackle],
[DesktopNotificationSounds.DOWN, down],
[DesktopNotificationSounds.HELLO, hello],
[DesktopNotificationSounds.RIPPLE, ripple],
[DesktopNotificationSounds.UPSTAIRS, upstairs],
]);
export const notificationSoundKeys = Array.from(notificationSounds.keys());
export const optionsOfMessageNotificationSoundsSelect: Array<{value: string; label: ReactNode}> = notificationSoundKeys.map((soundName) => {
if (soundName === 'Bing') {
if (soundName === DesktopNotificationSounds.BING) {
return {
value: soundName,
label: (
@ -39,7 +53,7 @@ export const optionsOfMessageNotificationSoundsSelect: Array<{value: string; lab
/>
),
};
} else if (soundName === 'Crackle') {
} else if (soundName === DesktopNotificationSounds.CRACKLE) {
return {
value: soundName,
label: (
@ -49,7 +63,7 @@ export const optionsOfMessageNotificationSoundsSelect: Array<{value: string; lab
/>
),
};
} else if (soundName === 'Down') {
} else if (soundName === DesktopNotificationSounds.DOWN) {
return {
value: soundName,
label: (
@ -59,7 +73,7 @@ export const optionsOfMessageNotificationSoundsSelect: Array<{value: string; lab
/>
),
};
} else if (soundName === 'Hello') {
} else if (soundName === DesktopNotificationSounds.HELLO) {
return {
value: soundName,
label: (
@ -69,7 +83,7 @@ export const optionsOfMessageNotificationSoundsSelect: Array<{value: string; lab
/>
),
};
} else if (soundName === 'Ripple') {
} else if (soundName === DesktopNotificationSounds.RIPPLE) {
return {
value: soundName,
label: (
@ -79,7 +93,7 @@ export const optionsOfMessageNotificationSoundsSelect: Array<{value: string; lab
/>
),
};
} else if (soundName === 'Upstairs') {
} else if (soundName === DesktopNotificationSounds.UPSTAIRS) {
return {
value: soundName,
label: (
@ -249,3 +263,15 @@ export function loopNotificationRing(name: string) {
export function hasSoundOptions() {
return (!UserAgent.isEdge());
}
/**
* This conversion is needed because User's preference for desktop sound is stored as either true or false. On the other hand,
* Channel's specific desktop sound is stored as either On or Off.
*/
export function convertDesktopSoundNotifyPropFromUserToDesktop(userNotifyDesktopSound?: UserNotifyProps['desktop_sound']): ChannelNotifyProps['desktop_sound'] {
if (userNotifyDesktopSound && userNotifyDesktopSound === 'false') {
return DesktopSound.OFF;
}
return DesktopSound.ON;
}

View File

@ -22,8 +22,8 @@ export type ChannelStats = {
export type ChannelNotifyProps = {
desktop_threads: 'default' | 'all' | 'mention' | 'none';
desktop: 'default' | 'all' | 'mention' | 'none';
desktop_sound: 'on' | 'off';
desktop_notification_sound?: 'Bing' | 'Crackle' | 'Down' | 'Hello' | 'Ripple' | 'Upstairs';
desktop_sound: 'default' | 'on' | 'off';
desktop_notification_sound?: 'default' | 'Bing' | 'Crackle' | 'Down' | 'Hello' | 'Ripple' | 'Upstairs';
email: 'default' | 'all' | 'mention' | 'none';
mark_unread: 'all' | 'mention';
push: 'default' | 'all' | 'mention' | 'none';

View File

@ -10,7 +10,7 @@ import type {IDMappedObjects, RelationOneToManyUnique, RelationOneToOne} from '.
export type UserNotifyProps = {
desktop: 'default' | 'all' | 'mention' | 'none';
desktop_sound: 'true' | 'false';
desktop_sound: 'default' | 'true' | 'false';
calls_desktop_sound: 'true' | 'false';
email: 'true' | 'false';
mark_unread: 'all' | 'mention';
@ -21,7 +21,7 @@ export type UserNotifyProps = {
channel: 'true' | 'false';
mention_keys: string;
highlight_keys: string;
desktop_notification_sound?: 'Bing' | 'Crackle' | 'Down' | 'Hello' | 'Ripple' | 'Upstairs';
desktop_notification_sound?: 'default' | 'Bing' | 'Crackle' | 'Down' | 'Hello' | 'Ripple' | 'Upstairs';
calls_notification_sound?: 'Dynamic' | 'Calm' | 'Urgent' | 'Cheerful';
desktop_threads?: 'default' | 'all' | 'mention' | 'none';
email_threads?: 'default' | 'all' | 'mention' | 'none';