Skip to content

Commit 548fd80

Browse files
authored
chore: remove What's Happening section from Homepage and add it to Perps home view (#30129)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> ## **Description** Removes the What's Happening carousel from the Homepage and adds it in PerpsHomeView between commodities and stocks. <img height="800" alt="Simulator Screenshot - iPhone 17 Pro - 2026-05-13 at 16 17 07" src="https://github.com/user-attachments/assets/46d06d81-e86b-45c8-9bb8-94f76b752024" /> <img height="800" alt="Simulator Screenshot - iPhone 17 Pro - 2026-05-13 at 16 17 33" src="https://github.com/user-attachments/assets/8a0b4ab2-59a6-4f29-bc57-d90886bd5d7a" /> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Moves a feature-flagged surface between major screens and refactors `WhatsHappeningSection` props/test IDs, which could affect navigation, analytics events, and UI layout if not exercised across all entry points. > > **Overview** > Removes the *What’s Happening* carousel from `Homepage` (including its refresh wiring and section-order accounting) and adds it to `PerpsHomeView`, gated by `selectWhatsHappeningEnabled` and styled as a dedicated section between commodities and stocks. > > Refactors `app/components/UI/WhatsHappening` to be a shared UI module: drops homepage-specific section-tracking props, adds a `Perps` source, centralizes test IDs, updates imports/paths across Trending, deeplinks, and the detail view, and relocates i18n keys under `whats_happening.*`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 4b64ee0. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent d49a4cd commit 548fd80

40 files changed

Lines changed: 210 additions & 220 deletions

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ app/components/Views/Homepage/Sections/Perpetuals/ @MetaMask/perps
206206
# Social & AI Team
207207
app/components/Views/SocialLeaderboard/ @MetaMask/social-ai
208208
app/components/UI/MarketInsights/ @MetaMask/social-ai
209-
app/components/Views/Homepage/Sections/WhatsHappening/ @MetaMask/social-ai
209+
app/components/UI/WhatsHappening/ @MetaMask/social-ai
210210
app/components/Views/Homepage/Sections/TopTraders/ @MetaMask/social-ai
211211
app/core/Engine/controllers/social-controller-init* @MetaMask/social-ai
212212
app/core/Engine/controllers/social-service-init* @MetaMask/social-ai

app/components/UI/Perps/Views/PerpsHomeView/PerpsHomeView.styles.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ const styleSheet = (params: { theme: Theme }) => {
129129
positionsOrdersContainer: {
130130
paddingHorizontal: 16,
131131
},
132+
whatsHappeningSection: {
133+
borderTopWidth: 1,
134+
borderTopColor: colors.border.muted,
135+
paddingTop: 28,
136+
marginBottom: 36,
137+
},
132138
});
133139
};
134140

app/components/UI/Perps/Views/PerpsHomeView/PerpsHomeView.test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { render, fireEvent } from '@testing-library/react-native';
33
import PerpsHomeView from './PerpsHomeView';
44
import { PERPS_EVENT_VALUE } from '@metamask/perps-controller';
55
import { selectPerpsFeedbackEnabledFlag } from '../../selectors/featureFlags';
6+
import { selectWhatsHappeningEnabled } from '../../../../../selectors/featureFlagController/whatsHappening';
67
import { mockTheme } from '../../../../../util/theme';
78
import { useDiscoveryScrollManager } from '../../../Predict/hooks/useDiscoveryScrollManager';
89

@@ -478,6 +479,18 @@ jest.mock(
478479
'../../components/PerpsRecentActivityList/PerpsRecentActivityList',
479480
() => 'PerpsRecentActivityList',
480481
);
482+
jest.mock('../../../../UI/WhatsHappening', () => {
483+
const { View } = jest.requireActual('react-native');
484+
return function MockWhatsHappeningSection() {
485+
return <View testID="whats-happening-section" />;
486+
};
487+
});
488+
jest.mock(
489+
'../../../../../selectors/featureFlagController/whatsHappening',
490+
() => ({
491+
selectWhatsHappeningEnabled: jest.fn(),
492+
}),
493+
);
481494
jest.mock('../../../../../component-library/components/Texts/Text', () => ({
482495
__esModule: true,
483496
default: 'Text',
@@ -969,4 +982,21 @@ describe('PerpsHomeView', () => {
969982
);
970983
});
971984
});
985+
986+
describe('WhatsHappening section', () => {
987+
it('renders WhatsHappeningSection when aiSocialWhatsHappeningEnabled flag is true', () => {
988+
mockUseSelector.mockImplementation((selector: unknown) => {
989+
if (selector === selectWhatsHappeningEnabled) return true;
990+
return false;
991+
});
992+
const { getByTestId } = render(<PerpsHomeView />);
993+
expect(getByTestId('whats-happening-section')).toBeOnTheScreen();
994+
});
995+
996+
it('does not render WhatsHappeningSection when aiSocialWhatsHappeningEnabled flag is false', () => {
997+
mockUseSelector.mockReturnValue(false);
998+
const { queryByTestId } = render(<PerpsHomeView />);
999+
expect(queryByTestId('whats-happening-section')).not.toBeOnTheScreen();
1000+
});
1001+
});
9721002
});

