Skip to content

Commit

Permalink
fix: refactor notifications unit tests (#11431)
Browse files Browse the repository at this point in the history
<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->

## **Description**

This PR addresses all comments from [this
PR](#11250) related to
unit tests best practices.

## **Related issues**

Fixes: #11250

## **Manual testing steps**

1. Go to this page...
2.
3.

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->

## **Pre-merge author checklist**

- [x] I’ve followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [x] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [x] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.

---------

Co-authored-by: Nico MASSART <[email protected]>
  • Loading branch information
Jonathansoufer and NicolasMassart authored Oct 14, 2024
1 parent 853cf92 commit 7bc75e9
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 357 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`NotificationsSettings should render correctly 1`] = `
exports[`NotificationsSettings render matches snapshot 1`] = `
<RCTScrollView
style={
{
Expand Down
150 changes: 3 additions & 147 deletions app/components/Views/Settings/NotificationsSettings/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import React, { useCallback } from 'react';
import { renderHook, act } from '@testing-library/react-hooks';

import React from 'react';
import renderWithProvider from '../../../../util/test/renderWithProvider';
import NotificationsService from '../../../../util/notifications/services/NotificationService';
import { backgroundState } from '../../../../util/test/initial-root-state';
import NotificationsSettings from '.';

import Routes from '../../../../constants/navigation/Routes';
import { Props } from './NotificationsSettings.types';
import { MOCK_ACCOUNTS_CONTROLLER_STATE } from '../../../../util/test/accountsControllerTestUtils';
import { MetaMetricsEvents } from '../../../../core/Analytics/MetaMetrics.events';
import { NavigationProp, ParamListBase } from '@react-navigation/native';

// Mock store.getState

let mockGetState: jest.Mock;
jest.mock('../../../../store', () => {
mockGetState = jest.fn();
Expand Down Expand Up @@ -57,130 +50,10 @@ jest.mock('../../../../util/notifications/services/NotificationService', () => (
getAllPermissions: jest.fn(),
}));

jest.mock('../../../../core/Analytics/MetaMetrics.events', () => ({
MetaMetricsEvents: {
NOTIFICATIONS_SETTINGS_UPDATED: 'NOTIFICATIONS_SETTINGS_UPDATED',
},
}));

const mockDisableNotifications = jest.fn();
const mockEnableNotifications = jest.fn();
const mockSetUiNotificationStatus = jest.fn();
const mockTrackEvent = jest.fn();

const mockNavigation = {
navigate: jest.fn(),
} as unknown as NavigationProp<ParamListBase>;

const setOptions = jest.fn();

describe('toggleNotificationsEnabled', () => {
beforeEach(() => {
jest.clearAllMocks();
});

const setup = (basicFunctionalityEnabled: boolean, isMetamaskNotificationsEnabled: boolean, isProfileSyncingEnabled: boolean) => renderHook(() =>
useCallback(async () => {
if (!basicFunctionalityEnabled) {
mockNavigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, {
screen: Routes.SHEET.BASIC_FUNCTIONALITY,
params: {
caller: Routes.SETTINGS.NOTIFICATIONS,
},
});
} else if (isMetamaskNotificationsEnabled) {
mockDisableNotifications();
mockSetUiNotificationStatus(false);
} else {
const { permission } = await NotificationsService.getAllPermissions(false);
if (permission !== 'authorized') {
return;
}

mockEnableNotifications();
mockSetUiNotificationStatus(true);
}
mockTrackEvent(MetaMetricsEvents.NOTIFICATIONS_SETTINGS_UPDATED, {
settings_type: 'notifications',
old_value: isMetamaskNotificationsEnabled,
new_value: !isMetamaskNotificationsEnabled,
was_profile_syncing_on: isMetamaskNotificationsEnabled ? true : isProfileSyncingEnabled,
});
}, [])
);

it('should navigate to basic functionality screen if basicFunctionalityEnabled is false', async () => {
const { result } = setup(false, false, false);

await act(async () => {
await result.current();
});

expect(mockNavigation.navigate).toHaveBeenCalledWith(Routes.MODAL.ROOT_MODAL_FLOW, {
screen: Routes.SHEET.BASIC_FUNCTIONALITY,
params: {
caller: Routes.SETTINGS.NOTIFICATIONS,
},
});
});

it('should disable notifications if isMetamaskNotificationsEnabled is true', async () => {
const { result } = setup(true, true, false);

await act(async () => {
await result.current();
});

expect(mockDisableNotifications).toHaveBeenCalled();
expect(mockSetUiNotificationStatus).toHaveBeenCalledWith(false);
});

it('should enable notifications if isMetamaskNotificationsEnabled is false and permission is authorized', async () => {
(NotificationsService.getAllPermissions as jest.Mock).mockResolvedValue({ permission: 'authorized' });

const { result } = setup(true, false, false);

await act(async () => {
await result.current();
});

expect(mockEnableNotifications).toHaveBeenCalled();
expect(mockSetUiNotificationStatus).toHaveBeenCalledWith(true);
});

it('should not enable notifications if permission is not authorized', async () => {
(NotificationsService.getAllPermissions as jest.Mock).mockResolvedValue({ permission: 'denied' });

const { result } = setup(true, false, false);

await act(async () => {
await result.current();
});

expect(mockEnableNotifications).not.toHaveBeenCalled();
expect(mockSetUiNotificationStatus).not.toHaveBeenCalled();
});

it('should track event when notifications settings are updated', async () => {
(NotificationsService.getAllPermissions as jest.Mock).mockResolvedValue({ permission: 'authorized' });

const { result } = setup(true, false, true);

await act(async () => {
await result.current();
});

expect(mockTrackEvent).toHaveBeenCalledWith(MetaMetricsEvents.NOTIFICATIONS_SETTINGS_UPDATED, {
settings_type: 'notifications',
old_value: false,
new_value: true,
was_profile_syncing_on: true,
});
});
});

describe('NotificationsSettings', () => {
it('should render correctly', () => {
it('render matches snapshot', () => {
mockGetState.mockImplementation(() => ({
notifications: {},
}));
Expand All @@ -199,21 +72,4 @@ describe('NotificationsSettings', () => {
);
expect(toJSON()).toMatchSnapshot();
});

it('should toggle notifications and handle permission correctly', async () => {
const isMetamaskNotificationsEnabled = true;
const basicFunctionalityEnabled = true;
const isProfileSyncingEnabled = true;

const toggleNotificationsEnabledImpl = jest.fn(() => Promise.resolve({
isMetamaskNotificationsEnabled,
basicFunctionalityEnabled,
isProfileSyncingEnabled,
}));

await toggleNotificationsEnabledImpl();

expect(NotificationsService.getAllPermissions).toHaveBeenCalledTimes(1);
expect(NotificationsService.getAllPermissions).toHaveBeenCalledWith(false);
});
});
42 changes: 8 additions & 34 deletions app/components/Views/Settings/NotificationsSettings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import {
selectIsProfileSyncingEnabled,
} from '../../../../selectors/notifications';

import NotificationsService from '../../../../util/notifications/services/NotificationService';
import Routes from '../../../../constants/navigation/Routes';

import ButtonIcon, {
Expand All @@ -57,6 +56,7 @@ import AppConstants from '../../../../core/AppConstants';
import notificationsRows from './notificationsRows';
import { IconName } from '../../../../component-library/components/Icons/Icon';
import { MetaMetricsEvents } from '../../../../core/Analytics/MetaMetrics.events';
import { useToggleNotifications } from './useToggleNotifications';

interface MainNotificationSettingsProps extends Props {
toggleNotificationsEnabled: () => void;
Expand Down Expand Up @@ -109,6 +109,7 @@ const NotificationsSettings = ({ navigation, route }: Props) => {
const { accounts } = useAccounts();
const { trackEvent } = useMetrics();
const theme = useTheme();

const isMetamaskNotificationsEnabled = useSelector(
selectIsMetamaskNotificationsEnabled,
);
Expand Down Expand Up @@ -177,42 +178,15 @@ const NotificationsSettings = ({ navigation, route }: Props) => {
* it will request the push notifications permission and enable the notifications
* if the permission is granted.
*/
const toggleNotificationsEnabled = useCallback(async () => {
if (!basicFunctionalityEnabled) {
navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, {
screen: Routes.SHEET.BASIC_FUNCTIONALITY,
params: {
caller: Routes.SETTINGS.NOTIFICATIONS,
},
});
} else if (isMetamaskNotificationsEnabled) {
disableNotifications();
setUiNotificationStatus(false);
} else {
const { permission } = await NotificationsService.getAllPermissions(false);
if (permission !== 'authorized') {
return;
}
enableNotifications();
setUiNotificationStatus(true);
}
trackEvent(MetaMetricsEvents.NOTIFICATIONS_SETTINGS_UPDATED, {
settings_type: 'notifications',
old_value: isMetamaskNotificationsEnabled,
new_value: !isMetamaskNotificationsEnabled,
was_profile_syncing_on: isMetamaskNotificationsEnabled
? true
: isProfileSyncingEnabled,
});
}, [
const { toggleNotificationsEnabled } = useToggleNotifications({
navigation,
basicFunctionalityEnabled,
disableNotifications,
enableNotifications,
isMetamaskNotificationsEnabled,
navigation,
trackEvent,
isProfileSyncingEnabled,
]);
disableNotifications,
enableNotifications,
setUiNotificationStatus,
});

const toggleCustomNotificationsEnabled = useCallback(async () => {
setPlatformAnnouncementsState(!platformAnnouncementsState);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { useToggleNotifications } from './useToggleNotifications';
import NotificationsService from '../../../../util/notifications/services/NotificationService';
import Routes from '../../../../constants/navigation/Routes';
import { NavigationProp, ParamListBase } from '@react-navigation/native';

jest.mock(
'../../../../util/notifications/services/NotificationService',
() => ({
getAllPermissions: jest.fn(),
}),
);

const mockNavigation = {
navigate: jest.fn(),
} as unknown as NavigationProp<ParamListBase>;

const mockDisableNotifications = jest.fn();
const mockEnableNotifications = jest.fn();
const mockSetUiNotificationStatus = jest.fn();

describe('useToggleNotifications', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('navigates to basic functionality screen if basic functionality is disabled', async () => {
const { result } = renderHook(() =>
useToggleNotifications({
navigation: mockNavigation,
basicFunctionalityEnabled: false,
isMetamaskNotificationsEnabled: false,
isProfileSyncingEnabled: false,
disableNotifications: mockDisableNotifications,
enableNotifications: mockEnableNotifications,
setUiNotificationStatus: mockSetUiNotificationStatus,
}),
);

await act(async () => {
await result.current.toggleNotificationsEnabled();
});

expect(mockNavigation.navigate).toHaveBeenCalledWith(
Routes.MODAL.ROOT_MODAL_FLOW,
{
screen: Routes.SHEET.BASIC_FUNCTIONALITY,
params: {
caller: Routes.SETTINGS.NOTIFICATIONS,
},
},
);
});

it('switches notifications OFF if notifications previously enabled', async () => {
const { result } = renderHook(() =>
useToggleNotifications({
navigation: mockNavigation,
basicFunctionalityEnabled: true,
isMetamaskNotificationsEnabled: true,
isProfileSyncingEnabled: false,
disableNotifications: mockDisableNotifications,
enableNotifications: mockEnableNotifications,
setUiNotificationStatus: mockSetUiNotificationStatus,
}),
);

await act(async () => {
await result.current.toggleNotificationsEnabled();
});

expect(mockDisableNotifications).toHaveBeenCalled();
expect(mockSetUiNotificationStatus).toHaveBeenCalledWith(false);
});

it('switches notifications ON if notifications previously disabled and permission is authorized', async () => {
(NotificationsService.getAllPermissions as jest.Mock).mockResolvedValue({
permission: 'authorized',
});

const { result } = renderHook(() =>
useToggleNotifications({
navigation: mockNavigation,
basicFunctionalityEnabled: true,
isMetamaskNotificationsEnabled: false,
isProfileSyncingEnabled: false,
disableNotifications: mockDisableNotifications,
enableNotifications: mockEnableNotifications,
setUiNotificationStatus: mockSetUiNotificationStatus,
}),
);

await act(async () => {
await result.current.toggleNotificationsEnabled();
});

expect(mockEnableNotifications).toHaveBeenCalled();
expect(mockSetUiNotificationStatus).toHaveBeenCalledWith(true);
});

it('switches notifications OFF if device permission is not authorized', async () => {
(NotificationsService.getAllPermissions as jest.Mock).mockResolvedValue({
permission: 'denied',
});

const { result } = renderHook(() =>
useToggleNotifications({
navigation: mockNavigation,
basicFunctionalityEnabled: true,
isMetamaskNotificationsEnabled: false,
isProfileSyncingEnabled: false,
disableNotifications: mockDisableNotifications,
enableNotifications: mockEnableNotifications,
setUiNotificationStatus: mockSetUiNotificationStatus,
}),
);

await act(async () => {
await result.current.toggleNotificationsEnabled();
});

expect(mockEnableNotifications).not.toHaveBeenCalled();
expect(mockSetUiNotificationStatus).not.toHaveBeenCalled();
});
});
Loading

0 comments on commit 7bc75e9

Please sign in to comment.