Skip to content

Commit e85bc15

Browse files
authored
feat(perps): display total balance instead of available balance in pe… (MetaMask#23466)
## **Description** The Perps tab on the home screen was displaying "Available balance" instead of "Total balance". Total balance includes margin locked in trades and should match the primary balance shown in the Perps home view. Changed `PerpsTabControlBar` to use `totalBalance` instead of `availableBalance` for consistency with the Perps home view. ## **Changelog** CHANGELOG entry: Fixed Perps tab to display total balance instead of available balance ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/TAT-2172 ## **Manual testing steps** ```gherkin Feature: Perps tab balance display Scenario: User views total balance in Perps tab Given user has open positions in Perps with margin locked When user views the Perps tab on home screen Then the balance shown matches the total balance in Perps home view And the label reads "Total Balance" instead of "Available Balance" ``` ## **Screenshots/Recordings** ### **Before** - Label: "Available balance" - Value: Shows withdrawable funds only <img width="418" height="848" alt="image" src="https://github.com/user-attachments/assets/d49b0b18-c762-4880-a545-0ba9cf6d132f" /> ### **After** - Label: "Total Balance" - Value: Shows total account value (includes margin locked in trades) https://github.com/user-attachments/assets/c04940c0-71ba-4ce9-9a2c-9ed726f87dfc ## **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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > PerpsTabControlBar now displays and animates against `totalBalance` with updated label and tests reflecting the change. > > - **UI (PerpsTabControlBar)** > - Display `totalBalance` instead of `availableBalance` for balance pill. > - Update label to `strings('perps.total_balance')`. > - Drive balance pulse animation and zero/empty checks from `totalBalance`. > - **Tests** > - Update mocks, assertions, and edge cases to use `totalBalance`. > - Verify animations trigger on `totalBalance` changes and errors are logged. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 51afbe2. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 22d21bf commit e85bc15

2 files changed

Lines changed: 38 additions & 39 deletions

File tree

app/components/UI/Perps/components/PerpsTabControlBar/PerpsTabControlBar.test.tsx

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ jest.mock('../../../../../core/SDKConnect/utils/DevLogger', () => ({
9191
jest.mock('../../../../../../locales/i18n', () => ({
9292
strings: jest.fn((key: string) => {
9393
const translations: Record<string, string> = {
94-
'perps.available_balance': 'Available Balance',
94+
'perps.total_balance': 'Total Balance',
9595
'perps.position.account.unrealized_pnl': 'Unrealized P&L',
9696
};
9797
return translations[key] || key;
@@ -132,9 +132,9 @@ jest.mock('react-native', () => {
132132
describe('PerpsTabControlBar', () => {
133133
// Helper function to get TouchableOpacity
134134
const getTouchableOpacity = () => {
135-
const balanceText = screen.queryByText('Available Balance');
135+
const balanceText = screen.queryByText('Total Balance');
136136
if (!balanceText) {
137-
throw new Error('Available Balance text not found');
137+
throw new Error('Total Balance text not found');
138138
}
139139
const touchableOpacity = balanceText.parent?.parent;
140140
if (!touchableOpacity) {
@@ -225,15 +225,14 @@ describe('PerpsTabControlBar', () => {
225225

226226
render(<PerpsTabControlBar hasPositions={false} hasOrders={false} />);
227227

228-
expect(screen.getByText('Available Balance')).toBeOnTheScreen();
229-
expect(screen.getByText('$800.25')).toBeOnTheScreen();
228+
expect(screen.getByText('Total Balance')).toBeOnTheScreen();
229+
expect(screen.getByText('$1000.50')).toBeOnTheScreen();
230230

231231
// PnL pill should not be rendered when no positions or orders
232232
expect(screen.queryByText('Unrealized P&L')).not.toBeOnTheScreen();
233233

234234
// Find TouchableOpacity by its content
235-
const touchableOpacity =
236-
screen.getByText('Available Balance').parent?.parent;
235+
const touchableOpacity = screen.getByText('Total Balance').parent?.parent;
237236
expect(touchableOpacity).toBeTruthy();
238237
});
239238

@@ -247,9 +246,9 @@ describe('PerpsTabControlBar', () => {
247246

248247
render(<PerpsTabControlBar hasPositions hasOrders={false} />);
249248

250-
expect(screen.getByText('Available Balance')).toBeOnTheScreen();
249+
expect(screen.getByText('Total Balance')).toBeOnTheScreen();
251250
expect(screen.getByText('Unrealized P&L')).toBeOnTheScreen();
252-
expect(screen.getByText('$800.25')).toBeOnTheScreen();
251+
expect(screen.getByText('$1000.50')).toBeOnTheScreen();
253252
expect(screen.getByText('+$50.75 (0.00%)')).toBeOnTheScreen(); // Formatted PnL
254253
});
255254

@@ -263,35 +262,35 @@ describe('PerpsTabControlBar', () => {
263262

264263
render(<PerpsTabControlBar hasPositions={false} hasOrders />);
265264

266-
expect(screen.getByText('Available Balance')).toBeOnTheScreen();
265+
expect(screen.getByText('Total Balance')).toBeOnTheScreen();
267266
expect(screen.queryByText('Unrealized P&L')).not.toBeOnTheScreen();
268267
});
269268

270269
it('hides balance pill when balance is zero and no positions/orders', async () => {
271270
jest
272271
.mocked(jest.requireMock('../../hooks/stream').usePerpsLiveAccount)
273272
.mockReturnValue({
274-
account: { ...defaultAccountState, availableBalance: '0.00' },
273+
account: { ...defaultAccountState, totalBalance: '0.00' },
275274
isInitialLoading: false,
276275
});
277276

278277
render(<PerpsTabControlBar hasPositions={false} hasOrders={false} />);
279278

280-
expect(screen.queryByText('Available Balance')).not.toBeOnTheScreen();
279+
expect(screen.queryByText('Total Balance')).not.toBeOnTheScreen();
281280
expect(screen.queryByText('Unrealized P&L')).not.toBeOnTheScreen();
282281
});
283282

284283
it('shows balance pill when balance is zero but has positions', async () => {
285284
jest
286285
.mocked(jest.requireMock('../../hooks/stream').usePerpsLiveAccount)
287286
.mockReturnValue({
288-
account: { ...defaultAccountState, availableBalance: '0.00' },
287+
account: { ...defaultAccountState, totalBalance: '0.00' },
289288
isInitialLoading: false,
290289
});
291290

292291
render(<PerpsTabControlBar hasPositions hasOrders={false} />);
293292

294-
expect(screen.getByText('Available Balance')).toBeOnTheScreen();
293+
expect(screen.getByText('Total Balance')).toBeOnTheScreen();
295294
expect(screen.getByText('Unrealized P&L')).toBeOnTheScreen();
296295
expect(screen.getByText('$0.00')).toBeOnTheScreen();
297296
});
@@ -307,8 +306,8 @@ describe('PerpsTabControlBar', () => {
307306

308307
render(<PerpsTabControlBar hasPositions={false} hasOrders={false} />);
309308

310-
// Should display the available balance from account state
311-
expect(screen.getByText('$800.25')).toBeOnTheScreen();
309+
// Should display the total balance from account state
310+
expect(screen.getByText('$1000.50')).toBeOnTheScreen();
312311
});
313312

314313
it('renders without onManageBalancePress prop', async () => {
@@ -368,7 +367,7 @@ describe('PerpsTabControlBar', () => {
368367

369368
// First render with initial balance - this sets the previousBalanceRef
370369
mockUsePerpsLiveAccount.mockReturnValueOnce({
371-
account: { ...defaultAccountState, availableBalance: '900.00' },
370+
account: { ...defaultAccountState, totalBalance: '900.00' },
372371
isInitialLoading: false,
373372
});
374373

@@ -382,7 +381,7 @@ describe('PerpsTabControlBar', () => {
382381

383382
// Now change the balance - this should trigger animation
384383
mockUsePerpsLiveAccount.mockReturnValueOnce({
385-
account: { ...defaultAccountState, availableBalance: '1000.50' },
384+
account: { ...defaultAccountState, totalBalance: '1000.50' },
386385
isInitialLoading: false,
387386
});
388387

@@ -403,7 +402,7 @@ describe('PerpsTabControlBar', () => {
403402

404403
// First render with higher balance - sets previousBalanceRef
405404
mockUsePerpsLiveAccount.mockReturnValueOnce({
406-
account: { ...defaultAccountState, availableBalance: '1200.00' },
405+
account: { ...defaultAccountState, totalBalance: '1200.00' },
407406
isInitialLoading: false,
408407
});
409408

@@ -418,7 +417,7 @@ describe('PerpsTabControlBar', () => {
418417

419418
// Now render with lower balance
420419
mockUsePerpsLiveAccount.mockReturnValueOnce({
421-
account: { ...defaultAccountState, availableBalance: '800.00' },
420+
account: { ...defaultAccountState, totalBalance: '800.00' },
422421
isInitialLoading: false,
423422
});
424423

@@ -436,7 +435,7 @@ describe('PerpsTabControlBar', () => {
436435

437436
// Render with initial balance
438437
mockUsePerpsLiveAccount.mockReturnValue({
439-
account: { ...defaultAccountState, availableBalance: '1000.50' },
438+
account: { ...defaultAccountState, totalBalance: '1000.50' },
440439
isInitialLoading: false,
441440
});
442441

@@ -450,7 +449,7 @@ describe('PerpsTabControlBar', () => {
450449

451450
// Render again with same balance
452451
mockUsePerpsLiveAccount.mockReturnValue({
453-
account: { ...defaultAccountState, availableBalance: '1000.50' },
452+
account: { ...defaultAccountState, totalBalance: '1000.50' },
454453
isInitialLoading: false,
455454
});
456455

@@ -511,7 +510,7 @@ describe('PerpsTabControlBar', () => {
511510
render(<PerpsTabControlBar hasPositions={false} hasOrders={false} />);
512511

513512
// With null account and no positions/orders, balance pill should be hidden
514-
expect(screen.queryByText('Available Balance')).not.toBeOnTheScreen();
513+
expect(screen.queryByText('Total Balance')).not.toBeOnTheScreen();
515514
expect(screen.queryByText('Unrealized P&L')).not.toBeOnTheScreen();
516515
});
517516

@@ -535,7 +534,7 @@ describe('PerpsTabControlBar', () => {
535534

536535
// Update with account data
537536
mockUsePerpsLiveAccount.mockReturnValueOnce({
538-
account: { ...defaultAccountState, availableBalance: '800.25' },
537+
account: { ...defaultAccountState, totalBalance: '800.25' },
539538
isInitialLoading: false,
540539
});
541540

@@ -559,7 +558,7 @@ describe('PerpsTabControlBar', () => {
559558

560559
// First render to set previousBalanceRef
561560
mockUsePerpsLiveAccount.mockReturnValueOnce({
562-
account: { ...defaultAccountState, availableBalance: '500.00' },
561+
account: { ...defaultAccountState, totalBalance: '500.00' },
563562
isInitialLoading: false,
564563
});
565564

@@ -572,7 +571,7 @@ describe('PerpsTabControlBar', () => {
572571

573572
// Change balance to trigger animation
574573
mockUsePerpsLiveAccount.mockReturnValueOnce({
575-
account: { ...defaultAccountState, availableBalance: '600.00' },
574+
account: { ...defaultAccountState, totalBalance: '600.00' },
576575
isInitialLoading: false,
577576
});
578577

@@ -595,7 +594,7 @@ describe('PerpsTabControlBar', () => {
595594
render(<PerpsTabControlBar hasPositions={false} hasOrders={false} />);
596595

597596
// During loading with no positions/orders, balance pill should be hidden
598-
expect(screen.queryByText('Available Balance')).not.toBeOnTheScreen();
597+
expect(screen.queryByText('Total Balance')).not.toBeOnTheScreen();
599598
expect(screen.queryByText('Unrealized P&L')).not.toBeOnTheScreen();
600599
});
601600
});
@@ -621,15 +620,15 @@ describe('PerpsTabControlBar', () => {
621620
.mockReturnValue({
622621
account: {
623622
...defaultAccountState,
624-
availableBalance: '',
623+
totalBalance: '',
625624
},
626625
isInitialLoading: false,
627626
});
628627

629628
render(<PerpsTabControlBar hasPositions={false} hasOrders={false} />);
630629

631630
// With empty balance and no positions/orders, pills should be hidden
632-
expect(screen.queryByText('Available Balance')).not.toBeOnTheScreen();
631+
expect(screen.queryByText('Total Balance')).not.toBeOnTheScreen();
633632
expect(screen.queryByText('Unrealized P&L')).not.toBeOnTheScreen();
634633
});
635634

@@ -639,15 +638,15 @@ describe('PerpsTabControlBar', () => {
639638
.mockReturnValue({
640639
account: {
641640
...defaultAccountState,
642-
availableBalance: null as unknown as string,
641+
totalBalance: null as unknown as string,
643642
},
644643
isInitialLoading: false,
645644
});
646645

647646
render(<PerpsTabControlBar hasPositions={false} hasOrders={false} />);
648647

649648
// With null balance and no positions/orders, pills should be hidden
650-
expect(screen.queryByText('Available Balance')).not.toBeOnTheScreen();
649+
expect(screen.queryByText('Total Balance')).not.toBeOnTheScreen();
651650
expect(screen.queryByText('Unrealized P&L')).not.toBeOnTheScreen();
652651
});
653652

@@ -696,7 +695,7 @@ describe('PerpsTabControlBar', () => {
696695

697696
// First render with initial balance - sets previousBalanceRef
698697
mockUsePerpsLiveAccount.mockReturnValueOnce({
699-
account: { ...defaultAccountState, availableBalance: '900.00' },
698+
account: { ...defaultAccountState, totalBalance: '900.00' },
700699
isInitialLoading: false,
701700
});
702701

@@ -710,7 +709,7 @@ describe('PerpsTabControlBar', () => {
710709

711710
// Now mock with changed balance
712711
mockUsePerpsLiveAccount.mockReturnValueOnce({
713-
account: { ...defaultAccountState, availableBalance: '1000.50' },
712+
account: { ...defaultAccountState, totalBalance: '1000.50' },
714713
isInitialLoading: false,
715714
});
716715

app/components/UI/Perps/components/PerpsTabControlBar/PerpsTabControlBar.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ export const PerpsTabControlBar: React.FC<PerpsTabControlBarProps> = ({
6363
useEffect(() => {
6464
if (!perpsAccount) return;
6565

66-
// Use availableBalance since that's what we display in the UI
67-
const currentBalance = perpsAccount.availableBalance;
66+
// Use totalBalance since that's what we display in the UI
67+
const currentBalance = perpsAccount.totalBalance;
6868

6969
// Only animate if balance actually changed (and we have a previous value to compare)
7070
if (
@@ -134,11 +134,11 @@ export const PerpsTabControlBar: React.FC<PerpsTabControlBarProps> = ({
134134
onManageBalancePress?.();
135135
};
136136

137-
const availableBalance = perpsAccount?.availableBalance || '0';
137+
const totalBalance = perpsAccount?.totalBalance || '0';
138138
const pnlNum = parseFloat(perpsAccount?.unrealizedPnl || '0');
139139
const roe = parseFloat(perpsAccount?.returnOnEquity || '0');
140140
const pnlColor = pnlNum >= 0 ? TextColor.Success : TextColor.Error;
141-
const isBalanceEmpty = BigNumber(availableBalance).isZero();
141+
const isBalanceEmpty = BigNumber(totalBalance).isZero();
142142
const shouldShowPnl = hasPositions;
143143
const shouldShowBalance = !isBalanceEmpty || shouldShowPnl;
144144
const balancePillContainerStyle =
@@ -162,7 +162,7 @@ export const PerpsTabControlBar: React.FC<PerpsTabControlBarProps> = ({
162162
color={TextColor.Alternative}
163163
style={styles.titleText}
164164
>
165-
{strings('perps.available_balance')}
165+
{strings('perps.total_balance')}
166166
</Text>
167167
</View>
168168
<View style={styles.rightSection}>
@@ -177,7 +177,7 @@ export const PerpsTabControlBar: React.FC<PerpsTabControlBarProps> = ({
177177
color={TextColor.Default}
178178
testID={PerpsTabViewSelectorsIDs.BALANCE_VALUE}
179179
>
180-
{formatPerpsFiat(availableBalance, {
180+
{formatPerpsFiat(totalBalance, {
181181
ranges: PRICE_RANGES_MINIMAL_VIEW,
182182
})}
183183
</Text>

0 commit comments

Comments
 (0)