app/components/UI/Perps/Views/PerpsHomeView/PerpsHomeView.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ import PerpsRecentActivityList from '../../components/PerpsRecentActivityList/Pe
5454
import PerpsHomeSection from '../../components/PerpsHomeSection';
5555
import PerpsRowSkeleton from '../../components/PerpsRowSkeleton';
5656
import PerpsHomeHeader from '../../components/PerpsHomeHeader';
57+
import WhatsHappeningSection from '../../../../UI/WhatsHappening';
58+
import { WhatsHappeningSource } from '../../../../UI/WhatsHappening/constants';
59+
import { selectWhatsHappeningEnabled } from '../../../../../selectors/featureFlagController/whatsHappening';
5760
import type { PerpsNavigationParamList } from '../../types/navigation';
5861
import { MetaMetricsEvents } from '../../../../../core/Analytics';
5962
import { useAnalytics } from '../../../../hooks/useAnalytics/useAnalytics';
@@ -108,6 +111,7 @@ const PerpsHomeView = ({
108111
// Feature flag for feedback button
109112
const isFeedbackEnabled = useSelector(selectPerpsFeedbackEnabledFlag);
110113
const privacyMode = useSelector(selectPrivacyMode);
114+
const isWhatsHappeningEnabled = useSelector(selectWhatsHappeningEnabled);
111115

112116
// Use centralized navigation hook
113117
const perpsNavigation = usePerpsNavigation();
@@ -583,6 +587,13 @@ const PerpsHomeView = ({
583587
source={PERPS_EVENT_VALUE.SOURCE.PERPS_HOME}
584588
/>
585589

590+
{/* What's Happening Section */}
591+
{isWhatsHappeningEnabled && (
592+
<View style={styles.whatsHappeningSection}>
593+
<WhatsHappeningSection source={WhatsHappeningSource.Perps} />
594+
</View>
595+
)}
596+
586597
{/* Stocks Markets List */}
587598
<View onLayout={handleSectionLayout('explore_stocks')}>
588599
<PerpsMarketTypeSection
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const WhatsHappeningSelectorsIDs = {
2+
CAROUSEL: 'whats-happening-carousel',
3+
SECTION_TITLE: 'whats-happening-section-title',
4+
} as const;

app/components/Views/Homepage/Sections/WhatsHappening/WhatsHappeningSection.test.tsx renamed to app/components/UI/WhatsHappening/WhatsHappeningSection.test.tsx

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import React from 'react';
22
import { screen, fireEvent } from '@testing-library/react-native';
3-
import renderWithProvider from '../../../../../util/test/renderWithProvider';
3+
import renderWithProvider from '../../../util/test/renderWithProvider';
44
import WhatsHappeningSection from './WhatsHappeningSection';
5-
import Routes from '../../../../../constants/navigation/Routes';
6-
import { MetaMetricsEvents } from '../../../../../core/Analytics/MetaMetrics.events';
5+
import Routes from '../../../constants/navigation/Routes';
6+
import { MetaMetricsEvents } from '../../../core/Analytics/MetaMetrics.events';
77
import {
88
WhatsHappeningInteractionType,
99
WhatsHappeningSource,
1010
WhatsHappeningView,
1111
} from './constants';
12+
import { WhatsHappeningSelectorsIDs } from './WhatsHappening.testIds';
1213

1314
const mockNavigate = jest.fn();
1415
const mockTrackEvent = jest.fn();
@@ -27,12 +28,9 @@ jest.mock('@react-navigation/native', () => {
2728
};
2829
});
2930

30-
jest.mock(
31-
'../../../../../selectors/featureFlagController/whatsHappening',
32-
() => ({
33-
selectWhatsHappeningEnabled: jest.fn(() => true),
34-
}),
35-
);
31+
jest.mock('../../../selectors/featureFlagController/whatsHappening', () => ({
32+
selectWhatsHappeningEnabled: jest.fn(() => true),
33+
}));
3634

3735
jest.mock('./hooks', () => ({
3836
useWhatsHappening: jest.fn(() => ({
@@ -43,7 +41,7 @@ jest.mock('./hooks', () => ({
4341
})),
4442
}));
4543

46-
jest.mock('../../../../hooks/useAnalytics/useAnalytics', () => ({
44+
jest.mock('../../hooks/useAnalytics/useAnalytics', () => ({
4745
useAnalytics: () => ({
4846
trackEvent: mockTrackEvent,
4947
createEventBuilder: mockCreateEventBuilder,
@@ -52,7 +50,7 @@ jest.mock('../../../../hooks/useAnalytics/useAnalytics', () => ({
5250

5351
const mockUseWhatsHappening = jest.requireMock('./hooks').useWhatsHappening;
5452
const mockSelectWhatsHappeningEnabled = jest.requireMock(
55-
'../../../../../selectors/featureFlagController/whatsHappening',
53+
'../../../selectors/featureFlagController/whatsHappening',
5654
).selectWhatsHappeningEnabled;
5755

5856
const mockItem = {
@@ -67,8 +65,6 @@ const mockItem = {
6765
};
6866

6967
const defaultProps = {
70-
sectionIndex: 1,
71-
totalSectionsLoaded: 3,
7268
source: 'homepage' as const,
7369
};
7470

@@ -88,14 +84,14 @@ describe('WhatsHappeningSection', () => {
8884
mockSelectWhatsHappeningEnabled.mockImplementation(() => false);
8985
renderWithProvider(<WhatsHappeningSection {...defaultProps} />);
9086
expect(
91-
screen.queryByTestId('homepage-whats-happening-carousel'),
87+
screen.queryByTestId(WhatsHappeningSelectorsIDs.CAROUSEL),
9288
).toBeNull();
9389
});
9490

9591
it('returns null when not loading and items are empty', () => {
9692
renderWithProvider(<WhatsHappeningSection {...defaultProps} />);
9793
expect(
98-
screen.queryByTestId('homepage-whats-happening-carousel'),
94+
screen.queryByTestId(WhatsHappeningSelectorsIDs.CAROUSEL),
9995
).toBeNull();
10096
});
10197

@@ -108,7 +104,7 @@ describe('WhatsHappeningSection', () => {
108104
});
109105
renderWithProvider(<WhatsHappeningSection {...defaultProps} />);
110106
expect(
111-
screen.getByTestId('homepage-whats-happening-carousel'),
107+
screen.getByTestId(WhatsHappeningSelectorsIDs.CAROUSEL),
112108
).toBeOnTheScreen();
113109
});
114110

@@ -121,7 +117,7 @@ describe('WhatsHappeningSection', () => {
121117
});
122118
renderWithProvider(<WhatsHappeningSection {...defaultProps} />);
123119
expect(
124-
screen.getByTestId('homepage-whats-happening-carousel'),
120+
screen.getByTestId(WhatsHappeningSelectorsIDs.CAROUSEL),
125121
).toBeOnTheScreen();
126122
expect(screen.getByText(mockItem.title)).toBeOnTheScreen();
127123
});
@@ -135,7 +131,7 @@ describe('WhatsHappeningSection', () => {
135131
});
136132
renderWithProvider(<WhatsHappeningSection {...defaultProps} />);
137133
expect(
138-
screen.queryByTestId('homepage-whats-happening-carousel'),
134+
screen.queryByTestId(WhatsHappeningSelectorsIDs.CAROUSEL),
139135
).toBeNull();
140136
expect(screen.getByText(/unable to load/i)).toBeOnTheScreen();
141137
});
@@ -212,7 +208,7 @@ describe('WhatsHappeningSection', () => {
212208
refresh: jest.fn(),
213209
});
214210
renderWithProvider(<WhatsHappeningSection {...defaultProps} />);
215-
const carousel = screen.getByTestId('homepage-whats-happening-carousel');
211+
const carousel = screen.getByTestId(WhatsHappeningSelectorsIDs.CAROUSEL);
216212
// CARD_WIDTH = 280, GAP = 12 → offset for index 1 = 292
217213
fireEvent(carousel, 'momentumScrollEnd', {
218214
nativeEvent: { contentOffset: { x: 292, y: 0 } },
@@ -242,7 +238,7 @@ describe('WhatsHappeningSection', () => {
242238
refresh: jest.fn(),
243239
});
244240
renderWithProvider(<WhatsHappeningSection {...defaultProps} />);
245-
const carousel = screen.getByTestId('homepage-whats-happening-carousel');
241+
const carousel = screen.getByTestId(WhatsHappeningSelectorsIDs.CAROUSEL);
246242

247243
fireEvent(carousel, 'momentumScrollEnd', {
248244
nativeEvent: { contentOffset: { x: 0, y: 0 } },
@@ -251,4 +247,34 @@ describe('WhatsHappeningSection', () => {
251247
MetaMetricsEvents.WHATS_HAPPENING_INTERACTED,
252248
);
253249
});
250+
251+
describe('without section-tracking props (Perps context)', () => {
252+
it('renders correctly when source is Perps', () => {
253+
mockUseWhatsHappening.mockReturnValue({
254+
items: [mockItem],
255+
isLoading: false,
256+
error: null,
257+
refresh: jest.fn(),
258+
});
259+
renderWithProvider(
260+
<WhatsHappeningSection source={WhatsHappeningSource.Perps} />,
261+
);
262+
expect(
263+
screen.getByTestId(WhatsHappeningSelectorsIDs.CAROUSEL),
264+
).toBeOnTheScreen();
265+
});
266+
267+
it('renders error state correctly when source is Perps', () => {
268+
mockUseWhatsHappening.mockReturnValue({
269+
items: [],
270+
isLoading: false,
271+
error: 'Network error',
272+
refresh: jest.fn(),
273+
});
274+
renderWithProvider(
275+
<WhatsHappeningSection source={WhatsHappeningSource.Perps} />,
276+
);
277+
expect(screen.getByText(/unable to load/i)).toBeOnTheScreen();
278+
});
279+
});
254280
});

0 commit comments

Comments
 (0)