2023-02-23 16:57:12 +01:00
import React from 'react' ;
import configureMockStore from 'redux-mock-store' ;
2023-04-19 01:23:45 +02:00
import { act , fireEvent } from '@testing-library/react' ;
2023-07-12 20:32:46 +02:00
import thunk from 'redux-thunk' ;
2023-08-03 19:31:35 +02:00
import { NetworkType } from '@metamask/controller-utils' ;
import { NetworkStatus } from '@metamask/network-controller' ;
2023-02-23 16:57:12 +01:00
import { renderWithProvider } from '../../../test/lib/render-helpers' ;
2023-03-21 15:43:22 +01:00
import { KeyringType } from '../../../shared/constants/keyring' ;
2023-02-23 16:57:12 +01:00
import TokenAllowance from './token-allowance' ;
const testTokenAddress = '0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F' ;
const state = {
appState : {
customTokenAmount : '1' ,
} ,
metamask : {
accounts : {
2023-03-14 18:29:31 +01:00
'0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' : {
address : '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' ,
balance : '0x0' ,
2023-02-23 16:57:12 +01:00
} ,
} ,
gasEstimateType : 'none' ,
2023-03-14 18:29:31 +01:00
selectedAddress : '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' ,
2023-02-23 16:57:12 +01:00
identities : {
2023-03-14 18:29:31 +01:00
'0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' : {
address : '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' ,
2023-02-23 16:57:12 +01:00
name : 'Account 1' ,
} ,
2023-04-28 09:59:53 +02:00
'0xc42edfcc21ed14dda456aa0756c153f7985d8813' : {
address : '0xc42edfcc21ed14dda456aa0756c153f7985d8813' ,
name : 'Account 2' ,
} ,
2023-02-23 16:57:12 +01:00
} ,
cachedBalances : { } ,
addressBook : [
{
address : '0xc42edfcc21ed14dda456aa0756c153f7985d8813' ,
chainId : '0x5' ,
isEns : false ,
memo : '' ,
name : 'Address Book Account 1' ,
} ,
] ,
2023-05-02 17:53:20 +02:00
providerConfig : {
2023-02-23 16:57:12 +01:00
type : 'mainnet' ,
nickname : '' ,
} ,
2023-08-03 19:31:35 +02:00
selectedNetworkClientId : NetworkType . mainnet ,
networksMetadata : {
[ NetworkType . mainnet ] : {
EIPS : { 1559 : true } ,
status : NetworkStatus . Available ,
2023-02-23 16:57:12 +01:00
} ,
} ,
preferences : {
showFiatInTestnets : true ,
} ,
knownMethodData : { } ,
tokens : [
{
address : testTokenAddress ,
symbol : 'SNX' ,
decimals : 18 ,
image : 'testImage' ,
isERC721 : false ,
} ,
{
address : '0xaD6D458402F60fD3Bd25163575031ACDce07538U' ,
symbol : 'DAU' ,
decimals : 18 ,
image : null ,
isERC721 : false ,
} ,
] ,
2023-09-04 17:48:25 +02:00
transactions : [ ] ,
2023-04-18 21:49:49 +02:00
keyringTypes : [ ] ,
2023-03-14 18:29:31 +01:00
keyrings : [
{
2023-04-18 21:49:49 +02:00
type : KeyringType . hdKeyTree ,
2023-03-14 18:29:31 +01:00
accounts : [ '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' ] ,
} ,
] ,
2023-07-12 20:32:46 +02:00
nextNonce : 1 ,
customNonceValue : '' ,
2023-02-23 16:57:12 +01:00
} ,
history : {
mostRecentOverviewPage : '/' ,
} ,
confirmTransaction : {
txData : { } ,
} ,
2023-04-17 16:34:26 +02:00
send : {
draftTransactions : { } ,
} ,
2023-02-23 16:57:12 +01:00
} ;
2023-07-12 20:32:46 +02:00
const mockShowModal = jest . fn ( ) ;
2023-02-23 16:57:12 +01:00
jest . mock ( '../../store/actions' , ( ) => ( {
disconnectGasFeeEstimatePoller : jest . fn ( ) ,
getGasFeeTimeEstimate : jest . fn ( ) . mockImplementation ( ( ) => Promise . resolve ( ) ) ,
getGasFeeEstimatesAndStartPolling : jest
. fn ( )
. mockImplementation ( ( ) => Promise . resolve ( ) ) ,
addPollingTokenToAppState : jest . fn ( ) ,
removePollingTokenFromAppState : jest . fn ( ) ,
updateTransactionGasFees : ( ) => ( { type : 'UPDATE_TRANSACTION_PARAMS' } ) ,
updatePreviousGasParams : ( ) => ( { type : 'UPDATE_TRANSACTION_PARAMS' } ) ,
createTransactionEventFragment : jest . fn ( ) ,
2023-07-12 20:32:46 +02:00
getNextNonce : ( ) => jest . fn ( ) ,
showModal : ( ) => mockShowModal ,
2023-02-23 16:57:12 +01:00
updateCustomNonce : ( ) => ( { type : 'UPDATE_TRANSACTION_PARAMS' } ) ,
2023-04-19 01:23:45 +02:00
estimateGas : jest . fn ( ) . mockImplementation ( ( ) => Promise . resolve ( ) ) ,
2023-02-23 16:57:12 +01:00
} ) ) ;
jest . mock ( '../../contexts/gasFee' , ( ) => ( {
useGasFeeContext : ( ) => ( {
maxPriorityFeePerGas : '0.1' ,
maxFeePerGas : '0.1' ,
2023-04-19 01:23:45 +02:00
updateTransaction : jest . fn ( ) ,
2023-02-23 16:57:12 +01:00
} ) ,
} ) ) ;
jest . mock ( 'react-router-dom' , ( ) => {
const original = jest . requireActual ( 'react-router-dom' ) ;
return {
... original ,
useHistory : ( ) => ( {
push : jest . fn ( ) ,
} ) ,
useParams : ( ) => ( {
address : testTokenAddress ,
} ) ,
} ;
} ) ;
describe ( 'TokenAllowancePage' , ( ) => {
const props = {
origin : 'https://metamask.github.io' ,
siteImage : 'https://metamask.github.io/test-dapp/metamask-fox.svg' ,
useNonceField : false ,
currentCurrency : 'usd' ,
nativeCurrency : 'GoerliETH' ,
ethTransactionTotal : '0.0012' ,
fiatTransactionTotal : '1.6' ,
hexTransactionTotal : '0x44364c5bb0000' ,
isMultiLayerFeeNetwork : false ,
supportsEIP1559 : true ,
2023-04-28 09:59:53 +02:00
userAddress : '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' ,
2023-02-23 16:57:12 +01:00
tokenAddress : '0x55797717b9947b31306f4aac7ad1365c6e3923bd' ,
data : '0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef40000000000000000000000000000000000000000000000000000000000011170' ,
isSetApproveForAll : false ,
setApproveForAllArg : false ,
decimals : '4' ,
dappProposedTokenAmount : '7' ,
currentTokenBalance : '10' ,
toAddress : '0x9bc5baf874d2da8d216ae9f137804184ee5afef4' ,
tokenSymbol : 'TST' ,
2023-07-12 20:32:46 +02:00
showCustomizeGasModal : jest . fn ( ) ,
warning : '' ,
2023-02-23 16:57:12 +01:00
txData : {
id : 3049568294499567 ,
time : 1664449552289 ,
status : 'unapproved' ,
metamaskNetworkId : '3' ,
originalGasEstimate : '0xea60' ,
userEditedGasLimit : false ,
chainId : '0x3' ,
loadingDefaults : false ,
dappSuggestedGasFees : {
gasPrice : '0x4a817c800' ,
gas : '0xea60' ,
} ,
sendFlowHistory : [ ] ,
txParams : {
2023-04-28 09:59:53 +02:00
from : '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' ,
2023-02-23 16:57:12 +01:00
to : '0x55797717b9947b31306f4aac7ad1365c6e3923bd' ,
value : '0x0' ,
data : '0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef40000000000000000000000000000000000000000000000000000000000011170' ,
gas : '0xea60' ,
gasPrice : '0x4a817c800' ,
maxFeePerGas : '0x4a817c800' ,
} ,
origin : 'https://metamask.github.io' ,
type : 'approve' ,
userFeeLevel : 'custom' ,
defaultGasEstimates : {
estimateType : 'custom' ,
gas : '0xea60' ,
maxFeePerGas : '0x4a817c800' ,
maxPriorityFeePerGas : '0x4a817c800' ,
gasPrice : '0x4a817c800' ,
} ,
} ,
} ;
let store ;
beforeEach ( ( ) => {
2023-07-12 20:32:46 +02:00
store = configureMockStore ( [ thunk ] ) ( state ) ;
2023-02-23 16:57:12 +01:00
} ) ;
2023-07-11 16:57:59 +02:00
it ( 'should match snapshot' , ( ) => {
const { container } = renderWithProvider (
< TokenAllowance { ... props } / > ,
store ,
) ;
expect ( container ) . toMatchSnapshot ( ) ;
} ) ;
it ( 'should render title "Spending cap request for your" in token allowance page' , ( ) => {
2023-02-23 16:57:12 +01:00
const { getByText } = renderWithProvider (
< TokenAllowance { ... props } / > ,
store ,
) ;
2023-07-11 16:57:59 +02:00
expect ( getByText ( 'Spending cap request for your' ) ) . toBeInTheDocument ( ) ;
2023-02-23 16:57:12 +01:00
} ) ;
it ( 'should render reject button' , ( ) => {
const { getByTestId } = renderWithProvider (
< TokenAllowance { ... props } / > ,
store ,
) ;
const onCloseBtn = getByTestId ( 'page-container-footer-cancel' ) ;
expect ( onCloseBtn ) . toBeInTheDocument ( ) ;
} ) ;
2023-07-12 20:32:46 +02:00
it ( 'should not render customize nonce modal if useNonceField is set to false' , ( ) => {
const { queryByText } = renderWithProvider (
< TokenAllowance { ... props } / > ,
store ,
) ;
expect ( queryByText ( 'Nonce' ) ) . not . toBeInTheDocument ( ) ;
expect ( queryByText ( '1' ) ) . not . toBeInTheDocument ( ) ;
expect ( mockShowModal ) . not . toHaveBeenCalledTimes ( 1 ) ;
} ) ;
it ( 'should render customize nonce modal if useNonceField is set to true' , ( ) => {
props . useNonceField = true ;
props . nextNonce = 1 ;
const { queryByText , getByText } = renderWithProvider (
< TokenAllowance { ... props } / > ,
store ,
) ;
const editButton = getByText ( 'Edit' ) ;
expect ( queryByText ( 'Nonce' ) ) . toBeInTheDocument ( ) ;
expect ( queryByText ( '1' ) ) . toBeInTheDocument ( ) ;
fireEvent . click ( editButton ) ;
expect ( mockShowModal ) . toHaveBeenCalledTimes ( 1 ) ;
} ) ;
it ( 'should render nextNonce value when custom nonce value is a empty string' , ( ) => {
props . useNonceField = true ;
props . customNonceValue = '' ;
const { queryByText , getByText } = renderWithProvider (
< TokenAllowance { ... props } / > ,
store ,
) ;
const editButton = getByText ( 'Edit' ) ;
expect ( queryByText ( 'Nonce' ) ) . toBeInTheDocument ( ) ;
expect ( queryByText ( '1' ) ) . toBeInTheDocument ( ) ;
fireEvent . click ( editButton ) ;
expect ( mockShowModal ) . toHaveBeenCalledTimes ( 2 ) ;
} ) ;
it ( 'should render edited custom nonce value' , ( ) => {
props . useNonceField = true ;
state . metamask . customNonceValue = '3' ;
const { queryByText , getByText } = renderWithProvider (
< TokenAllowance { ... props } / > ,
store ,
) ;
const editButton = getByText ( 'Edit' ) ;
expect ( queryByText ( 'Nonce' ) ) . toBeInTheDocument ( ) ;
expect ( queryByText ( '3' ) ) . toBeInTheDocument ( ) ;
fireEvent . click ( editButton ) ;
expect ( mockShowModal ) . toHaveBeenCalledTimes ( 3 ) ;
} ) ;
it ( 'should render customize nonce warning if custom nonce value is higher than nextNonce value' , ( ) => {
props . useNonceField = true ;
props . nextNonce = 2 ;
props . customNonceValue = '3' ;
props . warning = 'Nonce is higher than suggested nonce of 2' ;
const { getByText } = renderWithProvider (
< TokenAllowance { ... props } / > ,
store ,
) ;
expect (
getByText ( 'Nonce is higher than suggested nonce of 2' ) ,
) . toBeInTheDocument ( ) ;
} ) ;
it ( 'should not render customize nonce warning if custom nonce value is lower than nextNonce value' , ( ) => {
props . useNonceField = true ;
props . nextNonce = 2 ;
props . customNonceValue = '1' ;
props . warning = '' ;
const { container } = renderWithProvider (
< TokenAllowance { ... props } / > ,
store ,
) ;
const customizeNonceWarning = container . querySelector (
'.token-allowance-container__custom-nonce-warning' ,
) ;
expect ( customizeNonceWarning ) . not . toBeInTheDocument ( ) ;
} ) ;
it ( 'should render customize nonce modal when next button is clicked and if useNonceField is set to true' , ( ) => {
props . useNonceField = true ;
state . metamask . customNonceValue = '2' ;
const { getByText , getAllByText , queryByText } = renderWithProvider (
< TokenAllowance { ... props } / > ,
store ,
) ;
const nextButton = getByText ( 'Next' ) ;
fireEvent . click ( nextButton ) ;
const editButton = getAllByText ( 'Edit' ) ;
expect ( queryByText ( 'Nonce' ) ) . toBeInTheDocument ( ) ;
expect ( queryByText ( '2' ) ) . toBeInTheDocument ( ) ;
fireEvent . click ( editButton [ 1 ] ) ;
expect ( mockShowModal ) . toHaveBeenCalledTimes ( 4 ) ;
} ) ;
it ( 'should render customize nonce modal when next button is clicked, than back button is clicked, than return to previous page and if useNonceField is set to true' , ( ) => {
props . useNonceField = true ;
state . metamask . customNonceValue = '2' ;
const { getByText , queryByText } = renderWithProvider (
< TokenAllowance { ... props } / > ,
store ,
) ;
const nextButton = getByText ( 'Next' ) ;
fireEvent . click ( nextButton ) ;
const backButton = getByText ( '< Back' ) ;
fireEvent . click ( backButton ) ;
const editButton = getByText ( 'Edit' ) ;
expect ( queryByText ( 'Nonce' ) ) . toBeInTheDocument ( ) ;
expect ( queryByText ( '2' ) ) . toBeInTheDocument ( ) ;
fireEvent . click ( editButton ) ;
expect ( mockShowModal ) . toHaveBeenCalledTimes ( 5 ) ;
} ) ;
2023-02-23 16:57:12 +01:00
it ( 'should click View details and show function type' , ( ) => {
const { getByText } = renderWithProvider (
< TokenAllowance { ... props } / > ,
store ,
) ;
const viewDetailsButton = getByText ( 'View details' ) ;
fireEvent . click ( viewDetailsButton ) ;
expect ( getByText ( 'Function: Approve' ) ) . toBeInTheDocument ( ) ;
} ) ;
2023-07-11 16:57:59 +02:00
it ( 'should load the page with dappProposedAmount prefilled and "Use site suggestion" should not be displayed' , ( ) => {
const { queryByText , getByTestId } = renderWithProvider (
2023-02-23 16:57:12 +01:00
< TokenAllowance { ... props } / > ,
store ,
) ;
2023-04-19 01:23:45 +02:00
act ( ( ) => {
2023-07-11 16:57:59 +02:00
const useSiteSuggestion = queryByText ( 'Use site suggestion' ) ;
expect ( useSiteSuggestion ) . not . toBeInTheDocument ( ) ;
2023-04-19 01:23:45 +02:00
} ) ;
2023-02-23 16:57:12 +01:00
const input = getByTestId ( 'custom-spending-cap-input' ) ;
2023-07-11 16:57:59 +02:00
expect ( input . value ) . toBe ( '7' ) ;
2023-02-23 16:57:12 +01:00
} ) ;
2023-07-11 16:57:59 +02:00
it ( 'should click Use site suggestion and set input value to default' , ( ) => {
2023-02-23 16:57:12 +01:00
const { getByText , getByTestId } = renderWithProvider (
< TokenAllowance { ... props } / > ,
store ,
) ;
const textField = getByTestId ( 'custom-spending-cap-input' ) ;
2023-07-11 16:57:59 +02:00
expect ( textField . value ) . toBe ( '7' ) ;
2023-02-23 16:57:12 +01:00
fireEvent . change ( textField , { target : { value : '1' } } ) ;
2023-07-11 16:57:59 +02:00
expect ( textField . value ) . toBe ( '1' ) ;
act ( ( ) => {
const useSiteSuggestion = getByText ( 'Use site suggestion' ) ;
expect ( useSiteSuggestion ) . toBeInTheDocument ( ) ;
fireEvent . click ( useSiteSuggestion ) ;
} ) ;
expect ( textField . value ) . toBe ( '7' ) ;
} ) ;
it ( 'should call back button when button is clicked and return to previous page' , ( ) => {
const { getByText } = renderWithProvider (
< TokenAllowance { ... props } / > ,
store ,
) ;
2023-02-23 16:57:12 +01:00
const nextButton = getByText ( 'Next' ) ;
fireEvent . click ( nextButton ) ;
2023-07-11 16:57:59 +02:00
expect ( getByText ( 'Site requested spending cap' ) ) . toBeInTheDocument ( ) ;
2023-02-23 16:57:12 +01:00
const backButton = getByText ( '< Back' ) ;
fireEvent . click ( backButton ) ;
2023-07-11 16:57:59 +02:00
expect ( getByText ( 'Spending cap request for your' ) ) . toBeInTheDocument ( ) ;
2023-02-23 16:57:12 +01:00
} ) ;
2023-04-11 05:19:42 +02:00
it ( 'should click Verify third-party details and show popup Third-party details, then close popup' , ( ) => {
2023-02-23 16:57:12 +01:00
const { getByText } = renderWithProvider (
< TokenAllowance { ... props } / > ,
store ,
) ;
2023-04-11 05:19:42 +02:00
const verifyThirdPartyDetails = getByText ( 'Verify third-party details' ) ;
fireEvent . click ( verifyThirdPartyDetails ) ;
2023-02-23 16:57:12 +01:00
2023-04-11 05:19:42 +02:00
expect ( getByText ( 'Third-party details' ) ) . toBeInTheDocument ( ) ;
2023-02-23 16:57:12 +01:00
const gotIt = getByText ( 'Got it' ) ;
fireEvent . click ( gotIt ) ;
expect ( gotIt ) . not . toBeInTheDocument ( ) ;
} ) ;
2023-03-14 18:29:31 +01:00
2023-04-18 21:49:49 +02:00
it ( 'should show ledger info text if the sending address is ledger' , ( ) => {
2023-07-11 16:57:59 +02:00
const { queryByText , getByText } = renderWithProvider (
2023-04-18 21:49:49 +02:00
< TokenAllowance { ... props } fromAddressIsLedger / > ,
2023-03-14 18:29:31 +01:00
store ,
) ;
expect ( queryByText ( 'Prior to clicking confirm:' ) ) . toBeNull ( ) ;
const nextButton = getByText ( 'Next' ) ;
fireEvent . click ( nextButton ) ;
expect ( queryByText ( 'Prior to clicking confirm:' ) ) . toBeInTheDocument ( ) ;
} ) ;
2023-04-18 21:49:49 +02:00
it ( 'should not show ledger info text if the sending address is not ledger' , ( ) => {
2023-07-11 16:57:59 +02:00
const { queryByText , getByText } = renderWithProvider (
2023-04-18 21:49:49 +02:00
< TokenAllowance { ... props } fromAddressIsLedger = { false } / > ,
2023-03-14 18:29:31 +01:00
store ,
) ;
2023-04-18 21:49:49 +02:00
expect ( queryByText ( 'Prior to clicking confirm:' ) ) . toBeNull ( ) ;
const nextButton = getByText ( 'Next' ) ;
fireEvent . click ( nextButton ) ;
2023-03-14 18:29:31 +01:00
expect ( queryByText ( 'Prior to clicking confirm:' ) ) . toBeNull ( ) ;
} ) ;
2023-04-13 19:24:33 +02:00
it ( 'should render security provider response if transaction is malicious' , ( ) => {
const securityProviderResponse = {
flagAsDangerous : 1 ,
reason :
'This has been flagged as potentially suspicious. If you sign, you could lose access to all of your NFTs and any funds or other assets in your wallet.' ,
reason _header : 'Warning' ,
} ;
const { getByText } = renderWithProvider (
< TokenAllowance
{ ... props }
txData = { { ... props . txData , securityProviderResponse } }
/ > ,
store ,
) ;
expect ( getByText ( securityProviderResponse . reason ) ) . toBeInTheDocument ( ) ;
} ) ;
2023-04-28 09:59:53 +02:00
it ( 'should render from account name in header' , ( ) => {
const { getByText } = renderWithProvider (
< TokenAllowance { ... props } / > ,
store ,
) ;
expect ( getByText ( 'Account 1' ) ) . toBeInTheDocument ( ) ;
} ) ;
it ( 'should account name from transaction even if currently selected account is different' , ( ) => {
const newState = {
... state ,
metamask : {
... state . metamask ,
selectedAddress : '0xc42edfcc21ed14dda456aa0756c153f7985d8813' ,
} ,
} ;
2023-07-12 20:32:46 +02:00
const newStore = configureMockStore ( [ thunk ] ) ( newState ) ;
2023-04-28 09:59:53 +02:00
const { queryByText } = renderWithProvider (
< TokenAllowance { ... props } / > ,
newStore ,
) ;
expect ( queryByText ( 'Account 1' ) ) . toBeInTheDocument ( ) ;
expect ( queryByText ( 'Account 2' ) ) . not . toBeInTheDocument ( ) ;
} ) ;
2023-08-03 12:54:54 +02:00
it ( 'should display security alert if present' , ( ) => {
const { getByText } = renderWithProvider (
< TokenAllowance
{ ... props }
txData = { {
... props . txData ,
securityAlertResponse : {
resultType : 'Malicious' ,
reason : 'blur_farming' ,
description :
'A SetApprovalForAll request was made on {contract}. We found the operator {operator} to be malicious' ,
args : {
contract : '0xa7206d878c5c3871826dfdb42191c49b1d11f466' ,
operator : '0x92a3b9773b1763efa556f55ccbeb20441962d9b2' ,
} ,
} ,
} }
/ > ,
store ,
) ;
expect ( getByText ( 'This is a deceptive request' ) ) . toBeInTheDocument ( ) ;
} ) ;
2023-02-23 16:57:12 +01:00
} ) ;