Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ const PerpsEmptyStateMeta = {

export default PerpsEmptyStateMeta;

// Default story
export const Default = {
args: {
onStartTrading: () => {
onAction: () => {
// eslint-disable-next-line no-console
console.log('Start Trading pressed');
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import renderWithProvider from '../../../../../util/test/renderWithProvider';
import { PerpsEmptyState } from './PerpsEmptyState';

describe('PerpsEmptyState', () => {
const mockOnStartTrading = jest.fn();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating tests

const mockOnAction = jest.fn();

beforeEach(() => {
jest.clearAllMocks();
});

it('should render correctly', () => {
const { getByText } = renderWithProvider(
<PerpsEmptyState onStartTrading={mockOnStartTrading} />,
<PerpsEmptyState onAction={mockOnAction} />,
);

expect(
Expand All @@ -21,14 +21,14 @@ describe('PerpsEmptyState', () => {
expect(getByText('Start trading')).toBeTruthy();
});

it('should call onStartTrading when button is pressed', () => {
it('should call onAction when button is pressed', () => {
const { getByText } = renderWithProvider(
<PerpsEmptyState onStartTrading={mockOnStartTrading} />,
<PerpsEmptyState onAction={mockOnAction} />,
);

const startTradingButton = getByText('Start trading');
fireEvent.press(startTradingButton);

expect(mockOnStartTrading).toHaveBeenCalledTimes(1);
expect(mockOnAction).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ import emptyStatePerpsLight from '../../../../../images/empty-state-perps-light.
import emptyStatePerpsDark from '../../../../../images/empty-state-perps-dark.png';

export interface PerpsEmptyStateProps extends TabEmptyStateProps {
onStartTrading: () => void;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing onStartTrading and using the provided onAction prop from TabEmptyState component. Also changing it from a required prop.

testID?: string;
}

export const PerpsEmptyState: React.FC<PerpsEmptyStateProps> = ({
onStartTrading,
testID,
...props
}) => {
Expand All @@ -36,7 +34,6 @@ export const PerpsEmptyState: React.FC<PerpsEmptyStateProps> = ({
}
description={strings('perps.position.list.first_time_description')}
actionButtonText={strings('perps.position.list.start_trading')}
onAction={onStartTrading}
testID={testID}
{...props}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type { Position } from '../../controllers/types';
import { usePerpsLivePositions, usePerpsTPSLUpdate } from '../../hooks';
import { usePerpsLiveAccount } from '../../hooks/stream';
import { formatPnl, formatPrice } from '../../utils/formatUtils';
import { getPositionDirection } from '../../utils/positionCalculations';
import { calculateTotalPnL } from '../../utils/pnlCalculations';
import { createStyles } from './PerpsPositionsView.styles';
import { SafeAreaView } from 'react-native-safe-area-context';
Expand Down Expand Up @@ -125,14 +126,7 @@ const PerpsPositionsView: React.FC = () => {
</Text>
</View>
{positions.map((position, index) => {
const sizeValue = parseFloat(position.size);
const directionSegment = Number.isFinite(sizeValue)
? sizeValue > 0
? 'long'
: sizeValue < 0
? 'short'
: 'unknown'
: 'unknown';
const directionSegment = getPositionDirection(position.size);
Comment on lines -128 to +129
Copy link
Contributor Author

@georgewrmarshall georgewrmarshall Sep 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was getting SonarCloud code quality errors in this PR due to these ternary operators, which were unrelated to my changes. I moved the logic into a getPositionDirection util. This could likely be reused in other places where we get the position, but for now I only updated it here to reduce unrelated changes.

https://sonarcloud.io/project/issues?sinceLeakPeriod=true&issueStatuses=OPEN%2CCONFIRMED&pullRequest=20534&id=metamask-mobile&open=AZmWdBouHRaSraV4LiXl

Screenshot 2025-09-29 at 11 21 26 AM Screenshot 2025-09-29 at 11 21 30 AM

return (
<View
key={`${position.coin}-${index}`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,17 +184,17 @@ jest.mock('../../components/PerpsBottomSheetTooltip', () => ({
// Mock PerpsEmptyState component to avoid Redux context issues while preserving testID
jest.mock('../PerpsEmptyState', () => ({
PerpsEmptyState: ({
onStartTrading,
onAction,
testID,
}: {
onStartTrading: () => void;
onAction?: () => void;
testID?: string;
}) => {
const { TouchableOpacity, Text, View } = jest.requireActual('react-native');
return (
<View testID={testID}>
<Text>Bet on price movements with up to 40x leverage.</Text>
<TouchableOpacity onPress={onStartTrading}>
<TouchableOpacity onPress={onAction}>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating mock

<Text>Start trading</Text>
</TouchableOpacity>
</View>
Expand Down
12 changes: 3 additions & 9 deletions app/components/UI/Perps/Views/PerpsTabView/PerpsTabView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
usePerpsLivePositions,
usePerpsPerformance,
} from '../../hooks';
import { getPositionDirection } from '../../utils/positionCalculations';
import { usePerpsLiveAccount, usePerpsLiveOrders } from '../../hooks/stream';
import { selectPerpsEligibility } from '../../selectors/perpsController';
import styleSheet from './PerpsTabView.styles';
Expand Down Expand Up @@ -208,14 +209,7 @@ const PerpsTabView: React.FC<PerpsTabViewProps> = () => {
</View>
<View>
{positions.map((position, index) => {
const sizeValue = parseFloat(position.size);
const directionSegment = Number.isFinite(sizeValue)
? sizeValue > 0
? 'long'
: sizeValue < 0
? 'short'
: 'unknown'
: 'unknown';
const directionSegment = getPositionDirection(position.size);
return (
<View
key={`${position.coin}-${index}`}
Expand Down Expand Up @@ -246,7 +240,7 @@ const PerpsTabView: React.FC<PerpsTabViewProps> = () => {
<View style={styles.contentContainer}>
{!isInitialLoading && hasNoPositionsOrOrders ? (
<PerpsEmptyState
onStartTrading={handleNewTrade}
onAction={handleNewTrade}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating instance in PerpsTabView

testID="perps-empty-state"
twClassName="mx-auto"
/>
Expand Down
47 changes: 47 additions & 0 deletions app/components/UI/Perps/utils/positionCalculations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
calculateCloseValue,
calculatePercentageFromTokenAmount,
calculatePercentageFromUSDAmount,
getPositionDirection,
} from './positionCalculations';

describe('Position Calculations Utils', () => {
Expand Down Expand Up @@ -193,4 +194,50 @@ describe('Position Calculations Utils', () => {
expect(result).toBe(0);
});
});

describe('getPositionDirection', () => {
it('returns "long" for positive position sizes', () => {
expect(getPositionDirection('10.5')).toBe('long');
expect(getPositionDirection('0.01')).toBe('long');
expect(getPositionDirection('1000')).toBe('long');
});

it('returns "short" for negative position sizes', () => {
expect(getPositionDirection('-10.5')).toBe('short');
expect(getPositionDirection('-0.01')).toBe('short');
expect(getPositionDirection('-1000')).toBe('short');
});

it('returns "unknown" for zero position size', () => {
expect(getPositionDirection('0')).toBe('unknown');
expect(getPositionDirection('0.0')).toBe('unknown');
expect(getPositionDirection('-0')).toBe('unknown');
});

it('returns "unknown" for invalid strings', () => {
expect(getPositionDirection('abc')).toBe('unknown');
expect(getPositionDirection('not a number')).toBe('unknown');
expect(getPositionDirection('')).toBe('unknown');
expect(getPositionDirection(' ')).toBe('unknown');
});

it('returns "unknown" for non-finite values', () => {
expect(getPositionDirection('Infinity')).toBe('unknown');
expect(getPositionDirection('-Infinity')).toBe('unknown');
expect(getPositionDirection('NaN')).toBe('unknown');
});

it('handles edge cases correctly', () => {
expect(getPositionDirection('1e-10')).toBe('long'); // Very small positive
expect(getPositionDirection('-1e-10')).toBe('short'); // Very small negative
expect(getPositionDirection('1.23e15')).toBe('long'); // Large positive
expect(getPositionDirection('-1.23e15')).toBe('short'); // Large negative
});

it('handles strings with whitespace', () => {
expect(getPositionDirection(' 10.5 ')).toBe('long');
expect(getPositionDirection(' -10.5 ')).toBe('short');
expect(getPositionDirection(' 0 ')).toBe('unknown');
});
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding tests

});
25 changes: 25 additions & 0 deletions app/components/UI/Perps/utils/positionCalculations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,28 @@ export function calculatePercentageFromUSDAmount(
const percentage = (usdAmount / totalPositionValue) * 100;
return Math.max(0, Math.min(100, percentage));
}

/**
* Helper function to determine position direction based on size value
* @param sizeString - The position size as a string
* @returns 'long', 'short', or 'unknown'
*/
export function getPositionDirection(
sizeString: string,
): 'long' | 'short' | 'unknown' {
const sizeValue = parseFloat(sizeString);

if (!Number.isFinite(sizeValue)) {
return 'unknown';
}

if (sizeValue > 0) {
return 'long';
}

if (sizeValue < 0) {
return 'short';
}

return 'unknown';
}
Loading