diff --git a/app/components/UI/Predict/constants/feedConfig.test.ts b/app/components/UI/Predict/constants/feedConfig.test.ts index c279111e85c..f1af70a02fc 100644 --- a/app/components/UI/Predict/constants/feedConfig.test.ts +++ b/app/components/UI/Predict/constants/feedConfig.test.ts @@ -29,9 +29,9 @@ describe('feedConfig', () => { 'crypto', 'live', 'trending', - 'popular-today', ]); expect(isPredictFeedId('world-cup')).toBe(false); + expect(isPredictFeedId('popular-today')).toBe(false); }); it.each(PREDICT_FEED_IDS)('resolves known feed id %s', (feedId) => { @@ -95,9 +95,7 @@ describe('feedConfig', () => { }); it('represents hidden-tab feeds with exactly one tab', () => { - ( - ['politics', 'crypto', 'live', 'trending', 'popular-today'] as const - ).forEach((feedId) => { + (['politics', 'crypto', 'live', 'trending'] as const).forEach((feedId) => { expect(PREDICT_FEED_REGISTRY[feedId].tabs).toHaveLength(1); }); }); @@ -128,9 +126,6 @@ describe('feedConfig', () => { }); it('resolves the dynamic filter config for a feed from the registry', () => { - expect(resolvePredictFeedDynamicFilterConfig('popular-today')).toBe( - PREDICT_FEED_REGISTRY['popular-today'].tabs[0].filters.dynamic, - ); expect(resolvePredictFeedDynamicFilterConfig('trending')).toBe( PREDICT_FEED_REGISTRY.trending.tabs[0].filters.dynamic, ); diff --git a/app/components/UI/Predict/constants/feedConfig.ts b/app/components/UI/Predict/constants/feedConfig.ts index 9fef011b20d..22379a231b0 100644 --- a/app/components/UI/Predict/constants/feedConfig.ts +++ b/app/components/UI/Predict/constants/feedConfig.ts @@ -9,7 +9,6 @@ export const PREDICT_FEED_IDS = [ 'crypto', 'live', 'trending', - 'popular-today', ] as const; export type PredictFeedId = (typeof PREDICT_FEED_IDS)[number]; @@ -246,39 +245,6 @@ export const PREDICT_FEED_REGISTRY: Record = { }, ], }, - 'popular-today': { - id: 'popular-today', - titleKey: 'predict.feed.popular_today', - header: { - showBackButton: true, - showSearchButton: true, - }, - tabs: [ - { - id: 'all', - titleKey: 'predict.feed.popular_today', - defaultFilterId: 'all', - filters: { - static: [ - createAllFilter({ - status: 'open', - order: 'volume24hr', - limit: 12, - }), - ], - dynamic: { - source: 'related-tags', - baseTagSlug: 'all', - baseParams: { - status: 'open', - order: 'volume24hr', - limit: 12, - }, - }, - }, - }, - ], - }, }; const PREDICT_FEED_ID_SET = new Set(PREDICT_FEED_IDS); diff --git a/app/components/UI/Predict/hooks/usePredictFeedConfig.test.ts b/app/components/UI/Predict/hooks/usePredictFeedConfig.test.ts index 935e6c5f393..2d27218b4d0 100644 --- a/app/components/UI/Predict/hooks/usePredictFeedConfig.test.ts +++ b/app/components/UI/Predict/hooks/usePredictFeedConfig.test.ts @@ -187,7 +187,6 @@ describe('usePredictFeedConfig', () => { ['crypto', 'crypto'], ['sports', 'sports'], ['trending', 'all'], - ['popular-today', 'all'], ])( 'passes baseTagSlug "%s" -> "%s" to usePredictFilterOptions', (feedId, expectedSlug) => { diff --git a/app/components/UI/Predict/types/navigation.ts b/app/components/UI/Predict/types/navigation.ts index e2ee4c3d1ca..6eeaa7d7029 100644 --- a/app/components/UI/Predict/types/navigation.ts +++ b/app/components/UI/Predict/types/navigation.ts @@ -59,7 +59,7 @@ export interface PredictMarketListRouteParams { * Generic Predict feed route parameters. * * Consumed by the config-driven `PredictFeedView` (powers Sports / Politics / - * Crypto / Live / Trending / Popular Today). Carries stable IDs only — the + * Crypto / Live / Trending). Carries stable IDs only — the * view resolves them into a render-ready config via `usePredictFeedConfig`. * The route registration + deeplink parsing that populates these params lands * separately (route + deeplinks ticket); the view reads them via `useRoute`. diff --git a/app/components/UI/Predict/views/PredictFeedView/PredictFeedView.test.tsx b/app/components/UI/Predict/views/PredictFeedView/PredictFeedView.test.tsx index 0f28fb762a3..2e0fbe52c40 100644 --- a/app/components/UI/Predict/views/PredictFeedView/PredictFeedView.test.tsx +++ b/app/components/UI/Predict/views/PredictFeedView/PredictFeedView.test.tsx @@ -593,10 +593,10 @@ describe('PredictFeedView', () => { }); it('delays trackFeedViewed until a dynamic initialFilterId resolves', () => { - // Simulate entry from a Popular-Today home chip: initialFilterId targets a + // Simulate entry from a Popular Today home chip: initialFilterId targets a // dynamic filter that hasn't loaded yet. mockRouteParams = { - feedId: 'popular-today', + feedId: 'trending', initialFilterId: 'soccer', entryPoint: 'home_chip', }; @@ -605,7 +605,7 @@ describe('PredictFeedView', () => { // default ('all') because the dynamic option hasn't resolved yet. mockUsePredictFeedConfig.mockReturnValue( feedConfigResult({ - feedId: 'popular-today', + feedId: 'trending', dynamicFilters: { status: 'loading' }, activeFilterId: 'all', }), @@ -619,7 +619,7 @@ describe('PredictFeedView', () => { // Dynamic filters resolve; the pending filter is now applied. mockUsePredictFeedConfig.mockReturnValue( feedConfigResult({ - feedId: 'popular-today', + feedId: 'trending', dynamicFilters: { status: 'ready' }, activeFilterId: 'soccer', filters: [ @@ -649,7 +649,7 @@ describe('PredictFeedView', () => { expect(mockTrackFeedViewed).toHaveBeenCalledTimes(1); expect(mockTrackFeedViewed).toHaveBeenCalledWith({ - feedId: 'popular-today', + feedId: 'trending', tabId: 'basketball', filterId: 'soccer', trackingMode: 'focus', @@ -659,7 +659,7 @@ describe('PredictFeedView', () => { it('fires trackFeedViewed with fallback filter when dynamic loading fails (unavailable)', () => { mockRouteParams = { - feedId: 'popular-today', + feedId: 'trending', initialFilterId: 'soccer', entryPoint: 'home_chip', }; @@ -667,7 +667,7 @@ describe('PredictFeedView', () => { // Initially loading. mockUsePredictFeedConfig.mockReturnValue( feedConfigResult({ - feedId: 'popular-today', + feedId: 'trending', dynamicFilters: { status: 'loading' }, activeFilterId: 'all', }), @@ -679,7 +679,7 @@ describe('PredictFeedView', () => { // Dynamic filters fail — status becomes unavailable, filter stays 'all'. mockUsePredictFeedConfig.mockReturnValue( feedConfigResult({ - feedId: 'popular-today', + feedId: 'trending', dynamicFilters: { status: 'unavailable' }, activeFilterId: 'all', }), @@ -698,14 +698,14 @@ describe('PredictFeedView', () => { // response. The previous condition only fell back on 'unavailable', so // isFilterSettled stayed false permanently. mockRouteParams = { - feedId: 'popular-today', + feedId: 'trending', initialFilterId: 'soccer', entryPoint: 'home_chip', }; mockUsePredictFeedConfig.mockReturnValue( feedConfigResult({ - feedId: 'popular-today', + feedId: 'trending', dynamicFilters: { status: 'loading' }, activeFilterId: 'all', }), @@ -717,7 +717,7 @@ describe('PredictFeedView', () => { // Dynamic filters loaded successfully but 'soccer' is not in the list. mockUsePredictFeedConfig.mockReturnValue( feedConfigResult({ - feedId: 'popular-today', + feedId: 'trending', dynamicFilters: { status: 'ready' }, activeFilterId: 'all', // Only 'trending' is in the response — 'soccer' is absent. diff --git a/app/components/UI/Predict/views/PredictFeedView/PredictFeedView.tsx b/app/components/UI/Predict/views/PredictFeedView/PredictFeedView.tsx index f8525809aca..0522ebef342 100644 --- a/app/components/UI/Predict/views/PredictFeedView/PredictFeedView.tsx +++ b/app/components/UI/Predict/views/PredictFeedView/PredictFeedView.tsx @@ -46,8 +46,8 @@ const INITIAL_SKELETON_COUNT = 4; /** * Generic, config-driven Predict feed screen. * - * Powers every configured feed (Sports / Politics / Crypto / Live / Trending / - * Popular Today) from one component. Presentational: it consumes + * Powers every configured feed (Sports / Politics / Crypto / Live / Trending) + * from one component. Presentational: it consumes * `usePredictFeedConfig` for the render-ready config + tab/filter selection * state and `usePredictMarketList` for the active tab/filter's market data — * it owns no hydration/dedup/fallback logic itself. diff --git a/app/components/UI/Predict/views/PredictHome/components/PredictPopularTodaySection/PredictPopularTodaySection.test.tsx b/app/components/UI/Predict/views/PredictHome/components/PredictPopularTodaySection/PredictPopularTodaySection.test.tsx index 3914d473773..fb3931936c0 100644 --- a/app/components/UI/Predict/views/PredictHome/components/PredictPopularTodaySection/PredictPopularTodaySection.test.tsx +++ b/app/components/UI/Predict/views/PredictHome/components/PredictPopularTodaySection/PredictPopularTodaySection.test.tsx @@ -193,7 +193,7 @@ describe('PredictPopularTodaySection', () => { ).toBeNull(); }); - it('navigates to the Popular Today feed when the header is pressed', () => { + it('navigates to the Trending feed when the header is pressed', () => { setFilterOptions({ filterOptions: [createFilterOption('elections', 'Elections')], }); @@ -205,7 +205,7 @@ describe('PredictPopularTodaySection', () => { expect(mockNavigate).toHaveBeenCalledWith(Routes.PREDICT.ROOT, { screen: Routes.PREDICT.FEED, params: { - feedId: 'popular-today', + feedId: 'trending', entryPoint: PredictEventValues.ENTRY_POINT.HOME_SECTION, }, }); @@ -216,7 +216,7 @@ describe('PredictPopularTodaySection', () => { }); }); - it('navigates to the Popular Today feed with the selected filter when a chip is pressed', () => { + it('navigates to the Trending feed with the selected filter when a chip is pressed', () => { setFilterOptions({ filterOptions: [createFilterOption('elections', 'Elections')], }); @@ -232,7 +232,7 @@ describe('PredictPopularTodaySection', () => { expect(mockNavigate).toHaveBeenCalledWith(Routes.PREDICT.ROOT, { screen: Routes.PREDICT.FEED, params: { - feedId: 'popular-today', + feedId: 'trending', initialFilterId: 'elections', entryPoint: PredictEventValues.ENTRY_POINT.HOME_SECTION, }, diff --git a/app/components/UI/Predict/views/PredictHome/components/PredictPopularTodaySection/PredictPopularTodaySection.tsx b/app/components/UI/Predict/views/PredictHome/components/PredictPopularTodaySection/PredictPopularTodaySection.tsx index b72b39fc3d8..a675288763d 100644 --- a/app/components/UI/Predict/views/PredictHome/components/PredictPopularTodaySection/PredictPopularTodaySection.tsx +++ b/app/components/UI/Predict/views/PredictHome/components/PredictPopularTodaySection/PredictPopularTodaySection.tsx @@ -28,17 +28,16 @@ import type { import { PredictHomeSelectorsIDs } from '../../../../Predict.testIds'; /** - * Max number of filter chips rendered on the home section. The full - * `popular-today` feed shows the complete list; the home rail shows a compact - * prefix of the same ordered list. + * Max number of filter chips rendered on the home section. The home rail shows + * a compact prefix of the same ordered list as the Trending feed. */ const POPULAR_TODAY_FILTER_LIMIT = 10; const SKELETON_COUNT = 8; const DEFAULT_CHIP_ROW_COUNT = 2; /** - * Derive the section's filter-options params from the canonical `popular-today` - * feed registry so there is a single source of truth. Crucially, we do NOT pass + * Derive the section's filter-options params from the canonical `trending` feed + * registry so there is a single source of truth. Crucially, we do NOT pass * a top-level `limit` here: that keeps the React Query cache key identical to * the full feed's `usePredictFilterOptions` call (which omits `limit`), so the * home section and the feed share the same cached related-tags request instead @@ -46,7 +45,7 @@ const DEFAULT_CHIP_ROW_COUNT = 2; * `POPULAR_TODAY_FILTER_LIMIT` when slicing the returned options. */ const POPULAR_TODAY_FILTER_PARAMS: PredictFilterOptionsParams = (() => { - const dynamicConfig = resolvePredictFeedDynamicFilterConfig('popular-today'); + const dynamicConfig = resolvePredictFeedDynamicFilterConfig('trending'); return { source: dynamicConfig?.source ?? 'related-tags', @@ -100,9 +99,9 @@ interface PredictPopularTodaySectionProps { /** * Predict home "Popular today" tag rail (PRED-917). * - * Uses the same related-tag source as the full `popular-today` feed. The header - * opens the all-up Popular Today feed, while each chip opens that feed with the - * selected related-tag filter preselected. + * Uses the same related-tag source as the Trending feed. The header opens the + * all-up Trending feed, while each chip opens that feed with the selected + * related-tag filter preselected. */ const PredictPopularTodaySection: React.FC = ({ testID = PREDICT_POPULAR_TODAY_SECTION_TEST_IDS.SECTION, @@ -137,10 +136,10 @@ const PredictPopularTodaySection: React.FC = ({ [rowCount], ); - const navigateToPopularToday = useCallback( + const navigateToTrending = useCallback( (initialFilterId?: string) => { const params: PredictFeedRouteParams = { - feedId: 'popular-today', + feedId: 'trending', entryPoint: PredictEventValues.ENTRY_POINT.HOME_SECTION, ...(initialFilterId ? { initialFilterId } : {}), }; @@ -159,8 +158,8 @@ const PredictPopularTodaySection: React.FC = ({ actionType: PredictEventValues.ACTION_TYPE.SEE_ALL, entryPoint: PredictEventValues.ENTRY_POINT.HOME_SECTION, }); - navigateToPopularToday(); - }, [navigateToPopularToday]); + navigateToTrending(); + }, [navigateToTrending]); const handleChipPress = useCallback( (option: PredictFilterOption) => { @@ -173,9 +172,9 @@ const PredictPopularTodaySection: React.FC = ({ isDynamicFilter: true, entryPoint: PredictEventValues.ENTRY_POINT.HOME_SECTION, }); - navigateToPopularToday(option.id); + navigateToTrending(option.id); }, - [navigateToPopularToday], + [navigateToTrending], ); if (!isLoading && chips.length === 0) { diff --git a/app/core/DeeplinkManager/handlers/legacy/__tests__/handlePredictUrl.test.ts b/app/core/DeeplinkManager/handlers/legacy/__tests__/handlePredictUrl.test.ts index c8503f7c701..dd7462bbe41 100644 --- a/app/core/DeeplinkManager/handlers/legacy/__tests__/handlePredictUrl.test.ts +++ b/app/core/DeeplinkManager/handlers/legacy/__tests__/handlePredictUrl.test.ts @@ -428,7 +428,7 @@ describe('handlePredictUrl', () => { }); }); - it('parses filter separately from tab', async () => { + it('redirects Popular Today links to the selected Trending filter', async () => { await handlePredictUrl({ predictPath: '?feed=popular-today&filter=elections', }); @@ -436,7 +436,7 @@ describe('handlePredictUrl', () => { expect(mockNavigate).toHaveBeenCalledWith(Routes.PREDICT.ROOT, { screen: Routes.PREDICT.FEED, params: { - feedId: 'popular-today', + feedId: 'trending', initialFilterId: 'elections', entryPoint: 'deeplink', }, @@ -506,6 +506,21 @@ describe('handlePredictUrl', () => { }); }); + it('keeps the Popular Today redirect behind the home redesign flag', async () => { + jest.mocked(selectPredictHomeRedesignEnabledFlag).mockReturnValue(false); + + await handlePredictUrl({ + predictPath: '?feed=popular-today&filter=elections', + }); + + expect(mockNavigate).toHaveBeenCalledWith(Routes.PREDICT.ROOT, { + screen: Routes.PREDICT.MARKET_LIST, + params: { + entryPoint: 'deeplink', + }, + }); + }); + it('prioritizes market parameter over a generic feed', async () => { await handlePredictUrl({ predictPath: '?feed=sports&market=123' }); diff --git a/app/core/DeeplinkManager/handlers/legacy/handlePredictUrl.ts b/app/core/DeeplinkManager/handlers/legacy/handlePredictUrl.ts index 2e9ae999874..64ec5673ed3 100644 --- a/app/core/DeeplinkManager/handlers/legacy/handlePredictUrl.ts +++ b/app/core/DeeplinkManager/handlers/legacy/handlePredictUrl.ts @@ -236,7 +236,7 @@ const marketTarget = ( * - https://link.metamask.io/predict?feed=world-cup&tab=live * - https://link.metamask.io/predict?feed=sports * - https://link.metamask.io/predict?feed=sports&tab=all&filter=live - * - https://link.metamask.io/predict?feed=popular-today&filter=elections + * - https://link.metamask.io/predict?feed=popular-today&filter=elections (redirects to Trending) * - https://link.metamask.io/predict?feed=trending&q=bitcoin * * Origin/EntryPoint handling: @@ -248,7 +248,8 @@ const marketTarget = ( * - No market param: Navigate to market list * - market=X or marketId=X: Navigate directly to market details for market X * - feed=world-cup: Navigate to the dedicated World Cup feed when enabled - * - feed= (sports/politics/crypto/live/trending/popular-today): Navigate to the generic PredictFeedView when the predictHomeRedesign flag is enabled (tab -> initialTabId, filter -> initialFilterId) + * - feed= (sports/politics/crypto/live/trending): Navigate to the generic PredictFeedView when the predictHomeRedesign flag is enabled (tab -> initialTabId, filter -> initialFilterId) + * - feed=popular-today: Redirect to the Trending feed while preserving its filter * - Unknown feed (or flag disabled): Fall back to the Predict market list * - Optional tab param when no market: Open feed on a specific tab * - query=X or q=X: Open feed with search overlay showing results for X @@ -260,6 +261,8 @@ const resolvePredictTarget = ({ // Parse navigation parameters from URL const navParams = parsePredictNavigationParams(predictPath); DevLogger.log('[handlePredictUrl] Parsed navigation parameters:', navParams); + const genericFeed = + navParams.feed === 'popular-today' ? 'trending' : navParams.feed; // Determine entry point: // - Base is origin if provided, otherwise 'deeplink' @@ -281,12 +284,9 @@ const resolvePredictTarget = ({ requestedTab: navParams.worldCupTab, entryPoint, }); - } else if ( - isPredictFeedId(navParams.feed) && - getPredictHomeRedesignEnabled() - ) { + } else if (isPredictFeedId(genericFeed) && getPredictHomeRedesignEnabled()) { return genericFeedTarget({ - feedId: navParams.feed, + feedId: genericFeed, // worldCupTab holds the raw (unvalidated) tab value; the generic feed's // sub-tab ids (e.g. basketball/all/live) are resolved by the view. initialTabId: navParams.worldCupTab,