Repository: MetaMask/metamask-mobile
Pull request #14510: refactor: fix type strictness and improve tests on notifications
Author: login: Prithpal-Sooriya
Labels: name: team-assets
name: release-7.45.0
description: Issue or pull request that will be included in release 7.45.0
Created: 2025-04-08T14:34:29Z | Merged: 2025-04-08T16:04:02Z

## **Description**

This fixes some types and strictness in a notification component that has `@ts-nocheck`.

## **Related issues**

Fixes:

## **Manual testing steps**

N/A this was a cleanup that does not impact UI or behaviour.

## **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**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
- [ ] 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.



Diff:
diff --git a/app/components/UI/Notification/List/index.tsx b/app/components/UI/Notification/List/index.tsx
index f82d766d7f2..e8d181a34bd 100644
--- a/app/components/UI/Notification/List/index.tsx
+++ b/app/components/UI/Notification/List/index.tsx
@@ -103,7 +103,6 @@ export function useNotificationOnClick(
 }
 
 export function NotificationsListItem(props: NotificationsListItemProps) {
-  const { styles } = useStyles();
   const onNotificationClick = useNotificationOnClick(props);
 
   const menuItemState = useMemo(() => {
@@ -123,8 +122,6 @@ export function NotificationsListItem(props: NotificationsListItemProps) {
   return (
     <NotificationMenuItem.Root
       handleOnPress={() => onNotificationClick(props.notification)}
-      styles={styles}
-      simultaneousHandlers={undefined}
       isRead={props.notification.isRead}
       testID={NotificationMenuViewSelectorsIDs.ITEM(props.notification.id)}
     >
diff --git a/app/components/UI/Notification/NotificationMenuItem/Root.test.tsx b/app/components/UI/Notification/NotificationMenuItem/Root.test.tsx
index d897c8b07a8..67be66a405e 100644
--- a/app/components/UI/Notification/NotificationMenuItem/Root.test.tsx
+++ b/app/components/UI/Notification/NotificationMenuItem/Root.test.tsx
@@ -1,102 +1,51 @@
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-nocheck - Notifications team directory
 import React from 'react';
-
-import renderWithProvider from '../../../../util/test/renderWithProvider';
-import { PanGestureHandlerGestureEvent } from 'react-native-gesture-handler';
-import { useSharedValue, runOnJS, withTiming } from 'react-native-reanimated';
-
+import { userEvent } from '@testing-library/react-native';
+import { lightTheme } from '@metamask/design-tokens';
 import NotificationRoot from './Root';
-
-const children = <></>;
-const styles = {
-  wrapper: {},
-  trashIconContainer: {},
-};
-const handleOnPress = jest.fn();
-jest.mock('react-native-reanimated', () => ({
-  ...jest.requireActual('react-native-reanimated/mock'),
-  useSharedValue: jest.fn().mockImplementation((init) => ({
-    value: init,
-    withTiming: jest.fn((toValue, _, callback) => ({ toValue, callback })),
-  })),
-  runOnJS: jest.fn((callback) => callback()),
-}));
+import renderWithProvider from '../../../../util/test/renderWithProvider';
 
 describe('NotificationRoot', () => {
-  const SWIPE_THRESHOLD = -100;
-  const SCREEN_WIDTH = 300; // Assume some screen width
-
-  // Mock callbacks
-  const onDismiss = jest.fn();
-
-  // Shared values setup
-  const transX = useSharedValue(0);
-  const itemHeight = useSharedValue(100); // Assume initial height
-  const paddingVertical = useSharedValue(20); // Assume initial padding
-  const opacity = useSharedValue(1); // Assume full opacity
-  const onActive = (event: PanGestureHandlerGestureEvent) => {
-    const isSwipingLeft = event.translationX > 0;
-
-    if (isSwipingLeft) {
-      transX.value = 0;
-      return;
-    }
-
-    transX.value = event.translationX;
+  const arrange = () => {
+    const mockOnPress = jest.fn();
+    const testID = 'notification-root';
+    const renderComponent = (props = { isRead: false }) =>
+      renderWithProvider(
+        <NotificationRoot
+          handleOnPress={mockOnPress}
+          isRead={props.isRead}
+          testID={testID}
+        >
+          Hello
+        </NotificationRoot>,
+      );
+
+    return {
+      mockOnPress,
+      renderComponent,
+      testID,
+    };
   };
-  const onEnd = () => {
-    const isDismissed = transX.value < SWIPE_THRESHOLD;
-    if (isDismissed) {
-      transX.value = withTiming(-SCREEN_WIDTH);
-      itemHeight.value = withTiming(0);
-      paddingVertical.value = withTiming(0);
-      opacity.value = withTiming(0, undefined, (isFinished: boolean) => {
-        if (isFinished && onDismiss) {
-          runOnJS(onDismiss);
-        }
-      });
-    } else {
-      transX.value = withTiming(0);
-    }
-  };
-
-  it('matches snapshot', () => {
-    const { toJSON } = renderWithProvider(
-      <NotificationRoot
-        handleOnPress={handleOnPress}
-        onDismiss={onDismiss}
-        styles={styles}
-      >
-        {children}
-      </NotificationRoot>,
-    );
-    expect(toJSON()).toMatchSnapshot();
-  });
 
-  it('should dismiss the item if transX.value is less than SWIPE_THRESHOLD', () => {
-    transX.value = -150;
-    onEnd();
-    expect(transX.value).toBe(-SCREEN_WIDTH);
-    expect(itemHeight.value).toBe(0);
-    expect(paddingVertical.value).toBe(0);
-    expect(opacity.value).toBe(0);
-    expect(onDismiss).toHaveBeenCalled();
+  it('renders component with read styles', () => {
+    const { renderComponent, testID } = arrange();
+    const { getByTestId } = renderComponent({ isRead: true });
+    expect(getByTestId(testID)).toHaveStyle({
+      backgroundColor: lightTheme.colors.background.default,
+    });
   });
 
-  it('should set transX.value to 0 if swiping left', () => {
-    const event = {
-      translationX: 10,
-    } as unknown as PanGestureHandlerGestureEvent;
-    onActive(event);
-    expect(transX.value).toBe(0);
+  it('renders component with unread styles', () => {
+    const { renderComponent, testID } = arrange();
+    const { getByTestId } = renderComponent({ isRead: false });
+    expect(getByTestId(testID)).toHaveStyle({
+      backgroundColor: lightTheme.colors.info.muted,
+    });
   });
 
-  it('should set transX.value to event.translationX if not swiping left', () => {
-    const event = {
-      translationX: -10,
-    } as unknown as PanGestureHandlerGestureEvent;
-    onActive(event);
-    expect(transX.value).toBe(-10);
+  it('invokes callback when pressed', async () => {
+    const { renderComponent, testID, mockOnPress } = arrange();
+    const { getByTestId } = renderComponent();
+    await userEvent.press(getByTestId(testID));
+    expect(mockOnPress).toHaveBeenCalled();
   });
 });
diff --git a/app/components/UI/Notification/NotificationMenuItem/Root.tsx b/app/components/UI/Notification/NotificationMenuItem/Root.tsx
index 66bf9022fa0..aca35fc4310 100644
--- a/app/components/UI/Notification/NotificationMenuItem/Root.tsx
+++ b/app/components/UI/Notification/NotificationMenuItem/Root.tsx
@@ -1,16 +1,9 @@
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-nocheck - Notifications team directory
 import React from 'react';
 import { TouchableOpacity } from 'react-native';
-
-import { NotificationListStyles } from '../List/styles';
-
-interface NotificationRootProps
-  extends Pick<PanGestureHandlerProps, 'simultaneousHandlers'> {
+import useStyles from '../List/useStyles';
+interface NotificationRootProps {
   children: React.ReactNode;
-  styles: NotificationListStyles;
   handleOnPress: () => void;
-  onDismiss?: () => void;
   isRead?: boolean;
   testID?: string;
 }
@@ -18,10 +11,11 @@ interface NotificationRootProps
 function NotificationRoot({
   children,
   handleOnPress,
-  styles,
   isRead,
   testID,
 }: NotificationRootProps) {
+  const { styles } = useStyles();
+
   return (
     <TouchableOpacity
       onPress={handleOnPress}
diff --git a/app/components/UI/Notification/NotificationMenuItem/__snapshots__/Root.test.tsx.snap b/app/components/UI/Notification/NotificationMenuItem/__snapshots__/Root.test.tsx.snap
deleted file mode 100644
index 0a3c705f1ec..00000000000
--- a/app/components/UI/Notification/NotificationMenuItem/__snapshots__/Root.test.tsx.snap
+++ /dev/null
@@ -1,13 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`NotificationRoot matches snapshot 1`] = `
-<TouchableOpacity
-  onPress={[MockFunction]}
-  style={
-    [
-      undefined,
-      undefined,
-    ]
-  }
-/>
-`;


Reviews:
user: login: Prithpal-Sooriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T14:35:08Z
user: login: Prithpal-Sooriya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T14:35:52Z
user: login: sahar-fehri
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T14:37:25Z

Review comments:
path: app/components/UI/Notification/NotificationMenuItem/__snapshots__/Root.test.tsx.snap
user: login: Prithpal-Sooriya
body: This snapshot test was useless, removed it in favour of more strict tests.
created_at: 2025-04-08T14:35:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,102 +1,51 @@
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-nocheck - Notifications team directory
 import React from 'react';
-
-import renderWithProvider from '../../../../util/test/renderWithProvider';
-import { PanGestureHandlerGestureEvent } from 'react-native-gesture-handler';
-import { useSharedValue, runOnJS, withTiming } from 'react-native-reanimated';
-
+import { userEvent } from '@testing-library/react-native';
+import { lightTheme } from '@metamask/design-tokens';
 import NotificationRoot from './Root';
-
-const children = <></>;
-const styles = {
-  wrapper: {},
-  trashIconContainer: {},
-};
-const handleOnPress = jest.fn();
-jest.mock('react-native-reanimated', () => ({
-  ...jest.requireActual('react-native-reanimated/mock'),
-  useSharedValue: jest.fn().mockImplementation((init) => ({
-    value: init,
-    withTiming: jest.fn((toValue, _, callback) => ({ toValue, callback })),
-  })),
-  runOnJS: jest.fn((callback) => callback()),
-}));
+import renderWithProvider from '../../../../util/test/renderWithProvider';
 
 describe('NotificationRoot', () => {
-  const SWIPE_THRESHOLD = -100;
-  const SCREEN_WIDTH = 300; // Assume some screen width
-
-  // Mock callbacks
-  const onDismiss = jest.fn();
-
-  // Shared values setup
-  const transX = useSharedValue(0);
path: app/components/UI/Notification/NotificationMenuItem/Root.test.tsx
user: login: Prithpal-Sooriya
body: All of this translation work does not exist anymore so test can be tidied.
created_at: 2025-04-08T14:35:52Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-08T14:34:43Z
body: **CLA Signature Action:** All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-08T14:36:36Z
body: ## [<img alt="https://bitrise.io/" src="https://assets-global.website-files.com/5db35de024bb983af1b4e151/5e6f9ccc3e129dfd8a205e4e_Bitrise%20Logo%20-%20Eggplant%20Bg.png" height="20">](https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/771393c2-027e-4b06-8051-1049e63cbb4c) **Bitrise**

✅✅✅ `pr_smoke_e2e_pipeline` passed on Bitrise! ✅✅✅

Commit hash: 73a9d46b29831892494724bd073bb89f61537aa2
Build link: https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/771393c2-027e-4b06-8051-1049e63cbb4c

>[!NOTE]
>- You can kick off another `pr_smoke_e2e_pipeline` on Bitrise by removing and re-applying the `Run Smoke E2E` label on the pull request

<!-- BITRISE_TAG -->
<!-- BITRISE_SUCCESS_TAG -->

<!-- 73a9d46b29831892494724bd073bb89f61537aa2 -->
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-04-08T14:52:25Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=metamask-mobile&pullRequest=14510) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=metamask-mobile&pullRequest=14510&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=metamask-mobile&pullRequest=14510&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=metamask-mobile&pullRequest=14510&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=metamask-mobile&pullRequest=14510&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=metamask-mobile&pullRequest=14510&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=metamask-mobile&pullRequest=14510)


author_association: NONE