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 @@ -10,6 +10,8 @@ import {
import { PerpsConnectionProvider } from '../../providers/PerpsConnectionProvider';
import { useDefaultPayWithTokenWhenNoPerpsBalance } from '../../hooks/useDefaultPayWithTokenWhenNoPerpsBalance';
import { Linking } from 'react-native';
import { MetaMetricsEvents } from '../../../../../core/Analytics';
import Routes from '../../../../../constants/navigation/Routes';

// Mock Linking
jest.mock('react-native/Libraries/Linking/Linking', () => ({
Expand Down Expand Up @@ -394,6 +396,34 @@ jest.mock('../../hooks/usePerpsEventTracking', () => ({
})),
}));

const mockUseMarketInsights = jest.fn(
(_assetId?: string | null, _isEnabled?: boolean) => ({
report: null as Record<string, unknown> | null,
isLoading: false,
error: null,
timeAgo: '',
}),
);

jest.mock('../../../MarketInsights', () => ({
useMarketInsights: (assetId: string | null | undefined, isEnabled: boolean) =>
mockUseMarketInsights(assetId, isEnabled),
MarketInsightsEntryCard: ({ onPress }: { onPress: () => void }) => {
const { TouchableOpacity } = jest.requireActual('react-native');
return (
<TouchableOpacity testID="market-insights-entry-card" onPress={onPress} />
);
},
selectMarketInsightsEnabled: jest.fn(),
}));

jest.mock(
'../../../../../selectors/featureFlagController/marketInsights',
() => ({
selectMarketInsightsPerpsEnabled: jest.fn(),
}),
);

jest.mock('../../hooks/usePerpsPrices', () => ({
usePerpsPrices: jest.fn(() => ({})),
}));
Expand Down Expand Up @@ -3264,4 +3294,134 @@ describe('PerpsMarketDetailsView', () => {
expect(queryByText('25x')).toBeNull();
});
});

