Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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', () => ({
Expand Down Expand Up @@ -33,56 +39,104 @@ jest.mock('../../hooks/usePerpsEventTracking', () => ({
usePerpsEventTracking: jest.fn(),
}));

jest.mock(
'../../../../../component-library/components/BottomSheets/BottomSheet',
() => {
const mockReact = jest.requireActual<typeof React>('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<typeof React>('react');
const { View, Pressable, Text: RNText } = jest.requireActual('react-native');
const actual = jest.requireActual('@metamask/design-system-react-native');

const BottomSheet = MockReact.forwardRef(
(
{
children,
goBack,
Comment thread
brianacnguyen marked this conversation as resolved.
Outdated
onClose,
}: {
buttonPropsArray?: {
label: string;
onPress: () => void;
disabled?: boolean;
}[];
}) => (
<View>
{buttonPropsArray?.map((buttonProps, index) => (
<TouchableOpacity
key={index}
onPress={buttonProps.onPress}
disabled={buttonProps.disabled}
>
<Text>{buttonProps.label}</Text>
</TouchableOpacity>
))}
</View>
),
ButtonsAlignment: {
Horizontal: 'Horizontal',
Vertical: 'Vertical',
children: React.ReactNode;
goBack?: () => void;
onClose?: () => void;
},
_ref: React.Ref<{
onOpenBottomSheet: () => void;
onCloseBottomSheet: (callback?: () => void) => void;
}>,
) => {
mockCapturedBottomSheetProps.goBack = goBack;
mockCapturedBottomSheetProps.onClose = onClose;
return <View>{children}</View>;
},
);
BottomSheet.displayName = 'BottomSheet';

const BottomSheetHeader = ({
children,
onClose,
}: {
children: React.ReactNode;
onClose?: () => void;
}) => (
<View>
{typeof children === 'string' ? <RNText>{children}</RNText> : children}
<Pressable testID="header-close" onPress={onClose} />
</View>
);

const BottomSheetFooter = ({
primaryButtonProps,
secondaryButtonProps,
}: {
primaryButtonProps?: {
children: React.ReactNode;
onPress: () => void;
isDisabled?: boolean;
isLoading?: boolean;
isDanger?: boolean;
};
secondaryButtonProps?: {
children: React.ReactNode;
onPress: () => void;
isDisabled?: boolean;
};
},
);
}) => (
<View>
{secondaryButtonProps ? (
<Pressable
onPress={secondaryButtonProps.onPress}
disabled={secondaryButtonProps.isDisabled}
>
<RNText>{secondaryButtonProps.children}</RNText>
</Pressable>
) : null}
{primaryButtonProps ? (
<Pressable
onPress={primaryButtonProps.onPress}
disabled={
primaryButtonProps.isDisabled || primaryButtonProps.isLoading
}
accessibilityState={{
disabled:
primaryButtonProps.isDisabled || primaryButtonProps.isLoading,
busy: primaryButtonProps.isLoading,
}}
>
<RNText>{primaryButtonProps.children}</RNText>
</Pressable>
) : null}
</View>
);

return {
...actual,
BottomSheet,
BottomSheetHeader,
BottomSheetFooter,
};
});

const mockUsePerpsLiveOrders = usePerpsLiveOrders as jest.MockedFunction<
typeof usePerpsLiveOrders
Expand All @@ -105,7 +159,7 @@ describe('PerpsCancelAllOrdersView', () => {
filledSize: '0',
remainingSize: '0.1',
status: 'open' as const,
timestamp: Date.now(),
timestamp: 1722470400000,
},
{
orderId: 'order-2',
Expand All @@ -118,7 +172,7 @@ describe('PerpsCancelAllOrdersView', () => {
filledSize: '0',
remainingSize: '1.0',
status: 'open' as const,
timestamp: Date.now(),
timestamp: 1722470400000,
},
];

Expand All @@ -132,6 +186,8 @@ describe('PerpsCancelAllOrdersView', () => {

beforeEach(() => {
jest.clearAllMocks();
mockCapturedBottomSheetProps.goBack = undefined;
mockCapturedBottomSheetProps.onClose = undefined;
mockUsePerpsLiveOrders.mockReturnValue({
orders: mockOrders,
isInitialLoading: false,
Expand Down Expand Up @@ -166,19 +222,20 @@ 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,
isCanceling: true,
});

// Act
const { getAllByText } = render(<PerpsCancelAllOrdersView />);
const { getByText, queryByText } = render(<PerpsCancelAllOrdersView />);

// 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', () => {
Expand All @@ -190,23 +247,36 @@ describe('PerpsCancelAllOrdersView', () => {
expect(getByText('perps.cancel_all_modal.confirm')).toBeTruthy();
});

it('shows canceling label on confirm button when in progress', () => {
it('renders with empty orders gracefully', () => {
// Arrange
mockUsePerpsLiveOrders.mockReturnValue({
orders: [],
isInitialLoading: false,
});
mockUsePerpsCancelAllOrders.mockReturnValue({
...mockCancelAllHook,
isCanceling: true,
orderCount: 0,
});

// Act
const { getAllByText } = render(<PerpsCancelAllOrdersView />);
const { getByText } = render(<PerpsCancelAllOrdersView />);

// Assert
const cancelingElements = getAllByText('perps.cancel_all_modal.canceling');
expect(cancelingElements.length).toBeGreaterThan(0);
expect(getByText('perps.order.no_orders')).toBeTruthy();
});

it('renders with empty orders gracefully', () => {
// Arrange
it('provides goBack to BottomSheet and navigates back when invoked', () => {
// Arrange & Act — with orders
render(<PerpsCancelAllOrdersView />);

// 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,
Expand All @@ -215,11 +285,66 @@ describe('PerpsCancelAllOrdersView', () => {
...mockCancelAllHook,
orderCount: 0,
});
render(<PerpsCancelAllOrdersView />);

// Act
const { getByText } = render(<PerpsCancelAllOrdersView />);
mockCapturedBottomSheetProps.goBack?.();
expect(mockGoBack).toHaveBeenCalled();
});

// Assert
expect(getByText('perps.order.no_orders')).toBeTruthy();
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(
<PerpsCancelAllOrdersView
sheetRef={mockSheetRef}
onClose={mockOnClose}
/>,
);

// 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(
<PerpsCancelAllOrdersView
sheetRef={mockSheetRef}
onClose={mockOnClose}
/>,
);

expect(mockCapturedBottomSheetProps.goBack).toBeUndefined();
expect(mockCapturedBottomSheetProps.onClose).toBe(mockOnClose);
fireEvent.press(getEmptyTestId('header-close'));
expect(mockOnCloseBottomSheet).toHaveBeenCalled();
expect(mockOnClose).toHaveBeenCalled();
expect(mockGoBack).not.toHaveBeenCalled();
});
});
Loading
Loading