Skip to content

Commit 4246be9

Browse files
runway-github[bot]wachuneichloeYue
authored
chore(runway): cherry-pick refactor: simplify rampsUnifiedBuyV2 feature flag to single selector cp-7.71.0 (#27762)
- refactor: simplify rampsUnifiedBuyV2 feature flag to single selector cp-7.71.0 (#27760) ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> The `rampsUnifiedBuyV2` feature flag previously used three chained selectors (`selectRampsUnifiedBuyV2Config` → `selectRampsUnifiedBuyV2ActiveFlag` / `selectRampsUnifiedBuyV2MinimumVersionFlag`) and a custom 2-arg `hasMinimumRequiredVersion` utility. This was inconsistent with how other feature flags (e.g. homepage redesign) are handled in the codebase. This PR consolidates the three selectors into a single `selectRampsUnifiedBuyV2Enabled` selector that uses the shared `validatedVersionGatedFeatureFlag` utility from `app/util/remoteFeatureFlag`. The remote flag shape is updated from `{ active, minimumVersion }` to `{ enabled, minimumVersion }` to match the standard `VersionGatedFeatureFlag` type. **Key changes:** - **Selector file** (`rampsUnifiedBuyV2.ts`): Replaced 3 selectors + `RampsUnifiedBuyV2Config` interface with a single `selectRampsUnifiedBuyV2Enabled` selector - **Hook** (`useRampsUnifiedV2Enabled.ts`): Simplified from two `useSelector` calls + `hasMinimumRequiredVersion` to a single `useSelector` - **Utility** (`isRampsUnifiedV2Enabled.ts`): Simplified to delegate directly to the selector - **Controller init** (`ramps-controller-init.ts`): Replaced local interface + `hasMinimumRequiredVersion` with `validatedVersionGatedFeatureFlag`; imports shared flag key constant - **Flag key constant**: Exported `RAMPS_UNIFIED_BUY_V2_FLAG_KEY` from the selector file as single source of truth - **E2E mocks/fixtures**: Updated flag shape from `active` to `enabled` in `FixtureBuilder`, `feature-flags-mocks`, and `feature-flag-registry` ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: ## **Manual testing steps** N/A — this is a pure refactor of internal selector structure. Behavior is unchanged. All unit tests have been updated and pass. ## **Screenshots/Recordings** ### **Before** N/A ### **After** https://github.com/user-attachments/assets/13ab83a0-f3b1-4862-95cc-ec02fc5b89b5 ## **Pre-merge author checklist** - [x] 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). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] 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. ## **Pre-merge reviewer checklist** - [ ] 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** > Touches feature-flag gating that controls whether the ramps V2 flow and controller initialization run, and changes the expected remote flag shape from `active` to `enabled`. Main risk is misconfigured/older flag payloads causing the feature to be incorrectly disabled. > > **Overview** > Simplifies `rampsUnifiedBuyV2` enablement checks by replacing the chained config/active/min-version selectors and custom gating logic with a single `selectRampsUnifiedBuyV2Enabled` that delegates to `validatedVersionGatedFeatureFlag`. > > Updates the Ramp hook/utility and `ramps-controller-init` to consume this unified version-gated flag (keeping the build-flag override), and standardizes the remote flag payload from `{ active, minimumVersion }` to `{ enabled, minimumVersion }` across unit tests, E2E mocks/fixtures, and the feature-flag registry defaults. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 3edd562. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> [73198ff](73198ff) Co-authored-by: Pedro Pablo Aste Kompen <wachunei@gmail.com> Co-authored-by: chloeYue <105063779+chloeYue@users.noreply.github.com>
1 parent b29c766 commit 4246be9

11 files changed

Lines changed: 139 additions & 280 deletions

File tree

app/components/UI/Ramp/hooks/useRampsUnifiedV2Enabled.test.ts

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import useRampsUnifiedV2Enabled from './useRampsUnifiedV2Enabled';
66
import { getVersion } from 'react-native-device-info';
77

88
function mockInitialState({
9-
rampsUnifiedBuyV2ActiveFlag = true,
10-
rampsUnifiedBuyV2MinimumVersionFlag,
9+
enabled = true,
10+
minimumVersion,
1111
}: {
12-
rampsUnifiedBuyV2ActiveFlag?: boolean;
13-
rampsUnifiedBuyV2MinimumVersionFlag?: string | null;
12+
enabled?: boolean;
13+
minimumVersion?: string | null;
1414
} = {}) {
1515
return {
1616
...initialRootState,
@@ -20,9 +20,9 @@ function mockInitialState({
2020
RemoteFeatureFlagController: {
2121
remoteFeatureFlags: {
2222
rampsUnifiedBuyV2: {
23-
active: rampsUnifiedBuyV2ActiveFlag,
24-
...(rampsUnifiedBuyV2MinimumVersionFlag !== undefined && {
25-
minimumVersion: rampsUnifiedBuyV2MinimumVersionFlag,
23+
enabled,
24+
...(minimumVersion !== undefined && {
25+
minimumVersion,
2626
}),
2727
},
2828
},
@@ -59,8 +59,8 @@ describe('useRampsUnifiedV2Enabled', () => {
5959
() => useRampsUnifiedV2Enabled(),
6060
{
6161
state: mockInitialState({
62-
rampsUnifiedBuyV2ActiveFlag: false,
63-
rampsUnifiedBuyV2MinimumVersionFlag: '2.0.0',
62+
enabled: false,
63+
minimumVersion: '2.0.0',
6464
}),
6565
},
6666
);
@@ -76,8 +76,8 @@ describe('useRampsUnifiedV2Enabled', () => {
7676
() => useRampsUnifiedV2Enabled(),
7777
{
7878
state: mockInitialState({
79-
rampsUnifiedBuyV2ActiveFlag: true,
80-
rampsUnifiedBuyV2MinimumVersionFlag: '1.0.0',
79+
enabled: true,
80+
minimumVersion: '1.0.0',
8181
}),
8282
},
8383
);
@@ -93,8 +93,8 @@ describe('useRampsUnifiedV2Enabled', () => {
9393
() => useRampsUnifiedV2Enabled(),
9494
{
9595
state: mockInitialState({
96-
rampsUnifiedBuyV2ActiveFlag: true,
97-
rampsUnifiedBuyV2MinimumVersionFlag: '2.0.0',
96+
enabled: true,
97+
minimumVersion: '2.0.0',
9898
}),
9999
},
100100
);
@@ -111,8 +111,8 @@ describe('useRampsUnifiedV2Enabled', () => {
111111
() => useRampsUnifiedV2Enabled(),
112112
{
113113
state: mockInitialState({
114-
rampsUnifiedBuyV2ActiveFlag: true,
115-
rampsUnifiedBuyV2MinimumVersionFlag: '7.63.0',
114+
enabled: true,
115+
minimumVersion: '7.63.0',
116116
}),
117117
},
118118
);
@@ -127,8 +127,8 @@ describe('useRampsUnifiedV2Enabled', () => {
127127
() => useRampsUnifiedV2Enabled(),
128128
{
129129
state: mockInitialState({
130-
rampsUnifiedBuyV2ActiveFlag: false,
131-
rampsUnifiedBuyV2MinimumVersionFlag: '7.63.0',
130+
enabled: false,
131+
minimumVersion: '7.63.0',
132132
}),
133133
},
134134
);
@@ -143,8 +143,8 @@ describe('useRampsUnifiedV2Enabled', () => {
143143
() => useRampsUnifiedV2Enabled(),
144144
{
145145
state: mockInitialState({
146-
rampsUnifiedBuyV2ActiveFlag: true,
147-
rampsUnifiedBuyV2MinimumVersionFlag: '7.63.0',
146+
enabled: true,
147+
minimumVersion: '7.63.0',
148148
}),
149149
},
150150
);
@@ -159,8 +159,8 @@ describe('useRampsUnifiedV2Enabled', () => {
159159
() => useRampsUnifiedV2Enabled(),
160160
{
161161
state: mockInitialState({
162-
rampsUnifiedBuyV2ActiveFlag: true,
163-
rampsUnifiedBuyV2MinimumVersionFlag: null,
162+
enabled: true,
163+
minimumVersion: null,
164164
}),
165165
},
166166
);
@@ -175,8 +175,8 @@ describe('useRampsUnifiedV2Enabled', () => {
175175
() => useRampsUnifiedV2Enabled(),
176176
{
177177
state: mockInitialState({
178-
rampsUnifiedBuyV2ActiveFlag: true,
179-
rampsUnifiedBuyV2MinimumVersionFlag: undefined,
178+
enabled: true,
179+
minimumVersion: undefined,
180180
}),
181181
},
182182
);
@@ -191,24 +191,24 @@ describe('useRampsUnifiedV2Enabled', () => {
191191
() => useRampsUnifiedV2Enabled(),
192192
{
193193
state: mockInitialState({
194-
rampsUnifiedBuyV2ActiveFlag: true,
195-
rampsUnifiedBuyV2MinimumVersionFlag: '7.63.0',
194+
enabled: true,
195+
minimumVersion: '7.63.0',
196196
}),
197197
},
198198
);
199199

200200
expect(result.current).toBe(true);
201201
});
202202

203-
it('returns false when both active flag and minimum version are not set', () => {
203+
it('returns false when both enabled flag and minimum version are not set', () => {
204204
mockGetVersion.mockReturnValue('8.0.0');
205205

206206
const { result } = renderHookWithProvider(
207207
() => useRampsUnifiedV2Enabled(),
208208
{
209209
state: mockInitialState({
210-
rampsUnifiedBuyV2ActiveFlag: false,
211-
rampsUnifiedBuyV2MinimumVersionFlag: null,
210+
enabled: false,
211+
minimumVersion: null,
212212
}),
213213
},
214214
);
Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,13 @@
11
import { useSelector } from 'react-redux';
2-
import {
3-
selectRampsUnifiedBuyV2ActiveFlag,
4-
selectRampsUnifiedBuyV2MinimumVersionFlag,
5-
} from '../../../../selectors/featureFlagController/ramps/rampsUnifiedBuyV2';
6-
import { hasMinimumRequiredVersion } from '../utils/hasMinimumRequiredVersion';
2+
import { selectRampsUnifiedBuyV2Enabled } from '../../../../selectors/featureFlagController/ramps/rampsUnifiedBuyV2';
73

84
export default function useRampsUnifiedV2Enabled() {
9-
const rampsUnifiedBuyV2MinimumVersionFlag = useSelector(
10-
selectRampsUnifiedBuyV2MinimumVersionFlag,
11-
);
12-
const rampsUnifiedBuyV2ActiveFlag = useSelector(
13-
selectRampsUnifiedBuyV2ActiveFlag,
14-
);
5+
const isEnabled = useSelector(selectRampsUnifiedBuyV2Enabled);
156

16-
const rampsUnifiedBuyV2BuildFlag =
17-
process.env.MM_RAMPS_UNIFIED_BUY_V2_ENABLED;
18-
19-
// if build flag is defined, it takes precedence over remote feature flag
20-
if (
21-
rampsUnifiedBuyV2BuildFlag === 'true' ||
22-
rampsUnifiedBuyV2BuildFlag === 'false'
23-
) {
24-
return rampsUnifiedBuyV2BuildFlag === 'true';
7+
const buildFlag = process.env.MM_RAMPS_UNIFIED_BUY_V2_ENABLED;
8+
if (buildFlag === 'true' || buildFlag === 'false') {
9+
return buildFlag === 'true';
2510
}
2611

27-
const isRampsUnifiedV2Enabled = hasMinimumRequiredVersion(
28-
rampsUnifiedBuyV2MinimumVersionFlag,
29-
rampsUnifiedBuyV2ActiveFlag,
30-
);
31-
32-
return isRampsUnifiedV2Enabled;
12+
return isEnabled;
3313
}

app/components/UI/Ramp/utils/isRampsUnifiedV2Enabled.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ jest.mock('react-native-device-info', () => ({
99
}));
1010

1111
function buildState({
12-
active = true,
12+
enabled = true,
1313
minimumVersion,
1414
}: {
15-
active?: boolean;
15+
enabled?: boolean;
1616
minimumVersion?: string | null;
1717
} = {}) {
1818
return {
@@ -24,7 +24,7 @@ function buildState({
2424
...backgroundState.RemoteFeatureFlagController,
2525
remoteFeatureFlags: {
2626
rampsUnifiedBuyV2: {
27-
active,
27+
enabled,
2828
...(minimumVersion !== undefined && { minimumVersion }),
2929
},
3030
},
@@ -53,7 +53,7 @@ describe('isRampsUnifiedV2Enabled', () => {
5353
process.env.MM_RAMPS_UNIFIED_BUY_V2_ENABLED = 'true';
5454

5555
const result = isRampsUnifiedV2Enabled(
56-
buildState({ active: false, minimumVersion: '99.0.0' }),
56+
buildState({ enabled: false, minimumVersion: '99.0.0' }),
5757
);
5858

5959
expect(result).toBe(true);
@@ -63,29 +63,29 @@ describe('isRampsUnifiedV2Enabled', () => {
6363
process.env.MM_RAMPS_UNIFIED_BUY_V2_ENABLED = 'false';
6464

6565
const result = isRampsUnifiedV2Enabled(
66-
buildState({ active: true, minimumVersion: '1.0.0' }),
66+
buildState({ enabled: true, minimumVersion: '1.0.0' }),
6767
);
6868

6969
expect(result).toBe(false);
7070
});
7171
});
7272

7373
describe('remote feature flag behavior when build flag is not set', () => {
74-
it('returns true when active and version meets minimum requirement', () => {
74+
it('returns true when enabled and version meets minimum requirement', () => {
7575
mockGetVersion.mockReturnValue('8.0.0');
7676

7777
const result = isRampsUnifiedV2Enabled(
78-
buildState({ active: true, minimumVersion: '7.63.0' }),
78+
buildState({ enabled: true, minimumVersion: '7.63.0' }),
7979
);
8080

8181
expect(result).toBe(true);
8282
});
8383

84-
it('returns false when active flag is false', () => {
84+
it('returns false when enabled flag is false', () => {
8585
mockGetVersion.mockReturnValue('8.0.0');
8686

8787
const result = isRampsUnifiedV2Enabled(
88-
buildState({ active: false, minimumVersion: '7.63.0' }),
88+
buildState({ enabled: false, minimumVersion: '7.63.0' }),
8989
);
9090

9191
expect(result).toBe(false);
@@ -95,7 +95,7 @@ describe('isRampsUnifiedV2Enabled', () => {
9595
mockGetVersion.mockReturnValue('7.0.0');
9696

9797
const result = isRampsUnifiedV2Enabled(
98-
buildState({ active: true, minimumVersion: '7.63.0' }),
98+
buildState({ enabled: true, minimumVersion: '7.63.0' }),
9999
);
100100

101101
expect(result).toBe(false);
@@ -105,7 +105,7 @@ describe('isRampsUnifiedV2Enabled', () => {
105105
mockGetVersion.mockReturnValue('8.0.0');
106106

107107
const result = isRampsUnifiedV2Enabled(
108-
buildState({ active: true, minimumVersion: null }),
108+
buildState({ enabled: true, minimumVersion: null }),
109109
);
110110

111111
expect(result).toBe(false);
@@ -115,7 +115,7 @@ describe('isRampsUnifiedV2Enabled', () => {
115115
mockGetVersion.mockReturnValue('7.63.0');
116116

117117
const result = isRampsUnifiedV2Enabled(
118-
buildState({ active: true, minimumVersion: '7.63.0' }),
118+
buildState({ enabled: true, minimumVersion: '7.63.0' }),
119119
);
120120

121121
expect(result).toBe(true);
Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import {
2-
selectRampsUnifiedBuyV2ActiveFlag,
3-
selectRampsUnifiedBuyV2MinimumVersionFlag,
4-
} from '../../../../selectors/featureFlagController/ramps/rampsUnifiedBuyV2';
5-
import { hasMinimumRequiredVersion } from './hasMinimumRequiredVersion';
1+
import { selectRampsUnifiedBuyV2Enabled } from '../../../../selectors/featureFlagController/ramps/rampsUnifiedBuyV2';
62
import { RootState } from '../../../../reducers';
73

84
/**
@@ -16,7 +12,5 @@ export function isRampsUnifiedV2Enabled(state: RootState): boolean {
1612
return buildFlag === 'true';
1713
}
1814

19-
const activeFlag = selectRampsUnifiedBuyV2ActiveFlag(state);
20-
const minimumVersion = selectRampsUnifiedBuyV2MinimumVersionFlag(state);
21-
return hasMinimumRequiredVersion(minimumVersion, activeFlag);
15+
return selectRampsUnifiedBuyV2Enabled(state);
2216
}

app/core/Engine/controllers/ramps-controller/ramps-controller-init.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,17 @@ jest.mock('react-native-device-info', () => ({
6767

6868
const createMockInitMessenger = (
6969
overrides: {
70-
active?: boolean;
70+
enabled?: boolean;
7171
minimumVersion?: string | null;
7272
} = {},
7373
): RampsControllerInitMessenger => {
74-
const { active = false, minimumVersion = null } = overrides;
74+
const { enabled = false, minimumVersion = null } = overrides;
7575

7676
return {
7777
call: jest.fn().mockReturnValue({
7878
remoteFeatureFlags: {
7979
rampsUnifiedBuyV2: {
80-
active,
80+
enabled,
8181
minimumVersion,
8282
},
8383
},
@@ -196,7 +196,7 @@ describe('ramps controller init', () => {
196196
describe('when V2 feature flag is enabled', () => {
197197
it('calls init at startup', async () => {
198198
initRequestMock.initMessenger = createMockInitMessenger({
199-
active: true,
199+
enabled: true,
200200
minimumVersion: '1.0.0',
201201
});
202202

@@ -209,7 +209,7 @@ describe('ramps controller init', () => {
209209

210210
it('handles init failure gracefully', async () => {
211211
initRequestMock.initMessenger = createMockInitMessenger({
212-
active: true,
212+
enabled: true,
213213
minimumVersion: '1.0.0',
214214
});
215215
mockInit.mockRejectedValue(new Error('Network error'));
@@ -225,7 +225,7 @@ describe('ramps controller init', () => {
225225
describe('when V2 feature flag is disabled', () => {
226226
it('does not call init at startup', async () => {
227227
initRequestMock.initMessenger = createMockInitMessenger({
228-
active: false,
228+
enabled: false,
229229
});
230230

231231
rampsControllerInit(initRequestMock);
@@ -235,9 +235,9 @@ describe('ramps controller init', () => {
235235
});
236236
});
237237

238-
it('does not call init when active is true but minimumVersion is missing', async () => {
238+
it('does not call init when enabled is true but minimumVersion is missing', async () => {
239239
initRequestMock.initMessenger = createMockInitMessenger({
240-
active: true,
240+
enabled: true,
241241
minimumVersion: null,
242242
});
243243

@@ -265,7 +265,7 @@ describe('ramps controller init', () => {
265265

266266
it('always returns the controller instance regardless of flag state', () => {
267267
initRequestMock.initMessenger = createMockInitMessenger({
268-
active: false,
268+
enabled: false,
269269
});
270270

271271
const result = rampsControllerInit(initRequestMock);

0 commit comments

Comments
 (0)