Skip to content
Open
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 @@ -64,7 +64,6 @@ const PredictGameDetailsContent: React.FC<PredictGameDetailsContentProps> = ({
const { data: activePositions = [] } = usePredictPositions({
marketId: market.id,
claimable: false,
refetchInterval: 10000,
});
const { data: claimablePositions = [] } = usePredictPositions({
marketId: market.id,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react-native';
import { useSelector } from 'react-redux';
import PredictPicks from './PredictPicks';
import { usePredictActionGuard } from '../../hooks/usePredictActionGuard';
import {
PredictMarketStatus,
PredictPositionStatus,
Recurrence,
type PredictPosition,
type PredictMarket,
type PredictMarketGame,
} from '../../types';
import { formatPrice } from '../../utils/format';
import Routes from '../../../../../constants/navigation/Routes';
Expand All @@ -19,6 +22,28 @@ jest.mock('@react-navigation/native', () => ({
navigate: mockNavigate,
}),
}));
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useSelector: jest.fn(),
}));
jest.mock('../PredictPositionDetail', () => {
const { View } = jest.requireActual('react-native');
return {
__esModule: true,
default: ({
position,
marketStatus,
}: {
position: { id: string };
marketStatus: string;
}) => (
<View
testID={`predict-position-detail-${position.id}`}
accessibilityLabel={marketStatus}
/>
),
};
});
jest.mock('../../hooks/usePredictActionGuard');
jest.mock('../../hooks/usePredictLivePositions', () => ({
usePredictLivePositions: jest.fn((positions: unknown[]) => ({
Expand All @@ -29,6 +54,8 @@ jest.mock('../../hooks/usePredictLivePositions', () => ({
}));
jest.mock('../../utils/format');

const mockUseSelector = useSelector as jest.MockedFunction<typeof useSelector>;

const mockUsePredictActionGuard = usePredictActionGuard as jest.MockedFunction<
typeof usePredictActionGuard
>;
Expand Down Expand Up @@ -79,6 +106,35 @@ const createMockMarket = (
...overrides,
});

const createMockGame = (
overrides: Partial<PredictMarketGame> = {},
): PredictMarketGame => ({
id: 'game-1',
startTime: '2025-12-31T00:00:00Z',
status: 'scheduled',
league: 'nba',
elapsed: null,
period: null,
score: null,
homeTeam: {
id: 'team-1',
name: 'Lakers',
logo: 'https://example.com/lakers.png',
abbreviation: 'LAL',
color: 'purple',
alias: 'Lakers',
},
awayTeam: {
id: 'team-2',
name: 'Celtics',
logo: 'https://example.com/celtics.png',
abbreviation: 'BOS',
color: 'green',
alias: 'Celtics',
},
...overrides,
});

const createMockPosition = (
overrides: Partial<PredictPosition> = {},
): PredictPosition => ({
Expand Down Expand Up @@ -111,6 +167,7 @@ describe('PredictPicks', () => {
beforeEach(() => {
jest.clearAllMocks();
mockNavigate.mockClear();
mockUseSelector.mockReturnValue([]);
mockUsePredictActionGuard.mockReturnValue({
executeGuardedAction: mockExecuteGuardedAction,
isEligible: true,
Expand Down Expand Up @@ -550,4 +607,118 @@ describe('PredictPicks', () => {
);
});
});

describe('PredictPositionDetail rendering (extendedSportsMarkets flag)', () => {
it('renders PredictPositionDetail for live positions when league is in extendedLeagues', () => {
mockUseSelector.mockReturnValue(['nba']);
const market = createMockMarket({ game: createMockGame() });
const position = createMockPosition({ id: 'pos-live' });

render(
<PredictPicks
market={market}
positions={[position]}
claimablePositions={[]}
/>,
);

expect(
screen.getByTestId('predict-position-detail-pos-live'),
).toBeOnTheScreen();
});

it('renders PredictPositionDetail for claimable positions with CLOSED status', () => {
mockUseSelector.mockReturnValue(['nba']);
const market = createMockMarket({ game: createMockGame() });
const position = createMockPosition({
id: 'pos-claimable',
claimable: true,
});

render(
<PredictPicks
market={market}
positions={[]}
claimablePositions={[position]}
/>,
);

const detail = screen.getByTestId(
'predict-position-detail-pos-claimable',
);
expect(detail).toBeOnTheScreen();
expect(detail.props.accessibilityLabel).toBe(PredictMarketStatus.CLOSED);
});

it('renders PredictPositionDetail with market status for live positions', () => {
mockUseSelector.mockReturnValue(['nba']);
const market = createMockMarket({
status: 'open',
game: createMockGame(),
});

render(
<PredictPicks
market={market}
positions={[createMockPosition({ id: 'pos-1' })]}
claimablePositions={[]}
/>,
);

const detail = screen.getByTestId('predict-position-detail-pos-1');
expect(detail.props.accessibilityLabel).toBe(PredictMarketStatus.OPEN);
});

it('renders PredictPickItem when league is not in extendedLeagues', () => {
mockUseSelector.mockReturnValue(['ucl']);
const market = createMockMarket({ game: createMockGame() });

render(
<PredictPicks
market={market}
positions={[createMockPosition({ id: 'pos-1' })]}
claimablePositions={[]}
/>,
);

expect(screen.queryByTestId('predict-position-detail-pos-1')).toBeNull();
expect(screen.getByText(/Yes/)).toBeOnTheScreen();
});

it('renders PredictPickItem when market has no game', () => {
mockUseSelector.mockReturnValue(['nba']);

render(
<PredictPicks
market={createMockMarket()}
positions={[createMockPosition({ id: 'pos-1' })]}
claimablePositions={[]}
/>,
);

expect(screen.queryByTestId('predict-position-detail-pos-1')).toBeNull();
});

it('renders both live and claimable as PredictPositionDetail when flag enabled', () => {
mockUseSelector.mockReturnValue(['nba']);
const market = createMockMarket({ game: createMockGame() });

render(
<PredictPicks
market={market}
positions={[createMockPosition({ id: 'pos-live' })]}
claimablePositions={[
createMockPosition({ id: 'pos-claim', claimable: true }),
]}
/>,
);

expect(
screen.getByTestId('predict-position-detail-pos-live'),
).toBeOnTheScreen();
expect(
screen.getByTestId('predict-position-detail-pos-claim'),
).toBeOnTheScreen();
});
});
});
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { Box } from '@metamask/design-system-react-native';
import React from 'react';
import { useSelector } from 'react-redux';
import { NavigationProp, useNavigation } from '@react-navigation/native';
import { usePredictLivePositions } from '../../hooks/usePredictLivePositions';
import { PredictEventValues } from '../../constants/eventNames';
import { NavigationProp, useNavigation } from '@react-navigation/native';
import { usePredictActionGuard } from '../../hooks/usePredictActionGuard';
import { PredictMarket, PredictPosition } from '../../types';
import {
PredictMarket,
PredictMarketStatus,
PredictPosition,
} from '../../types';
import Routes from '../../../../../constants/navigation/Routes';
import { PredictNavigationParamList } from '../../types/navigation';
import { selectExtendedSportsMarketsLeagues } from '../../selectors/featureFlags';
import PredictPickItem from './PredictPickItem';
import PredictPositionDetail from '../PredictPositionDetail';
import {
PREDICT_PICKS_TEST_ID,
PREDICT_PICKS_TEST_IDS,
Expand All @@ -34,6 +41,11 @@ const PredictPicks: React.FC<PredictPicksProps> = ({
navigation,
});

const extendedLeagues = useSelector(selectExtendedSportsMarketsLeagues);
const usePositionDetail = market.game?.league
? extendedLeagues.includes(market.game.league)
: false;

const onCashOut = (position: PredictPosition) => {
executeGuardedAction(
() => {
Expand All @@ -51,6 +63,29 @@ const PredictPicks: React.FC<PredictPicksProps> = ({
);
};

if (usePositionDetail) {
return (
<Box testID={testID} twClassName="flex-col pt-3">
{livePositions.map((position) => (
<PredictPositionDetail
key={position.id}
position={position}
market={market}
marketStatus={market.status as PredictMarketStatus}
/>
))}
{claimablePositions.map((position) => (
<PredictPositionDetail
key={position.id}
position={position}
market={market}
marketStatus={PredictMarketStatus.CLOSED}
/>
))}
</Box>
);
}

return (
<Box testID={testID} twClassName="flex-col">
{livePositions.map((position) => (
Expand Down
4 changes: 2 additions & 2 deletions app/components/UI/Predict/hooks/useGameDetailsTabs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ describe('useGameDetailsTabs', () => {
expect(result.current.activeTab).toBe(0);
});

it('resets activeTab to 0 when it exceeds tabs length', () => {
it('preserves activeTab when tabs change', () => {
const { result, rerender } = renderHook(
(props) => useGameDetailsTabs(props),
{
Expand All @@ -156,7 +156,7 @@ describe('useGameDetailsTabs', () => {
expect(result.current.activeTab).toBe(1);

rerender({ ...defaultParams, activePositions: [] });
expect(result.current.activeTab).toBe(0);
expect(result.current.activeTab).toBe(1);
});
});

Expand Down
9 changes: 1 addition & 8 deletions app/components/UI/Predict/hooks/useGameDetailsTabs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { strings } from '../../../../../locales/i18n';
import { selectExtendedSportsMarketsLeagues } from '../selectors/featureFlags';
Expand Down Expand Up @@ -39,13 +39,6 @@ export function useGameDetailsTabs({
return result;
}, [hasPositions]);

useEffect(() => {
if (!enabled) return;
if (activeTab >= tabs.length) {
setActiveTab(0);
}
}, [tabs, activeTab, enabled]);

const handleTabPress = useCallback((tabIndex: number) => {
setActiveTab(tabIndex);
}, []);
Expand Down
Loading