describe('Market Insights analytics', () => {
const mockReport = {
summary: 'BTC momentum is building with increased buying pressure.',
sentiment: 'bullish',
generatedAt: new Date().toISOString(),
};

// Stable track mock reference set up in beforeEach via mockImplementation
const mockTrack = jest.fn();

beforeEach(() => {
// Override usePerpsEventTracking to expose a capturable track mock
const { usePerpsEventTracking: mockUsePerpsEventTrackingFn } =
jest.requireMock('../../hooks/usePerpsEventTracking');
mockUsePerpsEventTrackingFn.mockImplementation(() => ({
track: mockTrack,
}));

// Enable perps market insights feature flag
const { useSelector } = jest.requireMock('react-redux');
const { selectPerpsEligibility } = jest.requireMock(
'../../selectors/perpsController',
);
const { selectMarketInsightsPerpsEnabled } = jest.requireMock(
'../../../../../selectors/featureFlagController/marketInsights',
);
useSelector.mockImplementation((selector: unknown) => {
if (selector === selectPerpsEligibility) return true;
if (selector === selectMarketInsightsPerpsEnabled) return true;
return undefined;
});

// Default: a report is available and loading is complete
mockUseMarketInsights.mockReturnValue({
report: mockReport,
isLoading: false,
error: null,
timeAgo: '5m ago',
});
});

afterEach(() => {
mockTrack.mockClear();
});

it('fires MARKET_INSIGHTS_OPENED with perps_market when entry card is pressed', () => {
const { getByTestId } = renderWithProvider(
<PerpsConnectionProvider>
<PerpsMarketDetailsView />
</PerpsConnectionProvider>,
{ state: initialState },
);

fireEvent.press(getByTestId('market-insights-entry-card'));

expect(mockTrack).toHaveBeenCalledWith(
MetaMetricsEvents.MARKET_INSIGHTS_OPENED,
expect.objectContaining({ perps_market: 'BTC' }),
);
});

it('navigates to MarketInsightsView with isPerps flag when entry card is pressed', () => {
const { getByTestId } = renderWithProvider(
<PerpsConnectionProvider>
<PerpsMarketDetailsView />
</PerpsConnectionProvider>,
{ state: initialState },
);

fireEvent.press(getByTestId('market-insights-entry-card'));

expect(mockNavigate).toHaveBeenCalledWith(
Routes.MARKET_INSIGHTS.VIEW,
expect.objectContaining({
assetIdentifier: 'BTC',
isPerps: true,
}),
);
});

it('passes market_insights_displayed: true to PERPS_SCREEN_VIEWED when a report is available', () => {
renderWithProvider(
<PerpsConnectionProvider>
<PerpsMarketDetailsView />
</PerpsConnectionProvider>,
{ state: initialState },
);

const { usePerpsEventTracking: mockUsePerpsEventTrackingFn } =
jest.requireMock('../../hooks/usePerpsEventTracking');

expect(mockUsePerpsEventTrackingFn).toHaveBeenCalledWith(
expect.objectContaining({
eventName: MetaMetricsEvents.PERPS_SCREEN_VIEWED,
properties: expect.objectContaining({
market_insights_displayed: true,
}),
}),
);
});

it('passes market_insights_displayed: false to PERPS_SCREEN_VIEWED when no report is returned', () => {
mockUseMarketInsights.mockReturnValue({
report: null,
isLoading: false,
error: null,
timeAgo: '',
});

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

const { usePerpsEventTracking: mockUsePerpsEventTrackingFn } =
jest.requireMock('../../hooks/usePerpsEventTracking');

expect(mockUsePerpsEventTrackingFn).toHaveBeenCalledWith(
expect.objectContaining({
eventName: MetaMetricsEvents.PERPS_SCREEN_VIEWED,
properties: expect.objectContaining({
market_insights_displayed: false,
}),
}),
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,11 @@ const PerpsMarketDetailsView: React.FC<PerpsMarketDetailsViewProps> = () => {

// Feature flag for Market Insights in Perps
const isPerpsInsightsEnabled = useSelector(selectMarketInsightsPerpsEnabled);
const { report: perpsInsightsReport, timeAgo: perpsInsightsTimeAgo } =
useMarketInsights(market?.symbol, isPerpsInsightsEnabled);
const {
report: perpsInsightsReport,
timeAgo: perpsInsightsTimeAgo,
isLoading: isPerpsInsightsLoading,
} = useMarketInsights(market?.symbol, isPerpsInsightsEnabled);

// Check if current market is in watchlist
const selectIsWatchlist = useMemo(
Expand Down Expand Up @@ -542,13 +545,16 @@ const PerpsMarketDetailsView: React.FC<PerpsMarketDetailsViewProps> = () => {
});

// Track asset screen viewed event - declarative (main's event name)
// Waits for market insights to finish loading so market_insights_displayed
// reflects the actual display state rather than a loading-time snapshot.
usePerpsEventTracking({
eventName: MetaMetricsEvents.PERPS_SCREEN_VIEWED,
conditions: [
!!market,
!!marketStats,
!isLoadingHistory,
!isLoadingPosition,
!isPerpsInsightsLoading,
],
properties: {
[PERPS_EVENT_PROPERTY.SCREEN_TYPE]:
Expand All @@ -558,6 +564,8 @@ const PerpsMarketDetailsView: React.FC<PerpsMarketDetailsViewProps> = () => {
source || PERPS_EVENT_VALUE.SOURCE.PERP_MARKETS,
[PERPS_EVENT_PROPERTY.OPEN_POSITION]: existingPosition ? 1 : 0,
[PERPS_EVENT_PROPERTY.OPEN_ORDER]: openOrders.length,
market_insights_displayed:
isPerpsInsightsEnabled && Boolean(perpsInsightsReport),
// A/B Test context (TAT-1937) - for baseline exposure tracking
...(isButtonColorTestEnabled && {
[PERPS_EVENT_PROPERTY.AB_TEST_BUTTON_COLOR]: buttonColorVariant,
Expand Down Expand Up @@ -1021,6 +1029,9 @@ const PerpsMarketDetailsView: React.FC<PerpsMarketDetailsViewProps> = () => {
// Handler for market insights card tap - navigates to full market insights view
const handleMarketInsightsPress = useCallback(() => {
if (!market?.symbol) return;
track(MetaMetricsEvents.MARKET_INSIGHTS_OPENED, {
perps_market: market.symbol,
});
trace({
name: TraceName.MarketInsightsViewLoad,
op: TraceOperation.MarketInsightsLoad,
Expand All @@ -1030,7 +1041,7 @@ const PerpsMarketDetailsView: React.FC<PerpsMarketDetailsViewProps> = () => {
assetIdentifier: market.symbol,
isPerps: true,
});
}, [market?.symbol, navigation]);
}, [market?.symbol, navigation, track]);

// Handler for order selection - navigates to order details
const handleOrderSelect = useCallback(
Expand Down
Loading