Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
5 changes: 4 additions & 1 deletion app/components/UI/Perps/Perps.testIds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,8 @@ export const PerpsMarketDetailsViewSelectorsIDs = {
LOADING: 'perps-market-details-loading',
ERROR: 'perps-market-details-error',
HEADER: 'perps-market-header',
MARKET_SUMMARY: 'perps-market-details-market-summary',
FULLSCREEN_CHART_BUTTON: 'perps-market-details-fullscreen-chart-button',
STATISTICS_HIGH_24H: 'perps-statistics-high-24h',
STATISTICS_LOW_24H: 'perps-statistics-low-24h',
STATISTICS_VOLUME_24H: 'perps-statistics-volume-24h',
Expand Down Expand Up @@ -441,7 +443,8 @@ export const PerpsMarketHeaderSelectorsIDs = {
PRICE_CHANGE: 'perps-market-header-price-change',
MORE_BUTTON: 'perps-market-header-more-button',
FAVORITE_BUTTON: 'perps-market-header-favorite-button',
CATEGORY_SEARCH_BUTTON: 'perps-market-header-category-search-button',
MARKET_LIST_BUTTON: 'perps-market-header-market-list-button',
SUBTITLE: 'perps-market-header-subtitle',
};

// ========================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ export const createStyles = ({ theme }: { theme: Theme }) =>
paddingBottom: 16, // Consistent bottom padding
},

// Keeps the fullscreen button fixed while the price row shrinks
marketSummaryFullscreenButton: {
flexShrink: 0,
},

