Skip to content

Commit ff9686a

Browse files
authored
Merge branch 'main' into feat/musd-795-money-home-screen-onboarding-stepper
2 parents 03fbd73 + ea0f772 commit ff9686a

24 files changed

Lines changed: 900 additions & 55 deletions

app/components/UI/Predict/hooks/useFeaturedCarouselData.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,26 @@ describe('useFeaturedCarouselData', () => {
103103
expect(result.current.error).toBeNull();
104104
});
105105

106+
it('filters child more-market cards', async () => {
107+
const { Wrapper } = createWrapper();
108+
const parentMarket = createMockMarket({ id: 'parent-market' });
109+
const childMarket = createMockMarket({
110+
id: 'child-market',
111+
parentMarketId: 'parent-market',
112+
});
113+
mockGetCarouselMarkets.mockResolvedValue([parentMarket, childMarket]);
114+
115+
const { result } = renderHook(() => useFeaturedCarouselData(), {
116+
wrapper: Wrapper,
117+
});
118+
119+
await waitFor(() => {
120+
expect(result.current.isLoading).toBe(false);
121+
});
122+
123+
expect(result.current.markets).toEqual([parentMarket]);
124+
});
125+
106126
it('returns error when controller throws', async () => {
107127
const { Wrapper } = createWrapper();
108128
mockGetCarouselMarkets.mockRejectedValue(new Error('Network error'));

app/components/UI/Predict/hooks/useFeaturedCarouselData.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { predictQueries } from '../queries';
77
import { selectPredictUpDownEnabledFlag } from '../selectors/featureFlags';
88
import type { PredictMarket } from '../types';
99
import { isCryptoUpDown } from '../utils/cryptoUpDown';
10+
import { filterStandaloneMarkets } from '../utils/feed';
1011
import { ensureError } from '../utils/predictErrorHandler';
1112

1213
export interface UseFeaturedCarouselDataResult {
@@ -40,7 +41,7 @@ export const useFeaturedCarouselData = (): UseFeaturedCarouselDataResult => {
4041
}, [query.error]);
4142

4243
const markets = useMemo(() => {
43-
const data = query.data ?? [];
44+
const data = filterStandaloneMarkets(query.data ?? []);
4445
if (upDownEnabled) {
4546
return data;
4647
}

app/components/UI/Predict/hooks/usePredictMarketData.test.tsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,63 @@ describe('usePredictMarketData', () => {
219219
);
220220
});
221221

222+
it('filters child more-market cards without disabling pagination', async () => {
223+
const rawMarkets = Array.from({ length: 20 }, (_, index) => ({
224+
...mockMarketData[0],
225+
id: `market-${index}`,
226+
slug: `market-${index}`,
227+
parentMarketId: index >= 18 ? 'parent-market' : undefined,
228+
}));
229+
mockGetMarkets.mockResolvedValue(rawMarkets);
230+
231+
const { result } = renderHook(() => usePredictMarketData({ pageSize: 20 }));
232+
233+
await waitFor(() => {
234+
expect(result.current.isFetching).toBe(false);
235+
});
236+
237+
expect(result.current.marketData).toHaveLength(18);
238+
expect(result.current.marketData.map((market) => market.id)).toEqual(
239+
rawMarkets.slice(0, 18).map((market) => market.id),
240+
);
241+
expect(result.current.hasMore).toBe(true);
242+
});
243+
244+
it('uses raw page offsets when loading more after child cards are filtered', async () => {
245+
const firstRawPage = Array.from({ length: 20 }, (_, index) => ({
246+
...mockMarketData[0],
247+
id: `first-page-market-${index}`,
248+
slug: `first-page-market-${index}`,
249+
parentMarketId: index >= 18 ? 'parent-market' : undefined,
250+
}));
251+
const secondRawPage = Array.from({ length: 5 }, (_, index) => ({
252+
...mockMarketData[0],
253+
id: `second-page-market-${index}`,
254+
slug: `second-page-market-${index}`,
255+
}));
256+
257+
mockGetMarkets
258+
.mockResolvedValueOnce(firstRawPage)
259+
.mockResolvedValueOnce(secondRawPage);
260+
261+
const { result } = renderHook(() => usePredictMarketData({ pageSize: 20 }));
262+
263+
await waitFor(() => {
264+
expect(result.current.isFetching).toBe(false);
265+
});
266+
267+
await act(async () => {
268+
await result.current.fetchMore();
269+
});
270+
271+
expect(mockGetMarkets).toHaveBeenNthCalledWith(
272+
2,
273+
expect.objectContaining({ limit: 20, offset: 20 }),
274+
);
275+
expect(result.current.marketData).toHaveLength(23);
276+
expect(result.current.hasMore).toBe(false);
277+
});
278+
222279
it('handle null market data', async () => {
223280
mockGetMarkets.mockResolvedValue(null);
224281

app/components/UI/Predict/hooks/usePredictMarketData.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Logger from '../../../../util/Logger';
1313
import { PREDICT_CONSTANTS } from '../constants/errors';
1414
import { ensureError } from '../utils/predictErrorHandler';
1515
import { PredictCategory, PredictMarket } from '../types';
16+
import { filterStandaloneMarkets } from '../utils/feed';
1617

1718
export interface UsePredictMarketDataOptions {
1819
q?: string;
@@ -147,12 +148,13 @@ export const usePredictMarketData = (
147148

148149
const hasMoreData = markets.length >= pageSize;
149150
setHasMore(hasMoreData);
151+
const visibleMarkets = filterStandaloneMarkets(markets);
150152

151153
if (isLoadMore) {
152154
setMarketData((prevData) => {
153155
// Use a Set to efficiently deduplicate by ID
154156
const existingIds = new Set(prevData.map((event) => event.id));
155-
const newEvents = markets.filter(
157+
const newEvents = visibleMarkets.filter(
156158
(event) => !existingIds.has(event.id),
157159
);
158160
const accumulated = [...prevData, ...newEvents];
@@ -162,7 +164,7 @@ export const usePredictMarketData = (
162164
currentOffsetRef.current += pageSize;
163165
} else {
164166
// Replace data for initial load or refresh
165-
setMarketData(refine ? refine(markets) : markets);
167+
setMarketData(refine ? refine(visibleMarkets) : visibleMarkets);
166168
setCurrentOffset(pageSize);
167169
currentOffsetRef.current = pageSize;
168170
}

app/components/UI/Predict/providers/polymarket/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export interface PolymarketApiEvent {
124124
period?: PredictGamePeriod;
125125
live?: boolean;
126126
ended?: boolean;
127-
parentEventId?: string | number;
127+
parentEventId?: string | number | null;
128128
}
129129

130130
export interface PolymarketApiActivity {

app/components/UI/Predict/providers/polymarket/utils.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,4 +746,28 @@ describe('polymarket utils', () => {
746746
}),
747747
).resolves.toBe(false);
748748
});
749+
750+
it('preserves parent market id when parsing Polymarket events', () => {
751+
const event: PolymarketApiEvent = {
752+
id: 'child-event',
753+
slug: 'child-event',
754+
title: 'Child Event',
755+
description: 'Child event description',
756+
icon: '',
757+
closed: false,
758+
series: [],
759+
markets: [],
760+
tags: [],
761+
liquidity: 0,
762+
volume: 0,
763+
parentEventId: 'parent-market',
764+
};
765+
766+
expect(parsePolymarketEvents([event], 'trending')).toEqual([
767+
expect.objectContaining({
768+
id: 'child-event',
769+
parentMarketId: 'parent-market',
770+
}),
771+
]);
772+
});
749773
});

app/components/UI/Predict/providers/polymarket/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,6 +1112,9 @@ export const parsePolymarketEvents = (
11121112
volume: event.volume,
11131113
game,
11141114
...(seriesData && { series: seriesData }),
1115+
...(event.parentEventId !== undefined && {
1116+
parentMarketId: event.parentEventId,
1117+
}),
11151118
};
11161119
},
11171120
);

app/components/UI/Predict/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ export type PredictMarket = {
116116
volume: number;
117117
game?: PredictMarketGame;
118118
series?: PredictSeries;
119+
parentMarketId?: string | number | null;
119120
childMarketIds?: string[];
120121
};
121122

app/components/UI/Predict/utils/feed.test.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { deduplicateSeriesMarkets } from './feed';
1+
import { deduplicateSeriesMarkets, filterStandaloneMarkets } from './feed';
22
import { Recurrence, type PredictMarket } from '../types';
33

44
const createMockMarket = (
@@ -122,3 +122,31 @@ describe('deduplicateSeriesMarkets', () => {
122122
expect(result).toEqual([single]);
123123
});
124124
});
125+
126+
describe('filterStandaloneMarkets', () => {
127+
it('removes markets with a parent market id', () => {
128+
const parent = createMockMarket('parent');
129+
const emptyParent = createMockMarket('empty-parent', {
130+
parentMarketId: '',
131+
});
132+
const nullParent = createMockMarket('null-parent', {
133+
parentMarketId: null,
134+
});
135+
const child = createMockMarket('child', {
136+
parentMarketId: 'parent',
137+
});
138+
const numericChild = createMockMarket('numeric-child', {
139+
parentMarketId: 123,
140+
});
141+
142+
const result = filterStandaloneMarkets([
143+
parent,
144+
emptyParent,
145+
nullParent,
146+
child,
147+
numericChild,
148+
]);
149+
150+
expect(result).toEqual([parent, emptyParent, nullParent]);
151+
});
152+
});

app/components/UI/Predict/utils/feed.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
import type { PredictMarket } from '../types';
22
import { isCryptoUpDown } from './cryptoUpDown';
33

4+
export function isStandaloneMarket(market: PredictMarket): boolean {
5+
const { parentMarketId } = market;
6+
return (
7+
parentMarketId === undefined ||
8+
parentMarketId === null ||
9+
String(parentMarketId).trim() === ''
10+
);
11+
}
12+
13+
export function filterStandaloneMarkets(
14+
markets: PredictMarket[],
15+
): PredictMarket[] {
16+
return markets.filter(isStandaloneMarket);
17+
}
18+
419
/**
520
* Keeps only the first occurrence of each Crypto Up/Down series slug.
621
* Non-Up/Down markets pass through unchanged.

0 commit comments

Comments
 (0)