Skip to content

Commit 46c79cb

Browse files
authored
fix(perps): missing max leverage badge in screen header (MetaMask#24133)
## **Description** Fixes missing max leverage badge in the asset screen header when navigating from Recent Activity → Trade Details → "Trade again" button. **Root cause:** `PerpsPositionTransactionView` was creating a minimal market object with only `symbol` and `name` fields, missing `maxLeverage` and other required properties. **Solution:** Use `usePerpsMarkets` hook to get the complete market data (same pattern as `PerpsCard`). This hook: - Uses shared cached market data (no duplicate network requests) - Provides immediate data if already cached - Follows established patterns in the codebase ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/TAT-2187 ## **Manual testing steps** ```gherkin Feature: Max leverage badge in asset screen header Scenario: User navigates from Recent Activity to asset details Given user has recent trading activity And user is on Perps home screen When user taps on a recent activity item Then user sees trade details screen When user taps "Trade again" button Then user sees asset details screen And max leverage badge is visible in header (e.g., "25x" for ETH) ``` ## **Screenshots/Recordings** ### **Before** Max leverage badge missing in header when navigating from Recent Activity https://github.com/user-attachments/assets/41ddb087-9c41-419d-ade9-29d0c47b091d ### **After** Max leverage badge visible in header (e.g., "25x" for ETH) https://github.com/user-attachments/assets/e1aa5432-abb4-4ae9-9404-9c7290bb40a1 ## **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] > Ensures the market header shows correct max leverage by enriching minimal route market data via `usePerpsMarkets` and hiding the badge when unavailable, with accompanying tests. > > - **Perps Market Details**: > - Enriches route `market` using `usePerpsMarkets` when `maxLeverage` is missing; skips fetch if not needed. > - Uses enriched/route `market` across the view; updates navbar setup accordingly. > - **Header**: > - Renders `PerpsLeverage` only when `market.maxLeverage` exists. > - **Transactions**: > - `PerpsPositionTransactionView` navigates with minimal `{ symbol, name }`; relies on details view enrichment. > - **Tests**: > - Add market data enrichment tests in `PerpsMarketDetailsView.test.tsx` (full, minimal, and missing enrichment cases). > - Update transaction view test to assert navigation with minimal market data. > - Add header test for rendering without `maxLeverage`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9ed6c86. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 3f58ef5 commit 46c79cb

6 files changed

Lines changed: 271 additions & 91 deletions

File tree

app/components/UI/Perps/Views/PerpsMarketDetailsView/PerpsMarketDetailsView.test.tsx

Lines changed: 146 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,23 @@ jest.mock('../../hooks/usePerpsOrderFills', () => ({
269269
usePerpsOrderFills: () => mockUsePerpsOrderFillsImpl(),
270270
}));
271271

272+
// Mock for usePerpsMarkets that can be modified per test
273+
const mockUsePerpsMarketsImpl = jest.fn<
274+
ReturnType<typeof import('../../hooks/usePerpsMarkets').usePerpsMarkets>,
275+
[]
276+
>(() => ({
277+
markets: [],
278+
isLoading: false,
279+
error: null,
280+
refresh: jest.fn(),
281+
isRefreshing: false,
282+
}));
283+
284+
// Mock the direct import path for usePerpsMarkets
285+
jest.mock('../../hooks/usePerpsMarkets', () => ({
286+
usePerpsMarkets: () => mockUsePerpsMarketsImpl(),
287+
}));
288+
272289
const mockRefreshMarketStats = jest.fn();
273290
jest.mock('../../hooks/usePerpsMarketStats', () => ({
274291
usePerpsMarketStats: () => ({
@@ -356,13 +373,7 @@ jest.mock('../../hooks', () => ({
356373
closePosition: jest.fn(),
357374
isClosing: false,
358375
})),
359-
usePerpsMarkets: jest.fn(() => ({
360-
markets: [],
361-
isLoading: false,
362-
error: null,
363-
refresh: jest.fn(),
364-
isRefreshing: false,
365-
})),
376+
usePerpsMarkets: () => mockUsePerpsMarketsImpl(),
366377
usePerpsTrading: jest.fn(() => ({
367378
placeOrder: jest.fn(),
368379
cancelOrder: jest.fn(),
@@ -2187,4 +2198,132 @@ describe('PerpsMarketDetailsView', () => {
21872198
expect(chart).toBeTruthy();
21882199
});
21892200
});
2201+
2202+
describe('Market data enrichment', () => {
2203+
beforeEach(() => {
2204+
// Reset to default market with maxLeverage
2205+
mockRouteParams.market = {
2206+
symbol: 'BTC',
2207+
name: 'Bitcoin',
2208+
price: '$45,000.00',
2209+
change24h: '+$1,125.00',
2210+
change24hPercent: '+2.50%',
2211+
volume: '$1.23B',
2212+
maxLeverage: '40x',
2213+
};
2214+
});
2215+
2216+
it('uses route market data when maxLeverage is present (no enrichment needed)', () => {
2217+
// Route has complete market data including maxLeverage
2218+
mockRouteParams.market = {
2219+
symbol: 'ETH',
2220+
name: 'Ethereum',
2221+
price: '$3,000.00',
2222+
change24h: '+$50.00',
2223+
change24hPercent: '+1.50%',
2224+
volume: '$500M',
2225+
maxLeverage: '25x',
2226+
};
2227+
2228+
// Mock usePerpsMarkets to return empty (should not be used)
2229+
mockUsePerpsMarketsImpl.mockReturnValue({
2230+
markets: [],
2231+
isLoading: false,
2232+
error: null,
2233+
refresh: jest.fn(),
2234+
isRefreshing: false,
2235+
});
2236+
2237+
const { getByText } = renderWithProvider(
2238+
<PerpsConnectionProvider>
2239+
<PerpsMarketDetailsView />
2240+
</PerpsConnectionProvider>,
2241+
{
2242+
state: initialState,
2243+
},
2244+
);
2245+
2246+
// Should show the route market's leverage badge
2247+
expect(getByText('25x')).toBeTruthy();
2248+
expect(getByText('ETH-USD')).toBeTruthy();
2249+
});
2250+
2251+
it('enriches market data from usePerpsMarkets when route has minimal data', async () => {
2252+
// Route has minimal market data (no maxLeverage)
2253+
mockRouteParams.market = {
2254+
symbol: 'BTC',
2255+
name: 'Bitcoin',
2256+
} as typeof mockRouteParams.market;
2257+
2258+
// Mock usePerpsMarkets to return full market with maxLeverage
2259+
mockUsePerpsMarketsImpl.mockImplementation(() => ({
2260+
markets: [
2261+
{
2262+
symbol: 'BTC',
2263+
name: 'Bitcoin',
2264+
price: '$45,000.00',
2265+
change24h: '+$1,125.00',
2266+
change24hPercent: '+2.50%',
2267+
volume: '$1.23B',
2268+
maxLeverage: '40x',
2269+
volumeNumber: 1230000000,
2270+
},
2271+
],
2272+
isLoading: false,
2273+
error: null,
2274+
refresh: jest.fn(),
2275+
isRefreshing: false,
2276+
}));
2277+
2278+
const { getByText, getByTestId } = renderWithProvider(
2279+
<PerpsConnectionProvider>
2280+
<PerpsMarketDetailsView />
2281+
</PerpsConnectionProvider>,
2282+
{
2283+
state: initialState,
2284+
},
2285+
);
2286+
2287+
// Verify the header renders with correct market symbol
2288+
expect(getByTestId('perps-market-header')).toBeTruthy();
2289+
expect(getByText('BTC-USD')).toBeTruthy();
2290+
2291+
// Should show the enriched market's leverage badge from usePerpsMarkets
2292+
await waitFor(() => {
2293+
expect(getByText('40x')).toBeTruthy();
2294+
});
2295+
});
2296+
2297+
it('gracefully handles when enrichment data is not available', () => {
2298+
// Route has minimal market data (no maxLeverage)
2299+
mockRouteParams.market = {
2300+
symbol: 'UNKNOWN',
2301+
name: 'Unknown Asset',
2302+
} as typeof mockRouteParams.market;
2303+
2304+
// Mock usePerpsMarkets to return empty (market not found)
2305+
mockUsePerpsMarketsImpl.mockReturnValue({
2306+
markets: [],
2307+
isLoading: false,
2308+
error: null,
2309+
refresh: jest.fn(),
2310+
isRefreshing: false,
2311+
});
2312+
2313+
const { getByText, queryByText } = renderWithProvider(
2314+
<PerpsConnectionProvider>
2315+
<PerpsMarketDetailsView />
2316+
</PerpsConnectionProvider>,
2317+
{
2318+
state: initialState,
2319+
},
2320+
);
2321+
2322+
// Should show the asset name but no leverage badge (since no maxLeverage available)
2323+
expect(getByText('UNKNOWN-USD')).toBeTruthy();
2324+
// No leverage badge should be shown
2325+
expect(queryByText('40x')).toBeNull();
2326+
expect(queryByText('25x')).toBeNull();
2327+
});
2328+
});
21902329
});

0 commit comments

Comments
 (0)