diff --git a/app/components/Views/confirmations/Confirm/Confirm.test.tsx b/app/components/Views/confirmations/Confirm/Confirm.test.tsx index 8007a4c9866..6f6da173c48 100644 --- a/app/components/Views/confirmations/Confirm/Confirm.test.tsx +++ b/app/components/Views/confirmations/Confirm/Confirm.test.tsx @@ -57,7 +57,7 @@ describe('Confirm', () => { expect(getByText('Estimated changes')).toBeDefined(); expect( getByText( - 'You’re signing into a site and there are no predicted changes to your account.', + "You're signing into a site and there are no predicted changes to your account.", ), ).toBeDefined(); expect(getByText('Request from')).toBeDefined(); diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/TypedSignV1.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/TypedSignV1.test.tsx index 0c533f651c9..31af8bd8955 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/TypedSignV1.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV1/TypedSignV1.test.tsx @@ -12,7 +12,7 @@ describe('TypedSignV1', () => { expect(getByText('Estimated changes')).toBeDefined(); expect( getByText( - 'You’re signing into a site and there are no predicted changes to your account.', + "You're signing into a site and there are no predicted changes to your account.", ), ).toBeDefined(); expect(getByText('Request from')).toBeDefined(); diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.test.tsx new file mode 100644 index 00000000000..6f015ab9b16 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.test.tsx @@ -0,0 +1,142 @@ +import React from 'react'; +import cloneDeep from 'lodash/cloneDeep'; +import { + DecodingData, + DecodingDataChangeType, + DecodingDataStateChanges, + SignatureRequest, +} from '@metamask/signature-controller'; +import useGetTokenStandardAndDetails from '../../../../../hooks/useGetTokenStandardAndDetails'; +import { typedSignV4ConfirmationState } from '../../../../../../../../util/test/confirm-data-helpers'; +import renderWithProvider from '../../../../../../../../util/test/renderWithProvider'; +import { memoizedGetTokenStandardAndDetails } from '../../../../../utils/token'; +import TypedSignV3V4Simulation from './Simulation'; + +jest.mock('../../../../../hooks/useGetTokenStandardAndDetails'); + +jest.mock('../../../../../../../../core/Engine', () => ({ + context: { + NetworkController: { + findNetworkClientIdByChainId: () => 'mainnet', + }, + }, +})); + +const stateChangesApprove = [ + { + assetType: 'ERC20', + changeType: DecodingDataChangeType.Approve, + address: '0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad', + amount: '12345', + contractAddress: '0x6b175474e89094c44da98b954eedeac495271d0f', + }, +]; + +const mockState = ( + mockStateChanges: DecodingDataStateChanges, + { + mockDecodingDataProps, + stubDecodingLoading = false, + }: { + mockDecodingDataProps?: Partial; + stubDecodingLoading?: boolean; + } = { + mockDecodingDataProps: {}, + stubDecodingLoading: false, + }, +) => { + const clonedMockState = cloneDeep(typedSignV4ConfirmationState); + const request = clonedMockState.engine.backgroundState.SignatureController + .signatureRequests[ + 'fb2029e1-b0ab-11ef-9227-05a11087c334' + ] as SignatureRequest; + + request.decodingLoading = stubDecodingLoading; + request.decodingData = { + ...mockDecodingDataProps, + stateChanges: mockStateChanges, + }; + + return clonedMockState; +}; + +describe('PermitSimulation', () => { + afterEach(() => { + jest.clearAllMocks(); + + /** Reset memoized function using getTokenStandardAndDetails for each test */ + memoizedGetTokenStandardAndDetails?.cache?.clear?.(); + }); + + it('renders DecodedSimulation loader if decodingLoading is true', async () => { + const { queryByTestId } = renderWithProvider(, { + state: mockState(stateChangesApprove, { + stubDecodingLoading: true, + }), + }); + + expect(await queryByTestId('confirm-v3v4-simulation-loader')).toBeDefined(); + }); + + it('renders DecodingSimulation with "Unavailable" if decoding data is empty', async () => { + const { getByText } = renderWithProvider(, { + state: mockState([]), + }); + + expect(await getByText('Estimated changes')).toBeDefined(); + expect(await getByText('Unavailable')).toBeDefined(); + }); + + it('renders DecodingSimulation for permits', async () => { + ( + useGetTokenStandardAndDetails as jest.MockedFn< + typeof useGetTokenStandardAndDetails + > + ).mockReturnValue({ + symbol: 'TST', + decimals: '4', + balance: undefined, + standard: 'ERC20', + decimalsNumber: 4, + }); + + const { getByText } = renderWithProvider(, { + state: mockState(stateChangesApprove), + }); + + expect(await getByText('Estimated changes')).toBeDefined(); + expect(await getByText('Spending cap')).toBeDefined(); + expect(await getByText('1.235')).toBeDefined(); + }); + + it('renders PermitSimulation if decoding api returns error', async () => { + ( + useGetTokenStandardAndDetails as jest.MockedFn< + typeof useGetTokenStandardAndDetails + > + ).mockReturnValue({ + symbol: 'TST', + decimals: '2', + balance: undefined, + standard: 'ERC20', + decimalsNumber: 4, + }); + + const { getByText } = renderWithProvider(, { + state: mockState([], { + mockDecodingDataProps: { + error: { message: 'some error', type: 'SOME_ERROR' }, + } as Partial, + }), + }); + + expect(await getByText('Estimated changes')).toBeDefined(); + expect(await getByText('Spending cap')).toBeDefined(); + expect(await getByText('0.3')).toBeDefined(); + expect( + await getByText( + "You're giving the spender permission to spend this many tokens from your account.", + ), + ).toBeDefined(); + }); +}); diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Static/Static.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Static/Static.test.tsx new file mode 100644 index 00000000000..237ceffd226 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Static/Static.test.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { Text } from 'react-native'; +import { render } from '@testing-library/react-native'; +import StaticSimulation from './Static'; + +const mockProps = { + title: 'Test Title', + titleTooltip: 'Test Tooltip', + description: 'Test Description', + simulationElements: <>, +}; + +describe('StaticSimulation', () => { + it('renders correctly with basic props', () => { + const { getByText } = render(); + + expect(getByText('Test Title')).toBeDefined(); + expect(getByText('Test Description')).toBeDefined(); + }); + + it('shows loader when isLoading is true', () => { + const { queryByTestId } = render( + , + ); + + expect(queryByTestId('confirm-v3v4-simulation-loader')).toBeDefined(); + }); + + it('shows simulation elements when not loading', () => { + const simulationElements = Test Simulation; + const { getByText } = render( + , + ); + + expect(getByText('Test Simulation')).toBeDefined(); + }); +}); diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx index b974d951443..cc9da83b409 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx @@ -9,7 +9,11 @@ import { import { strings } from '../../../../../../../../../../locales/i18n'; import { typedSignV4ConfirmationState } from '../../../../../../../../../util/test/confirm-data-helpers'; import renderWithProvider from '../../../../../../../../../util/test/renderWithProvider'; -import TypedSignDecoded, { getStateChangeToolip, getStateChangeType, StateChangeType } from './TypedSignDecoded'; +import TypedSignDecoded, { + getStateChangeToolip, + getStateChangeType, + StateChangeType, +} from './TypedSignDecoded'; const stateChangesApprove = [ { @@ -75,9 +79,15 @@ const stateChangesNftBidding: DecodingDataStateChanges = [ }, ]; -const mockState = (mockStateChanges: DecodingDataStateChanges, stubDecodingLoading: boolean = false) => { +const mockState = ( + mockStateChanges: DecodingDataStateChanges, + stubDecodingLoading: boolean = false, +) => { const clonedMockState = cloneDeep(typedSignV4ConfirmationState); - const request = clonedMockState.engine.backgroundState.SignatureController.signatureRequests['fb2029e1-b0ab-11ef-9227-05a11087c334'] as SignatureRequest; + const request = clonedMockState.engine.backgroundState.SignatureController + .signatureRequests[ + 'fb2029e1-b0ab-11ef-9227-05a11087c334' + ] as SignatureRequest; request.decodingLoading = stubDecodingLoading; request.decodingData = { stateChanges: mockStateChanges, @@ -134,7 +144,11 @@ describe('DecodedSimulation', () => { it('renders label only once if there are multiple state changes of same changeType', async () => { const { getAllByText } = renderWithProvider(, { - state: mockState([stateChangesApprove[0], stateChangesApprove[0], stateChangesApprove[0]]), + state: mockState([ + stateChangesApprove[0], + stateChangesApprove[0], + stateChangesApprove[0], + ]), }); expect(await getAllByText('12,345')).toHaveLength(3); @@ -152,28 +166,34 @@ describe('DecodedSimulation', () => { describe('getStateChangeToolip', () => { it('return correct tooltip when permit is for listing NFT', () => { - const tooltip = getStateChangeToolip( - StateChangeType.NFTListingReceive, + const tooltip = getStateChangeToolip(StateChangeType.NFTListingReceive); + expect(tooltip).toBe( + strings('confirm.simulation.decoded_tooltip_list_nft'), ); - expect(tooltip).toBe(strings('confirm.simulation.decoded_tooltip_list_nft')); }); it('return correct tooltip when permit is for bidding NFT', () => { - const tooltip = getStateChangeToolip( - StateChangeType.NFTBiddingReceive, + const tooltip = getStateChangeToolip(StateChangeType.NFTBiddingReceive); + expect(tooltip).toBe( + strings('confirm.simulation.decoded_tooltip_bid_nft'), ); - expect(tooltip).toBe(strings('confirm.simulation.decoded_tooltip_bid_nft')); }); }); describe('getStateChangeType', () => { it('return correct state change type for NFT listing receive', () => { - const stateChange = getStateChangeType(stateChangesNftListing, stateChangesNftListing[1]); + const stateChange = getStateChangeType( + stateChangesNftListing, + stateChangesNftListing[1], + ); expect(stateChange).toBe(StateChangeType.NFTListingReceive); }); it('return correct state change type for NFT bidding receive', () => { - const stateChange = getStateChangeType(stateChangesNftBidding, stateChangesNftBidding[1]); + const stateChange = getStateChangeType( + stateChangesNftBidding, + stateChangesNftBidding[1], + ); expect(stateChange).toBe(StateChangeType.NFTBiddingReceive); }); }); diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.test.tsx index 2b814f9629b..e7ff8ddf91d 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.test.tsx @@ -19,7 +19,11 @@ describe('PermitSimulation', () => { }); expect(getByText('Estimated changes')).toBeDefined(); - expect(getByText('You’re giving the spender permission to spend this many tokens from your account.')).toBeDefined(); + expect( + getByText( + "You're giving the spender permission to spend this many tokens from your account.", + ), + ).toBeDefined(); expect(getByText('Spending cap')).toBeDefined(); expect(getByText('3,000')).toBeDefined(); expect(getByText('0xCcCCc...ccccC')).toBeDefined(); diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/NativeValueDisplay.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/NativeValueDisplay.test.tsx new file mode 100644 index 00000000000..f0ab367e2ee --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/NativeValueDisplay.test.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import NativeValueDisplay from './NativeValueDisplay'; +import { backgroundState } from '../../../../../../../../../../util/test/initial-root-state'; +import renderWithProvider from '../../../../../../../../../../util/test/renderWithProvider'; +import { fireEvent } from '@testing-library/react-native'; + +const mockInitialState = { + engine: { + backgroundState, + }, +}; + +describe('NativeValueDisplay', () => { + it('renders component correctly', async () => { + const { findByText } = renderWithProvider( + , + { state: mockInitialState }, + ); + + expect(await findByText('< 0.000001')).toBeDefined(); + expect(await findByText('ETH')).toBeDefined(); + }); + + it('displays modal when clicking on the value', async () => { + const { findByText } = renderWithProvider( + , + { state: mockInitialState }, + ); + + const button = await findByText('< 0.000001'); + fireEvent.press(button); + + expect(await findByText('Spending Cap')).toBeDefined(); + }); +}); diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx index 04d399656f5..8cf784909dc 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx @@ -1,4 +1,3 @@ - import React from 'react'; import { act } from '@testing-library/react-native'; import SimulationValueDisplay from './ValueDisplay'; @@ -25,7 +24,9 @@ jest.mock('../../../../../../../hooks/useGetTokenStandardAndDetails'); jest.mock('../../../../../../../../../../util/address', () => ({ getTokenDetails: jest.fn(), - renderShortAddress: jest.requireActual('../../../../../../../../../../util/address').renderShortAddress + renderShortAddress: jest.requireActual( + '../../../../../../../../../../util/address', + ).renderShortAddress, })); describe('SimulationValueDisplay', () => { @@ -53,7 +54,11 @@ describe('SimulationValueDisplay', () => { }); it('renders component correctly', async () => { - (useGetTokenStandardAndDetails as jest.MockedFn).mockReturnValue({ + ( + useGetTokenStandardAndDetails as jest.MockedFn< + typeof useGetTokenStandardAndDetails + > + ).mockReturnValue({ symbol: 'TST', decimals: '4', balance: undefined, @@ -98,7 +103,11 @@ describe('SimulationValueDisplay', () => { }); it('should invoke method to track missing decimal information for ERC20 tokens only once', async () => { - (useGetTokenStandardAndDetails as jest.MockedFn).mockReturnValue({ + ( + useGetTokenStandardAndDetails as jest.MockedFn< + typeof useGetTokenStandardAndDetails + > + ).mockReturnValue({ symbol: 'TST', decimals: undefined, balance: undefined, @@ -120,7 +129,11 @@ describe('SimulationValueDisplay', () => { }); it('should not invoke method to track missing decimal information for ERC20 tokens', async () => { - (useGetTokenStandardAndDetails as jest.MockedFn).mockReturnValue({ + ( + useGetTokenStandardAndDetails as jest.MockedFn< + typeof useGetTokenStandardAndDetails + > + ).mockReturnValue({ symbol: 'TST', decimals: '4', balance: undefined, @@ -144,10 +157,10 @@ describe('SimulationValueDisplay', () => { describe('when token is an ERC721 token', () => { beforeEach(() => { jest.mocked(getTokenDetails).mockResolvedValue({ - name: 'TST', - symbol: 'TST', - standard: TokenStandard.ERC721, - }); + name: 'TST', + symbol: 'TST', + standard: TokenStandard.ERC721, + }); }); it('should not invoke method to track missing decimal information', async () => { diff --git a/app/components/Views/confirmations/components/Confirm/NoChangeSimulation/NoChangeSimulation.test.tsx b/app/components/Views/confirmations/components/Confirm/NoChangeSimulation/NoChangeSimulation.test.tsx index 0f11ada192e..838361aee88 100644 --- a/app/components/Views/confirmations/components/Confirm/NoChangeSimulation/NoChangeSimulation.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/NoChangeSimulation/NoChangeSimulation.test.tsx @@ -12,7 +12,7 @@ describe('NoChangeSimulation', () => { expect(getByText('Estimated changes')).toBeDefined(); expect( getByText( - 'You’re signing into a site and there are no predicted changes to your account.', + "You're signing into a site and there are no predicted changes to your account." ), ).toBeDefined(); }); diff --git a/app/util/test/confirm-data-helpers.ts b/app/util/test/confirm-data-helpers.ts index 535b3cf4d4a..485228a065a 100644 --- a/app/util/test/confirm-data-helpers.ts +++ b/app/util/test/confirm-data-helpers.ts @@ -255,8 +255,17 @@ export const typedSignV4ConfirmationState = { messageParams: { data: '{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Permit":[{"name":"owner","type":"address"},{"name":"spender","type":"address"},{"name":"value","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"deadline","type":"uint256"}]},"primaryType":"Permit","domain":{"name":"MyToken","version":"1","verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC","chainId":1},"message":{"owner":"0x935e73edb9ff52e23bac7f7e043a1ecd06d05477","spender":"0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","value":3000,"nonce":0,"deadline":50000000000}}', from: '0x935e73edb9ff52e23bac7f7e043a1ecd06d05477', + version: 'V4', + requestId: 14, + signatureMethod: 'eth_signTypedData_v4', + origin: 'https://metamask.github.io', metamaskId: 'fb2029e0-b0ab-11ef-9227-05a11087c334', - origin: 'https://metamask.github.io' + meta: { + url: 'https://metamask.github.io/test-dapp/', + title: 'E2E Test Dapp', + icon: { uri: 'https://metamask.github.io/metamask-fox.svg' }, + analytics: { request_source: 'In-App-Browser' }, + }, }, networkClientId: '1', status: SignatureRequestStatus.Unapproved, diff --git a/locales/languages/en.json b/locales/languages/en.json index 666c154faf3..b993d227b59 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -3609,7 +3609,7 @@ "simulation": { "decoded_tooltip_bid_nft": "The NFT will be reflected in your wallet, when the bid is accepted.", "decoded_tooltip_list_nft": "Expect changes only if someone buys your NFTs.", - "info_permit": "You’re giving the spender permission to spend this many tokens from your account.", + "info_permit": "You're giving the spender permission to spend this many tokens from your account.", "label_change_type_bidding": "You bid", "label_change_type_listing": "You list", "label_change_type_nft_listing": "Listing price", @@ -3618,7 +3618,7 @@ "label_change_type_receive": "You receive", "label_change_type_revoke": "Revoke", "label_change_type_transfer": "You send", - "personal_sign_info": "You’re signing into a site and there are no predicted changes to your account.", + "personal_sign_info": "You're signing into a site and there are no predicted changes to your account.", "title": "Estimated changes", "tooltip": "Estimated changes are what might happen if you go through with this transaction. This is just a prediction, not a guarantee.", "unavailable": "Unavailable"