diff --git a/app/components/UI/OnboardingWizard/index.tsx b/app/components/UI/OnboardingWizard/index.tsx index d771dbf9a96..e6379a29095 100644 --- a/app/components/UI/OnboardingWizard/index.tsx +++ b/app/components/UI/OnboardingWizard/index.tsx @@ -1,7 +1,6 @@ import React, { useContext } from 'react'; import { View, StyleSheet, TextStyle } from 'react-native'; import { useDispatch, useSelector } from 'react-redux'; -import DefaultPreference from 'react-native-default-preference'; import Modal from 'react-native-modal'; import type { Theme } from '@metamask/design-tokens'; import { DrawerContext } from '../../../components/Nav/Main/MainNavigator'; @@ -23,7 +22,7 @@ import { } from '../../../core/Analytics'; import { useTheme } from '../../../util/theme'; import Device from '../../../util/device'; -import AsyncStorageWrapper from '../../../store/async-storage-wrapper'; +import StorageWrapper from '../../../store/async-storage-wrapper'; import { isTest } from '../../../util/test/utils'; import { useMetrics } from '../../hooks/useMetrics'; @@ -103,7 +102,7 @@ const OnboardingWizard = ({ * Close onboarding wizard setting step to 0 and closing drawer */ const closeOnboardingWizard = async () => { - await DefaultPreference.set(ONBOARDING_WIZARD, EXPLORED); + await StorageWrapper.setItem(ONBOARDING_WIZARD, EXPLORED); dispatch(setOnboardingWizardStep(0)); drawerRef?.current?.dismissDrawer?.(); trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_SKIPPED, { @@ -118,7 +117,7 @@ const OnboardingWizard = ({ // it indicates that it was provided by fixtures, triggering the call to closeOnboardingWizard(). if (isTest && step === 1) { const inTestCloseOnboardingWizard = async () => { - const wizardStep = await AsyncStorageWrapper.getItem(ONBOARDING_WIZARD); + const wizardStep = await StorageWrapper.getItem(ONBOARDING_WIZARD); if (wizardStep === EXPLORED) { await closeOnboardingWizard(); } diff --git a/app/components/UI/OptinMetrics/index.js b/app/components/UI/OptinMetrics/index.js index ef195031657..8d4a0d4c981 100644 --- a/app/components/UI/OptinMetrics/index.js +++ b/app/components/UI/OptinMetrics/index.js @@ -25,7 +25,7 @@ import { MetaMetricsEvents, withMetricsAwareness, } from '../../hooks/useMetrics'; -import DefaultPreference from 'react-native-default-preference'; +import StorageWrapper from '../../../store/async-storage-wrapper'; import { ThemeContext } from '../../../util/theme'; import { MetaMetricsOptInSelectorsIDs } from '../../../../e2e/selectors/Onboarding/MetaMetricsOptIn.selectors'; import Checkbox from '../../../component-library/components/Checkbox'; @@ -236,7 +236,7 @@ class OptinMetrics extends PureComponent { } // Get onboarding wizard state - const onboardingWizard = await DefaultPreference.get(ONBOARDING_WIZARD); + const onboardingWizard = await StorageWrapper.getItem(ONBOARDING_WIZARD); if (onboardingWizard) { this.props.navigation.reset({ routes: [{ name: 'HomeNav' }] }); } else { diff --git a/app/components/Views/AccountBackupStep1/index.js b/app/components/Views/AccountBackupStep1/index.js index 1700173ba3f..6f9a3e13152 100644 --- a/app/components/Views/AccountBackupStep1/index.js +++ b/app/components/Views/AccountBackupStep1/index.js @@ -27,7 +27,7 @@ import { connect } from 'react-redux'; import setOnboardingWizardStep from '../../../actions/wizard'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import DefaultPreference from 'react-native-default-preference'; +import StorageWrapper from '../../../store/async-storage-wrapper'; import { useTheme } from '../../../util/theme'; import { ManualBackUpStepsSelectorsIDs } from '../../../../e2e/selectors/Onboarding/ManualBackUpSteps.selectors'; import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; @@ -192,7 +192,7 @@ const AccountBackupStep1 = (props) => { hideRemindLaterModal(); track(MetaMetricsEvents.WALLET_SECURITY_SKIP_CONFIRMED); // Get onboarding wizard state - const onboardingWizard = await DefaultPreference.get(ONBOARDING_WIZARD); + const onboardingWizard = await StorageWrapper.getItem(ONBOARDING_WIZARD); !onboardingWizard && props.setOnboardingWizardStep(1); props.navigation.reset({ index: 1, diff --git a/app/components/Views/ImportFromSecretRecoveryPhrase/index.js b/app/components/Views/ImportFromSecretRecoveryPhrase/index.js index dcb53ccf776..f590f186987 100644 --- a/app/components/Views/ImportFromSecretRecoveryPhrase/index.js +++ b/app/components/Views/ImportFromSecretRecoveryPhrase/index.js @@ -11,12 +11,11 @@ import { Platform, } from 'react-native'; import { connect } from 'react-redux'; -import AsyncStorage from '../../../store/async-storage-wrapper'; import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; import zxcvbn from 'zxcvbn'; import Icon from 'react-native-vector-icons/FontAwesome'; import { OutlinedTextField } from 'react-native-material-textfield'; -import DefaultPreference from 'react-native-default-preference'; +import StorageWrapper from '../../../store/async-storage-wrapper'; import Clipboard from '@react-native-clipboard/clipboard'; import AppConstants from '../../../core/AppConstants'; import Device from '../../../util/device'; @@ -117,10 +116,10 @@ const ImportFromSecretRecoveryPhrase = ({ const setBiometricsOption = async () => { const authData = await Authentication.getType(); - const previouslyDisabled = await AsyncStorage.getItem( + const previouslyDisabled = await StorageWrapper.getItem( BIOMETRY_CHOICE_DISABLED, ); - const passcodePreviouslyDisabled = await AsyncStorage.getItem( + const passcodePreviouslyDisabled = await StorageWrapper.getItem( PASSCODE_DISABLED, ); if (authData.currentAuthType === AUTHENTICATION_TYPE.PASSCODE) { @@ -228,7 +227,9 @@ const ImportFromSecretRecoveryPhrase = ({ await handleRejectedOsBiometricPrompt(parsedSeed); } // Get onboarding wizard state - const onboardingWizard = await DefaultPreference.get(ONBOARDING_WIZARD); + const onboardingWizard = await StorageWrapper.getItem( + ONBOARDING_WIZARD, + ); setLoading(false); passwordSet(); setLockTime(AppConstants.DEFAULT_LOCK_TIMEOUT); diff --git a/app/components/Views/Login/index.js b/app/components/Views/Login/index.js index b68e300af1d..26eef8a49e0 100644 --- a/app/components/Views/Login/index.js +++ b/app/components/Views/Login/index.js @@ -16,7 +16,6 @@ import Text, { TextColor, TextVariant, } from '../../../component-library/components/Texts/Text'; -import AsyncStorage from '../../../store/async-storage-wrapper'; import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; import Button from '@metamask/react-native-button'; import StyledButton from '../../UI/StyledButton'; @@ -44,7 +43,7 @@ import Routes from '../../../constants/navigation/Routes'; import { passwordRequirementsMet } from '../../../util/password'; import ErrorBoundary from '../ErrorBoundary'; import { toLowerCaseEquals } from '../../../util/general'; -import DefaultPreference from 'react-native-default-preference'; +import StorageWrapper from '../../../store/async-storage-wrapper'; import { Authentication } from '../../../core'; import AUTHENTICATION_TYPE from '../../../constants/userProperties'; import { ThemeContext, mockTheme } from '../../../util/theme'; @@ -251,10 +250,10 @@ class Login extends PureComponent { const authData = await Authentication.getType(); //Setup UI to handle Biometric - const previouslyDisabled = await AsyncStorage.getItem( + const previouslyDisabled = await StorageWrapper.getItem( BIOMETRY_CHOICE_DISABLED, ); - const passcodePreviouslyDisabled = await AsyncStorage.getItem( + const passcodePreviouslyDisabled = await StorageWrapper.getItem( PASSCODE_DISABLED, ); @@ -373,7 +372,7 @@ class Login extends PureComponent { Keyboard.dismiss(); // Get onboarding wizard state - const onboardingWizard = await DefaultPreference.get(ONBOARDING_WIZARD); + const onboardingWizard = await StorageWrapper.getItem(ONBOARDING_WIZARD); if (onboardingWizard) { this.props.navigation.replace(Routes.ONBOARDING.HOME_NAV); } else { @@ -434,7 +433,7 @@ class Login extends PureComponent { field?.blur(); try { await Authentication.appTriggeredAuth(); - const onboardingWizard = await DefaultPreference.get(ONBOARDING_WIZARD); + const onboardingWizard = await StorageWrapper.getItem(ONBOARDING_WIZARD); if (!onboardingWizard) this.props.setOnboardingWizardStep(1); this.props.navigation.replace(Routes.ONBOARDING.HOME_NAV); // Only way to land back on Login is to log out, which clears credentials (meaning we should not show biometric button) diff --git a/app/components/Views/ManualBackupStep3/index.js b/app/components/Views/ManualBackupStep3/index.js index f3ed4cb1b1c..b42c2cfccf0 100644 --- a/app/components/Views/ManualBackupStep3/index.js +++ b/app/components/Views/ManualBackupStep3/index.js @@ -3,7 +3,6 @@ import { Alert, BackHandler, View, StyleSheet, Keyboard } from 'react-native'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { fontStyles } from '../../../styles/common'; -import AsyncStorage from '../../../store/async-storage-wrapper'; import OnboardingProgress from '../../UI/OnboardingProgress'; import { strings } from '../../../../locales/i18n'; import { showAlert } from '../../../actions/alert'; @@ -18,7 +17,7 @@ import { SEED_PHRASE_HINTS, } from '../../../constants/storage'; import { MetaMetricsEvents } from '../../../core/Analytics'; -import DefaultPreference from 'react-native-default-preference'; +import StorageWrapper from '../../../store/async-storage-wrapper'; import { ThemeContext, mockTheme } from '../../../util/theme'; import trackOnboarding from '../../../util/metrics/TrackOnboarding/trackOnboarding'; import OnboardingSuccess from '../OnboardingSuccess'; @@ -118,7 +117,7 @@ class ManualBackupStep3 extends PureComponent { componentDidMount = async () => { this.updateNavBar(); - const currentSeedphraseHints = await AsyncStorage.getItem( + const currentSeedphraseHints = await StorageWrapper.getItem( SEED_PHRASE_HINTS, ); const parsedHints = @@ -165,11 +164,11 @@ class ManualBackupStep3 extends PureComponent { return; } this.toggleHint(); - const currentSeedphraseHints = await AsyncStorage.getItem( + const currentSeedphraseHints = await StorageWrapper.getItem( SEED_PHRASE_HINTS, ); const parsedHints = JSON.parse(currentSeedphraseHints); - await AsyncStorage.setItem( + await StorageWrapper.setItem( SEED_PHRASE_HINTS, JSON.stringify({ ...parsedHints, manualBackup: hintText }), ); @@ -177,7 +176,7 @@ class ManualBackupStep3 extends PureComponent { }; done = async () => { - const onboardingWizard = await DefaultPreference.get(ONBOARDING_WIZARD); + const onboardingWizard = await StorageWrapper.getItem(ONBOARDING_WIZARD); if (onboardingWizard) { this.props.navigation.reset({ routes: [{ name: 'HomeNav' }] }); } else { diff --git a/app/components/hooks/DeleteWallet/useDeleteWallet.test.tsx b/app/components/hooks/DeleteWallet/useDeleteWallet.test.tsx index fddd8207349..3d129a2d6d9 100644 --- a/app/components/hooks/DeleteWallet/useDeleteWallet.test.tsx +++ b/app/components/hooks/DeleteWallet/useDeleteWallet.test.tsx @@ -16,6 +16,11 @@ jest.mock('../../../core/Engine', () => ({ }, })); +jest.mock('../../../store/async-storage-wrapper', () => ({ + getItem: jest.fn(), + removeItem: jest.fn(), +})); + describe('useDeleteWallet', () => { test('it should provide two outputs of type function', () => { const { result } = renderHook(() => useDeleteWallet()); diff --git a/app/core/Analytics/MetaMetrics.test.ts b/app/core/Analytics/MetaMetrics.test.ts index beac29ed296..14b0ebd0636 100644 --- a/app/core/Analytics/MetaMetrics.test.ts +++ b/app/core/Analytics/MetaMetrics.test.ts @@ -1,5 +1,5 @@ import MetaMetrics from './MetaMetrics'; -import DefaultPreference from 'react-native-default-preference'; +import StorageWrapper from '../../store/async-storage-wrapper'; import { AGREED, ANALYTICS_DATA_DELETION_DATE, @@ -16,7 +16,7 @@ import { IMetaMetricsEvent, } from './MetaMetrics.types'; -jest.mock('react-native-default-preference'); +jest.mock('../../store/async-storage-wrapper'); const mockGet = jest.fn(); const mockSet = jest.fn(); const mockClear = jest.fn(); @@ -34,9 +34,9 @@ class TestMetaMetrics extends MetaMetrics { describe('MetaMetrics', () => { beforeEach(async () => { - DefaultPreference.get = mockGet; - DefaultPreference.set = mockSet; - DefaultPreference.clear = mockClear; + StorageWrapper.getItem = mockGet; + StorageWrapper.setItem = mockSet; + StorageWrapper.clearAll = mockClear; TestMetaMetrics.resetInstance(); }); @@ -64,7 +64,7 @@ describe('MetaMetrics', () => { expect(await metaMetrics.configure()).toBeTruthy(); }); it('fails silently', async () => { - DefaultPreference.get = jest.fn().mockRejectedValue(new Error('error')); + StorageWrapper.getItem = jest.fn().mockRejectedValue(new Error('error')); const metaMetrics = TestMetaMetrics.getInstance(); expect(await metaMetrics.configure()).toBeFalsy(); }); @@ -76,7 +76,7 @@ describe('MetaMetrics', () => { const metaMetrics = TestMetaMetrics.getInstance(); expect(await metaMetrics.configure()).toBeTruthy(); - expect(DefaultPreference.get).toHaveBeenCalledWith(METRICS_OPT_IN); + expect(StorageWrapper.getItem).toHaveBeenCalledWith(METRICS_OPT_IN); expect(metaMetrics.isEnabled()).toBeFalsy(); }); @@ -85,7 +85,7 @@ describe('MetaMetrics', () => { const metaMetrics = TestMetaMetrics.getInstance(); expect(await metaMetrics.configure()).toBeTruthy(); - expect(DefaultPreference.get).toHaveBeenCalledWith(METRICS_OPT_IN); + expect(StorageWrapper.getItem).toHaveBeenCalledWith(METRICS_OPT_IN); expect(metaMetrics.isEnabled()).toBeTruthy(); }); @@ -94,7 +94,7 @@ describe('MetaMetrics', () => { expect(await metaMetrics.configure()).toBeTruthy(); await metaMetrics.enable(); - expect(DefaultPreference.set).toHaveBeenLastCalledWith( + expect(StorageWrapper.setItem).toHaveBeenLastCalledWith( METRICS_OPT_IN, AGREED, ); @@ -107,7 +107,7 @@ describe('MetaMetrics', () => { // Enable first as it is disabled by default await metaMetrics.enable(); // Test it is enabled before disabling - expect(DefaultPreference.set).toHaveBeenLastCalledWith( + expect(StorageWrapper.setItem).toHaveBeenLastCalledWith( METRICS_OPT_IN, AGREED, ); @@ -115,7 +115,7 @@ describe('MetaMetrics', () => { await metaMetrics.enable(false); - expect(DefaultPreference.set).toHaveBeenLastCalledWith( + expect(StorageWrapper.setItem).toHaveBeenLastCalledWith( METRICS_OPT_IN, DENIED, ); @@ -133,7 +133,7 @@ describe('MetaMetrics', () => { metaMetrics.trackEvent(event, properties); - expect(DefaultPreference.get).toHaveBeenCalledWith(METRICS_OPT_IN); + expect(StorageWrapper.getItem).toHaveBeenCalledWith(METRICS_OPT_IN); // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any const { segmentMockClient } = global as any; @@ -151,7 +151,7 @@ describe('MetaMetrics', () => { metaMetrics.trackEvent(event); - expect(DefaultPreference.get).toHaveBeenCalledWith(METRICS_OPT_IN); + expect(StorageWrapper.getItem).toHaveBeenCalledWith(METRICS_OPT_IN); // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any const { segmentMockClient } = global as any; @@ -169,7 +169,7 @@ describe('MetaMetrics', () => { metaMetrics.trackEvent(event, properties); - expect(DefaultPreference.get).toHaveBeenCalledWith(METRICS_OPT_IN); + expect(StorageWrapper.getItem).toHaveBeenCalledWith(METRICS_OPT_IN); // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any const { segmentMockClient } = global as any; @@ -241,7 +241,7 @@ describe('MetaMetrics', () => { metaMetrics.trackEvent(event, properties, false); - expect(DefaultPreference.get).toHaveBeenCalledWith(METRICS_OPT_IN); + expect(StorageWrapper.getItem).toHaveBeenCalledWith(METRICS_OPT_IN); // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any const { segmentMockClient } = global as any; @@ -379,7 +379,7 @@ describe('MetaMetrics', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const { segmentMockClient } = global as any; expect(segmentMockClient.reset).toHaveBeenCalledWith(true); - expect(DefaultPreference.set).toHaveBeenCalledWith(METAMETRICS_ID, ''); + expect(StorageWrapper.setItem).toHaveBeenCalledWith(METAMETRICS_ID, ''); }); it('flushes the segment client', async () => { @@ -393,14 +393,14 @@ describe('MetaMetrics', () => { }); describe('Ids', () => { - it('is returned from DefaultPreference when instance not configured', async () => { + it('is returned from StorageWrapper when instance not configured', async () => { const UUID = '00000000-0000-0000-0000-000000000000'; mockGet.mockImplementation(async (key: string) => key === METAMETRICS_ID ? UUID : '', ); const metaMetrics = TestMetaMetrics.getInstance(); expect(await metaMetrics.getMetaMetricsId()).toEqual(UUID); - expect(DefaultPreference.get).toHaveBeenCalledWith(METAMETRICS_ID); + expect(StorageWrapper.getItem).toHaveBeenCalledWith(METAMETRICS_ID); }); it('is returned from memory when instance configured', async () => { @@ -413,7 +413,7 @@ describe('MetaMetrics', () => { mockGet.mockClear(); expect(await metaMetrics.getMetaMetricsId()).toEqual(testID); - expect(DefaultPreference.get).not.toHaveBeenCalled(); + expect(StorageWrapper.getItem).not.toHaveBeenCalled(); }); it('uses Mixpanel ID if it is set', async () => { @@ -422,15 +422,15 @@ describe('MetaMetrics', () => { const metaMetrics = TestMetaMetrics.getInstance(); expect(await metaMetrics.configure()).toBeTruthy(); - expect(DefaultPreference.get).toHaveBeenNthCalledWith( + expect(StorageWrapper.getItem).toHaveBeenNthCalledWith( 2, MIXPANEL_METAMETRICS_ID, ); - expect(DefaultPreference.set).toHaveBeenCalledWith( + expect(StorageWrapper.setItem).toHaveBeenCalledWith( METAMETRICS_ID, mixPanelUUID, ); - expect(DefaultPreference.get).not.toHaveBeenCalledWith(METAMETRICS_ID); + expect(StorageWrapper.getItem).not.toHaveBeenCalledWith(METAMETRICS_ID); expect(await metaMetrics.getMetaMetricsId()).toEqual(mixPanelUUID); }); @@ -442,12 +442,12 @@ describe('MetaMetrics', () => { const metaMetrics = TestMetaMetrics.getInstance(); expect(await metaMetrics.configure()).toBeTruthy(); - expect(DefaultPreference.get).toHaveBeenNthCalledWith( + expect(StorageWrapper.getItem).toHaveBeenNthCalledWith( 2, MIXPANEL_METAMETRICS_ID, ); - expect(DefaultPreference.get).toHaveBeenNthCalledWith(3, METAMETRICS_ID); - expect(DefaultPreference.set).not.toHaveBeenCalled(); + expect(StorageWrapper.getItem).toHaveBeenNthCalledWith(3, METAMETRICS_ID); + expect(StorageWrapper.setItem).not.toHaveBeenCalled(); expect(await metaMetrics.getMetaMetricsId()).toEqual(UUID); }); @@ -460,11 +460,11 @@ describe('MetaMetrics', () => { // create a new instance and check that user id was not changed TestMetaMetrics.getInstance(); - expect(DefaultPreference.set).not.toHaveBeenCalledWith( + expect(StorageWrapper.setItem).not.toHaveBeenCalledWith( METAMETRICS_ID, '', ); - expect(DefaultPreference.get).toHaveBeenNthCalledWith(3, METAMETRICS_ID); + expect(StorageWrapper.getItem).toHaveBeenNthCalledWith(3, METAMETRICS_ID); expect(await metaMetrics.getMetaMetricsId()).toEqual(metricsId); }); @@ -478,7 +478,7 @@ describe('MetaMetrics', () => { await metaMetrics.reset(); // Check change on the MetaMerics class side - expect(DefaultPreference.set).toHaveBeenNthCalledWith( + expect(StorageWrapper.setItem).toHaveBeenNthCalledWith( 2, METAMETRICS_ID, '', @@ -515,7 +515,7 @@ describe('MetaMetrics', () => { expect(result).toEqual({ status: DataDeleteResponseStatus.ok }); - expect(DefaultPreference.set).toHaveBeenCalledWith( + expect(StorageWrapper.setItem).toHaveBeenCalledWith( METAMETRICS_DELETION_REGULATION_ID, 'TWV0YU1hc2t1c2Vzbm9wb2ludCE', ); @@ -524,7 +524,7 @@ describe('MetaMetrics', () => { const day = currentDate.getUTCDate(); const month = currentDate.getUTCMonth() + 1; const year = currentDate.getUTCFullYear(); - expect(DefaultPreference.set).toHaveBeenCalledWith( + expect(StorageWrapper.setItem).toHaveBeenCalledWith( ANALYTICS_DATA_DELETION_DATE, `${day}/${month}/${year}`, ); @@ -543,11 +543,11 @@ describe('MetaMetrics', () => { expect(result.status).toBe(DataDeleteResponseStatus.error); expect(result.error).toBe('Analytics Deletion Task Error'); - expect(DefaultPreference.set).not.toHaveBeenCalledWith( + expect(StorageWrapper.setItem).not.toHaveBeenCalledWith( METAMETRICS_DELETION_REGULATION_ID, expect.any(String), ); - expect(DefaultPreference.set).not.toHaveBeenCalledWith( + expect(StorageWrapper.setItem).not.toHaveBeenCalledWith( ANALYTICS_DATA_DELETION_DATE, expect.any(String), ); @@ -557,38 +557,38 @@ describe('MetaMetrics', () => { describe('Date', () => { it('gets date from preferences storage', async () => { const expectedDate = '04/05/2023'; - DefaultPreference.get = jest.fn().mockResolvedValue(expectedDate); + StorageWrapper.getItem = jest.fn().mockResolvedValue(expectedDate); const metaMetrics = TestMetaMetrics.getInstance(); expect(await metaMetrics.configure()).toBeTruthy(); expect(metaMetrics.getDeleteRegulationCreationDate()).toBe( expectedDate, ); - expect(DefaultPreference.get).toHaveBeenCalledWith( + expect(StorageWrapper.getItem).toHaveBeenCalledWith( ANALYTICS_DATA_DELETION_DATE, ); }); it('keeps date in instance', async () => { const expectedDate = '04/05/2023'; - DefaultPreference.get = jest.fn().mockResolvedValue(expectedDate); + StorageWrapper.getItem = jest.fn().mockResolvedValue(expectedDate); const metaMetrics = TestMetaMetrics.getInstance(); expect(await metaMetrics.configure()).toBeTruthy(); // this resets the call count and changes the return value to nothing - DefaultPreference.get = jest.fn().mockResolvedValue(null); + StorageWrapper.getItem = jest.fn().mockResolvedValue(null); expect(metaMetrics.getDeleteRegulationCreationDate()).toBe( expectedDate, ); - expect(DefaultPreference.get).not.toHaveBeenCalledWith( + expect(StorageWrapper.getItem).not.toHaveBeenCalledWith( ANALYTICS_DATA_DELETION_DATE, ); }); it('returns empty string if no date in preferences storage', async () => { - DefaultPreference.get = jest.fn().mockResolvedValue(undefined); + StorageWrapper.getItem = jest.fn().mockResolvedValue(undefined); const metaMetrics = TestMetaMetrics.getInstance(); expect(await metaMetrics.configure()).toBeTruthy(); expect(metaMetrics.getDeleteRegulationCreationDate()).toBeUndefined(); - expect(DefaultPreference.get).toHaveBeenCalledWith( + expect(StorageWrapper.getItem).toHaveBeenCalledWith( ANALYTICS_DATA_DELETION_DATE, ); }); @@ -597,38 +597,38 @@ describe('MetaMetrics', () => { describe('Regulation Id', () => { it('gets id from preferences storage', async () => { const expecterRegulationId = 'TWV0YU1hc2t1c2Vzbm9wb2ludCE'; - DefaultPreference.get = jest + StorageWrapper.getItem = jest .fn() .mockResolvedValue(expecterRegulationId); const metaMetrics = TestMetaMetrics.getInstance(); expect(await metaMetrics.configure()).toBeTruthy(); expect(metaMetrics.getDeleteRegulationId()).toBe(expecterRegulationId); - expect(DefaultPreference.get).toHaveBeenCalledWith( + expect(StorageWrapper.getItem).toHaveBeenCalledWith( METAMETRICS_DELETION_REGULATION_ID, ); }); it('keeps id in instance', async () => { const expecterRegulationId = 'TWV0YU1hc2t1c2Vzbm9wb2ludCE'; - DefaultPreference.get = jest + StorageWrapper.getItem = jest .fn() .mockResolvedValue(expecterRegulationId); const metaMetrics = TestMetaMetrics.getInstance(); expect(await metaMetrics.configure()).toBeTruthy(); // this resets the call count and changes the return value to nothing - DefaultPreference.get = jest.fn().mockResolvedValue(null); + StorageWrapper.getItem = jest.fn().mockResolvedValue(null); expect(metaMetrics.getDeleteRegulationId()).toBe(expecterRegulationId); - expect(DefaultPreference.get).not.toHaveBeenCalledWith( + expect(StorageWrapper.getItem).not.toHaveBeenCalledWith( METAMETRICS_DELETION_REGULATION_ID, ); }); it('returns empty string if no id in preferences storage', async () => { - DefaultPreference.get = jest.fn().mockResolvedValue(undefined); + StorageWrapper.getItem = jest.fn().mockResolvedValue(undefined); const metaMetrics = TestMetaMetrics.getInstance(); expect(await metaMetrics.configure()).toBeTruthy(); expect(metaMetrics.getDeleteRegulationId()).toBeUndefined(); - expect(DefaultPreference.get).toHaveBeenCalledWith( + expect(StorageWrapper.getItem).toHaveBeenCalledWith( METAMETRICS_DELETION_REGULATION_ID, ); }); @@ -636,7 +636,7 @@ describe('MetaMetrics', () => { describe('check request', () => { it('data deletion task check succeeds', async () => { - DefaultPreference.get = jest.fn((key) => { + StorageWrapper.getItem = jest.fn((key) => { switch (key) { case METAMETRICS_DELETION_REGULATION_ID: return Promise.resolve('TWV0YU1hc2t1c2Vzbm9wb2ludCE'); @@ -692,7 +692,7 @@ describe('MetaMetrics', () => { }); it('data deletion task check fails without METAMETRICS_DELETION_REGULATION_ID', async () => { - DefaultPreference.get = jest.fn().mockResolvedValue(undefined); + StorageWrapper.getItem = jest.fn().mockResolvedValue(undefined); const metaMetrics = TestMetaMetrics.getInstance(); const { diff --git a/app/core/Analytics/MetaMetrics.ts b/app/core/Analytics/MetaMetrics.ts index 82b1cebb691..af5c75fd4f9 100644 --- a/app/core/Analytics/MetaMetrics.ts +++ b/app/core/Analytics/MetaMetrics.ts @@ -6,7 +6,7 @@ import { UserTraits, } from '@segment/analytics-react-native'; import axios, { AxiosHeaderValue } from 'axios'; -import DefaultPreference from 'react-native-default-preference'; +import StorageWrapper from '../../store/async-storage-wrapper'; import Logger from '../../util/Logger'; import { AGREED, @@ -177,7 +177,7 @@ class MetaMetrics implements IMetaMetrics { * @returns Promise containing the enabled state */ #isMetaMetricsEnabled = async (): Promise => { - const enabledPref = await DefaultPreference.get(METRICS_OPT_IN); + const enabledPref = await StorageWrapper.getItem(METRICS_OPT_IN); this.enabled = AGREED === enabledPref; if (__DEV__) Logger.log(`Current MetaMatrics enable state: ${this.enabled}`); @@ -189,21 +189,21 @@ class MetaMetrics implements IMetaMetrics { * @private */ #getIsDataRecordedFromPrefs = async (): Promise => - (await DefaultPreference.get(ANALYTICS_DATA_RECORDED)) === 'true'; + (await StorageWrapper.getItem(ANALYTICS_DATA_RECORDED)) === 'true'; /** * Retrieve the analytics deletion request date from the preference * @private */ #getDeleteRegulationDateFromPrefs = async (): Promise => - await DefaultPreference.get(ANALYTICS_DATA_DELETION_DATE); + await StorageWrapper.getItem(ANALYTICS_DATA_DELETION_DATE); /** * Retrieve the analytics deletion regulation ID from the preference * @private */ #getDeleteRegulationIdFromPrefs = async (): Promise => - await DefaultPreference.get(METAMETRICS_DELETION_REGULATION_ID); + await StorageWrapper.getItem(METAMETRICS_DELETION_REGULATION_ID); /** * Persist the analytics recording status @@ -212,7 +212,7 @@ class MetaMetrics implements IMetaMetrics { */ #setIsDataRecorded = async (isDataRecorded = false): Promise => { this.dataRecorded = isDataRecorded; - await DefaultPreference.set( + await StorageWrapper.setItem( ANALYTICS_DATA_RECORDED, String(isDataRecorded), ); @@ -228,7 +228,7 @@ class MetaMetrics implements IMetaMetrics { deleteRegulationId: string, ): Promise => { this.deleteRegulationId = deleteRegulationId; - await DefaultPreference.set( + await StorageWrapper.setItem( METAMETRICS_DELETION_REGULATION_ID, deleteRegulationId, ); @@ -249,7 +249,7 @@ class MetaMetrics implements IMetaMetrics { this.deleteRegulationDate = deletionDate; // similar to the one used in the legacy Analytics - await DefaultPreference.set(ANALYTICS_DATA_DELETION_DATE, deletionDate); + await StorageWrapper.setItem(ANALYTICS_DATA_DELETION_DATE, deletionDate); }; /** @@ -265,19 +265,21 @@ class MetaMetrics implements IMetaMetrics { // If user later enables MetaMetrics, // this same ID should be retrieved from preferences and reused. // look for a legacy ID from MixPanel integration and use it - const legacyId = await DefaultPreference.get(MIXPANEL_METAMETRICS_ID); + const legacyId = await StorageWrapper.getItem(MIXPANEL_METAMETRICS_ID); if (legacyId) { this.metametricsId = legacyId; - await DefaultPreference.set(METAMETRICS_ID, legacyId); + await StorageWrapper.setItem(METAMETRICS_ID, legacyId); return legacyId; } // look for a new Metametics ID and use it or generate a new one - const metametricsId = await DefaultPreference.get(METAMETRICS_ID); + const metametricsId: string | undefined = await StorageWrapper.getItem( + METAMETRICS_ID, + ); if (!metametricsId) { // keep the id format compatible with MixPanel but base it on a UUIDv4 this.metametricsId = uuidv4(); - await DefaultPreference.set(METAMETRICS_ID, this.metametricsId); + await StorageWrapper.setItem(METAMETRICS_ID, this.metametricsId); } else { this.metametricsId = metametricsId; } @@ -289,7 +291,7 @@ class MetaMetrics implements IMetaMetrics { */ #resetMetaMetricsId = async (): Promise => { try { - await DefaultPreference.set(METAMETRICS_ID, ''); + await StorageWrapper.setItem(METAMETRICS_ID, ''); this.metametricsId = await this.#getMetaMetricsId(); // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -358,13 +360,13 @@ class MetaMetrics implements IMetaMetrics { /** * Update the user analytics preference and - * store in DefaultPreference + * store in StorageWrapper * * @param enabled - Boolean indicating if opts-in ({@link AGREED}) or opts-out ({@link DENIED}) */ #storeMetricsOptInPreference = async (enabled: boolean) => { try { - await DefaultPreference.set(METRICS_OPT_IN, enabled ? AGREED : DENIED); + await StorageWrapper.setItem(METRICS_OPT_IN, enabled ? AGREED : DENIED); // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { diff --git a/app/core/Authentication/Authentication.test.ts b/app/core/Authentication/Authentication.test.ts index 05d951415c4..f876923b772 100644 --- a/app/core/Authentication/Authentication.test.ts +++ b/app/core/Authentication/Authentication.test.ts @@ -13,6 +13,24 @@ import SecureKeychain from '../SecureKeychain'; import configureMockStore from 'redux-mock-store'; import Logger from '../../util/Logger'; +const storage: Record = {}; + +jest.mock('../../store/async-storage-wrapper', () => ({ + getItem: jest.fn((key) => Promise.resolve(storage[key] ?? null)), + setItem: jest.fn((key, value) => { + storage[key] = value; + return Promise.resolve(); + }), + removeItem: jest.fn((key) => { + delete storage[key]; + return Promise.resolve(); + }), + clearAll: jest.fn(() => { + Object.keys(storage).forEach((key) => delete storage[key]); + return Promise.resolve(); + }), +})); + describe('Authentication', () => { const initialState = { security: { @@ -96,21 +114,18 @@ describe('Authentication', () => { it('should return a type AUTHENTICATION_TYPE.PASSWORD if the user exists and there are no available biometrics options but the password does not exist in the keychain', async () => { SecureKeychain.getSupportedBiometryType = jest.fn().mockReturnValue(null); await AsyncStorage.setItem(EXISTING_USER, TRUE); + SecureKeychain.getGenericPassword = jest.fn().mockReturnValue(null); const result = await Authentication.getType(); expect(result.availableBiometryType).toBeNull(); - expect(result.currentAuthType).toEqual(AUTHENTICATION_TYPE.REMEMBER_ME); + expect(result.currentAuthType).toEqual(AUTHENTICATION_TYPE.PASSWORD); }); - it('should return a type AUTHENTICATION_TYPE.PASSWORD if the user does not exists and there are no available biometrics options', async () => { + it('should return a type AUTHENTICATION_TYPE.PASSWORD if the user does not exist and there are no available biometrics options', async () => { SecureKeychain.getSupportedBiometryType = jest.fn().mockReturnValue(null); - const mockCredentials = { username: 'test', password: 'test' }; - SecureKeychain.getGenericPassword = jest - .fn() - .mockReturnValue(mockCredentials); - await AsyncStorage.setItem(EXISTING_USER, TRUE); + await AsyncStorage.setItem(EXISTING_USER, null); const result = await Authentication.getType(); expect(result.availableBiometryType).toBeNull(); - expect(result.currentAuthType).toEqual(AUTHENTICATION_TYPE.REMEMBER_ME); + expect(result.currentAuthType).toEqual(AUTHENTICATION_TYPE.PASSWORD); }); it('should return a auth type for components AUTHENTICATION_TYPE.REMEMBER_ME', async () => { diff --git a/app/core/ReviewManager.ts b/app/core/ReviewManager.ts index edceb47c9a9..c35d2c31e48 100644 --- a/app/core/ReviewManager.ts +++ b/app/core/ReviewManager.ts @@ -3,7 +3,7 @@ import { Platform, Linking } from 'react-native'; /* eslint-disable-next-line */ import { NavigationContainerRef } from '@react-navigation/core'; import InAppReview from 'react-native-in-app-review'; -import DefaultPreference from 'react-native-default-preference'; +import StorageWrapper from '../store/async-storage-wrapper'; import { REVIEW_EVENT_COUNT, REVIEW_SHOWN_TIME } from '../constants/storage'; import Logger from '../util/Logger'; import { MM_APP_STORE_LINK, MM_PLAY_STORE_LINK } from '../constants/urls'; @@ -18,9 +18,9 @@ class ReviewManager { private addEventCount = async () => { try { const previousCount = - (await DefaultPreference.get(REVIEW_EVENT_COUNT)) || '0'; + (await StorageWrapper.getItem(REVIEW_EVENT_COUNT)) || '0'; const newCount = parseInt(previousCount) + 1; - await DefaultPreference.set(REVIEW_EVENT_COUNT, `${newCount}`); + await StorageWrapper.setItem(REVIEW_EVENT_COUNT, `${newCount}`); } catch (error) { // Failed to add event count } @@ -33,9 +33,9 @@ class ReviewManager { } try { - const eventCount = await DefaultPreference.get(REVIEW_EVENT_COUNT); + const eventCount = await StorageWrapper.getItem(REVIEW_EVENT_COUNT); const lastShownTime = - (await DefaultPreference.get(REVIEW_SHOWN_TIME)) || '0'; + (await StorageWrapper.getItem(REVIEW_SHOWN_TIME)) || '0'; const satisfiedEventCount = parseInt(eventCount || '0') >= EVENT_THRESHOLD; const satisfiedTime = @@ -49,8 +49,8 @@ class ReviewManager { private resetReviewCriteria = async () => { try { const currentUnixTime = Date.now(); - await DefaultPreference.set(REVIEW_EVENT_COUNT, '0'); - await DefaultPreference.set(REVIEW_SHOWN_TIME, `${currentUnixTime}`); + await StorageWrapper.setItem(REVIEW_EVENT_COUNT, '0'); + await StorageWrapper.setItem(REVIEW_SHOWN_TIME, `${currentUnixTime}`); } catch (error) { // Failed to reset criteria } diff --git a/app/core/SDKConnect/AndroidSDK/addDappConnection.test.ts b/app/core/SDKConnect/AndroidSDK/addDappConnection.test.ts index aca7173077b..515dfd4f2ed 100644 --- a/app/core/SDKConnect/AndroidSDK/addDappConnection.test.ts +++ b/app/core/SDKConnect/AndroidSDK/addDappConnection.test.ts @@ -5,8 +5,8 @@ import addDappConnection from './addDappConnection'; jest.mock('../Connection'); jest.mock('../SDKConnect'); jest.mock('../utils/DevLogger'); -jest.mock('react-native-default-preference', () => ({ - set: jest.fn().mockResolvedValue(''), +jest.mock('../../../store/async-storage-wrapper', () => ({ + setItem: jest.fn().mockResolvedValue(''), })); jest.mock('../../../core/AppConstants'); diff --git a/app/core/SDKConnect/AndroidSDK/loadDappConnections.test.ts b/app/core/SDKConnect/AndroidSDK/loadDappConnections.test.ts index a90a76650b9..0dd720109f4 100644 --- a/app/core/SDKConnect/AndroidSDK/loadDappConnections.test.ts +++ b/app/core/SDKConnect/AndroidSDK/loadDappConnections.test.ts @@ -1,10 +1,10 @@ -import DefaultPreference from 'react-native-default-preference'; +import StorageWrapper from '../../../store/async-storage-wrapper'; import loadDappConnections from './loadDappConnections'; jest.mock('../../../core/AppConstants'); -jest.mock('react-native-default-preference', () => ({ - get: jest.fn().mockResolvedValue(''), - set: jest.fn().mockResolvedValue(''), +jest.mock('../../../store/async-storage-wrapper', () => ({ + getItem: jest.fn().mockResolvedValue(''), + setItem: jest.fn().mockResolvedValue(''), })); jest.mock('../utils/DevLogger'); jest.mock('../../../store', () => ({ @@ -32,7 +32,7 @@ describe('loadDappConnections', () => { it('should parse the retrieved connections', async () => { const mockConnections = {}; - (DefaultPreference.get as jest.Mock).mockResolvedValueOnce( + (StorageWrapper.getItem as jest.Mock).mockResolvedValueOnce( JSON.stringify(mockConnections), ); diff --git a/app/core/SDKConnect/ConnectionManagement/approveHost.test.ts b/app/core/SDKConnect/ConnectionManagement/approveHost.test.ts index 4a95b6f16b2..8e38c2a38ae 100644 --- a/app/core/SDKConnect/ConnectionManagement/approveHost.test.ts +++ b/app/core/SDKConnect/ConnectionManagement/approveHost.test.ts @@ -8,9 +8,9 @@ jest.mock('../../../core/AppConstants'); jest.mock('../SDKConnect'); jest.mock('../SDKConnectConstants'); jest.mock('../utils/DevLogger'); -jest.mock('react-native-default-preference', () => ({ - set: jest.fn().mockResolvedValue([]), - get: jest.fn().mockResolvedValue(JSON.stringify({})), +jest.mock('../../../store/async-storage-wrapper', () => ({ + setItem: jest.fn().mockResolvedValue([]), + getItem: jest.fn().mockResolvedValue(JSON.stringify({})), })); describe('approveHost', () => { diff --git a/app/core/SDKConnect/ConnectionManagement/connectToChannel.test.ts b/app/core/SDKConnect/ConnectionManagement/connectToChannel.test.ts index baa6b5eb5ca..e64201da586 100644 --- a/app/core/SDKConnect/ConnectionManagement/connectToChannel.test.ts +++ b/app/core/SDKConnect/ConnectionManagement/connectToChannel.test.ts @@ -3,9 +3,9 @@ import { DEFAULT_SESSION_TIMEOUT_MS } from '../SDKConnectConstants'; import { SDKConnect } from './../SDKConnect'; import connectToChannel from './connectToChannel'; -jest.mock('react-native-default-preference', () => ({ - set: jest.fn().mockResolvedValue(''), - get: jest.fn().mockResolvedValue(''), +jest.mock('../../../store/async-storage-wrapper', () => ({ + setItem: jest.fn().mockResolvedValue(''), + getItem: jest.fn().mockResolvedValue(''), })); jest.mock('../../AppConstants'); jest.mock('../Connection'); diff --git a/app/core/SDKConnect/ConnectionManagement/invalidateChannel.test.ts b/app/core/SDKConnect/ConnectionManagement/invalidateChannel.test.ts index 94d89889cb2..84694839ebc 100644 --- a/app/core/SDKConnect/ConnectionManagement/invalidateChannel.test.ts +++ b/app/core/SDKConnect/ConnectionManagement/invalidateChannel.test.ts @@ -4,9 +4,9 @@ import invalidateChannel from './invalidateChannel'; jest.mock('../../../core/AppConstants'); jest.mock('../SDKConnect'); -jest.mock('react-native-default-preference', () => ({ - set: jest.fn().mockResolvedValue([]), - get: jest.fn().mockResolvedValue(JSON.stringify({})), +jest.mock('../../../store/async-storage-wrapper', () => ({ + setItem: jest.fn().mockResolvedValue([]), + getItem: jest.fn().mockResolvedValue(JSON.stringify({})), })); describe('invalidateChannel', () => { diff --git a/app/core/SDKConnect/ConnectionManagement/removeAll.test.ts b/app/core/SDKConnect/ConnectionManagement/removeAll.test.ts index 3e12cdf42ef..7341193627a 100644 --- a/app/core/SDKConnect/ConnectionManagement/removeAll.test.ts +++ b/app/core/SDKConnect/ConnectionManagement/removeAll.test.ts @@ -1,10 +1,10 @@ import SDKConnect from '../SDKConnect'; import removeAll from './removeAll'; -jest.mock('react-native-default-preference', () => ({ - set: jest.fn().mockResolvedValue([]), - get: jest.fn().mockResolvedValue(JSON.stringify({})), - clear: jest.fn().mockResolvedValue([]), +jest.mock('../../../store/async-storage-wrapper', () => ({ + setItem: jest.fn().mockResolvedValue([]), + getItem: jest.fn().mockResolvedValue(JSON.stringify({})), + clearAll: jest.fn().mockResolvedValue([]), })); jest.mock('../../AppConstants'); jest.mock('../SDKConnect'); diff --git a/app/core/SDKConnect/ConnectionManagement/removeChannel.test.ts b/app/core/SDKConnect/ConnectionManagement/removeChannel.test.ts index c6a63fa14a5..c7e66dfe2a4 100644 --- a/app/core/SDKConnect/ConnectionManagement/removeChannel.test.ts +++ b/app/core/SDKConnect/ConnectionManagement/removeChannel.test.ts @@ -5,9 +5,9 @@ import removeChannel from './removeChannel'; jest.mock('../../../core/AppConstants'); jest.mock('../SDKConnect'); jest.mock('../utils/DevLogger'); -jest.mock('react-native-default-preference', () => ({ - set: jest.fn().mockResolvedValue([]), - get: jest.fn().mockResolvedValue(JSON.stringify({})), +jest.mock('../../../store/async-storage-wrapper', () => ({ + setItem: jest.fn().mockResolvedValue([]), + getItem: jest.fn().mockResolvedValue(JSON.stringify({})), })); describe('removeChannel', () => { diff --git a/app/core/SDKConnect/InitializationManagement/asyncInit.test.ts b/app/core/SDKConnect/InitializationManagement/asyncInit.test.ts index 032baf9fb74..0125dd3de16 100644 --- a/app/core/SDKConnect/InitializationManagement/asyncInit.test.ts +++ b/app/core/SDKConnect/InitializationManagement/asyncInit.test.ts @@ -4,15 +4,10 @@ import { wait } from '../utils/wait.util'; import asyncInit from './asyncInit'; jest.mock('@react-navigation/native'); -jest.mock('react-native-default-preference', () => ({ - getMultiple: jest.fn().mockResolvedValue([]), - setMultiple: jest.fn().mockResolvedValue([]), - clearMultiple: jest.fn().mockResolvedValue([]), - set: jest.fn().mockResolvedValue([]), - clear: jest.fn().mockResolvedValue([]), - getAll: jest.fn().mockResolvedValue([]), - getAllKeys: jest.fn().mockResolvedValue([]), - get: jest.fn().mockResolvedValue(JSON.stringify({})), +jest.mock('../../../store/async-storage-wrapper', () => ({ + getItem: jest.fn().mockResolvedValue([]), + setItem: jest.fn(), + clearAll: jest.fn().mockResolvedValue([]), })); jest.mock('../../AppConstants'); jest.mock('../../../util/Logger'); diff --git a/app/core/SDKConnect/StateManagement/updateOriginatorInfos.test.ts b/app/core/SDKConnect/StateManagement/updateOriginatorInfos.test.ts index b685eb08e5c..3149b78d5e2 100644 --- a/app/core/SDKConnect/StateManagement/updateOriginatorInfos.test.ts +++ b/app/core/SDKConnect/StateManagement/updateOriginatorInfos.test.ts @@ -1,18 +1,22 @@ import { OriginatorInfo } from '@metamask/sdk-communication-layer'; -import DefaultPreference from 'react-native-default-preference'; +import StorageWrapper from '../../../store/async-storage-wrapper'; import SDKConnect from '../SDKConnect'; import updateOriginatorInfos from './updateOriginatorInfos'; jest.mock('@metamask/sdk-communication-layer'); jest.mock('../SDKConnect'); -jest.mock('react-native-default-preference'); +jest.mock('../../../store/async-storage-wrapper'); jest.mock('../../AppConstants'); +jest.mock('../../../store/async-storage-wrapper', () => ({ + setItem: jest.fn(), +})); + describe('updateOriginatorInfos', () => { let mockInstance = {} as unknown as SDKConnect; - const mockDefaultPreferenceSet = DefaultPreference.set as jest.MockedFunction< - typeof DefaultPreference.set + const mockStorageWrapperSet = StorageWrapper.setItem as jest.MockedFunction< + typeof StorageWrapper.setItem >; const mockEmit = jest.fn(); @@ -22,7 +26,7 @@ describe('updateOriginatorInfos', () => { beforeEach(() => { jest.clearAllMocks(); - mockDefaultPreferenceSet.mockResolvedValue(undefined); + mockStorageWrapperSet.mockResolvedValue(undefined); mockInstance = { state: { diff --git a/app/selectors/accountsController.test.ts b/app/selectors/accountsController.test.ts index abb7dbaa089..28817edd344 100644 --- a/app/selectors/accountsController.test.ts +++ b/app/selectors/accountsController.test.ts @@ -2,7 +2,7 @@ import { AccountsControllerState } from '@metamask/accounts-controller'; import { captureException } from '@sentry/react-native'; import { Hex, isValidChecksumAddress } from '@metamask/utils'; import { InternalAccount } from '@metamask/keyring-api'; -import DefaultPreference from 'react-native-default-preference'; +import StorageWrapper from '../store/async-storage-wrapper'; import { selectSelectedInternalAccount, selectInternalAccounts, @@ -65,7 +65,7 @@ const mockedCaptureException = jest.mocked(captureException); describe('Accounts Controller Selectors', () => { beforeEach(() => { jest.resetAllMocks(); - DefaultPreference.get = jest.fn(() => Promise.resolve(AGREED)); + StorageWrapper.getItem = jest.fn(() => Promise.resolve(AGREED)); }); describe('selectSelectedInternalAccount', () => { it('returns selected internal account', () => { diff --git a/app/store/migrations/050.test.ts b/app/store/migrations/050.test.ts new file mode 100644 index 00000000000..43812b27aa2 --- /dev/null +++ b/app/store/migrations/050.test.ts @@ -0,0 +1,40 @@ +import migrate from './050'; +import DefaultPreference from 'react-native-default-preference'; +import StorageWrapper from '../async-storage-wrapper'; + +const defaultPreferenceItems: { [key: string]: string | null } = { + valueA: 'a', + valueB: 'true', + valueC: 'myValue', + valueD: null, +}; + +jest.mock('../async-storage-wrapper', () => ({ + setItem: jest.fn().mockResolvedValue(''), +})); +jest.mock('react-native-default-preference', () => ({ + set: jest.fn(), + clear: jest.fn(), + getAll: jest.fn().mockReturnValue(defaultPreferenceItems), +})); + +describe('Migration #50', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('migrates default preferences values to mmkv and clears DefaultPreference', async () => { + await migrate({}); + + expect(StorageWrapper.setItem).toHaveBeenCalledTimes(3); + expect(StorageWrapper.setItem).toHaveBeenCalledWith('valueA', 'a'); + expect(StorageWrapper.setItem).toHaveBeenCalledWith('valueB', 'true'); + expect(StorageWrapper.setItem).toHaveBeenCalledWith('valueC', 'myValue'); + + expect(DefaultPreference.clear).toHaveBeenCalledTimes(4); + expect(DefaultPreference.clear).toHaveBeenCalledWith('valueA'); + expect(DefaultPreference.clear).toHaveBeenCalledWith('valueB'); + expect(DefaultPreference.clear).toHaveBeenCalledWith('valueC'); + expect(DefaultPreference.clear).toHaveBeenCalledWith('valueD'); + }); +}); diff --git a/app/store/migrations/050.ts b/app/store/migrations/050.ts new file mode 100644 index 00000000000..601ac1062b2 --- /dev/null +++ b/app/store/migrations/050.ts @@ -0,0 +1,28 @@ +import DefaultPreference from 'react-native-default-preference'; +import { captureException } from '@sentry/react-native'; +import StorageWrapper from '../async-storage-wrapper'; + +/** + * The goal of this migration is set all the data that was on DefaultPreference to MMKV + * and clean DefaultPreference data + * @param state + * @returns state + */ +export default async function migrate(state: unknown) { + const keyValues = await DefaultPreference.getAll(); + + for (const key of Object.keys(keyValues)) { + try { + if (keyValues[key] != null) { + StorageWrapper.setItem(key, keyValues[key]); + } + await DefaultPreference.clear(key); + } catch (error) { + captureException( + `Migration 50: Failed to migrate key "${key}" from DefaultPreference to MMKV! Error: ${error}`, + ); + } + } + + return state; +} diff --git a/app/store/migrations/index.test.ts b/app/store/migrations/index.test.ts index 771cfd4c2c5..ce765e41efb 100644 --- a/app/store/migrations/index.test.ts +++ b/app/store/migrations/index.test.ts @@ -7,6 +7,11 @@ import { const defaultNodeEnv = process.env.NODE_ENV; jest.unmock('redux-persist'); jest.mock('../../store', () => jest.fn()); +jest.mock('react-native-default-preference', () => ({ + set: jest.fn(), + clear: jest.fn(), + getAll: jest.fn().mockReturnValue({}), +})); // Only test migrations 25 and up const migrationNumberToTestFrom = 25; diff --git a/app/store/migrations/index.ts b/app/store/migrations/index.ts index bfdbce04cb8..f56f82c8ce4 100644 --- a/app/store/migrations/index.ts +++ b/app/store/migrations/index.ts @@ -50,6 +50,7 @@ import migration46 from './046'; import migration47 from './047'; import migration48 from './048'; import migration49 from './049'; +import migration50 from './050'; type MigrationFunction = (state: unknown) => unknown; type AsyncMigrationFunction = (state: unknown) => Promise; @@ -112,6 +113,7 @@ export const migrationList: MigrationsList = { 47: migration47, 48: migration48, 49: migration49, + 50: migration50, }; // Enable both synchronous and asynchronous migrations diff --git a/app/util/Logger/index.test.ts b/app/util/Logger/index.test.ts index 0b0ec078a4c..919599eeb2c 100644 --- a/app/util/Logger/index.test.ts +++ b/app/util/Logger/index.test.ts @@ -1,7 +1,7 @@ import Logger from '.'; import { captureException, withScope } from '@sentry/react-native'; import { AGREED, METRICS_OPT_IN } from '../../constants/storage'; -import DefaultPreference from 'react-native-default-preference'; +import StorageWrapper from '../../store/async-storage-wrapper'; jest.mock('@sentry/react-native', () => ({ captureException: jest.fn(), @@ -13,7 +13,7 @@ const mockedWithScope = jest.mocked(withScope); describe('Logger', () => { beforeEach(() => { - DefaultPreference.get = jest.fn((key: string) => { + StorageWrapper.getItem = jest.fn((key: string) => { switch (key) { case METRICS_OPT_IN: return Promise.resolve(AGREED); @@ -37,7 +37,7 @@ describe('Logger', () => { }); it('skips captureException if metrics is opted out', async () => { - DefaultPreference.get = jest.fn((key: string) => { + StorageWrapper.getItem = jest.fn((key: string) => { switch (key) { case METRICS_OPT_IN: return Promise.resolve(''); diff --git a/app/util/Logger/index.ts b/app/util/Logger/index.ts index 5b23c94788f..c1c424fed99 100644 --- a/app/util/Logger/index.ts +++ b/app/util/Logger/index.ts @@ -3,7 +3,7 @@ import { captureException, withScope, } from '@sentry/react-native'; -import DefaultPreference from 'react-native-default-preference'; +import StorageWrapper from '../../store/async-storage-wrapper'; import { METRICS_OPT_IN, AGREED, DEBUG } from '../../constants/storage'; interface ExtraInfo { @@ -39,7 +39,7 @@ export class AsyncLogger { } // Check if user passed accepted opt-in to metrics - const metricsOptIn = await DefaultPreference.get(METRICS_OPT_IN); + const metricsOptIn = await StorageWrapper.getItem(METRICS_OPT_IN); if (metricsOptIn === AGREED) { addBreadcrumb({ message: JSON.stringify(args), @@ -70,7 +70,7 @@ export class AsyncLogger { } // Check if user passed accepted opt-in to metrics - const metricsOptIn = await DefaultPreference.get(METRICS_OPT_IN); + const metricsOptIn = await StorageWrapper.getItem(METRICS_OPT_IN); if (metricsOptIn === AGREED) { let exception = error; diff --git a/app/util/sentry/utils.js b/app/util/sentry/utils.js index 353e839970f..27ba67cdb11 100644 --- a/app/util/sentry/utils.js +++ b/app/util/sentry/utils.js @@ -2,7 +2,7 @@ import * as Sentry from '@sentry/react-native'; import { Dedupe, ExtraErrorData } from '@sentry/integrations'; import extractEthJsErrorMessage from '../extractEthJsErrorMessage'; -import DefaultPreference from 'react-native-default-preference'; +import StorageWrapper from '../../store/async-storage-wrapper'; import { regex } from '../regex'; import { AGREED, METRICS_OPT_IN } from '../../constants/storage'; import { isTest } from '../test/utils'; @@ -480,7 +480,7 @@ export function setupSentry() { const init = async () => { const dsn = process.env.MM_SENTRY_DSN; - const metricsOptIn = await DefaultPreference.get(METRICS_OPT_IN); + const metricsOptIn = await StorageWrapper.getItem(METRICS_OPT_IN); const integrations = [new Dedupe(), new ExtraErrorData()]; const environment = deriveSentryEnvironment( diff --git a/app/util/test/testSetup.js b/app/util/test/testSetup.js index 3b8b49ba068..7b8c8f7a196 100644 --- a/app/util/test/testSetup.js +++ b/app/util/test/testSetup.js @@ -313,9 +313,9 @@ jest.mock('redux-persist', () => ({ createMigrate: jest.fn(), })); -jest.mock('react-native-default-preference', () => ({ - get: jest.fn(), - set: jest.fn(), +jest.mock('../../store/async-storage-wrapper', () => ({ + getItem: jest.fn(), + setItem: jest.fn(), })); // eslint-disable-next-line import/no-commonjs