From f694400de64977ece32789890b02ec500a7b26db Mon Sep 17 00:00:00 2001 From: Brian Nguyen Date: Tue, 14 Jul 2026 22:41:51 -0700 Subject: [PATCH 1/5] refactor(perps): migrate Cancel All Orders sheet to MMDS Use design-system BottomSheet, Header, Footer, and Text so the sheet matches MMDS patterns and Confirm loading uses isLoading. Co-authored-by: Cursor --- .../PerpsCancelAllOrdersView.styles.ts | 27 --- .../PerpsCancelAllOrdersView.test.tsx | 158 ++++++++++-------- .../PerpsCancelAllOrdersView.tsx | 113 +++++-------- .../Views/PerpsHomeView/PerpsHomeView.tsx | 3 +- 4 files changed, 137 insertions(+), 164 deletions(-) delete mode 100644 app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.styles.ts diff --git a/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.styles.ts b/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.styles.ts deleted file mode 100644 index 1bd70533361..00000000000 --- a/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.styles.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { StyleSheet } from 'react-native'; -import type { Theme } from '../../../../../util/theme/models'; - -export const createStyles = (_theme: Theme) => - StyleSheet.create({ - contentContainer: { - paddingHorizontal: 16, - paddingVertical: 16, - }, - loadingContainer: { - paddingVertical: 32, - alignItems: 'center', - justifyContent: 'center', - }, - loadingText: { - marginTop: 12, - }, - emptyContainer: { - paddingVertical: 32, - paddingHorizontal: 16, - alignItems: 'center', - justifyContent: 'center', - }, - footerContainer: { - paddingHorizontal: 16, - }, - }); diff --git a/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.test.tsx b/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.test.tsx index 9da2c1efae7..5d11f022572 100644 --- a/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.test.tsx +++ b/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.test.tsx @@ -33,56 +33,96 @@ jest.mock('../../hooks/usePerpsEventTracking', () => ({ usePerpsEventTracking: jest.fn(), })); -jest.mock( - '../../../../../component-library/components/BottomSheets/BottomSheet', - () => { - const mockReact = jest.requireActual('react'); - return mockReact.forwardRef( - (props: { children: React.ReactNode }, _ref) => <>{props.children}, - ); - }, -); - -jest.mock( - '../../../../../component-library/components/BottomSheets/BottomSheetHeader', - () => 'BottomSheetHeader', -); - -jest.mock( - '../../../../../component-library/components/BottomSheets/BottomSheetFooter', - () => { - const { View, TouchableOpacity, Text } = jest.requireActual('react-native'); - - return { - __esModule: true, - default: ({ - buttonPropsArray, +jest.mock('@metamask/design-system-twrnc-preset', () => { + const tw = (..._args: unknown[]) => ({}); + tw.style = jest.fn(() => ({})); + return { useTailwind: () => tw }; +}); + +jest.mock('@metamask/design-system-react-native', () => { + const MockReact = jest.requireActual('react'); + const { View, Pressable, Text: RNText } = jest.requireActual('react-native'); + const actual = jest.requireActual('@metamask/design-system-react-native'); + + const BottomSheet = MockReact.forwardRef( + ( + { + children, }: { - buttonPropsArray?: { - label: string; - onPress: () => void; - disabled?: boolean; - }[]; - }) => ( - - {buttonPropsArray?.map((buttonProps, index) => ( - - {buttonProps.label} - - ))} - - ), - ButtonsAlignment: { - Horizontal: 'Horizontal', - Vertical: 'Vertical', + children: React.ReactNode; }, + _ref: React.Ref<{ + onOpenBottomSheet: () => void; + onCloseBottomSheet: (callback?: () => void) => void; + }>, + ) => {children}, + ); + BottomSheet.displayName = 'BottomSheet'; + + const BottomSheetHeader = ({ + children, + onClose, + }: { + children: React.ReactNode; + onClose?: () => void; + }) => ( + + {typeof children === 'string' ? {children} : children} + + + ); + + const BottomSheetFooter = ({ + primaryButtonProps, + secondaryButtonProps, + }: { + primaryButtonProps?: { + children: React.ReactNode; + onPress: () => void; + isDisabled?: boolean; + isLoading?: boolean; + isDanger?: boolean; }; - }, -); + secondaryButtonProps?: { + children: React.ReactNode; + onPress: () => void; + isDisabled?: boolean; + }; + }) => ( + + {secondaryButtonProps ? ( + + {secondaryButtonProps.children} + + ) : null} + {primaryButtonProps ? ( + + {primaryButtonProps.children} + + ) : null} + + ); + + return { + ...actual, + BottomSheet, + BottomSheetHeader, + BottomSheetFooter, + }; +}); const mockUsePerpsLiveOrders = usePerpsLiveOrders as jest.MockedFunction< typeof usePerpsLiveOrders @@ -166,7 +206,7 @@ describe('PerpsCancelAllOrdersView', () => { expect(getByText('perps.order.no_orders')).toBeTruthy(); }); - it('renders loading state when canceling', () => { + it('keeps description and confirm label visible while canceling', () => { // Arrange mockUsePerpsCancelAllOrders.mockReturnValue({ ...mockCancelAllHook, @@ -174,11 +214,12 @@ describe('PerpsCancelAllOrdersView', () => { }); // Act - const { getAllByText } = render(); + const { getByText, queryByText } = render(); // Assert - const cancelingElements = getAllByText('perps.cancel_all_modal.canceling'); - expect(cancelingElements.length).toBeGreaterThan(0); + expect(getByText('perps.cancel_all_modal.description')).toBeTruthy(); + expect(getByText('perps.cancel_all_modal.confirm')).toBeTruthy(); + expect(queryByText('perps.cancel_all_modal.canceling')).toBeNull(); }); it('displays footer buttons with correct labels', () => { @@ -190,21 +231,6 @@ describe('PerpsCancelAllOrdersView', () => { expect(getByText('perps.cancel_all_modal.confirm')).toBeTruthy(); }); - it('shows canceling label on confirm button when in progress', () => { - // Arrange - mockUsePerpsCancelAllOrders.mockReturnValue({ - ...mockCancelAllHook, - isCanceling: true, - }); - - // Act - const { getAllByText } = render(); - - // Assert - const cancelingElements = getAllByText('perps.cancel_all_modal.canceling'); - expect(cancelingElements.length).toBeGreaterThan(0); - }); - it('renders with empty orders gracefully', () => { // Arrange mockUsePerpsLiveOrders.mockReturnValue({ diff --git a/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.tsx b/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.tsx index acb8b02de87..c5e623bd2ef 100644 --- a/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.tsx +++ b/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.tsx @@ -1,30 +1,25 @@ import { useNavigation } from '@react-navigation/native'; import React, { useCallback, useMemo, useRef } from 'react'; -import { View, ActivityIndicator } from 'react-native'; import { NotificationMoment } from '../../../../../util/haptics'; import { strings } from '../../../../../../locales/i18n'; -import BottomSheet, { - BottomSheetRef, -} from '../../../../../component-library/components/BottomSheets/BottomSheet'; -import BottomSheetHeader from '../../../../../component-library/components/BottomSheets/BottomSheetHeader'; -import BottomSheetFooter, { +import { + BottomSheet, + BottomSheetFooter, + BottomSheetHeader, + type BottomSheetRef, + Box, + ButtonSize, ButtonsAlignment, -} from '../../../../../component-library/components/BottomSheets/BottomSheetFooter'; -import Text, { + Text, TextColor, TextVariant, -} from '../../../../../component-library/components/Texts/Text'; -import { - ButtonSize, - ButtonVariants, -} from '../../../../../component-library/components/Buttons/Button'; +} from '@metamask/design-system-react-native'; import { IconName } from '../../../../../component-library/components/Icons/Icon'; import { ToastVariants } from '../../../../../component-library/components/Toast/Toast.types'; import { usePerpsLiveOrders, usePerpsCancelAllOrders } from '../../hooks'; import usePerpsToasts, { type PerpsToastOptions, } from '../../hooks/usePerpsToasts'; -import { createStyles } from './PerpsCancelAllOrdersView.styles'; import { useTheme } from '../../../../../util/theme'; import { MetaMetricsEvents } from '../../../../../core/Analytics'; import { usePerpsEventTracking } from '../../hooks/usePerpsEventTracking'; @@ -44,7 +39,6 @@ const PerpsCancelAllOrdersView: React.FC = ({ onClose: onExternalClose, }) => { const theme = useTheme(); - const styles = createStyles(theme); const navigation = useNavigation(); const internalSheetRef = useRef(null); const sheetRef = externalSheetRef || internalSheetRef; @@ -190,27 +184,26 @@ const PerpsCancelAllOrdersView: React.FC = ({ } }, [externalSheetRef, handleClose, handleKeepOrders]); - const footerButtons = useMemo( - () => [ - { - label: strings('perps.cancel_all_modal.keep_orders'), - onPress: handleKeepButtonPress, - variant: ButtonVariants.Secondary, - size: ButtonSize.Lg, - disabled: isCanceling, - }, - { - label: isCanceling - ? strings('perps.cancel_all_modal.canceling') - : strings('perps.cancel_all_modal.confirm'), - onPress: handleCancelAll, - variant: ButtonVariants.Primary, - size: ButtonSize.Lg, - disabled: isCanceling, - danger: true, - }, - ], - [handleKeepButtonPress, handleCancelAll, isCanceling], + const secondaryButtonProps = useMemo( + () => ({ + children: strings('perps.cancel_all_modal.keep_orders'), + onPress: handleKeepButtonPress, + size: ButtonSize.Lg, + isDisabled: isCanceling, + }), + [handleKeepButtonPress, isCanceling], + ); + + const primaryButtonProps = useMemo( + () => ({ + children: strings('perps.cancel_all_modal.confirm'), + onPress: handleCancelAll, + size: ButtonSize.Lg, + isDanger: true, + isLoading: isCanceling, + isDisabled: isCanceling, + }), + [handleCancelAll, isCanceling], ); // Show empty state if no orders (WebSocket data loads instantly, no loading state needed) @@ -218,19 +211,17 @@ const PerpsCancelAllOrdersView: React.FC = ({ return ( navigation.goBack() : undefined} onClose={externalSheetRef ? onExternalClose : undefined} > - - {strings('perps.cancel_all_modal.title')} - + {strings('perps.cancel_all_modal.title')} - - + + {strings('perps.order.no_orders')} - + ); } @@ -238,41 +229,23 @@ const PerpsCancelAllOrdersView: React.FC = ({ return ( navigation.goBack() : undefined} onClose={externalSheetRef ? onExternalClose : undefined} > - - {strings('perps.cancel_all_modal.title')} - + {strings('perps.cancel_all_modal.title')} - - {isCanceling ? ( - - - - {strings('perps.cancel_all_modal.canceling')} - - - ) : ( - - {strings('perps.cancel_all_modal.description')} - - )} - + + + {strings('perps.cancel_all_modal.description')} + + ); diff --git a/app/components/UI/Perps/Views/PerpsHomeView/PerpsHomeView.tsx b/app/components/UI/Perps/Views/PerpsHomeView/PerpsHomeView.tsx index ceda24a063e..cb04ace5111 100644 --- a/app/components/UI/Perps/Views/PerpsHomeView/PerpsHomeView.tsx +++ b/app/components/UI/Perps/Views/PerpsHomeView/PerpsHomeView.tsx @@ -15,6 +15,7 @@ import { type RouteProp, } from '@react-navigation/native'; import { + BottomSheetRef as MmdsBottomSheetRef, Button, ButtonVariant, ButtonSize, @@ -209,7 +210,7 @@ const PerpsHomeView = ({ const [showCloseAllSheet, setShowCloseAllSheet] = useState(false); const [showCancelAllSheet, setShowCancelAllSheet] = useState(false); const closeAllSheetRef = useRef(null); - const cancelAllSheetRef = useRef(null); + const cancelAllSheetRef = useRef(null); // Use hook for eligibility checks and action handlers // Pass button location for tracking deposit entry point From 168a299f4ec5b125a43fd90fa1ccea61007ce6c9 Mon Sep 17 00:00:00 2001 From: Brian Nguyen Date: Wed, 15 Jul 2026 09:12:11 -0700 Subject: [PATCH 2/5] fix(perps): match Cancel All Orders footer spacing to MMDS sheets Add pt-6 on BottomSheetFooter so content-to-button gap matches other Perps MMDS bottom sheets. Co-authored-by: Cursor --- .../Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.tsx b/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.tsx index c5e623bd2ef..a360be1e281 100644 --- a/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.tsx +++ b/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.tsx @@ -246,6 +246,7 @@ const PerpsCancelAllOrdersView: React.FC = ({ buttonsAlignment={ButtonsAlignment.Horizontal} secondaryButtonProps={secondaryButtonProps} primaryButtonProps={primaryButtonProps} + twClassName="pt-6" /> ); From 594efdefd6d1f2136e3a32fbe906f00d63d1d73e Mon Sep 17 00:00:00 2001 From: Brian Nguyen Date: Wed, 15 Jul 2026 10:39:05 -0700 Subject: [PATCH 3/5] test(perps): stabilize Cancel All Orders fixtures and cover goBack paths Co-authored-by: Cursor --- .../PerpsCancelAllOrdersView.test.tsx | 111 +++++++++++++++++- 1 file changed, 105 insertions(+), 6 deletions(-) diff --git a/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.test.tsx b/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.test.tsx index 5d11f022572..4fef9761b90 100644 --- a/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.test.tsx +++ b/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.test.tsx @@ -1,11 +1,17 @@ import React from 'react'; -import { render } from '@testing-library/react-native'; +import { fireEvent, render } from '@testing-library/react-native'; import PerpsCancelAllOrdersView from './PerpsCancelAllOrdersView'; import { usePerpsCancelAllOrders, usePerpsLiveOrders } from '../../hooks'; +const mockGoBack = jest.fn(); +const mockCapturedBottomSheetProps: { + goBack?: () => void; + onClose?: () => void; +} = {}; + // Mock all dependencies jest.mock('@react-navigation/native', () => ({ - useNavigation: jest.fn(() => ({ navigate: jest.fn(), goBack: jest.fn() })), + useNavigation: jest.fn(() => ({ navigate: jest.fn(), goBack: mockGoBack })), })); jest.mock('../../../../../../locales/i18n', () => ({ @@ -48,14 +54,22 @@ jest.mock('@metamask/design-system-react-native', () => { ( { children, + goBack, + onClose, }: { children: React.ReactNode; + goBack?: () => void; + onClose?: () => void; }, _ref: React.Ref<{ onOpenBottomSheet: () => void; onCloseBottomSheet: (callback?: () => void) => void; }>, - ) => {children}, + ) => { + mockCapturedBottomSheetProps.goBack = goBack; + mockCapturedBottomSheetProps.onClose = onClose; + return {children}; + }, ); BottomSheet.displayName = 'BottomSheet'; @@ -68,7 +82,7 @@ jest.mock('@metamask/design-system-react-native', () => { }) => ( {typeof children === 'string' ? {children} : children} - + ); @@ -145,7 +159,7 @@ describe('PerpsCancelAllOrdersView', () => { filledSize: '0', remainingSize: '0.1', status: 'open' as const, - timestamp: Date.now(), + timestamp: 1722470400000, }, { orderId: 'order-2', @@ -158,7 +172,7 @@ describe('PerpsCancelAllOrdersView', () => { filledSize: '0', remainingSize: '1.0', status: 'open' as const, - timestamp: Date.now(), + timestamp: 1722470400000, }, ]; @@ -172,6 +186,8 @@ describe('PerpsCancelAllOrdersView', () => { beforeEach(() => { jest.clearAllMocks(); + mockCapturedBottomSheetProps.goBack = undefined; + mockCapturedBottomSheetProps.onClose = undefined; mockUsePerpsLiveOrders.mockReturnValue({ orders: mockOrders, isInitialLoading: false, @@ -248,4 +264,87 @@ describe('PerpsCancelAllOrdersView', () => { // Assert expect(getByText('perps.order.no_orders')).toBeTruthy(); }); + + it('provides goBack to BottomSheet and navigates back when invoked', () => { + // Arrange & Act — with orders + render(); + + // Assert + expect(mockCapturedBottomSheetProps.goBack).toBeDefined(); + expect(mockCapturedBottomSheetProps.onClose).toBeUndefined(); + mockCapturedBottomSheetProps.goBack?.(); + expect(mockGoBack).toHaveBeenCalled(); + + // Arrange & Act — empty state uses the same goBack wiring + mockGoBack.mockClear(); + mockUsePerpsLiveOrders.mockReturnValue({ + orders: [], + isInitialLoading: false, + }); + mockUsePerpsCancelAllOrders.mockReturnValue({ + ...mockCancelAllHook, + orderCount: 0, + }); + render(); + + mockCapturedBottomSheetProps.goBack?.(); + expect(mockGoBack).toHaveBeenCalled(); + }); + + it('omits goBack and wires onClose when used with an external sheetRef', () => { + // Arrange + const mockOnClose = jest.fn(); + const mockOnCloseBottomSheet = jest.fn((callback?: () => void) => + callback?.(), + ); + const mockSheetRef = { + current: { + onOpenBottomSheet: jest.fn(), + onCloseBottomSheet: mockOnCloseBottomSheet, + }, + }; + + // Act — with orders + const { getByTestId, unmount } = render( + , + ); + + // Assert — overlay mode uses onClose instead of navigation goBack + expect(mockCapturedBottomSheetProps.goBack).toBeUndefined(); + expect(mockCapturedBottomSheetProps.onClose).toBe(mockOnClose); + + fireEvent.press(getByTestId('header-close')); + expect(mockOnCloseBottomSheet).toHaveBeenCalled(); + expect(mockOnClose).toHaveBeenCalled(); + expect(mockGoBack).not.toHaveBeenCalled(); + + // Act — empty state overlay path + unmount(); + mockOnClose.mockClear(); + mockOnCloseBottomSheet.mockClear(); + mockUsePerpsLiveOrders.mockReturnValue({ + orders: [], + isInitialLoading: false, + }); + mockUsePerpsCancelAllOrders.mockReturnValue({ + ...mockCancelAllHook, + orderCount: 0, + }); + const { getByTestId: getEmptyTestId } = render( + , + ); + + expect(mockCapturedBottomSheetProps.goBack).toBeUndefined(); + expect(mockCapturedBottomSheetProps.onClose).toBe(mockOnClose); + fireEvent.press(getEmptyTestId('header-close')); + expect(mockOnCloseBottomSheet).toHaveBeenCalled(); + expect(mockOnClose).toHaveBeenCalled(); + expect(mockGoBack).not.toHaveBeenCalled(); + }); }); From cb462678883efe730af79ee0e9b464c4ac972db5 Mon Sep 17 00:00:00 2001 From: Brian Nguyen Date: Wed, 15 Jul 2026 20:15:36 -0700 Subject: [PATCH 4/5] Removed unnecessary mocks --- .../PerpsCancelAllOrdersView.test.tsx | 132 ++++-------------- .../PerpsCancelAllOrdersView.tsx | 10 +- 2 files changed, 32 insertions(+), 110 deletions(-) diff --git a/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.test.tsx b/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.test.tsx index 4fef9761b90..3d854554707 100644 --- a/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.test.tsx +++ b/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.test.tsx @@ -4,10 +4,6 @@ import PerpsCancelAllOrdersView from './PerpsCancelAllOrdersView'; import { usePerpsCancelAllOrders, usePerpsLiveOrders } from '../../hooks'; const mockGoBack = jest.fn(); -const mockCapturedBottomSheetProps: { - goBack?: () => void; - onClose?: () => void; -} = {}; // Mock all dependencies jest.mock('@react-navigation/native', () => ({ @@ -45,96 +41,22 @@ jest.mock('@metamask/design-system-twrnc-preset', () => { return { useTailwind: () => tw }; }); +// BottomSheet overwrites refs via useImperativeHandle; stub keeps external sheetRef usable. jest.mock('@metamask/design-system-react-native', () => { const MockReact = jest.requireActual('react'); - const { View, Pressable, Text: RNText } = jest.requireActual('react-native'); + const { View } = jest.requireActual('react-native'); const actual = jest.requireActual('@metamask/design-system-react-native'); const BottomSheet = MockReact.forwardRef( - ( - { - children, - goBack, - onClose, - }: { - children: React.ReactNode; - goBack?: () => void; - onClose?: () => void; - }, - _ref: React.Ref<{ - onOpenBottomSheet: () => void; - onCloseBottomSheet: (callback?: () => void) => void; - }>, - ) => { - mockCapturedBottomSheetProps.goBack = goBack; - mockCapturedBottomSheetProps.onClose = onClose; - return {children}; - }, + ({ children }: { children: React.ReactNode }, _ref: React.Ref) => ( + {children} + ), ); BottomSheet.displayName = 'BottomSheet'; - const BottomSheetHeader = ({ - children, - onClose, - }: { - children: React.ReactNode; - onClose?: () => void; - }) => ( - - {typeof children === 'string' ? {children} : children} - - - ); - - const BottomSheetFooter = ({ - primaryButtonProps, - secondaryButtonProps, - }: { - primaryButtonProps?: { - children: React.ReactNode; - onPress: () => void; - isDisabled?: boolean; - isLoading?: boolean; - isDanger?: boolean; - }; - secondaryButtonProps?: { - children: React.ReactNode; - onPress: () => void; - isDisabled?: boolean; - }; - }) => ( - - {secondaryButtonProps ? ( - - {secondaryButtonProps.children} - - ) : null} - {primaryButtonProps ? ( - - {primaryButtonProps.children} - - ) : null} - - ); - return { ...actual, BottomSheet, - BottomSheetHeader, - BottomSheetFooter, }; }); @@ -186,8 +108,6 @@ describe('PerpsCancelAllOrdersView', () => { beforeEach(() => { jest.clearAllMocks(); - mockCapturedBottomSheetProps.goBack = undefined; - mockCapturedBottomSheetProps.onClose = undefined; mockUsePerpsLiveOrders.mockReturnValue({ orders: mockOrders, isInitialLoading: false, @@ -200,8 +120,8 @@ describe('PerpsCancelAllOrdersView', () => { const { getByText } = render(); // Assert - expect(getByText('perps.cancel_all_modal.title')).toBeTruthy(); - expect(getByText('perps.cancel_all_modal.description')).toBeTruthy(); + expect(getByText('perps.cancel_all_modal.title')).toBeOnTheScreen(); + expect(getByText('perps.cancel_all_modal.description')).toBeOnTheScreen(); }); it('renders empty state when no orders', () => { @@ -219,7 +139,7 @@ describe('PerpsCancelAllOrdersView', () => { const { getByText } = render(); // Assert - expect(getByText('perps.order.no_orders')).toBeTruthy(); + expect(getByText('perps.order.no_orders')).toBeOnTheScreen(); }); it('keeps description and confirm label visible while canceling', () => { @@ -233,8 +153,8 @@ describe('PerpsCancelAllOrdersView', () => { const { getByText, queryByText } = render(); // Assert - expect(getByText('perps.cancel_all_modal.description')).toBeTruthy(); - expect(getByText('perps.cancel_all_modal.confirm')).toBeTruthy(); + expect(getByText('perps.cancel_all_modal.description')).toBeOnTheScreen(); + expect(getByText('perps.cancel_all_modal.confirm')).toBeOnTheScreen(); expect(queryByText('perps.cancel_all_modal.canceling')).toBeNull(); }); @@ -243,8 +163,8 @@ describe('PerpsCancelAllOrdersView', () => { const { getByText } = render(); // Assert - expect(getByText('perps.cancel_all_modal.keep_orders')).toBeTruthy(); - expect(getByText('perps.cancel_all_modal.confirm')).toBeTruthy(); + expect(getByText('perps.cancel_all_modal.keep_orders')).toBeOnTheScreen(); + expect(getByText('perps.cancel_all_modal.confirm')).toBeOnTheScreen(); }); it('renders with empty orders gracefully', () => { @@ -262,20 +182,19 @@ describe('PerpsCancelAllOrdersView', () => { const { getByText } = render(); // Assert - expect(getByText('perps.order.no_orders')).toBeTruthy(); + expect(getByText('perps.order.no_orders')).toBeOnTheScreen(); }); - it('provides goBack to BottomSheet and navigates back when invoked', () => { + it('navigates back when header close is pressed', () => { // Arrange & Act — with orders - render(); + const { getByTestId, unmount } = render(); // Assert - expect(mockCapturedBottomSheetProps.goBack).toBeDefined(); - expect(mockCapturedBottomSheetProps.onClose).toBeUndefined(); - mockCapturedBottomSheetProps.goBack?.(); + fireEvent.press(getByTestId('header-close')); expect(mockGoBack).toHaveBeenCalled(); - // Arrange & Act — empty state uses the same goBack wiring + // Arrange & Act — empty state uses the same close wiring + unmount(); mockGoBack.mockClear(); mockUsePerpsLiveOrders.mockReturnValue({ orders: [], @@ -285,13 +204,15 @@ describe('PerpsCancelAllOrdersView', () => { ...mockCancelAllHook, orderCount: 0, }); - render(); + const { getByTestId: getEmptyTestId } = render( + , + ); - mockCapturedBottomSheetProps.goBack?.(); + fireEvent.press(getEmptyTestId('header-close')); expect(mockGoBack).toHaveBeenCalled(); }); - it('omits goBack and wires onClose when used with an external sheetRef', () => { + it('closes via sheetRef when used with an external sheetRef', () => { // Arrange const mockOnClose = jest.fn(); const mockOnCloseBottomSheet = jest.fn((callback?: () => void) => @@ -312,10 +233,7 @@ describe('PerpsCancelAllOrdersView', () => { />, ); - // Assert — overlay mode uses onClose instead of navigation goBack - expect(mockCapturedBottomSheetProps.goBack).toBeUndefined(); - expect(mockCapturedBottomSheetProps.onClose).toBe(mockOnClose); - + // Assert — overlay mode closes via sheetRef instead of navigation goBack fireEvent.press(getByTestId('header-close')); expect(mockOnCloseBottomSheet).toHaveBeenCalled(); expect(mockOnClose).toHaveBeenCalled(); @@ -340,8 +258,6 @@ describe('PerpsCancelAllOrdersView', () => { />, ); - expect(mockCapturedBottomSheetProps.goBack).toBeUndefined(); - expect(mockCapturedBottomSheetProps.onClose).toBe(mockOnClose); fireEvent.press(getEmptyTestId('header-close')); expect(mockOnCloseBottomSheet).toHaveBeenCalled(); expect(mockOnClose).toHaveBeenCalled(); diff --git a/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.tsx b/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.tsx index a360be1e281..2379a7a4c79 100644 --- a/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.tsx +++ b/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.tsx @@ -214,7 +214,10 @@ const PerpsCancelAllOrdersView: React.FC = ({ goBack={!externalSheetRef ? () => navigation.goBack() : undefined} onClose={externalSheetRef ? onExternalClose : undefined} > - + {strings('perps.cancel_all_modal.title')} @@ -232,7 +235,10 @@ const PerpsCancelAllOrdersView: React.FC = ({ goBack={!externalSheetRef ? () => navigation.goBack() : undefined} onClose={externalSheetRef ? onExternalClose : undefined} > - + {strings('perps.cancel_all_modal.title')} From ecbab46eb61787952d539fdb925e250db3e84402 Mon Sep 17 00:00:00 2001 From: Brian Nguyen Date: Wed, 15 Jul 2026 20:21:41 -0700 Subject: [PATCH 5/5] Removed more unnecessary mocks --- .../PerpsCancelAllOrdersView.test.tsx | 53 +++---------------- 1 file changed, 8 insertions(+), 45 deletions(-) diff --git a/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.test.tsx b/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.test.tsx index 3d854554707..020eb352194 100644 --- a/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.test.tsx +++ b/app/components/UI/Perps/Views/PerpsCancelAllOrdersView/PerpsCancelAllOrdersView.test.tsx @@ -1,5 +1,6 @@ -import React from 'react'; +import React, { createRef } from 'react'; import { fireEvent, render } from '@testing-library/react-native'; +import type { BottomSheetRef } from '@metamask/design-system-react-native'; import PerpsCancelAllOrdersView from './PerpsCancelAllOrdersView'; import { usePerpsCancelAllOrders, usePerpsLiveOrders } from '../../hooks'; @@ -41,25 +42,6 @@ jest.mock('@metamask/design-system-twrnc-preset', () => { return { useTailwind: () => tw }; }); -// BottomSheet overwrites refs via useImperativeHandle; stub keeps external sheetRef usable. -jest.mock('@metamask/design-system-react-native', () => { - const MockReact = jest.requireActual('react'); - const { View } = jest.requireActual('react-native'); - const actual = jest.requireActual('@metamask/design-system-react-native'); - - const BottomSheet = MockReact.forwardRef( - ({ children }: { children: React.ReactNode }, _ref: React.Ref) => ( - {children} - ), - ); - BottomSheet.displayName = 'BottomSheet'; - - return { - ...actual, - BottomSheet, - }; -}); - const mockUsePerpsLiveOrders = usePerpsLiveOrders as jest.MockedFunction< typeof usePerpsLiveOrders >; @@ -212,37 +194,23 @@ describe('PerpsCancelAllOrdersView', () => { expect(mockGoBack).toHaveBeenCalled(); }); - it('closes via sheetRef when used with an external sheetRef', () => { + it('does not navigate back when closing with an external sheetRef', () => { // Arrange const mockOnClose = jest.fn(); - const mockOnCloseBottomSheet = jest.fn((callback?: () => void) => - callback?.(), - ); - const mockSheetRef = { - current: { - onOpenBottomSheet: jest.fn(), - onCloseBottomSheet: mockOnCloseBottomSheet, - }, - }; + const sheetRef = createRef(); // Act — with orders const { getByTestId, unmount } = render( - , + , ); - // Assert — overlay mode closes via sheetRef instead of navigation goBack + // Assert — overlay mode closes via sheetRef, not navigation.goBack fireEvent.press(getByTestId('header-close')); - expect(mockOnCloseBottomSheet).toHaveBeenCalled(); - expect(mockOnClose).toHaveBeenCalled(); expect(mockGoBack).not.toHaveBeenCalled(); // Act — empty state overlay path unmount(); - mockOnClose.mockClear(); - mockOnCloseBottomSheet.mockClear(); + mockGoBack.mockClear(); mockUsePerpsLiveOrders.mockReturnValue({ orders: [], isInitialLoading: false, @@ -252,15 +220,10 @@ describe('PerpsCancelAllOrdersView', () => { orderCount: 0, }); const { getByTestId: getEmptyTestId } = render( - , + , ); fireEvent.press(getEmptyTestId('header-close')); - expect(mockOnCloseBottomSheet).toHaveBeenCalled(); - expect(mockOnClose).toHaveBeenCalled(); expect(mockGoBack).not.toHaveBeenCalled(); }); });