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
1 change: 1 addition & 0 deletions app/actions/rewards/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export {
setGeoRewardsMetadataLoading,
setActiveBoosts,
setActiveBoostsLoading,
acceptVipInvite,
} from '../../reducers/rewards';

export type { RewardsState } from '../../reducers/rewards';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2185,6 +2185,33 @@ describe('PredictBuyPreview', () => {
).toBeOnTheScreen();
});

it('tracks initiated trade transaction with explore entry point', () => {
mockUseRoute.mockReturnValue({
...mockRoute,
params: {
...mockRoute.params,
entryPoint: PredictEventValues.ENTRY_POINT.EXPLORE,
},
});
mockBalance = 1000;
mockBalanceLoading = false;

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

const trackPredictOrderEvent =
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('../../../../../core/Engine').context.PredictController
.trackPredictOrderEvent;

expect(trackPredictOrderEvent).toHaveBeenCalledWith(
expect.objectContaining({
analyticsProperties: expect.objectContaining({
entryPoint: PredictEventValues.ENTRY_POINT.EXPLORE,
}),
}),
);
});

it('handles undefined entryPoint with fallback', () => {
const routeWithoutEntryPoint = {
...mockRoute,
Expand Down
78 changes: 76 additions & 2 deletions app/components/UI/Predict/views/PredictFeed/PredictFeed.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,22 @@ jest.mock('../../components/PredictMarket', () => {
const { View, Text } = jest.requireActual('react-native');
return {
__esModule: true,
default: jest.fn(({ testID }) => (
default: jest.fn(({ testID, entryPoint }) => (
<View testID={testID}>
<Text>Market Card</Text>
</View>
)),
};
});

import PredictMarket from '../../components/PredictMarket';
import { PredictEventValues } from '../../constants/eventNames';

const mockPredictMarket = PredictMarket as jest.Mock;

const getPredictMarketEntryPoints = () =>
mockPredictMarket.mock.calls.map(([props]) => props.entryPoint);

jest.mock('../../components/PredictMarketSkeleton', () => {
const { View } = jest.requireActual('react-native');
return {
Expand Down Expand Up @@ -795,7 +803,7 @@ describe('PredictFeed', () => {
);
});

it('starts session with undefined entry point when not provided', () => {
it('preserves an unattributed session when entry point is not provided', () => {
mockUseRoute.mockReturnValue({
params: {},
});
Expand All @@ -807,6 +815,72 @@ describe('PredictFeed', () => {
'trending',
);
});

it('defaults market list items to predict_feed when entry point is not provided', () => {
mockUseRoute.mockReturnValue({
params: {},
});

render(<PredictFeed />);

const entryPoints = getPredictMarketEntryPoints();
expect(entryPoints.length).toBeGreaterThan(0);
expect(
entryPoints.every(
(entryPoint) =>
entryPoint === PredictEventValues.ENTRY_POINT.PREDICT_FEED,
),
).toBe(true);
});

it('passes explore entryPoint to market list items from route params', () => {
mockUseRoute.mockReturnValue({
params: {
entryPoint: PredictEventValues.ENTRY_POINT.EXPLORE,
},
});

render(<PredictFeed />);

expect(mockSessionManager.startSession).toHaveBeenCalledWith(
PredictEventValues.ENTRY_POINT.EXPLORE,
'trending',
);
const entryPoints = getPredictMarketEntryPoints();
expect(entryPoints.length).toBeGreaterThan(0);
expect(
entryPoints.every(
(entryPoint) => entryPoint === PredictEventValues.ENTRY_POINT.EXPLORE,
),
).toBe(true);
});

it('uses prop entryPoint for embedded feed list items and session attribution', () => {
mockUseRoute.mockReturnValue({
params: {
entryPoint: PredictEventValues.ENTRY_POINT.EXPLORE,
},
});

render(
<PredictFeed
entryPoint={PredictEventValues.ENTRY_POINT.HOME_SECTION}
/>,
);

expect(mockSessionManager.startSession).toHaveBeenCalledWith(
PredictEventValues.ENTRY_POINT.HOME_SECTION,
'trending',
);
const entryPoints = getPredictMarketEntryPoints();
expect(entryPoints.length).toBeGreaterThan(0);
expect(
entryPoints.every(
(entryPoint) =>
entryPoint === PredictEventValues.ENTRY_POINT.HOME_SECTION,
),
).toBe(true);
});
});

describe('search debounce behavior', () => {
Expand Down
21 changes: 16 additions & 5 deletions app/components/UI/Predict/views/PredictFeed/PredictFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ const PredictMarketListItem: React.FC<PredictMarketListItemProps> = ({
interface PredictTabContentProps {
category: PredictCategory;
isActive: boolean;
listEntryPoint: PredictEntryPoint;
scrollHandler: ReturnType<typeof useAnimatedScrollHandler>;
headerHeight: number;
tabBarHeight: number;
Expand All @@ -263,6 +264,7 @@ interface PredictTabContentProps {
const PredictTabContent: React.FC<PredictTabContentProps> = ({
category,
isActive,
listEntryPoint,
scrollHandler,
headerHeight,
tabBarHeight,
Expand Down Expand Up @@ -317,15 +319,15 @@ const PredictTabContent: React.FC<PredictTabContentProps> = ({
(info: { item: PredictMarketType; index: number }) => (
<PredictMarketListItem
market={info.item}
entryPoint={PredictEventValues.ENTRY_POINT.PREDICT_FEED}
entryPoint={listEntryPoint}
testID={getPredictMarketListSelector.marketCardByCategory(
category,
info.index + 1, // E2E tests use 1-based indexing
)}
transactionActiveAbTests={transactionActiveAbTests}
/>
),
[category, transactionActiveAbTests],
[category, listEntryPoint, transactionActiveAbTests],
);

const keyExtractor = useCallback((item: PredictMarketType) => item.id, []);
Expand Down Expand Up @@ -448,6 +450,7 @@ interface PredictFeedTabsProps {
tabs: FeedTab[];
activeIndex: number;
onPageChange: (index: number) => void;
listEntryPoint: PredictEntryPoint;
scrollHandler: ReturnType<typeof useAnimatedScrollHandler>;
headerHeight: number;
tabBarHeight: number;
Expand All @@ -460,6 +463,7 @@ const PredictFeedTabs: React.FC<PredictFeedTabsProps> = ({
tabs,
activeIndex,
onPageChange,
listEntryPoint,
scrollHandler,
headerHeight,
tabBarHeight,
Expand Down Expand Up @@ -499,6 +503,7 @@ const PredictFeedTabs: React.FC<PredictFeedTabsProps> = ({
<PredictTabContent
category={tab.key}
isActive={index === activeIndex}
listEntryPoint={listEntryPoint}
scrollHandler={scrollHandler}
headerHeight={headerHeight}
tabBarHeight={tabBarHeight}
Expand Down Expand Up @@ -644,13 +649,15 @@ const PredictSearchOverlay: React.FC<PredictSearchOverlayProps> = ({

interface PredictFeedProps {
hideHeader?: boolean;
entryPoint?: PredictEntryPoint;
onHeaderHiddenChange?: (hidden: boolean) => void;
walletHeaderTranslateY?: SharedValue<number>;
walletHeaderHeight?: number;
}

const PredictFeed: React.FC<PredictFeedProps> = ({
hideHeader = false,
entryPoint: propEntryPoint,
onHeaderHiddenChange,
walletHeaderTranslateY,
walletHeaderHeight,
Expand All @@ -663,6 +670,9 @@ const PredictFeed: React.FC<PredictFeedProps> = ({
const route =
useRoute<RouteProp<PredictNavigationParamList, 'PredictMarketList'>>();
const transactionActiveAbTests = route.params?.transactionActiveAbTests;
const feedEntryPoint = propEntryPoint ?? route.params?.entryPoint;
const listEntryPoint =
feedEntryPoint ?? PredictEventValues.ENTRY_POINT.PREDICT_FEED;

const headerRef = useRef<View>(null);
const tabBarRef = useRef<View>(null);
Expand Down Expand Up @@ -691,20 +701,20 @@ const PredictFeed: React.FC<PredictFeedProps> = ({
traceName: TraceName.PredictFeedView,
conditions: [!isSearchVisible],
debugContext: {
entryPoint: route.params?.entryPoint,
entryPoint: feedEntryPoint,
isSearchVisible,
},
});

useEffect(() => {
sessionManager.enableAppStateListener();
sessionManager.startSession(route.params?.entryPoint, initialTabKey);
sessionManager.startSession(feedEntryPoint, initialTabKey);

return () => {
sessionManager.endSession();
sessionManager.disableAppStateListener();
};
}, [route.params?.entryPoint, sessionManager, initialTabKey]);
}, [feedEntryPoint, sessionManager, initialTabKey]);

useFocusEffect(
useCallback(() => {
Expand Down Expand Up @@ -808,6 +818,7 @@ const PredictFeed: React.FC<PredictFeedProps> = ({
tabs={tabs}
activeIndex={activeIndex}
onPageChange={handlePageChange}
listEntryPoint={listEntryPoint}
scrollHandler={scrollHandler}
headerHeight={headerHeight}
tabBarHeight={tabBarHeight + 6}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,31 @@ describe('PredictMarketDetails', () => {
expect(screen.getByText(mockMarket.title)).toBeOnTheScreen();
});

it('tracks market details opened with explore entry point from route params', async () => {
setupPredictMarketDetailsTest(
{},
{
params: {
marketId: 'market-1',
entryPoint: PredictEventValues.ENTRY_POINT.EXPLORE,
},
},
);

const trackMarketDetailsOpened =
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('../../../../../core/Engine').context.PredictController
.trackMarketDetailsOpened;

await waitFor(() => {
expect(trackMarketDetailsOpened).toHaveBeenCalledWith(
expect.objectContaining({
entryPoint: PredictEventValues.ENTRY_POINT.EXPLORE,
}),
);
});
});

it('displays loading state when market is fetching', () => {
setupPredictMarketDetailsTest(
{},
Expand Down
12 changes: 12 additions & 0 deletions app/components/UI/Rewards/RewardsNavigator.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ jest.mock('./Views/RewardsVipView', () => {
};
});

jest.mock('./Views/RewardsVipSplashView', () => {
const ReactActual = jest.requireActual('react');
const { View, Text } = jest.requireActual('react-native');
return function MockRewardsVipSplashView() {
return ReactActual.createElement(
View,
{ testID: 'rewards-vip-splash-view' },
ReactActual.createElement(Text, null, 'Rewards VIP Splash View'),
);
};
});

jest.mock('./Views/CampaignTourStepView', () => {
const ReactActual = jest.requireActual('react');
const { View, Text } = jest.requireActual('react-native');
Expand Down
12 changes: 12 additions & 0 deletions app/components/UI/Rewards/RewardsNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import OnboardingNavigator from './OnboardingNavigator';
import RewardsDashboard from './Views/RewardsDashboard';
import ReferralRewardsView from './Views/RewardsReferralView';
import RewardsSettingsView from './Views/RewardsSettingsView';
import RewardsVipSplashView from './Views/RewardsVipSplashView';
import RewardsVipView from './Views/RewardsVipView';
import RewardsVipTiersView from './Views/RewardsVipTiersView';
import CampaignsView from './Views/CampaignsView';
Expand Down Expand Up @@ -225,6 +226,17 @@ const RewardsNavigator: React.FC = () => {
component={RewardsSettingsView}
options={{ headerShown: false }}
/>
<Stack.Screen
name={Routes.REWARDS_VIP_SPLASH_VIEW}
component={RewardsVipSplashView}
options={{
headerShown: false,
...TransitionPresets.SlideFromRightIOS,
cardStyle: {
backgroundColor: darkTheme.colors.background.default,
},
}}
/>
<Stack.Screen
name={Routes.REWARDS_VIP_VIEW}
component={RewardsVipView}
Expand Down
Loading
Loading