diff --git a/app/actions/onboarding/index.ts b/app/actions/onboarding/index.ts index e85dd902fe3..641bf5568bd 100644 --- a/app/actions/onboarding/index.ts +++ b/app/actions/onboarding/index.ts @@ -14,7 +14,9 @@ interface ClearEventsAction { export type OnboardingActionTypes = SaveEventAction | ClearEventsAction; -export function saveOnboardingEvent(eventArgs: [IMetaMetricsEvent]): SaveEventAction { +export function saveOnboardingEvent( + eventArgs: [IMetaMetricsEvent], +): SaveEventAction { return { type: SAVE_EVENT, event: eventArgs, diff --git a/app/component-library/components/Avatars/Avatar/variants/AvatarIcon/AvatarIcon.test.tsx b/app/component-library/components/Avatars/Avatar/variants/AvatarIcon/AvatarIcon.test.tsx index f6366e5ff33..f6ab447037e 100644 --- a/app/component-library/components/Avatars/Avatar/variants/AvatarIcon/AvatarIcon.test.tsx +++ b/app/component-library/components/Avatars/Avatar/variants/AvatarIcon/AvatarIcon.test.tsx @@ -1,6 +1,6 @@ // Third party dependencies. import React from 'react'; -import { shallow } from 'enzyme'; +import { render } from '@testing-library/react-native'; // External dependencies. import AvatarIcon from './AvatarIcon'; @@ -10,7 +10,7 @@ import { SAMPLE_AVATARICON_PROPS } from './AvatarIcon.constants'; describe('AvatarIcon', () => { it('should render correctly', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + const { toJSON } = render(); + expect(toJSON()).toMatchSnapshot(); }); }); diff --git a/app/component-library/components/Avatars/Avatar/variants/AvatarIcon/__snapshots__/AvatarIcon.test.tsx.snap b/app/component-library/components/Avatars/Avatar/variants/AvatarIcon/__snapshots__/AvatarIcon.test.tsx.snap index 6c4f0afa310..1d343e9fea0 100644 --- a/app/component-library/components/Avatars/Avatar/variants/AvatarIcon/__snapshots__/AvatarIcon.test.tsx.snap +++ b/app/component-library/components/Avatars/Avatar/variants/AvatarIcon/__snapshots__/AvatarIcon.test.tsx.snap @@ -1,20 +1,30 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`AvatarIcon should render correctly 1`] = ` - - - + `; diff --git a/app/component-library/components/Buttons/Button/variants/ButtonLink/ButtonLink.test.tsx b/app/component-library/components/Buttons/Button/variants/ButtonLink/ButtonLink.test.tsx index e8d3d91de54..f472c0f90d3 100644 --- a/app/component-library/components/Buttons/Button/variants/ButtonLink/ButtonLink.test.tsx +++ b/app/component-library/components/Buttons/Button/variants/ButtonLink/ButtonLink.test.tsx @@ -1,15 +1,13 @@ // Third party dependencies. import React from 'react'; -import { shallow } from 'enzyme'; +import { render, screen } from '@testing-library/react-native'; // Internal dependencies. import ButtonLink from './ButtonLink'; -describe('Link', () => { +describe('ButtonLink', () => { it('should render correctly', () => { - const wrapper = shallow( - , - ); - expect(wrapper).toMatchSnapshot(); + render(); + expect(screen.toJSON()).toMatchSnapshot(); }); }); diff --git a/app/component-library/components/Buttons/Button/variants/ButtonLink/__snapshots__/ButtonLink.test.tsx.snap b/app/component-library/components/Buttons/Button/variants/ButtonLink/__snapshots__/ButtonLink.test.tsx.snap index ff32c2ade80..1c09de8a64e 100644 --- a/app/component-library/components/Buttons/Button/variants/ButtonLink/__snapshots__/ButtonLink.test.tsx.snap +++ b/app/component-library/components/Buttons/Button/variants/ButtonLink/__snapshots__/ButtonLink.test.tsx.snap @@ -1,27 +1,39 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Link should render correctly 1`] = ` - +exports[`ButtonLink should render correctly 1`] = ` + - - I'm a Link! - + I'm a Link! - + `; diff --git a/app/components/Approvals/ApprovalModal/ApprovalModal.test.tsx b/app/components/Approvals/ApprovalModal/ApprovalModal.test.tsx index 879cf8a5f16..f16fe201007 100644 --- a/app/components/Approvals/ApprovalModal/ApprovalModal.test.tsx +++ b/app/components/Approvals/ApprovalModal/ApprovalModal.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { render } from '@testing-library/react-native'; import ApprovalModal from './ApprovalModal'; describe('ApprovalModal', () => { @@ -8,11 +8,11 @@ describe('ApprovalModal', () => { }); it('renders', () => { - const wrapper = shallow( + const { toJSON } = render( undefined}>
test
, ); - expect(wrapper).toMatchSnapshot(); + expect(toJSON()).toMatchSnapshot(); }); }); diff --git a/app/components/Approvals/ApprovalModal/__snapshots__/ApprovalModal.test.tsx.snap b/app/components/Approvals/ApprovalModal/__snapshots__/ApprovalModal.test.tsx.snap index cc6203e74a7..323137ebfa6 100644 --- a/app/components/Approvals/ApprovalModal/__snapshots__/ApprovalModal.test.tsx.snap +++ b/app/components/Approvals/ApprovalModal/__snapshots__/ApprovalModal.test.tsx.snap @@ -1,43 +1,24 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ApprovalModal renders 1`] = ` - -
- test -
-
+ + +
+ test +
+
+ `; diff --git a/app/components/UI/AnimatedTransactionModal/__snapshots__/index.test.tsx.snap b/app/components/UI/AnimatedTransactionModal/__snapshots__/index.test.tsx.snap index e99df64bf59..edb1bdd219f 100644 --- a/app/components/UI/AnimatedTransactionModal/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/AnimatedTransactionModal/__snapshots__/index.test.tsx.snap @@ -1,28 +1,23 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`AnimatedTransactionModal should render correctly 1`] = ` - `; diff --git a/app/components/UI/AnimatedTransactionModal/index.test.tsx b/app/components/UI/AnimatedTransactionModal/index.test.tsx index 9d8108ca3ef..4a22108e3df 100644 --- a/app/components/UI/AnimatedTransactionModal/index.test.tsx +++ b/app/components/UI/AnimatedTransactionModal/index.test.tsx @@ -1,15 +1,23 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { render, screen } from '@testing-library/react-native'; import AnimatedTransactionModal from './'; import { View } from 'react-native'; +import { ThemeContext } from '../../../util/theme'; + +const mockTheme = { + colors: { background: { default: 'white' } }, + themeAppearance: 'light', +}; describe('AnimatedTransactionModal', () => { it('should render correctly', () => { - const wrapper = shallow( - - - , + render( + + + + + , ); - expect(wrapper).toMatchSnapshot(); + expect(screen.toJSON()).toMatchSnapshot(); }); }); diff --git a/app/components/UI/AssetList/index.test.tsx b/app/components/UI/AssetList/index.test.tsx index cde9e86a876..b3a7e119ad1 100644 --- a/app/components/UI/AssetList/index.test.tsx +++ b/app/components/UI/AssetList/index.test.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { render } from '@testing-library/react-native'; import AssetList from './'; describe('AssetList', () => { it('should render correctly', () => { - const wrapper = shallow( + const { toJSON } = render( { selectedAsset={{ address: '0xABC', symbol: 'ABC', decimals: 0 }} />, ); - expect(wrapper).toMatchSnapshot(); + expect(toJSON()).toMatchSnapshot(); }); }); diff --git a/app/components/UI/BlockingActionModal/__snapshots__/index.test.tsx.snap b/app/components/UI/BlockingActionModal/__snapshots__/index.test.tsx.snap index 757058bdf97..80fd1c281a9 100644 --- a/app/components/UI/BlockingActionModal/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/BlockingActionModal/__snapshots__/index.test.tsx.snap @@ -1,42 +1,23 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`BlockingActionModal should render correctly 1`] = ` - + - - Please wait - - + > + + Please wait + + + - + `; diff --git a/app/components/UI/BlockingActionModal/index.test.tsx b/app/components/UI/BlockingActionModal/index.test.tsx index bb386a79554..c48dfcb4cc8 100644 --- a/app/components/UI/BlockingActionModal/index.test.tsx +++ b/app/components/UI/BlockingActionModal/index.test.tsx @@ -1,15 +1,15 @@ import React from 'react'; import { Text } from 'react-native'; -import { shallow } from 'enzyme'; +import { render, screen } from '@testing-library/react-native'; import BlockingActionModal from './'; describe('BlockingActionModal', () => { it('should render correctly', () => { - const wrapper = shallow( + render( {'Please wait'} , ); - expect(wrapper).toMatchSnapshot(); + expect(screen.toJSON()).toMatchSnapshot(); }); }); diff --git a/app/components/UI/PaymentRequest/__snapshots__/index.test.tsx.snap b/app/components/UI/PaymentRequest/__snapshots__/index.test.tsx.snap index 2f8efc7e4ba..7e9fba97161 100644 --- a/app/components/UI/PaymentRequest/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/PaymentRequest/__snapshots__/index.test.tsx.snap @@ -1,32 +1,460 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PaymentRequest should render correctly 1`] = ` - - - + + + + + + Choose an asset to request + + + + +  + + + + + + Top picks + + + + + + + + + + + + + + + ETH + + + Ether + + + + + + + + + + + + + + + + + SAI + + + Sai Stablecoin v1.0 + + + + + + + + + + `; diff --git a/app/components/UI/PaymentRequest/index.test.tsx b/app/components/UI/PaymentRequest/index.test.tsx index 5915fdecd57..391fe7e3b89 100644 --- a/app/components/UI/PaymentRequest/index.test.tsx +++ b/app/components/UI/PaymentRequest/index.test.tsx @@ -1,18 +1,198 @@ import React from 'react'; -import { shallow } from 'enzyme'; -import PaymentRequest from './'; +import { render, fireEvent, act } from '@testing-library/react-native'; +import PaymentRequest from './index'; import { Provider } from 'react-redux'; import configureMockStore from 'redux-mock-store'; +import { ThemeContext, mockTheme } from '../../../util/theme'; +import { MOCK_ACCOUNTS_CONTROLLER_STATE } from '../../../util/test/accountsControllerTestUtils'; + +jest.mock('react', () => ({ + ...jest.requireActual('react'), + useState: jest.fn(), +})); const mockStore = configureMockStore(); -const store = mockStore({}); + +const initialState = { + engine: { + backgroundState: { + CurrencyRateController: { + conversionRate: 1, + currentCurrency: 'USD', + }, + TokenRatesController: { + contractExchangeRates: {}, + marketData: { + '0x1': { + '0x0d8775f59023cbe76e541b6497bbed3cd21acbdc': { + price: 1, + }, + }, + }, + }, + TokensController: { + marketData: { + '0x1': { + '0x0d8775f59023cbe76e541b6497bbed3cd21acbdc': { + price: 1, + }, + }, + }, + tokens: [], + }, + NetworkController: { + provider: { + ticker: 'ETH', + chainId: '1', + }, + }, + AccountsController: { + ...MOCK_ACCOUNTS_CONTROLLER_STATE, + internalAccounts: { + ...MOCK_ACCOUNTS_CONTROLLER_STATE.internalAccounts, + selectedAccount: {}, + }, + }, + TokenListController: { + tokenList: { + '0x1': { + '0x0d8775f59023cbe76e541b6497bbed3cd21acbdc': { + address: '0x0d8775f59023cbe76e541b6497bbed3cd21acbdc', + symbol: 'BAT', + decimals: 18, + name: 'Basic Attention Token', + iconUrl: + 'https://assets.coingecko.com/coins/images/677/thumb/basic-attention-token.png?1547034427', + type: 'erc20', + }, + }, + }, + }, + PreferencesController: { + ipfsGateway: {}, + }, + }, + }, + settings: { + primaryCurrency: 'ETH', + }, +}; + +let mockSetShowError: jest.Mock; +let mockShowError = false; + +beforeEach(() => { + mockSetShowError = jest.fn((value) => { + mockShowError = value; + }); + (React.useState as jest.Mock).mockImplementation((state) => [ + state, + mockSetShowError, + ]); +}); + +const store = mockStore(initialState); + +const mockNavigation = { + setOptions: jest.fn(), + setParams: jest.fn(), + navigate: jest.fn(), + goBack: jest.fn(), +}; + +const mockRoute = { + params: { + dispatch: jest.fn(), + }, +}; + +const renderComponent = (props = {}) => + render( + + + + + , + ); + describe('PaymentRequest', () => { - it('should render correctly', () => { - const wrapper = shallow( - - - , - ); - expect(wrapper).toMatchSnapshot(); + it('renders correctly', () => { + const { toJSON } = renderComponent(); + expect(toJSON()).toMatchSnapshot(); + }); + + it('displays the correct title for asset selection', () => { + const { getByText } = renderComponent(); + expect(getByText('Choose an asset to request')).toBeTruthy(); + }); + + it('allows searching for assets', () => { + const { getByPlaceholderText } = renderComponent(); + const searchInput = getByPlaceholderText('Search assets'); + fireEvent.changeText(searchInput, 'ETH'); + expect(searchInput.props.value).toBe('ETH'); + }); + + it('switches to amount input mode when an asset is selected', async () => { + const { getByText } = renderComponent({ navigation: mockNavigation }); + + await act(async () => { + fireEvent.press(getByText('ETH')); + }); + + expect(getByText('Enter amount')).toBeTruthy(); + expect(mockNavigation.setParams).toHaveBeenCalledWith({ + mode: 'amount', + dispatch: expect.any(Function), + }); + }); + + it('updates amount when input changes', async () => { + const { getByText, getByPlaceholderText } = renderComponent(); + + // First, select an asset + await act(async () => { + fireEvent.press(getByText('ETH')); + }); + + const amountInput = getByPlaceholderText('0.00'); + await act(async () => { + fireEvent.changeText(amountInput, '1.5'); + }); + + expect(amountInput.props.value).toBe('1.5'); + }); + + it('displays an error when an invalid amount is entered', async () => { + const { getByText, getByPlaceholderText, debug, queryByText } = + renderComponent(); + + (React.useState as jest.Mock).mockImplementation(() => [ + mockShowError, + mockSetShowError, + ]); + + mockSetShowError(true); + + await act(async () => { + fireEvent.press(getByText('ETH')); + }); + + const amountInput = getByPlaceholderText('0.00'); + const nextButton = getByText('Next'); + + await act(async () => { + fireEvent.changeText(amountInput, '0'); + fireEvent.press(nextButton); + }); + + debug(); + + expect(mockSetShowError).toHaveBeenCalledWith(true); + expect(queryByText('Invalid request, please try again')).toBeTruthy(); }); }); diff --git a/app/components/UI/ReusableModal/__snapshots__/index.test.tsx.snap b/app/components/UI/ReusableModal/__snapshots__/index.test.tsx.snap index 840c56c6f50..b46a1d36dd2 100644 --- a/app/components/UI/ReusableModal/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/ReusableModal/__snapshots__/index.test.tsx.snap @@ -2,6 +2,74 @@ exports[`ReusableModal should render correctly 1`] = ` - + + + + + + `; diff --git a/app/components/UI/ReusableModal/index.test.tsx b/app/components/UI/ReusableModal/index.test.tsx index 2c93fa2b437..f97cf05d210 100644 --- a/app/components/UI/ReusableModal/index.test.tsx +++ b/app/components/UI/ReusableModal/index.test.tsx @@ -1,15 +1,28 @@ import React from 'react'; import { SafeAreaView } from 'react-native'; -import { shallow } from 'enzyme'; +import { render, screen } from '@testing-library/react-native'; import ReusableModal from './'; +import { useNavigation } from '@react-navigation/native'; + +jest.mock('@react-navigation/native', () => ({ + ...jest.requireActual('@react-navigation/native'), + useNavigation: jest.fn(), +})); describe('ReusableModal', () => { + beforeEach(() => { + (useNavigation as jest.Mock).mockReturnValue({ + navigate: jest.fn(), + goBack: jest.fn(), + }); + }); + it('should render correctly', () => { - const wrapper = shallow( + render( {null} , ); - expect(wrapper).toMatchSnapshot(); + expect(screen.toJSON()).toMatchSnapshot(); }); }); diff --git a/app/components/UI/Screen/index.test.tsx b/app/components/UI/Screen/index.test.tsx index 2707be55149..a32016dc0d9 100644 --- a/app/components/UI/Screen/index.test.tsx +++ b/app/components/UI/Screen/index.test.tsx @@ -1,15 +1,15 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { render } from '@testing-library/react-native'; import { View } from 'react-native'; import Screen from './'; describe('Screen', () => { it('should render correctly', () => { - const wrapper = shallow( + const { toJSON } = render( Foobar , ); - expect(wrapper).toMatchSnapshot(); + expect(toJSON()).toMatchSnapshot(); }); }); diff --git a/app/components/UI/SliderButton/__snapshots__/index.test.tsx.snap b/app/components/UI/SliderButton/__snapshots__/index.test.tsx.snap index f1d879adf06..ce6e987c71f 100644 --- a/app/components/UI/SliderButton/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/SliderButton/__snapshots__/index.test.tsx.snap @@ -54,7 +54,8 @@ exports[`SliderButton should render correctly 1`] = ` ] } /> - - Incomplete Text - +
- - - + diff --git a/app/components/UI/SliderButton/index.test.tsx b/app/components/UI/SliderButton/index.test.tsx index 609b8615192..4a41f222ba4 100644 --- a/app/components/UI/SliderButton/index.test.tsx +++ b/app/components/UI/SliderButton/index.test.tsx @@ -1,15 +1,15 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { render, screen } from '@testing-library/react-native'; import SliderButton from './index'; describe('SliderButton', () => { it('should render correctly', () => { - const wrapper = shallow( + render( , ); - expect(wrapper).toMatchSnapshot(); + expect(screen.toJSON()).toMatchSnapshot(); }); }); diff --git a/app/components/UI/SlippageSlider/__snapshots__/index.test.tsx.snap b/app/components/UI/SlippageSlider/__snapshots__/index.test.tsx.snap index 1ab792850fd..14cdaa1c8f1 100644 --- a/app/components/UI/SlippageSlider/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/SlippageSlider/__snapshots__/index.test.tsx.snap @@ -44,7 +44,6 @@ exports[`SlippageSlider should render correctly 1`] = ` } > - - - + - - - + + + + undefined% - - + diff --git a/app/components/UI/SlippageSlider/index.test.tsx b/app/components/UI/SlippageSlider/index.test.tsx index 75f7754b500..9aea690ccce 100644 --- a/app/components/UI/SlippageSlider/index.test.tsx +++ b/app/components/UI/SlippageSlider/index.test.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { render, screen } from '@testing-library/react-native'; import SlippageSlider from './index'; describe('SlippageSlider', () => { it('should render correctly', () => { - const wrapper = shallow( + render( { formatTooltipText={(text) => `${text}%`} />, ); - expect(wrapper).toMatchSnapshot(); + expect(screen.toJSON()).toMatchSnapshot(); }); }); diff --git a/app/components/UI/Swaps/components/InfoModal.tsx b/app/components/UI/Swaps/components/InfoModal.tsx index 7edd24ccb48..a1d8731977d 100644 --- a/app/components/UI/Swaps/components/InfoModal.tsx +++ b/app/components/UI/Swaps/components/InfoModal.tsx @@ -91,7 +91,13 @@ interface InfoViewProps { }; } -const InfoView: React.FC = ({ message, urlText, url, onClose, style }) => { +const InfoView: React.FC = ({ + message, + urlText, + url, + onClose, + style, +}) => { if (!message) { return ; } diff --git a/app/components/UI/WarningAlert/WarningAlert.test.tsx b/app/components/UI/WarningAlert/WarningAlert.test.tsx index 26797f65705..e1bd11ce231 100644 --- a/app/components/UI/WarningAlert/WarningAlert.test.tsx +++ b/app/components/UI/WarningAlert/WarningAlert.test.tsx @@ -1,16 +1,14 @@ // Third party dependencies. import React from 'react'; -import { shallow } from 'enzyme'; +import { render, screen } from '@testing-library/react-native'; // Internal dependencies. import WarningAlert from './WarningAlert'; -describe('ButtonBase', () => { +describe('WarningAlert', () => { it('should render correctly', () => { const mockDismissFunction = jest.fn(); - const wrapper = shallow( - , - ); - expect(wrapper).toMatchSnapshot(); + render(); + expect(screen.toJSON()).toMatchSnapshot(); }); }); diff --git a/app/components/UI/WarningAlert/__snapshots__/WarningAlert.test.tsx.snap b/app/components/UI/WarningAlert/__snapshots__/WarningAlert.test.tsx.snap index b32e14968a8..526889ea45f 100644 --- a/app/components/UI/WarningAlert/__snapshots__/WarningAlert.test.tsx.snap +++ b/app/components/UI/WarningAlert/__snapshots__/WarningAlert.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ButtonBase should render correctly 1`] = ` +exports[`WarningAlert should render correctly 1`] = ` - - - test + +  + + + + + test + - + + + +  + + + + `; diff --git a/app/components/UI/WhatsNewModal/__snapshots__/index.test.tsx.snap b/app/components/UI/WhatsNewModal/__snapshots__/index.test.tsx.snap index 1af5d938500..b4279da8a31 100644 --- a/app/components/UI/WhatsNewModal/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/WhatsNewModal/__snapshots__/index.test.tsx.snap @@ -1,31 +1,599 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`WhatsNewModal should render correctly 1`] = ` - - + - - - - - + + + + + + What's new + + + + + + + + + + + + + + + + Stay safe with security alerts + + + + + + + + + + Steer safe from known scams while still protecting your privacy with security alerts powered by Blockaid. This feature is now on by default for all MetaMask users. + + + + + Always do your own due diligence before approving requests. + + + + + Available on Arbitrum, Avalanche, BNB chain, Ethereum Mainnet, Linea, Optimism, Polygon, Sepolia and Base. + + + + + + + Got it + + + + + + + + + + + + + + + + + + Estimated balance changes + + + + + Now you can see the potential outcome of your transactions before you make them! + + + + + This is just a simulation, so we can’t guarantee the final outcome. You can turn this off anytime in Settings > Security & Privacy. + + + + + + + + + + + + + + + + `; diff --git a/app/components/UI/WhatsNewModal/index.test.tsx b/app/components/UI/WhatsNewModal/index.test.tsx index 4086b05de28..024c836722f 100644 --- a/app/components/UI/WhatsNewModal/index.test.tsx +++ b/app/components/UI/WhatsNewModal/index.test.tsx @@ -1,15 +1,49 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { render, screen } from '@testing-library/react-native'; import WhatsNewModal from './'; -import { NavigationContainer } from '@react-navigation/native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { ThemeContext } from '../../../util/theme'; + +jest.mock('@react-navigation/native', () => ({ + ...jest.requireActual('@react-navigation/native'), + useNavigation: jest.fn(), +})); + +const mockTheme = { + colors: { + icon: { default: 'red' }, + background: { default: 'white' }, + primary: { default: 'blue' }, + warning: { default: 'yellow' }, + alternative: { default: 'orange' }, + text: { alternative: 'orange' }, + error: { default: 'red' }, + overlay: { default: 'green' }, + }, + themeAppearance: 'light', +}; describe('WhatsNewModal', () => { + beforeEach(() => { + (useNavigation as jest.Mock).mockReturnValue({ + navigate: jest.fn(), + goBack: jest.fn(), + }); + }); + it('should render correctly', () => { - const wrapper = shallow( + const wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + + ); + + render( , + { wrapper }, ); - expect(wrapper).toMatchSnapshot(); + expect(screen.toJSON()).toMatchSnapshot(); }); }); diff --git a/app/components/Views/AddBookmark/__snapshots__/index.test.tsx.snap b/app/components/Views/AddBookmark/__snapshots__/index.test.tsx.snap index 4fab65f2eba..7aeda121c8b 100644 --- a/app/components/Views/AddBookmark/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/AddBookmark/__snapshots__/index.test.tsx.snap @@ -4,123 +4,319 @@ exports[`AddBookmark should render correctly 1`] = ` - - - - + + - Name - - - + + Name + + + + + - - - + + Url + + + + + + - Url - - - + + Cancel + + + + testID="add-bookmark-confirm-button" + > + + Add + + + - - + + `; diff --git a/app/components/Views/AddBookmark/index.test.tsx b/app/components/Views/AddBookmark/index.test.tsx index eae5e0ff487..7e121a7e04d 100644 --- a/app/components/Views/AddBookmark/index.test.tsx +++ b/app/components/Views/AddBookmark/index.test.tsx @@ -1,15 +1,31 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { screen, render } from '@testing-library/react-native'; import AddBookmark from './'; +import { ThemeContext } from '../../../util/theme'; + +const mockTheme = { + colors: { + background: { default: 'white' }, + border: { default: 'red' }, + text: { default: 'black' }, + error: { default: 'red' }, + warning: { default: 'yellow' }, + primary: { default: 'blue', inverse: 'orange' }, + overlay: { inverse: 'blue' }, + }, + themeAppearance: 'light', +}; describe('AddBookmark', () => { it('should render correctly', () => { - const wrapper = shallow( - null }} - route={{ params: {} }} - />, + render( + + null }} + route={{ params: {} }} + /> + , ); - expect(wrapper).toMatchSnapshot(); + expect(screen.toJSON()).toMatchSnapshot(); }); }); diff --git a/app/components/Views/EnterPasswordSimple/__snapshots__/index.test.tsx.snap b/app/components/Views/EnterPasswordSimple/__snapshots__/index.test.tsx.snap index f7c3b4baa2d..eba359a5f90 100644 --- a/app/components/Views/EnterPasswordSimple/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/EnterPasswordSimple/__snapshots__/index.test.tsx.snap @@ -1,37 +1,163 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`EnterPasswordSimple should render correctly 1`] = ` - - - - - - - + } + scrollEventThrottle={1} + scrollForExtraHeightOnAndroid={[Function]} + scrollIntoView={[Function]} + scrollToEnd={[Function]} + scrollToFocusedInput={[Function]} + scrollToPosition={[Function]} + showsVerticalScrollIndicator={true} + style={ + { + "flex": 1, + "padding": 16, + } + } + update={[Function]} + viewIsInsideTabBar={false} + > + + + + + Please enter your password in order to continue + + + + + + + Confirm + + + + + + + + `; diff --git a/app/components/Views/EnterPasswordSimple/index.test.tsx b/app/components/Views/EnterPasswordSimple/index.test.tsx index c7d15bcce7d..2169eaf28de 100644 --- a/app/components/Views/EnterPasswordSimple/index.test.tsx +++ b/app/components/Views/EnterPasswordSimple/index.test.tsx @@ -1,15 +1,42 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { render, screen } from '@testing-library/react-native'; import EnterPasswordSimple from './'; import { NavigationContainer } from '@react-navigation/native'; +import { ThemeContext } from '../../../util/theme'; + +const mockTheme = { + colors: { + background: { default: 'white' }, + border: { default: 'red' }, + text: { default: 'black' }, + primary: { default: 'blue' }, + warning: { default: 'yellow' }, + error: { default: 'red' }, + overlay: { default: 'white' }, + }, + themeAppearance: 'light', +}; + +const mockNavigation = { + setOptions: jest.fn(), + goBack: jest.fn(), + navigate: jest.fn(), + route: { + params: { + accountAddress: '0x123', + }, + }, +}; describe('EnterPasswordSimple', () => { it('should render correctly', () => { - const wrapper = shallow( - - - , + render( + + + + + , ); - expect(wrapper).toMatchSnapshot(); + expect(screen.toJSON()).toMatchSnapshot(); }); }); diff --git a/app/components/Views/NavigationUnitTest/index.js b/app/components/Views/NavigationUnitTest/index.js index b81c3f8d469..0e314b9b3bd 100644 --- a/app/components/Views/NavigationUnitTest/index.js +++ b/app/components/Views/NavigationUnitTest/index.js @@ -4,6 +4,7 @@ */ /* eslint-disable react/prop-types */ +/* eslint-disable react/no-unstable-nested-components */ import React from 'react'; import { createStackNavigator } from '@react-navigation/stack'; import { diff --git a/app/components/Views/PickComponent/__snapshots__/index.test.tsx.snap b/app/components/Views/PickComponent/__snapshots__/index.test.tsx.snap index 02350b470bd..cebc2a1a747 100644 --- a/app/components/Views/PickComponent/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/PickComponent/__snapshots__/index.test.tsx.snap @@ -16,11 +16,57 @@ exports[`PickComponent should render correctly 1`] = ` } } > - + style={ + { + "alignItems": "center", + "flexDirection": "row", + "height": 24, + "opacity": 1, + } + } + > + + + + Text First + + + - + style={ + { + "alignItems": "center", + "flexDirection": "row", + "height": 24, + "opacity": 1, + } + } + > + + + + + + Text Second + + + `; diff --git a/app/components/Views/PickComponent/index.test.tsx b/app/components/Views/PickComponent/index.test.tsx index 46d1c301f60..ad7e12c6a52 100644 --- a/app/components/Views/PickComponent/index.test.tsx +++ b/app/components/Views/PickComponent/index.test.tsx @@ -1,18 +1,26 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { render, screen } from '@testing-library/react-native'; import PickComponent from './'; +import { ThemeContext } from '../../../util/theme'; + +const mockTheme = { + colors: {}, + themeAppearance: 'light', +}; describe('PickComponent', () => { it('should render correctly', () => { - const wrapper = shallow( - , + render( + + + , ); - expect(wrapper).toMatchSnapshot(); + expect(screen.toJSON()).toMatchSnapshot(); }); }); diff --git a/app/components/Views/ResetPassword/index.js b/app/components/Views/ResetPassword/index.js index 9f2f2261f95..af66ee3fd0c 100644 --- a/app/components/Views/ResetPassword/index.js +++ b/app/components/Views/ResetPassword/index.js @@ -341,7 +341,7 @@ class ResetPassword extends PureComponent { }, 100); } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate(_, prevState) { this.updateNavBar(); const prevLoading = prevState.loading; const { loading } = this.state; @@ -460,7 +460,6 @@ class ResetPassword extends PureComponent { }; tryExportSeedPhrase = async (password) => { - // const { originalPassword } = this.state; const { KeyringController } = Engine.context; await KeyringController.exportSeedPhrase(password); }; diff --git a/app/components/Views/TransactionsView/__snapshots__/index.test.js.snap b/app/components/Views/TransactionsView/__snapshots__/index.test.js.snap deleted file mode 100644 index 82fb991c997..00000000000 --- a/app/components/Views/TransactionsView/__snapshots__/index.test.js.snap +++ /dev/null @@ -1,32 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TransactionsView should render correctly 1`] = ` - - - -`; diff --git a/app/components/Views/TransactionsView/index.test.js b/app/components/Views/TransactionsView/index.test.js deleted file mode 100644 index 148b1297c45..00000000000 --- a/app/components/Views/TransactionsView/index.test.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import TransactionsView from './'; -import { Provider } from 'react-redux'; -import configureMockStore from 'redux-mock-store'; - -const mockStore = configureMockStore(); -const store = mockStore({}); -describe('TransactionsView', () => { - it('should render correctly', () => { - const wrapper = shallow( - - - , - ); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/app/core/Engine.test.js b/app/core/Engine.test.js index b21f4c5ce91..7b14d7f6b03 100644 --- a/app/core/Engine.test.js +++ b/app/core/Engine.test.js @@ -2,12 +2,24 @@ import Engine from './Engine'; import { backgroundState } from '../util/test/initial-root-state'; import { zeroAddress } from 'ethereumjs-util'; import { createMockAccountsControllerState } from '../util/test/accountsControllerTestUtils'; +import { store } from '../store'; import { mockNetworkState } from '../util/test/network'; jest.unmock('./Engine'); -jest.mock('../store', () => ({ store: { getState: jest.fn(() => ({})) } })); +jest.mock('../store', () => ({ + store: { + getState: jest.fn(), + }, +})); describe('Engine', () => { + beforeEach(() => { + jest.resetAllMocks(); + store.getState.mockReturnValue({ + settings: { showFiatOnTestnets: true }, + }); + }); + it('should expose an API', () => { const engine = Engine.init({}); expect(engine.context).toHaveProperty('AccountTrackerController'); @@ -51,7 +63,16 @@ describe('Engine', () => { // Use this to keep the unit test initial background state fixture up-to-date it('matches initial state fixture', () => { const engine = Engine.init({}); - const initialBackgroundState = engine.datamodel.state; + const initialBackgroundState = { + ...engine.datamodel.state, + PhishingController: { + phishingLists: [], + whitelist: [], + c2DomainBlocklistLastFetched: 0, + hotlistLastFetched: 0, + stalelistLastFetched: 0, + }, + }; expect(initialBackgroundState).toStrictEqual(backgroundState); }); diff --git a/app/core/SDKConnect/handlers/handleConnectionReady.test.ts b/app/core/SDKConnect/handlers/handleConnectionReady.test.ts deleted file mode 100644 index 79d5fec68b5..00000000000 --- a/app/core/SDKConnect/handlers/handleConnectionReady.test.ts +++ /dev/null @@ -1,335 +0,0 @@ -import handleConnectionReady from './handleConnectionReady'; - -import { ApprovalController } from '@metamask/approval-controller'; -import { OriginatorInfo } from '@metamask/sdk-communication-layer'; -import AppConstants from '../../../../app/core/AppConstants'; -import { Connection } from '../Connection'; -import checkPermissions from './checkPermissions'; - -import Engine from '../../Engine'; -import { HOUR_IN_MS } from '../SDKConnectConstants'; -import { setupBridge } from './setupBridge'; - -jest.mock('@metamask/approval-controller'); -jest.mock('@metamask/sdk-communication-layer'); -jest.mock('../../../../app/core/AppConstants'); -jest.mock('../../../util/Logger'); -jest.mock('../Connection'); -jest.mock('../utils/wait.util'); -jest.mock('./setupBridge'); -jest.mock('./checkPermissions'); -jest.mock('./handleSendMessage'); - -// FIXME: re-create the test suite with v2 protocol -describe.skip('handleConnectionReady', () => { - let originatorInfo = {} as OriginatorInfo; - let engine = {} as unknown as typeof Engine; - let connection = {} as unknown as Connection; - const mockCheckPermissions = checkPermissions as jest.MockedFunction< - typeof checkPermissions - >; - const mockSetupBridge = setupBridge as jest.MockedFunction< - typeof setupBridge - >; - const approveHost = jest.fn(); - const disapprove = jest.fn(); - const onError = jest.fn(); - const updateOriginatorInfos = jest.fn(); - const mockSendAuthorized = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - - originatorInfo = { - apiVersion: '0.2.0', - source: 'fakeExtensionId', - url: 'fakeUrl', - dappId: 'fakeUrl', - icon: 'fakeIcon', - color: 'fakeColor', - title: 'asdfsdf', - platform: 'fakePlatform', - } as OriginatorInfo; - - engine = { - context: { - ApprovalController: { - get: jest.fn(), - reject: jest.fn(), - } as unknown as ApprovalController, - }, - } as unknown as typeof Engine; - - connection = { - channelId: 'fakeChannelId', - origin: 'fakeOrigin', - trigger: 'fakeTrigger', - receivedClientsReady: false, - approvalPromise: Promise.resolve(), - otps: undefined, - sendAuthorized: mockSendAuthorized, - remote: { - sendMessage: jest.fn(), - }, - } as unknown as Connection; - - mockCheckPermissions.mockResolvedValue(true); - }); - - describe('Handling specific originator info and versions', () => { - it('should update receivedClientsReady flag', async () => { - await handleConnectionReady({ - originatorInfo, - engine, - connection, - approveHost, - disapprove, - onError, - updateOriginatorInfos, - }); - - expect(connection.receivedClientsReady).toBe(true); - }); - it('should handle missing apiVersion for backward compatibility', async () => { - originatorInfo.apiVersion = undefined; - - await handleConnectionReady({ - originatorInfo, - engine, - connection, - approveHost, - disapprove, - onError, - updateOriginatorInfos, - }); - - expect(connection.approvalPromise).toBe(undefined); - }); - - it('should return early if originatorInfo is missing', async () => { - originatorInfo = undefined as unknown as OriginatorInfo; - - await handleConnectionReady({ - originatorInfo, - engine, - connection, - approveHost, - disapprove, - onError, - updateOriginatorInfos, - }); - - expect(connection.originatorInfo).toBe(undefined); - }); - it('should update originatorInfo', async () => { - await handleConnectionReady({ - originatorInfo, - engine, - connection, - approveHost, - disapprove, - onError, - updateOriginatorInfos, - }); - - expect(connection.originatorInfo).toBe(originatorInfo); - }); - }); - - describe('Connection readiness handling', () => { - beforeEach(() => { - connection.isReady = true; - }); - it('should return early if connection is already ready', async () => { - await handleConnectionReady({ - originatorInfo, - engine, - connection, - approveHost, - disapprove, - onError, - updateOriginatorInfos, - }); - - expect(mockCheckPermissions).not.toHaveBeenCalled(); - }); - it('should handle initial QR code connection', async () => { - await handleConnectionReady({ - originatorInfo, - engine, - connection, - approveHost, - disapprove, - onError, - updateOriginatorInfos, - }); - - expect(updateOriginatorInfos).toHaveBeenCalled(); - }); - it('should handle reconnection via QR code with recent activity', async () => { - connection.lastAuthorized = Date.now() - HOUR_IN_MS; - - await handleConnectionReady({ - originatorInfo, - engine, - connection, - approveHost, - disapprove, - onError, - updateOriginatorInfos, - }); - - expect(updateOriginatorInfos).toHaveBeenCalled(); - }); - - it('should handle reconnection via deeplink', async () => { - connection.trigger = 'deeplink'; - - await handleConnectionReady({ - originatorInfo, - engine, - connection, - approveHost, - disapprove, - onError, - updateOriginatorInfos, - }); - - expect(updateOriginatorInfos).toHaveBeenCalled(); - }); - it('should handle initial deeplink connection', async () => { - connection.trigger = 'deeplink'; - - await handleConnectionReady({ - originatorInfo, - engine, - connection, - approveHost, - disapprove, - onError, - updateOriginatorInfos, - }); - - expect(updateOriginatorInfos).toHaveBeenCalled(); - }); - }); - - describe('Error handling during permission check and bridge setup', () => { - beforeEach(() => { - connection.initialConnection = true; - connection.origin = AppConstants.DEEPLINKS.ORIGIN_QR_CODE; - mockCheckPermissions.mockRejectedValue(new Error('fakeError')); - }); - it('should catch errors and call onError callback', async () => { - await handleConnectionReady({ - originatorInfo, - engine, - connection, - approveHost, - disapprove, - onError, - updateOriginatorInfos, - }); - - expect(onError).toHaveBeenCalled(); - }); - }); - - describe('Approval Controller interactions', () => { - beforeEach(() => { - connection.approvalPromise = undefined; - }); - it('should reset approvalPromise to undefined', async () => { - const mockApprovalController = engine.context - .ApprovalController as jest.Mocked; - - // TODO: Replace "any" with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockApprovalController.get.mockReturnValueOnce('fakeApproval' as any); - - await handleConnectionReady({ - originatorInfo, - engine, - connection, - approveHost, - disapprove, - onError, - updateOriginatorInfos, - }); - expect(connection.approvalPromise).toBe(undefined); - }); - }); - - describe('OTP handling for QR code origin', () => { - beforeEach(() => { - connection.approvalPromise = undefined; - }); - - describe('when channel was NOT ActiveRecently', () => { - beforeEach(() => { - connection.lastAuthorized = Date.now() - HOUR_IN_MS; - connection.initialConnection = false; - connection.origin = AppConstants.DEEPLINKS.ORIGIN_QR_CODE; - }); - - it('should generate and send OTP if needed', async () => { - await handleConnectionReady({ - originatorInfo, - engine, - connection, - approveHost, - disapprove, - onError, - updateOriginatorInfos, - }); - expect(connection.approvalPromise).toBe(undefined); - expect(connection?.otps?.[0]).toBeDefined(); - }); - }); - }); - - describe('Approval and bridge setup', () => { - beforeEach(() => { - connection.isReady = false; - connection.approvalPromise = undefined; - connection.trigger = 'reconnect'; - - connection.initialConnection = true; - connection.origin = AppConstants.DEEPLINKS.ORIGIN_DEEPLINK; - mockCheckPermissions.mockResolvedValue(true); - }); - it('should setup the background bridge', async () => { - mockSetupBridge.mockReturnValueOnce({ - backgroundBridge: 'fakeBackgroundBridge', - // TODO: Replace "any" with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any); - - await handleConnectionReady({ - originatorInfo, - engine, - connection, - approveHost, - disapprove, - onError, - updateOriginatorInfos, - }); - - expect(mockSetupBridge).toHaveBeenCalled(); - expect(connection.backgroundBridge).toBeDefined(); - }); - it('should mark connection as ready', async () => { - await handleConnectionReady({ - originatorInfo, - engine, - connection, - approveHost, - disapprove, - onError, - updateOriginatorInfos, - }); - - expect(connection.isReady).toBe(true); - }); - }); -}); diff --git a/app/selectors/preferencesController.ts b/app/selectors/preferencesController.ts index 3f46e968dfa..e1269521253 100644 --- a/app/selectors/preferencesController.ts +++ b/app/selectors/preferencesController.ts @@ -3,7 +3,7 @@ import { PreferencesState } from '@metamask/preferences-controller'; import { RootState } from '../reducers'; const selectPreferencesControllerState = (state: RootState) => - state.engine.backgroundState.PreferencesController; + state.engine?.backgroundState?.PreferencesController; export const selectIpfsGateway = createSelector( selectPreferencesControllerState, diff --git a/app/util/notifications/services/NotificationService.test.ts b/app/util/notifications/services/NotificationService.test.ts index e40e78dd47e..26ce917f539 100644 --- a/app/util/notifications/services/NotificationService.test.ts +++ b/app/util/notifications/services/NotificationService.test.ts @@ -8,7 +8,39 @@ import { Linking } from 'react-native'; import { ChannelId } from '../../../util/notifications/androidChannels'; import NotificationsService from './NotificationService'; -jest.mock('@notifee/react-native'); +jest.mock('@notifee/react-native', () => ({ + getNotificationSettings: jest.fn(), + getChannels: jest.fn(), + requestPermission: jest.fn(), + cancelTriggerNotification: jest.fn(), + createChannel: jest.fn(), + onForegroundEvent: jest.fn(), + onBackgroundEvent: jest.fn(), + incrementBadgeCount: jest.fn(), + decrementBadgeCount: jest.fn(), + setBadgeCount: jest.fn(), + getBadgeCount: jest.fn(), + getInitialNotification: jest.fn(), + openNotificationSettings: jest.fn(), + AndroidImportance: { + DEFAULT: 'default', + HIGH: 'high', + LOW: 'low', + MIN: 'min', + NONE: 'none', + }, + AuthorizationStatus: { + AUTHORIZED: 'authorized', + DENIED: 'denied', + NOT_DETERMINED: 'not_determined', + PROVISIONAL: 'provisional', + }, + EventType: { + DELIVERED: 'delivered', + PRESS: 'press', + DISMISSED: 'dismissed', + }, +})); jest.mock('react-native', () => ({ Linking: { openSettings: jest.fn() }, Platform: { OS: 'ios' }, @@ -83,16 +115,23 @@ describe('NotificationsService', () => { expect(notifee.createChannel).toHaveBeenCalledWith(channel); }); - it.concurrent( - 'should return authorized from getAllPermissions', - async () => { - const result = await NotificationsService.getAllPermissions(); - expect(result.permission).toBe('authorized'); - }, - 10000, - ); + it('should return authorized from getAllPermissions', async () => { + (notifee.requestPermission as jest.Mock).mockResolvedValue({ + authorizationStatus: AuthorizationStatus.AUTHORIZED, + }); + (notifee.getNotificationSettings as jest.Mock).mockResolvedValue({ + authorizationStatus: AuthorizationStatus.AUTHORIZED, + }); + (notifee.getChannels as jest.Mock).mockResolvedValue([]); + + const result = await NotificationsService.getAllPermissions(); + expect(result.permission).toBe('authorized'); + }); - it('should return authorized from requestPermission ', async () => { + it('should return authorized from requestPermission', async () => { + (notifee.requestPermission as jest.Mock).mockResolvedValue({ + authorizationStatus: AuthorizationStatus.AUTHORIZED, + }); const result = await NotificationsService.requestPermission(); expect(result).toBe('authorized'); }); @@ -118,7 +157,7 @@ describe('NotificationsService', () => { callback, }); - expect(NotificationsService.incrementBadgeCount).toBeInstanceOf(Function); + expect(notifee.incrementBadgeCount).toHaveBeenCalledWith(1); await NotificationsService.handleNotificationEvent({ type: EventType.PRESS, @@ -130,9 +169,7 @@ describe('NotificationsService', () => { callback, }); - expect(NotificationsService.decrementBadgeCount).toBeInstanceOf(Function); - expect(NotificationsService.cancelTriggerNotification).toBeInstanceOf( - Function, - ); + expect(notifee.decrementBadgeCount).toHaveBeenCalledWith(1); + expect(notifee.cancelTriggerNotification).toHaveBeenCalledWith('123'); }); });