Skip to content

Commit

Permalink
[MM-60484 & MM-60485] Add disabled notification banners and section n…
Browse files Browse the repository at this point in the history
…otices for web (mattermost#28372)
  • Loading branch information
M-ZubairAhmed authored Nov 1, 2024
1 parent 2d2c039 commit 857fd87
Show file tree
Hide file tree
Showing 29 changed files with 652 additions and 106 deletions.
1 change: 0 additions & 1 deletion webapp/channels/src/components/announcement_bar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ function mapStateToProps(state: GlobalState) {
};
}

//
function mapDispatchToProps(dispatch: Dispatch) {
const dismissFirstError = dismissError.bind(null, 0);
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`NotificationPermissionBar should render the NotificationPermissionNeverGrantedBar when permission is never granted yet 1`] = `
<div>
<div
class="_StyledDiv-BRlth ksRYxX announcement-bar"
>
<div
class="announcement-bar__text"
>
<i
class="icon icon-alert-circle-outline"
/>
<span>
We need your permission to show notifications in the browser.
</span>
<button>
Enable notifications
</button>
</div>
<a
class="announcement-bar__close"
href="#"
>
×
</a>
</div>
</div>
`;

exports[`NotificationPermissionBar should render the NotificationUnsupportedBar if notifications are not supported 1`] = `
<div>
<div
class="_StyledDiv-BRlth ksRYxX announcement-bar"
>
<div
class="announcement-bar__text"
>
<i
class="icon icon-alert-circle-outline"
/>
<span>
Your browser does not support browser notifications.
</span>
<button>
Update your browser
</button>
</div>
<a
class="announcement-bar__close"
href="#"
>
×
</a>
</div>
</div>
`;
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import {screen, waitFor} from '@testing-library/react';
import React from 'react';

import {renderWithContext, userEvent} from 'tests/react_testing_utils';
import {requestNotificationPermission, isNotificationAPISupported} from 'utils/notifications';
import {renderWithContext, userEvent, screen, waitFor} from 'tests/react_testing_utils';
import * as utilsNotifications from 'utils/notifications';

import NotificationPermissionBar from './index';

jest.mock('utils/notifications', () => ({
requestNotificationPermission: jest.fn(),
isNotificationAPISupported: jest.fn(),
}));

