Skip to content
Open
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 @@ -470,7 +470,7 @@ describe('TraderPositionView', () => {
renderWithProvider(<TraderPositionView />, { state: mockState });

expect(screen.getByText('10x')).toBeOnTheScreen();
expect(screen.getByText('SHORT')).toBeOnTheScreen();
expect(screen.getByText('Short')).toBeOnTheScreen();
});

it('does not render the copy token address button for a perp position', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,19 @@ const TabButton: React.FC<TabButtonProps> = ({
testID,
}) => (
<TouchableOpacity onPress={onPress} testID={testID}>
<Box twClassName={`pb-2 ${isActive ? 'border-b-2 border-default' : ''}`}>
<Box twClassName="gap-2">
<Text
variant={TextVariant.BodyMd}
fontWeight={FontWeight.Medium}
color={isActive ? TextColor.TextDefault : TextColor.TextAlternative}
>
{label}
</Text>
<Box
twClassName={`h-0.5 w-full ${
isActive ? 'bg-icon-default' : 'bg-transparent'
}`}
/>
</Box>
</TouchableOpacity>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,46 +82,37 @@ describe('PositionRow', () => {
expect(screen.getByText('$2,259.96')).toBeOnTheScreen();
});

it('renders an up triangle with the unsigned percent on the bottom-right (no absolute PnL)', () => {
it('renders a signed positive percent on the bottom-right (no absolute PnL)', () => {
renderWithProvider(<PositionRow position={basePosition} />);
// Direction lives in the caret, so the percent is unsigned.
expect(screen.getByText('▲')).toBeOnTheScreen();
expect(screen.getByText('182.00%')).toBeOnTheScreen();
expect(screen.queryByText('+182.00%')).toBeNull();
expect(screen.getByText('+182.00%')).toBeOnTheScreen();
expect(screen.queryByText('+$1,059.96 (+182%)')).toBeNull();
expect(screen.queryByText('+$1,059.96')).toBeNull();
});

it('colors the percent to match the up triangle for a winning position', () => {
it('colors the percent for a winning position', () => {
renderWithProvider(<PositionRow position={basePosition} />);
const triangleColor = colorOf(screen.getByText('▲'));
const percentColor = colorOf(screen.getByText('182.00%'));
const percentColor = colorOf(screen.getByText('+182.00%'));
expect(percentColor).toBeDefined();
expect(percentColor).toBe(triangleColor);
});

it('renders a down triangle with the unsigned percent for a losing open position', () => {
it('renders a signed negative percent for a losing open position', () => {
const position = {
...basePosition,
pnlValueUsd: -250,
pnlPercent: -25,
};

renderWithProvider(<PositionRow position={position} />);
expect(screen.getByText('▼')).toBeOnTheScreen();
expect(screen.getByText('25.00%')).toBeOnTheScreen();
expect(screen.queryByText('-25.00%')).toBeNull();
expect(screen.getByText('-25.00%')).toBeOnTheScreen();
expect(screen.queryByText('-$250.00 (-25%)')).toBeNull();
});

it('colors the percent to match the down triangle for a losing position', () => {
it('colors the percent for a losing position', () => {
const position = { ...basePosition, pnlValueUsd: -250, pnlPercent: -25 };

renderWithProvider(<PositionRow position={position} />);
const triangleColor = colorOf(screen.getByText('▼'));
const percentColor = colorOf(screen.getByText('25.00%'));
const percentColor = colorOf(screen.getByText('-25.00%'));
expect(percentColor).toBeDefined();
expect(percentColor).toBe(triangleColor);
});

it('renders the percent even when pnlValueUsd is missing', () => {
Expand All @@ -131,7 +122,7 @@ describe('PositionRow', () => {
} as unknown as Position;

renderWithProvider(<PositionRow position={position} />);
expect(screen.getByText('182.00%')).toBeOnTheScreen();
expect(screen.getByText('+182.00%')).toBeOnTheScreen();
});

it('renders dash when pnlPercent is null', () => {
Expand All @@ -157,18 +148,15 @@ describe('PositionRow', () => {
expect(dashes.length).toBeGreaterThanOrEqual(1);
});

it('renders a neutral minus and unsigned 0% when unrealized PnL is zero', () => {
it('renders +0% with neutral styling when unrealized PnL is zero', () => {
const position = {
...basePosition,
pnlValueUsd: 0,
pnlPercent: 0,
};

renderWithProvider(<PositionRow position={position} />);
// U+2212 minus glyph (not a triangle) for break-even, then unsigned 0%.
expect(screen.getByText('−')).toBeOnTheScreen();
expect(screen.getByText('0.00%')).toBeOnTheScreen();
expect(screen.queryByText('+0.00%')).toBeNull();
expect(screen.getByText('+0.00%')).toBeOnTheScreen();
expect(screen.queryByText('$0.00 (+0%)')).toBeNull();
});

Expand Down Expand Up @@ -262,12 +250,11 @@ describe('PositionRow', () => {
expect(screen.getByText('Apr 15 at 2:00 pm')).toBeOnTheScreen();
});

it('renders realized PnL percent as unsigned magnitude (sign comes from caret)', () => {
it('renders realized PnL percent with a signed value', () => {
renderWithProvider(<PositionRow position={closedPosition} />);

// realizedPnl (300) / boughtUsd (1200) * 100 = 25%
expect(screen.getByText('25.00%')).toBeOnTheScreen();
expect(screen.queryByText('+25%')).toBeNull();
expect(screen.getByText('+25.00%')).toBeOnTheScreen();
});

it('renders dash for PnL when boughtUsd is zero', () => {
Expand All @@ -278,33 +265,30 @@ describe('PositionRow', () => {
expect(screen.getByText('—')).toBeOnTheScreen();
});

it('renders negative realized PnL percent as unsigned magnitude', () => {
it('renders negative realized PnL percent with a signed value', () => {
const position = { ...closedPosition, realizedPnl: -300 };

renderWithProvider(<PositionRow position={position} />);

// -300 / 1200 * 100 = -25%
expect(screen.getByText('25.00%')).toBeOnTheScreen();
expect(screen.queryByText('-25.00%')).toBeNull();
expect(screen.getByText('-25.00%')).toBeOnTheScreen();
});

it('uses realized PnL percent even when pnlPercent is 0', () => {
const position = { ...closedPosition, pnlPercent: 0 };

renderWithProvider(<PositionRow position={position} />);

expect(screen.getByText('25.00%')).toBeOnTheScreen();
expect(screen.getByText('+25.00%')).toBeOnTheScreen();
});

it('renders break-even realized PnL with a neutral minus and 0% percent', () => {
it('renders break-even realized PnL with +0% percent', () => {
const position = { ...closedPosition, realizedPnl: 0, boughtUsd: 1200 };

renderWithProvider(<PositionRow position={position} />);

expect(screen.getByText('$0.00')).toBeOnTheScreen();
// U+2212 minus glyph, not a hyphen
expect(screen.getByText('−')).toBeOnTheScreen();
expect(screen.getByText('0.00%')).toBeOnTheScreen();
expect(screen.getByText('+0.00%')).toBeOnTheScreen();
});

it('renders realized PnL value when boughtUsd is zero (percent null)', () => {
Expand Down Expand Up @@ -339,19 +323,19 @@ describe('PositionRow', () => {
positionAmountWithLeverage: 25,
};

it('renders the leverage and LONG direction badges for a long perp', () => {
it('renders the leverage and Long direction badges for a long perp', () => {
renderWithProvider(<PositionRow position={perpPosition} />);

expect(screen.getByText('5x')).toBeOnTheScreen();
expect(screen.getByText('LONG')).toBeOnTheScreen();
expect(screen.getByText('Long')).toBeOnTheScreen();
});

it('renders a SHORT badge for a short perp', () => {
it('renders a Short badge for a short perp', () => {
const position = { ...perpPosition, perpPositionType: 'short' as const };

renderWithProvider(<PositionRow position={position} />);

expect(screen.getByText('SHORT')).toBeOnTheScreen();
expect(screen.getByText('Short')).toBeOnTheScreen();
});

it('omits the leverage badge when perpLeverage is null', () => {
Expand All @@ -360,14 +344,14 @@ describe('PositionRow', () => {
renderWithProvider(<PositionRow position={position} />);

expect(screen.queryByText('5x')).not.toBeOnTheScreen();
expect(screen.getByText('LONG')).toBeOnTheScreen();
expect(screen.getByText('Long')).toBeOnTheScreen();
});

it('does not render perp badges for a spot position', () => {
renderWithProvider(<PositionRow position={basePosition} />);

expect(screen.queryByText('LONG')).not.toBeOnTheScreen();
expect(screen.queryByText('SHORT')).not.toBeOnTheScreen();
expect(screen.queryByText('Long')).not.toBeOnTheScreen();
expect(screen.queryByText('Short')).not.toBeOnTheScreen();
});

it('hides the HIP-3 provider prefix in the symbol and amount subtitle', () => {
Expand Down Expand Up @@ -404,7 +388,7 @@ describe('PositionRow', () => {
expect(screen.queryByText('1.50B ETH')).not.toBeOnTheScreen();
});

it('renders an up triangle with a colored, unsigned percent for a winning closed perp (matching spot)', () => {
it('renders a colored signed percent for a winning closed perp (matching spot)', () => {
const closedPerp = {
...perpPosition,
currentValueUSD: 0,
Expand All @@ -415,13 +399,11 @@ describe('PositionRow', () => {

renderWithProvider(<PositionRow position={closedPerp} isClosed />);

// 300 / 1200 * 100 = 25%, rendered unsigned with the caret carrying direction.
expect(screen.getByText('▲')).toBeOnTheScreen();
expect(screen.getByText('25.00%')).toBeOnTheScreen();
expect(screen.queryByText('+25%')).toBeNull();
// 300 / 1200 * 100 = 25%
expect(screen.getByText('+25.00%')).toBeOnTheScreen();
});

it('renders a down triangle with a colored, unsigned percent for a losing closed perp', () => {
it('renders a colored signed percent for a losing closed perp', () => {
const closedPerp = {
...perpPosition,
currentValueUSD: 0,
Expand All @@ -432,18 +414,13 @@ describe('PositionRow', () => {

renderWithProvider(<PositionRow position={closedPerp} isClosed />);

expect(screen.getByText('▼')).toBeOnTheScreen();
expect(screen.getByText('25.00%')).toBeOnTheScreen();
expect(screen.queryByText('-25.00%')).toBeNull();
expect(screen.getByText('-25.00%')).toBeOnTheScreen();
});

it('renders the directional triangle with an unsigned percent for an open perp', () => {
it('renders a signed percent for an open perp', () => {
renderWithProvider(<PositionRow position={perpPosition} />);

// Open positions now match closed: caret carries direction, percent is unsigned.
expect(screen.getByText('▲')).toBeOnTheScreen();
expect(screen.getByText('182.00%')).toBeOnTheScreen();
expect(screen.queryByText('+182.00%')).toBeNull();
expect(screen.getByText('+182.00%')).toBeOnTheScreen();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ const PositionRowComponent: React.FC<PositionRowProps> = ({
: null;

const displayPnlPercent = isClosed ? closedPnlPercent : position.pnlPercent;
const hasPnlPercent = displayPnlPercent != null;
// Perps surface realized/unrealized PnL ($) as the headline figure rather
// than spot proceeds / current value.
const perpPnlValue = position.pnlValueUsd ?? position.realizedPnl ?? null;
Expand Down Expand Up @@ -114,33 +113,17 @@ const PositionRowComponent: React.FC<PositionRowProps> = ({
</Text>
);

// All positions — open and closed, perp and spot — render a directional
// triangle (▲/▼) alongside an unsigned percentage, both colored by direction
// (green up / red down). A break-even position shows a neutral minus and a
// neutral percentage. The sign lives in the caret, so the percent is unsigned.
// All positions — open and closed, perp and spot — render a signed percentage
// (e.g. +182%, -25%) colored by direction (green up / red down). Break-even
// positions use neutral styling with +0%.
const bottomRight = (
<Box
flexDirection={BoxFlexDirection.Row}
alignItems={BoxAlignItems.Center}
gap={1}
<Text
variant={TextVariant.BodySm}
twClassName={pnlColorClass}
color={pnlColorClass ? undefined : TextColor.TextAlternative}
>
{!hasPnlPercent ? null : isPnlZero ? (
<Text variant={TextVariant.BodySm} color={TextColor.TextAlternative}>
{'−'}
</Text>
) : (
<Text variant={TextVariant.BodySm} twClassName={pnlColorClass}>
{isPnlPositive ? '▲' : '▼'}
</Text>
)}
<Text
variant={TextVariant.BodySm}
twClassName={pnlColorClass}
color={pnlColorClass ? undefined : TextColor.TextAlternative}
>
{formatPercent(displayPnlPercent, { showSign: false })}
</Text>
</Box>
{formatPercent(displayPnlPercent)}
</Text>
);

const content = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,31 @@ import renderWithProvider from '../../../../util/test/renderWithProvider';
import PerpBadges from './PerpBadges';

describe('PerpBadges', () => {
it('renders an uppercase LONG badge', () => {
it('renders a Long badge', () => {
renderWithProvider(<PerpBadges direction="long" leverage={10} />);

expect(screen.getByText('LONG')).toBeOnTheScreen();
expect(screen.getByText('Long')).toBeOnTheScreen();
expect(screen.getByText('10x')).toBeOnTheScreen();
});

it('renders an uppercase SHORT badge', () => {
it('renders a Short badge', () => {
renderWithProvider(<PerpBadges direction="short" leverage={5} />);

expect(screen.getByText('SHORT')).toBeOnTheScreen();
expect(screen.getByText('Short')).toBeOnTheScreen();
expect(screen.getByText('5x')).toBeOnTheScreen();
});

it('omits the leverage pill when leverage is null', () => {
renderWithProvider(<PerpBadges direction="long" leverage={null} />);

expect(screen.getByText('LONG')).toBeOnTheScreen();
expect(screen.getByText('Long')).toBeOnTheScreen();
expect(screen.queryByText(/x$/u)).not.toBeOnTheScreen();
});

it('omits the leverage pill when leverage is undefined', () => {
renderWithProvider(<PerpBadges direction="short" />);

expect(screen.getByText('SHORT')).toBeOnTheScreen();
expect(screen.getByText('Short')).toBeOnTheScreen();
});

it('forwards the testID', () => {
Expand Down
Loading
Loading