Skip to content

Commit 1f4fbdf

Browse files
authored
fix: use METAMASK_ENVIRONMENT instead of NODE_ENV (#22282)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **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? --> Use metamask enviornment variable instead of node_env to view feature override dashboard ## **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 navigates to Feature Flag Override Dashboard in settings Given the metamask eviornment is not production When user navigates to settings Then they will see and navigate to the Feature Flag Override Dashboard ``` ## **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** - [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] > Switches visibility gating from NODE_ENV to METAMASK_ENVIRONMENT for Feature Flag Override screens and adds tests; also memoizes feature flag context helpers. > > - **Navigation & Settings** > - Replace `process.env.NODE_ENV !== 'production'` with `process.env.METAMASK_ENVIRONMENT !== 'production'` to conditionally show `Routes.FEATURE_FLAG_OVERRIDE` in `MainNavigator` and the Feature Flag Override drawer in `Views/Settings`. > - **Tests** > - Update `MainNavigator.test.tsx` and `Views/Settings/index.test.tsx` to use `METAMASK_ENVIRONMENT`, including setup/teardown of env var and assertions that Feature Flag Override is present in non-production. > - **Feature Flag Context** > - Memoize `featureFlagsList`, `getFeatureFlag`, and the provided context value via `useMemo/useCallback`. > - Use `process.env.METAMASK_ENVIRONMENT` in minimum version validation. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 1ab6560. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent e9505c6 commit 1f4fbdf

File tree

5 files changed

+138
-36
lines changed

5 files changed

+138
-36
lines changed

app/components/Nav/Main/MainNavigator.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect, useMemo, useCallback } from 'react';
1+
import React, { useState, useEffect, useMemo } from 'react';
22
import { Image, StyleSheet, Keyboard, Platform } from 'react-native';
33
import { createStackNavigator } from '@react-navigation/stack';
44
import { useSelector } from 'react-redux';
@@ -1192,7 +1192,7 @@ const MainNavigator = () => {
11921192
...GeneralSettings.navigationOptions,
11931193
}}
11941194
/>
1195-
{process.env.NODE_ENV !== 'production' && (
1195+
{process.env.METAMASK_ENVIRONMENT !== 'production' && (
11961196
<Stack.Screen
11971197
name={Routes.FEATURE_FLAG_OVERRIDE}
11981198
component={FeatureFlagOverride}

app/components/Nav/Main/MainNavigator.test.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,16 @@ jest.mock('@react-navigation/stack', () => ({
1313
}));
1414

1515
describe('MainNavigator', () => {
16+
const originalEnv = process.env.METAMASK_ENVIRONMENT;
17+
1618
beforeEach(() => {
1719
jest.clearAllMocks();
1820
});
1921

22+
afterEach(() => {
23+
process.env.METAMASK_ENVIRONMENT = originalEnv;
24+
});
25+
2026
it('matches rendered snapshot', () => {
2127
// Given the initial app state
2228
// When rendering the MainNavigator
@@ -60,4 +66,41 @@ describe('MainNavigator', () => {
6066
expect(sampleFeatureScreen).toBeDefined();
6167
expect(sampleFeatureScreen?.component.name).toBe('SampleFeatureFlow');
6268
});
69+
70+
it('includes FeatureFlagOverride screen when METAMASK_ENVIRONMENT is not production', () => {
71+
// Given a non-production environment
72+
process.env.METAMASK_ENVIRONMENT = 'dev';
73+
74+
// When rendering the MainNavigator
75+
const container = renderWithProvider(<MainNavigator />, {
76+
state: initialRootState,
77+
});
78+
79+
// Then it should contain the FeatureFlagOverride screen
80+
interface ScreenChild {
81+
name: string;
82+
component: { name: string };
83+
}
84+
const screenProps: ScreenChild[] = container.root.children
85+
.filter(
86+
(child): child is ReactTestInstance =>
87+
typeof child === 'object' &&
88+
'type' in child &&
89+
'props' in child &&
90+
child.type?.toString() === 'Screen',
91+
)
92+
.map((child) => ({
93+
name: child.props.name,
94+
component: child.props.component,
95+
}));
96+
97+
const featureFlagOverrideScreen = screenProps?.find(
98+
(screen) => screen?.name === Routes.FEATURE_FLAG_OVERRIDE,
99+
);
100+
101+
expect(featureFlagOverrideScreen).toBeDefined();
102+
expect(featureFlagOverrideScreen?.component.name).toBe(
103+
'FeatureFlagOverride',
104+
);
105+
});
63106
});

app/components/Views/Settings/index.test.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { SettingsViewSelectorsIDs } from '../../../../e2e/selectors/Settings/Set
66
import { backgroundState } from '../../../util/test/initial-root-state';
77
import { fireEvent } from '@testing-library/react-native';
88
import Routes from '../../../constants/navigation/Routes';
9+
import { strings } from '../../../../locales/i18n';
910

1011
// Mock Authentication module
1112
jest.mock('../../../core', () => ({
@@ -171,4 +172,40 @@ describe('Settings', () => {
171172
});
172173
expect(Authentication.lockApp).toHaveBeenCalledTimes(1);
173174
});
175+
176+
describe('Feature Flag Override', () => {
177+
const originalEnv = process.env.METAMASK_ENVIRONMENT;
178+
179+
beforeEach(() => {
180+
jest.clearAllMocks();
181+
// Reset to original value before each test
182+
if (originalEnv !== undefined) {
183+
process.env.METAMASK_ENVIRONMENT = originalEnv;
184+
} else {
185+
delete process.env.METAMASK_ENVIRONMENT;
186+
}
187+
});
188+
189+
afterEach(() => {
190+
if (originalEnv !== undefined) {
191+
process.env.METAMASK_ENVIRONMENT = originalEnv;
192+
} else {
193+
delete process.env.METAMASK_ENVIRONMENT;
194+
}
195+
});
196+
197+
it('renders feature flag override drawer when METAMASK_ENVIRONMENT is not production', () => {
198+
process.env.METAMASK_ENVIRONMENT = 'development';
199+
200+
const { getByText } = renderWithProvider(<Settings />, {
201+
state: initialState,
202+
});
203+
204+
const featureFlagOverrideTitle = getByText(
205+
strings('app_settings.feature_flag_override.title'),
206+
);
207+
208+
expect(featureFlagOverrideTitle).toBeDefined();
209+
});
210+
});
174211
});

app/components/Views/Settings/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ const Settings = () => {
329329
onPress={onPressDeveloperOptions}
330330
/>
331331
)}
332-
{process.env.NODE_ENV !== 'production' && (
332+
{process.env.METAMASK_ENVIRONMENT !== 'production' && (
333333
<SettingsDrawer
334334
title={strings('app_settings.feature_flag_override.title')}
335335
description={strings(

app/contexts/FeatureFlagOverrideContext.tsx

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,16 @@ export const FeatureFlagOverrideProvider: React.FC<
139139
getAllOverrides,
140140
]);
141141

142-
const featureFlagsList = Object.values(featureFlags).sort((a, b) =>
143-
a.key.localeCompare(b.key),
142+
const featureFlagsList = useMemo(
143+
() =>
144+
Object.values(featureFlags).sort((a, b) => a.key.localeCompare(b.key)),
145+
[featureFlags],
144146
);
145147

146148
const validateMinimumVersion = useCallback(
147149
(flagKey: string, flagValue: MinimumVersionFlagValue) => {
148150
if (
149-
process.env.NODE_ENV !== 'production' &&
151+
process.env.METAMASK_ENVIRONMENT !== 'production' &&
150152
!isMinimumRequiredVersionSupported(flagValue.minimumVersion)
151153
) {
152154
toastRef?.current?.showToast({
@@ -172,42 +174,62 @@ export const FeatureFlagOverrideProvider: React.FC<
172174
/**
173175
* get a specific feature flag value with overrides applied
174176
*/
175-
const getFeatureFlag = (key: string) => {
176-
const flag = featureFlags[key];
177-
if (!flag) {
178-
return undefined;
179-
}
180-
181-
if (flag.type === 'boolean with minimumVersion') {
182-
return validateMinimumVersion(
183-
flag.key,
184-
flag.value as unknown as MinimumVersionFlagValue,
185-
);
186-
}
187-
188-
return flag.value;
189-
};
177+
const getFeatureFlag = useCallback(
178+
(key: string) => {
179+
const flag = featureFlags[key];
180+
if (!flag) {
181+
return undefined;
182+
}
183+
184+
if (flag.type === 'boolean with minimumVersion') {
185+
return validateMinimumVersion(
186+
flag.key,
187+
flag.value as unknown as MinimumVersionFlagValue,
188+
);
189+
}
190+
191+
return flag.value;
192+
},
193+
[featureFlags, validateMinimumVersion],
194+
);
190195

191196
const getOverrideCount = useCallback(
192197
(): number => Object.keys(overrides).length,
193198
[overrides],
194199
);
195200

196-
const contextValue: FeatureFlagOverrideContextType = {
197-
featureFlags,
198-
originalFlags: rawFeatureFlags,
199-
getFeatureFlag,
200-
featureFlagsList,
201-
overrides,
202-
setOverride,
203-
removeOverride,
204-
clearAllOverrides,
205-
hasOverride,
206-
getOverride,
207-
getAllOverrides,
208-
applyOverrides,
209-
getOverrideCount,
210-
};
201+
const contextValue: FeatureFlagOverrideContextType = useMemo(
202+
() => ({
203+
featureFlags,
204+
originalFlags: rawFeatureFlags,
205+
getFeatureFlag,
206+
featureFlagsList,
207+
overrides,
208+
setOverride,
209+
removeOverride,
210+
clearAllOverrides,
211+
hasOverride,
212+
getOverride,
213+
getAllOverrides,
214+
applyOverrides,
215+
getOverrideCount,
216+
}),
217+
[
218+
featureFlags,
219+
rawFeatureFlags,
220+
getFeatureFlag,
221+
featureFlagsList,
222+
overrides,
223+
setOverride,
224+
removeOverride,
225+
clearAllOverrides,
226+
hasOverride,
227+
getOverride,
228+
getAllOverrides,
229+
applyOverrides,
230+
getOverrideCount,
231+
],
232+
);
211233

212234
return (
213235
<FeatureFlagOverrideContext.Provider value={contextValue}>

0 commit comments

Comments
 (0)