describe('NotificationPermissionBar', () => {
const initialState = {
entities: {
Expand All @@ -27,52 +21,71 @@ describe('NotificationPermissionBar', () => {
},
};

beforeEach(() => {
(isNotificationAPISupported as jest.Mock).mockReturnValue(true);
(window as any).Notification = {permission: 'default'};
});

afterEach(() => {
jest.clearAllMocks();
delete (window as any).Notification;
jest.restoreAllMocks();
});

test('should render the notification bar when conditions are met', () => {
renderWithContext(<NotificationPermissionBar/>, initialState);
test('should not render anything if user is not logged in', () => {
const {container} = renderWithContext(<NotificationPermissionBar/>);

expect(screen.getByText('We need your permission to show desktop notifications.')).toBeInTheDocument();
expect(screen.getByText('Enable notifications')).toBeInTheDocument();
expect(container).toBeEmptyDOMElement();
});

test('should not render the notification bar if user is not logged in', () => {
renderWithContext(<NotificationPermissionBar/>);
test('should render the NotificationUnsupportedBar if notifications are not supported', () => {
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(false);

expect(screen.queryByText('We need your permission to show desktop notifications.')).not.toBeInTheDocument();
expect(screen.queryByText('Enable notifications')).not.toBeInTheDocument();
const {container} = renderWithContext(<NotificationPermissionBar/>, initialState);

expect(container).toMatchSnapshot();

expect(screen.queryByText('Your browser does not support browser notifications.')).toBeInTheDocument();
expect(screen.queryByText('Update your browser')).toBeInTheDocument();
});

test('should not render the notification bar if Notifications are not supported', () => {
delete (window as any).Notification;
(isNotificationAPISupported as jest.Mock).mockReturnValue(false);
test('should render the NotificationPermissionNeverGrantedBar when permission is never granted yet', () => {
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
jest.spyOn(utilsNotifications, 'getNotificationPermission').mockReturnValue(utilsNotifications.NotificationPermissionNeverGranted);

renderWithContext(<NotificationPermissionBar/>, initialState);
const {container} = renderWithContext(<NotificationPermissionBar/>, initialState);

expect(container).toMatchSnapshot();

expect(screen.queryByText('We need your permission to show desktop notifications.')).not.toBeInTheDocument();
expect(screen.queryByText('Enable notifications')).not.toBeInTheDocument();
expect(screen.getByText('We need your permission to show notifications in the browser.')).toBeInTheDocument();
expect(screen.getByText('Enable notifications')).toBeInTheDocument();
});

test('should call requestNotificationPermission and hide the bar when the button is clicked', async () => {
(requestNotificationPermission as jest.Mock).mockResolvedValue('granted');
test('should call requestNotificationPermission and hide the bar when the button is clicked in NotificationPermissionNeverGrantedBar', async () => {
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
jest.spyOn(utilsNotifications, 'getNotificationPermission').mockReturnValue(utilsNotifications.NotificationPermissionNeverGranted);
jest.spyOn(utilsNotifications, 'requestNotificationPermission').mockResolvedValue(utilsNotifications.NotificationPermissionGranted);

renderWithContext(<NotificationPermissionBar/>, initialState);

expect(screen.getByText('We need your permission to show desktop notifications.')).toBeInTheDocument();
expect(screen.getByText('We need your permission to show notifications in the browser.')).toBeInTheDocument();

await waitFor(async () => {
userEvent.click(screen.getByText('Enable notifications'));
});

expect(requestNotificationPermission).toHaveBeenCalled();
expect(screen.queryByText('We need your permission to show desktop notifications.')).not.toBeInTheDocument();
expect(utilsNotifications.requestNotificationPermission).toHaveBeenCalled();
expect(screen.queryByText('We need your permission to show browser notifications.')).not.toBeInTheDocument();
});

test('should not render anything if permission is denied', () => {
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
jest.spyOn(utilsNotifications, 'getNotificationPermission').mockReturnValue('denied');

const {container} = renderWithContext(<NotificationPermissionBar/>, initialState);

expect(container).toBeEmptyDOMElement();
});

test('should not render anything if permission is granted', () => {
jest.spyOn(utilsNotifications, 'isNotificationAPISupported').mockReturnValue(true);
jest.spyOn(utilsNotifications, 'getNotificationPermission').mockReturnValue('granted');

const {container} = renderWithContext(<NotificationPermissionBar/>, initialState);

expect(container).toBeEmptyDOMElement();
});
});
Original file line number Diff line number Diff line change
@@ -1,57 +1,42 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React, {useCallback, useState} from 'react';
import {FormattedMessage} from 'react-intl';
import React from 'react';
import {useSelector} from 'react-redux';

import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';

import AnnouncementBar from 'components/announcement_bar/default_announcement_bar';
import NotificationPermissionNeverGrantedBar from 'components/announcement_bar/notification_permission_bar/notification_permission_never_granted_bar';
import NotificationPermissionUnsupportedBar from 'components/announcement_bar/notification_permission_bar/notification_permission_unsupported_bar';

import {AnnouncementBarTypes} from 'utils/constants';
import {requestNotificationPermission, isNotificationAPISupported} from 'utils/notifications';
import {
isNotificationAPISupported,
NotificationPermissionDenied,
NotificationPermissionNeverGranted,
getNotificationPermission,
} from 'utils/notifications';

export default function NotificationPermissionBar() {
const isLoggedIn = Boolean(useSelector(getCurrentUserId));

const [show, setShow] = useState(isNotificationAPISupported() ? Notification.permission === 'default' : false);
if (!isLoggedIn) {
return null;
}

const handleClick = useCallback(async () => {
await requestNotificationPermission();
setShow(false);
}, []);
// When browser does not support notification API, we show the notification bar to update browser
if (!isNotificationAPISupported()) {
return <NotificationPermissionUnsupportedBar/>;
}

const handleClose = useCallback(() => {
// If the user closes the bar, don't show the notification bar any more for the rest of the session, but
// show it again on app refresh.
setShow(false);
}, []);
// When user has not granted permission, we show the notification bar to request permission
if (getNotificationPermission() === NotificationPermissionNeverGranted) {
return <NotificationPermissionNeverGrantedBar/>;
}

if (!show || !isLoggedIn || !isNotificationAPISupported()) {
// When user has denied permission, we don't show since user explicitly denied permission
if (getNotificationPermission() === NotificationPermissionDenied) {
return null;
}

return (
<AnnouncementBar
showCloseButton={true}
handleClose={handleClose}
type={AnnouncementBarTypes.ANNOUNCEMENT}
message={
<FormattedMessage
id='announcement_bar.notification.needs_permission'
defaultMessage='We need your permission to show desktop notifications.'
/>
}
ctaText={
<FormattedMessage
id='announcement_bar.notification.enable_notifications'
defaultMessage='Enable notifications'
/>
}
showCTA={true}
showLinkAsButton={true}
onButtonClick={handleClick}
/>
);
return null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React, {useCallback, useState} from 'react';
import {FormattedMessage} from 'react-intl';

import AnnouncementBar from 'components/announcement_bar/default_announcement_bar';

import {AnnouncementBarTypes} from 'utils/constants';
import {requestNotificationPermission} from 'utils/notifications';

export default function NotificationPermissionNeverGrantedBar() {
const [show, setShow] = useState(true);

const handleClick = useCallback(async () => {
try {
await requestNotificationPermission();
} catch (error) {
// eslint-disable-next-line no-console
console.error('Error requesting notification permission', error);
} finally {
// Dismiss the bar after user makes a choice
setShow(false);
}
}, []);

const handleClose = useCallback(() => {
// If the user closes the bar, don't show the notification bar any more for the rest of the session, but
// show it again on app refresh.
setShow(false);
}, []);

if (!show) {
return null;
}

return (
<AnnouncementBar
showCloseButton={true}
handleClose={handleClose}
type={AnnouncementBarTypes.ANNOUNCEMENT}
message={
<FormattedMessage
id='announcementBar.notification.permissionNeverGrantedBar.message'
defaultMessage='We need your permission to show notifications in the browser.'
/>
}
ctaText={
<FormattedMessage
id='announcementBar.notification.permissionNeverGrantedBar.cta'
defaultMessage='Enable notifications'
/>
}
showCTA={true}
showLinkAsButton={true}
onButtonClick={handleClick}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React, {useCallback, useState} from 'react';
import {FormattedMessage} from 'react-intl';

import AnnouncementBar from 'components/announcement_bar/default_announcement_bar';

import {AnnouncementBarTypes} from 'utils/constants';

export default function UnsupportedNotificationAnnouncementBar() {
const [show, setShow] = useState(true);

const handleClick = useCallback(async () => {
window.open('https://mattermost.com/pl/pc-web-requirements', '_blank', 'noopener,noreferrer');
}, []);

const handleClose = useCallback(() => {
// If the user closes the bar, don't show the notification bar any more for the rest of the session, but
// show it again on app refresh.
setShow(false);
}, []);

if (!show) {
return null;
}

return (
<AnnouncementBar
showCloseButton={true}
type={AnnouncementBarTypes.ANNOUNCEMENT}
handleClose={handleClose}
message={
<FormattedMessage
id='announcementBar.notification.unsupportedBar.message'
defaultMessage='Your browser does not support browser notifications.'
/>
}
ctaText={
<FormattedMessage
id='announcementBar.notification.unsupportedBar.cta'
defaultMessage='Update your browser'
/>
}
showCTA={true}
showLinkAsButton={true}
onButtonClick={handleClick}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ exports[`components/drafts/panel/panel_body should match snapshot for priority 1
uppercase={true}
>
<div
className="TagWrapper-keYggn hpsCJu Tag Tag--info Tag--xs"
className="TagWrapper-keYggn gnIgwl Tag Tag--info Tag--xs"
>
<AlertCircleOutlineIcon
size={10}
Expand Down
Loading

0 comments on commit 857fd87

Please sign in to comment.