diff --git a/app/components/Nav/Main/MainNavigator.js b/app/components/Nav/Main/MainNavigator.js index 8b30f6fa5373..5985222e7717 100644 --- a/app/components/Nav/Main/MainNavigator.js +++ b/app/components/Nav/Main/MainNavigator.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useMemo, useCallback } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import { Image, StyleSheet, Keyboard, Platform } from 'react-native'; import { createStackNavigator } from '@react-navigation/stack'; import { useSelector } from 'react-redux'; @@ -1192,7 +1192,7 @@ const MainNavigator = () => { ...GeneralSettings.navigationOptions, }} /> - {process.env.NODE_ENV !== 'production' && ( + {process.env.METAMASK_ENVIRONMENT !== 'production' && ( ({ })); describe('MainNavigator', () => { + const originalEnv = process.env.METAMASK_ENVIRONMENT; + beforeEach(() => { jest.clearAllMocks(); }); + afterEach(() => { + process.env.METAMASK_ENVIRONMENT = originalEnv; + }); + it('matches rendered snapshot', () => { // Given the initial app state // When rendering the MainNavigator @@ -60,4 +66,41 @@ describe('MainNavigator', () => { expect(sampleFeatureScreen).toBeDefined(); expect(sampleFeatureScreen?.component.name).toBe('SampleFeatureFlow'); }); + + it('includes FeatureFlagOverride screen when METAMASK_ENVIRONMENT is not production', () => { + // Given a non-production environment + process.env.METAMASK_ENVIRONMENT = 'dev'; + + // When rendering the MainNavigator + const container = renderWithProvider(, { + state: initialRootState, + }); + + // Then it should contain the FeatureFlagOverride screen + interface ScreenChild { + name: string; + component: { name: string }; + } + const screenProps: ScreenChild[] = container.root.children + .filter( + (child): child is ReactTestInstance => + typeof child === 'object' && + 'type' in child && + 'props' in child && + child.type?.toString() === 'Screen', + ) + .map((child) => ({ + name: child.props.name, + component: child.props.component, + })); + + const featureFlagOverrideScreen = screenProps?.find( + (screen) => screen?.name === Routes.FEATURE_FLAG_OVERRIDE, + ); + + expect(featureFlagOverrideScreen).toBeDefined(); + expect(featureFlagOverrideScreen?.component.name).toBe( + 'FeatureFlagOverride', + ); + }); }); diff --git a/app/components/Views/Settings/index.test.tsx b/app/components/Views/Settings/index.test.tsx index 17e8cc52097a..e12e1178b4bf 100644 --- a/app/components/Views/Settings/index.test.tsx +++ b/app/components/Views/Settings/index.test.tsx @@ -6,6 +6,7 @@ import { SettingsViewSelectorsIDs } from '../../../../e2e/selectors/Settings/Set import { backgroundState } from '../../../util/test/initial-root-state'; import { fireEvent } from '@testing-library/react-native'; import Routes from '../../../constants/navigation/Routes'; +import { strings } from '../../../../locales/i18n'; // Mock Authentication module jest.mock('../../../core', () => ({ @@ -171,4 +172,40 @@ describe('Settings', () => { }); expect(Authentication.lockApp).toHaveBeenCalledTimes(1); }); + + describe('Feature Flag Override', () => { + const originalEnv = process.env.METAMASK_ENVIRONMENT; + + beforeEach(() => { + jest.clearAllMocks(); + // Reset to original value before each test + if (originalEnv !== undefined) { + process.env.METAMASK_ENVIRONMENT = originalEnv; + } else { + delete process.env.METAMASK_ENVIRONMENT; + } + }); + + afterEach(() => { + if (originalEnv !== undefined) { + process.env.METAMASK_ENVIRONMENT = originalEnv; + } else { + delete process.env.METAMASK_ENVIRONMENT; + } + }); + + it('renders feature flag override drawer when METAMASK_ENVIRONMENT is not production', () => { + process.env.METAMASK_ENVIRONMENT = 'development'; + + const { getByText } = renderWithProvider(, { + state: initialState, + }); + + const featureFlagOverrideTitle = getByText( + strings('app_settings.feature_flag_override.title'), + ); + + expect(featureFlagOverrideTitle).toBeDefined(); + }); + }); }); diff --git a/app/components/Views/Settings/index.tsx b/app/components/Views/Settings/index.tsx index 166935878ec2..6b8ce875973f 100644 --- a/app/components/Views/Settings/index.tsx +++ b/app/components/Views/Settings/index.tsx @@ -329,7 +329,7 @@ const Settings = () => { onPress={onPressDeveloperOptions} /> )} - {process.env.NODE_ENV !== 'production' && ( + {process.env.METAMASK_ENVIRONMENT !== 'production' && ( - a.key.localeCompare(b.key), + const featureFlagsList = useMemo( + () => + Object.values(featureFlags).sort((a, b) => a.key.localeCompare(b.key)), + [featureFlags], ); const validateMinimumVersion = useCallback( (flagKey: string, flagValue: MinimumVersionFlagValue) => { if ( - process.env.NODE_ENV !== 'production' && + process.env.METAMASK_ENVIRONMENT !== 'production' && !isMinimumRequiredVersionSupported(flagValue.minimumVersion) ) { toastRef?.current?.showToast({ @@ -172,42 +174,62 @@ export const FeatureFlagOverrideProvider: React.FC< /** * get a specific feature flag value with overrides applied */ - const getFeatureFlag = (key: string) => { - const flag = featureFlags[key]; - if (!flag) { - return undefined; - } - - if (flag.type === 'boolean with minimumVersion') { - return validateMinimumVersion( - flag.key, - flag.value as unknown as MinimumVersionFlagValue, - ); - } - - return flag.value; - }; + const getFeatureFlag = useCallback( + (key: string) => { + const flag = featureFlags[key]; + if (!flag) { + return undefined; + } + + if (flag.type === 'boolean with minimumVersion') { + return validateMinimumVersion( + flag.key, + flag.value as unknown as MinimumVersionFlagValue, + ); + } + + return flag.value; + }, + [featureFlags, validateMinimumVersion], + ); const getOverrideCount = useCallback( (): number => Object.keys(overrides).length, [overrides], ); - const contextValue: FeatureFlagOverrideContextType = { - featureFlags, - originalFlags: rawFeatureFlags, - getFeatureFlag, - featureFlagsList, - overrides, - setOverride, - removeOverride, - clearAllOverrides, - hasOverride, - getOverride, - getAllOverrides, - applyOverrides, - getOverrideCount, - }; + const contextValue: FeatureFlagOverrideContextType = useMemo( + () => ({ + featureFlags, + originalFlags: rawFeatureFlags, + getFeatureFlag, + featureFlagsList, + overrides, + setOverride, + removeOverride, + clearAllOverrides, + hasOverride, + getOverride, + getAllOverrides, + applyOverrides, + getOverrideCount, + }), + [ + featureFlags, + rawFeatureFlags, + getFeatureFlag, + featureFlagsList, + overrides, + setOverride, + removeOverride, + clearAllOverrides, + hasOverride, + getOverride, + getAllOverrides, + applyOverrides, + getOverrideCount, + ], + ); return (