// Container for error state
container: {
flex: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3198,7 +3198,7 @@ describe('PerpsMarketDetailsView', () => {

// Press fullscreen button
const fullscreenButton = getByTestId(
'perps-market-header-fullscreen-button',
PerpsMarketDetailsViewSelectorsIDs.FULLSCREEN_CHART_BUTTON,
);
fireEvent.press(fullscreenButton);

Expand All @@ -3222,7 +3222,7 @@ describe('PerpsMarketDetailsView', () => {

// Open the modal first
const fullscreenButton = getByTestId(
'perps-market-header-fullscreen-button',
PerpsMarketDetailsViewSelectorsIDs.FULLSCREEN_CHART_BUTTON,
);
fireEvent.press(fullscreenButton);

Expand Down Expand Up @@ -3255,7 +3255,7 @@ describe('PerpsMarketDetailsView', () => {

// Open the modal
const fullscreenButton = getByTestId(
'perps-market-header-fullscreen-button',
PerpsMarketDetailsViewSelectorsIDs.FULLSCREEN_CHART_BUTTON,
);
fireEvent.press(fullscreenButton);

Expand Down Expand Up @@ -3320,9 +3320,7 @@ describe('PerpsMarketDetailsView', () => {
);

fireEvent.press(
getByTestId(
`${PerpsMarketDetailsViewSelectorsIDs.HEADER}-fullscreen-button`,
),
getByTestId(PerpsMarketDetailsViewSelectorsIDs.FULLSCREEN_CHART_BUTTON),
);

await waitFor(() => {
Expand All @@ -3345,8 +3343,8 @@ describe('PerpsMarketDetailsView', () => {
});
});

describe('Category search shortcut', () => {
it('navigates to market list without filters when search button is pressed', () => {
describe('Market list shortcut', () => {
it('navigates to market list without filters when the market list button is pressed', () => {
const { getByTestId } = renderWithProvider(
<PerpsConnectionProvider>
<PerpsMarketDetailsView />
Expand All @@ -3356,15 +3354,50 @@ describe('PerpsMarketDetailsView', () => {
},
);

const searchButton = getByTestId(
'perps-market-header-category-search-button',
const marketListButton = getByTestId(
PerpsMarketHeaderSelectorsIDs.MARKET_LIST_BUTTON,
);
fireEvent.press(searchButton);
fireEvent.press(marketListButton);

expect(mockNavigateToMarketList).toHaveBeenCalledWith({
source: 'magnifying_glass',
source: 'perp_asset_screen',
});
});

it('tracks the market list button click with the correct analytics values', () => {
const mockTrack = jest.fn();
const { usePerpsEventTracking: mockUsePerpsEventTrackingFn } =
jest.requireMock('../../hooks/usePerpsEventTracking');
mockUsePerpsEventTrackingFn.mockImplementation(() => ({
track: mockTrack,
}));

const { getByTestId } = renderWithProvider(
<PerpsConnectionProvider>
<PerpsMarketDetailsView />
</PerpsConnectionProvider>,
{
state: initialState,
},
);

fireEvent.press(
getByTestId(PerpsMarketHeaderSelectorsIDs.MARKET_LIST_BUTTON),
);

expect(mockTrack).toHaveBeenCalledWith(
MetaMetricsEvents.PERPS_UI_INTERACTION,
expect.objectContaining({
[PERPS_EVENT_PROPERTY.INTERACTION_TYPE]:
PERPS_EVENT_VALUE.INTERACTION_TYPE.BUTTON_CLICKED,
[PERPS_EVENT_PROPERTY.BUTTON_CLICKED]:
PERPS_EVENT_VALUE.BUTTON_CLICKED.MARKET_LIST,
[PERPS_EVENT_PROPERTY.BUTTON_LOCATION]:
PERPS_EVENT_VALUE.BUTTON_LOCATION.PERP_MARKET_DETAILS,
[PERPS_EVENT_PROPERTY.ASSET]: 'BTC',
}),
);
});
});

describe('Position opened timestamp', () => {
Expand Down Expand Up @@ -4318,7 +4351,9 @@ describe('PerpsMarketDetailsView', () => {

// Should show the route market's leverage badge
expect(getByText('25x')).toBeOnTheScreen();
expect(getAllByText('ETH-USD').length).toBeGreaterThanOrEqual(1);
// Header shows the full asset name and the market pair subtitle
expect(getByText('Ethereum')).toBeOnTheScreen();
expect(getAllByText('ETH-USD perp').length).toBeGreaterThanOrEqual(1);
});

it('enriches market data when route maxLeverage is unformatted', async () => {
Expand Down Expand Up @@ -4525,7 +4560,8 @@ describe('PerpsMarketDetailsView', () => {

// Verify the header renders with correct market symbol
expect(getByTestId('perps-market-header')).toBeOnTheScreen();
expect(getAllByText('BTC-USD').length).toBeGreaterThanOrEqual(1);
expect(getByText('Bitcoin')).toBeOnTheScreen();
expect(getAllByText('BTC-USD perp').length).toBeGreaterThanOrEqual(1);

// Should show the enriched market's leverage badge from usePerpsMarkets
await waitFor(() => {
Expand All @@ -4549,7 +4585,7 @@ describe('PerpsMarketDetailsView', () => {
isRefreshing: false,
});

const { getAllByText, queryByText } = renderWithProvider(
const { getByText, getAllByText, queryByText } = renderWithProvider(
<PerpsConnectionProvider>
<PerpsMarketDetailsView />
</PerpsConnectionProvider>,
Expand All @@ -4559,7 +4595,8 @@ describe('PerpsMarketDetailsView', () => {
);

// Should show the asset name but no leverage badge (since no maxLeverage available)
expect(getAllByText('UNKNOWN-USD').length).toBeGreaterThanOrEqual(1);
expect(getByText('Unknown Asset')).toBeOnTheScreen();
expect(getAllByText('UNKNOWN-USD perp').length).toBeGreaterThanOrEqual(1);
// No leverage badge should be shown
expect(queryByText('40x')).toBeNull();
expect(queryByText('25x')).toBeNull();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import {
Box,
BoxAlignItems,
BoxFlexDirection,
BoxJustifyContent,
Button as DSButton,
ButtonIcon,
ButtonIconSize,
ButtonSemantic,
ButtonSemanticSeverity,
ButtonVariant,
ButtonSize as ButtonSizeRNDesignSystem,
IconName,
Text,
TextColor,
TextVariant,
useHeaderStandardAnimated,
} from '@metamask/design-system-react-native';
import {
useNavigation,
Expand Down Expand Up @@ -37,7 +44,6 @@ import { useDispatch, useSelector } from 'react-redux';
import { strings } from '../../../../../../locales/i18n';
import { setPerpsChartPreferredCandlePeriod } from '../../../../../actions/settings';
import { Skeleton } from '../../../../../component-library/components-temp/Skeleton';
import useHeaderStandardAnimated from '../../../../../component-library/components-temp/HeaderStandardAnimated/useHeaderStandardAnimated';
import { useStyles } from '../../../../../component-library/hooks';
import Routes from '../../../../../constants/navigation/Routes';
import Engine from '../../../../../core/Engine';
Expand All @@ -55,10 +61,12 @@ import PerpsCompactOrderRow from '../../components/PerpsCompactOrderRow';
import PerpsFlipPositionConfirmSheet from '../../components/PerpsFlipPositionConfirmSheet';
import {
PerpsMarketDetailsViewSelectorsIDs,
PerpsMarketHeaderSelectorsIDs,
PerpsOrderViewSelectorsIDs,
PerpsTutorialSelectorsIDs,
PerpsCompactOrderRowSelectorsIDs,
} from '../../Perps.testIds';
import LivePriceHeader from '../../components/LivePriceDisplay/LivePriceHeader';
import PerpsMarketInlineHeader from '../../components/PerpsMarketInlineHeader';
import PerpsMarketHoursBanner from '../../components/PerpsMarketHoursBanner';
import PerpsMarketStatisticsCard from '../../components/PerpsMarketStatisticsCard';
Expand Down Expand Up @@ -770,21 +778,21 @@ const PerpsMarketDetailsView: React.FC<PerpsMarketDetailsViewProps> = () => {
});
}, [market, isWatchlist, track, showToast, PerpsToastOptions]);

const handleCategorySearchPress = useCallback(() => {
const handleMarketListPress = useCallback(() => {
if (!market) return;

track(MetaMetricsEvents.PERPS_UI_INTERACTION, {
[PERPS_EVENT_PROPERTY.INTERACTION_TYPE]:
PERPS_EVENT_VALUE.INTERACTION_TYPE.BUTTON_CLICKED,
[PERPS_EVENT_PROPERTY.BUTTON_CLICKED]:
PERPS_EVENT_VALUE.BUTTON_CLICKED.MAGNIFYING_GLASS,
PERPS_EVENT_VALUE.BUTTON_CLICKED.MARKET_LIST,
[PERPS_EVENT_PROPERTY.BUTTON_LOCATION]:
PERPS_EVENT_VALUE.BUTTON_LOCATION.ASSET_DETAILS,
PERPS_EVENT_VALUE.BUTTON_LOCATION.PERP_MARKET_DETAILS,
[PERPS_EVENT_PROPERTY.ASSET]: market.symbol,
});

navigateToMarketList({
source: PERPS_EVENT_VALUE.SOURCE.MAGNIFYING_GLASS,
source: PERPS_EVENT_VALUE.SOURCE.PERP_ASSET_SCREEN,
});
}, [market, track, navigateToMarketList]);

Expand Down Expand Up @@ -1450,14 +1458,42 @@ const PerpsMarketDetailsView: React.FC<PerpsMarketDetailsViewProps> = () => {
market={market}
currentPrice={syncedChartCurrentPrice}
onBackPress={handleBackPress}
onFullscreenPress={handleFullscreenChartOpen}
onFavoritePress={handleWatchlistPress}
onCategorySearchPress={handleCategorySearchPress}
onMarketListPress={handleMarketListPress}
isFavorite={isWatchlist}
useDetailLayout
testID={PerpsMarketDetailsViewSelectorsIDs.HEADER}
fullscreenButtonTestID={`${PerpsMarketDetailsViewSelectorsIDs.HEADER}-fullscreen-button`}
/>

{/* Below header: live price + 24h change and fullscreen chart button */}
<Box
flexDirection={BoxFlexDirection.Row}
alignItems={BoxAlignItems.Center}
justifyContent={BoxJustifyContent.Between}
gap={2}
twClassName="px-4 pb-2"
testID={PerpsMarketDetailsViewSelectorsIDs.MARKET_SUMMARY}
>
{/* Flexible wrapper lets the price shrink; the button stays fixed. */}
<Box twClassName="flex-1">
<LivePriceHeader
symbol={market.symbol}
testIDPrice={PerpsMarketHeaderSelectorsIDs.PRICE}
testIDChange={PerpsMarketHeaderSelectorsIDs.PRICE_CHANGE}
currentPrice={syncedChartCurrentPrice}
size="large"
/>
</Box>
Comment thread
cursor[bot] marked this conversation as resolved.
<ButtonIcon
iconName={IconName.Expand}
size={ButtonIconSize.Md}
onPress={handleFullscreenChartOpen}
style={styles.marketSummaryFullscreenButton}
testID={PerpsMarketDetailsViewSelectorsIDs.FULLSCREEN_CHART_BUTTON}
accessibilityLabel={strings('perps.market_details.fullscreen_chart')}
/>
</Box>

<View style={styles.scrollableContentContainer}>
<ScrollView
ref={scrollViewRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
*/
import '../../../../../../tests/component-view/mocks';
import type { ComponentType } from 'react';
import { act, fireEvent, screen, waitFor } from '@testing-library/react-native';
import {
act,
fireEvent,
screen,
waitFor,
within,
} from '@testing-library/react-native';
import type { Position } from '@metamask/perps-controller';
import {
MOCK_PERPS_MARKET_INSIGHTS_REPORT,
Expand Down Expand Up @@ -485,21 +491,44 @@ describe('PerpsMarketDetailsView', () => {
).toBeOnTheScreen();
expect(
screen.getByTestId(
`${PerpsMarketDetailsViewSelectorsIDs.HEADER}-fullscreen-button`,
PerpsMarketDetailsViewSelectorsIDs.FULLSCREEN_CHART_BUTTON,
),
).toBeOnTheScreen();
});

it('renders header with market title', async () => {
it('renders header with full asset name and market pair subtitle', async () => {
renderEligibleNoPositionPerpsDetails();

expect(
await screen.findByTestId(PerpsMarketDetailsViewSelectorsIDs.HEADER),
).toBeOnTheScreen();
expect(screen.getByText('ETH-USD')).toBeOnTheScreen();
expect(screen.getByText('Ethereum')).toBeOnTheScreen();
expect(screen.getByText('ETH-USD perp')).toBeOnTheScreen();
});

it('renders header price when market has no maxLeverage', async () => {
it('renders live price, 24h change, and fullscreen button inside the market summary row', async () => {
renderEligibleNoPositionPerpsDetails();

const marketSummary = await screen.findByTestId(
PerpsMarketDetailsViewSelectorsIDs.MARKET_SUMMARY,
);

expect(
within(marketSummary).getByTestId(PerpsMarketHeaderSelectorsIDs.PRICE),
).toBeOnTheScreen();
expect(
within(marketSummary).getByTestId(
PerpsMarketHeaderSelectorsIDs.PRICE_CHANGE,
),
).toBeOnTheScreen();
expect(
within(marketSummary).getByTestId(
PerpsMarketDetailsViewSelectorsIDs.FULLSCREEN_CHART_BUTTON,
),
).toBeOnTheScreen();
});

it('renders the market summary price when the market has no maxLeverage', async () => {
renderPerpsMarketDetailsView({
initialParams: {
market: {
Expand Down Expand Up @@ -536,19 +565,24 @@ describe('PerpsMarketDetailsView', () => {
expect(
await screen.findByTestId(PerpsMarketDetailsViewSelectorsIDs.HEADER),
).toBeOnTheScreen();
const marketSummary = await screen.findByTestId(
PerpsMarketDetailsViewSelectorsIDs.MARKET_SUMMARY,
);
expect(
await screen.findByTestId(PerpsMarketHeaderSelectorsIDs.PRICE),
within(marketSummary).getByTestId(PerpsMarketHeaderSelectorsIDs.PRICE),
).toBeOnTheScreen();
expect(
await screen.findByTestId(PerpsMarketHeaderSelectorsIDs.PRICE_CHANGE),
within(marketSummary).getByTestId(
PerpsMarketHeaderSelectorsIDs.PRICE_CHANGE,
),
).toBeOnTheScreen();
});

it('opens fullscreen chart modal and close button is pressable', async () => {
renderEligibleNoPositionPerpsDetails();

const fullscreenButton = await screen.findByTestId(
`${PerpsMarketDetailsViewSelectorsIDs.HEADER}-fullscreen-button`,
PerpsMarketDetailsViewSelectorsIDs.FULLSCREEN_CHART_BUTTON,
);
fireEvent.press(fullscreenButton);

Expand Down
Loading
Loading