1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-22 03:12:42 +02:00

Added customize tx nonce on ERC20 approve screen when feature is enabled (#17945)

This commit is contained in:
Vladimir Saric 2023-07-12 20:32:46 +02:00 committed by GitHub
parent 42c3a3958b
commit 36eaaa19ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 434 additions and 153 deletions

View File

@ -10,6 +10,7 @@
@import 'confirm-page-container/index';
@import 'confirm-data/index';
@import 'confirmation-warning-modal/index';
@import 'custom-nonce/index';
@import 'nfts-items/index';
@import 'nfts-tab/index';
@import 'nft-details/index';

View File

@ -0,0 +1,75 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { I18nContext } from '../../../../.storybook/i18n';
import { Box, ButtonLink, Text } from '../../component-library';
import {
AlignItems,
BorderRadius,
Display,
JustifyContent,
Size,
TextVariant,
} from '../../../helpers/constants/design-system';
export default function CustomNonce({
nextNonce,
customNonceValue,
showCustomizeNonceModal,
}) {
const t = useContext(I18nContext);
return (
<Box
display={Display.Flex}
marginBottom={4}
paddingTop={3}
paddingRight={3}
paddingBottom={4}
paddingLeft={3}
borderRadius={BorderRadius.MD}
alignItems={AlignItems.center}
className="custom-nonce__content"
>
<Box
className="custom-nonce__header"
display={Display.InlineFlex}
justifyContent={JustifyContent.flexStart}
alignItems={AlignItems.center}
>
<Text variant={TextVariant.bodySm} as="h6">
{t('nonce')}
</Text>
<ButtonLink
key="editCustomNonce"
size={Size.auto}
marginLeft={3}
onClick={() => showCustomizeNonceModal()}
>
{t('edit')}
</ButtonLink>
</Box>
<Text
className="custom-nonce__value"
variant={TextVariant.bodySmBold}
as="h6"
>
{customNonceValue || nextNonce}
</Text>
</Box>
);
}
CustomNonce.propTypes = {
/**
* Getting the next suggested nonce
*/
nextNonce: PropTypes.number,
/**
* Custom nonce value
*/
customNonceValue: PropTypes.string,
/**
* Function that is supposed to open the customized nonce modal
*/
showCustomizeNonceModal: PropTypes.func,
};

View File

@ -0,0 +1,65 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import { fireEvent } from '@testing-library/react';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import CustomNonce from './custom-nonce';
describe('CustomNonce', () => {
const store = configureMockStore()({});
let props = {};
beforeEach(() => {
props = {
nextNonce: 1,
customNonceValue: '',
showCustomizeNonceModal: jest.fn(),
};
});
it('should render CustomNonce component header', () => {
const { queryByText } = renderWithProvider(
<CustomNonce {...props} />,
store,
);
expect(queryByText('Nonce')).toBeInTheDocument();
expect(queryByText('Edit')).toBeInTheDocument();
});
it('should render CustomNonce component value when custom nonce value is a empty string', () => {
const { queryByText } = renderWithProvider(
<CustomNonce {...props} />,
store,
);
expect(queryByText('Nonce')).toBeInTheDocument();
expect(queryByText('Edit')).toBeInTheDocument();
expect(queryByText('1')).toBeInTheDocument();
});
it('should render CustomNonce component value when custom nonce value is edited', () => {
props.customNonceValue = '3';
const { queryByText } = renderWithProvider(
<CustomNonce {...props} />,
store,
);
expect(queryByText('Nonce')).toBeInTheDocument();
expect(queryByText('Edit')).toBeInTheDocument();
expect(queryByText('3')).toBeInTheDocument();
});
it('should render CustomNonce component to show customize nonce modal', () => {
const { queryByText, getByText } = renderWithProvider(
<CustomNonce {...props} />,
store,
);
const editButton = getByText('Edit');
expect(queryByText('Nonce')).toBeInTheDocument();
expect(editButton).toBeInTheDocument();
expect(queryByText('1')).toBeInTheDocument();
fireEvent.click(editButton);
expect(props.showCustomizeNonceModal).toHaveBeenCalledTimes(1);
});
});

View File

@ -0,0 +1 @@
export { default } from './custom-nonce';

View File

@ -0,0 +1,15 @@
.custom-nonce {
&__content {
height: 49px;
border: 1px solid var(--color-border-muted);
box-sizing: border-box;
}
&__header {
flex: 1;
}
&__value {
flex: 0;
}
}

View File

@ -15,7 +15,7 @@ exports[`Customize Nonce should match snapshot 1`] = `
class="customize-nonce-modal__main-header"
>
<h4
class="box mm-text customize-nonce-modal__main-title mm-text--heading-sm mm-text--font-weight-bold box--flex-direction-row box--color-text-default"
class="box mm-text customize-nonce-modal__main-title mm-text--heading-sm box--flex-direction-row box--color-text-default"
>
Edit nonce
</h4>
@ -30,14 +30,14 @@ exports[`Customize Nonce should match snapshot 1`] = `
</button>
</div>
<div
class="box box--margin-top-2 box--display-inline-flex box--flex-direction-row box--align-items-center"
class="mm-box mm-box--margin-top-2 mm-box--display-inline-flex mm-box--align-items-center"
>
<h6
class="box mm-text mm-text--body-sm mm-text--font-weight-normal box--flex-direction-row box--color-text-default"
class="box mm-text mm-text--body-md box--flex-direction-row box--color-text-default"
>
This is an advanced feature, use cautiously.
<a
class="button btn-link customize-nonce-modal__link"
class="box mm-text mm-button-base customize-nonce-modal__link mm-button-link mm-button-link--size-auto mm-text--body-md box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-default box--background-color-transparent"
href="https://metamask.zendesk.com/hc/en-us/articles/7417499333531-How-to-customize-a-transaction-nonce"
rel="noopener noreferrer"
target="_blank"
@ -47,28 +47,25 @@ exports[`Customize Nonce should match snapshot 1`] = `
</h6>
</div>
<div
class="box box--margin-top-3 box--flex-direction-row"
class="mm-box mm-box--margin-top-4"
>
<div
class="box box--display-flex box--flex-direction-row box--align-items-center"
class="mm-box mm-box--display-flex mm-box--align-items-center"
>
<h6
boxprops="[object Object]"
class="box mm-text mm-text--body-sm mm-text--font-weight-bold box--flex-direction-row box--color-text-default"
class="box mm-text mm-text--body-md-bold box--flex-direction-row box--width-5/6 box--color-text-default"
>
Edit nonce
</h6>
<div
class="box box--flex-direction-row box--width-1/6"
class="mm-box mm-box--width-1/6"
>
<a
class="button btn-link customize-nonce-modal__reset"
<button
class="box mm-text mm-button-base customize-nonce-modal__reset mm-button-link mm-button-link--size-auto mm-text--body-md box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-default box--background-color-transparent"
data-testid="customize-nonce-reset"
role="button"
tabindex="0"
>
Reset
</a>
</button>
</div>
</div>
<div

View File

@ -2,21 +2,20 @@ import React, { useState } from 'react';
import PropTypes from 'prop-types';
import Modal from '../../modal';
import TextField from '../../../ui/text-field';
import Button from '../../../ui/button';
import {
TextVariant,
FontWeight,
AlignItems,
BLOCK_SIZES,
DISPLAY,
BlockSize,
Display,
} from '../../../../helpers/constants/design-system';
import Box from '../../../ui/box';
import withModalProps from '../../../../helpers/higher-order-components/with-modal-props';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import ZENDESK_URLS from '../../../../helpers/constants/zendesk-url';
import {
Box,
ButtonIcon,
ButtonIconSize,
ButtonLink,
IconName,
Text,
} from '../../../component-library';
@ -54,7 +53,6 @@ const CustomizeNonce = ({
className="customize-nonce-modal__main-title"
variant={TextVariant.headingSm}
as="h4"
fontWeight={FontWeight.Bold}
>
{t('editNonceField')}
</Text>
@ -68,39 +66,32 @@ const CustomizeNonce = ({
</div>
<Box
marginTop={2}
display={DISPLAY.INLINE_FLEX}
display={Display.InlineFlex}
alignItems={AlignItems.center}
>
<Text
variant={TextVariant.bodySm}
as="h6"
fontWeight={FontWeight.Normal}
>
<Text variant={TextVariant.bodyMd} as="h6">
{t('editNonceMessage')}
<Button
type="link"
<ButtonLink
className="customize-nonce-modal__link"
rel="noopener noreferrer"
target="_blank"
href={ZENDESK_URLS.CUSTOMIZE_NONCE}
>
{t('learnMoreUpperCase')}
</Button>
</ButtonLink>
</Text>
</Box>
<Box marginTop={3}>
<Box alignItems={AlignItems.center} display={DISPLAY.FLEX}>
<Box marginTop={4}>
<Box alignItems={AlignItems.center} display={Display.Flex}>
<Text
variant={TextVariant.bodySm}
variant={TextVariant.bodyMdBold}
as="h6"
fontWeight={FontWeight.Bold}
boxProps={{ width: BLOCK_SIZES.FIVE_SIXTHS }}
width={BlockSize.FiveSixths}
>
{t('editNonceField')}
</Text>
<Box width={BLOCK_SIZES.ONE_SIXTH}>
<Button
type="link"
<Box width={BlockSize.OneSixth}>
<ButtonLink
className="customize-nonce-modal__reset"
data-testid="customize-nonce-reset"
onClick={() => {
@ -108,7 +99,7 @@ const CustomizeNonce = ({
}}
>
{t('reset')}
</Button>
</ButtonLink>
</Box>
</Box>
<div className="customize-nonce-modal__input">

View File

@ -31,7 +31,7 @@
}
& &__reset {
@include H7;
@include H6;
}
&__input {

View File

@ -129,26 +129,24 @@ exports[`ConfirmApproveContent Component should render Confirm approve page corr
class="confirm-approve-content__card-content"
>
<div
class="confirm-approve-content__custom-nonce-content"
class="mm-box custom-nonce__content mm-box--margin-bottom-4 mm-box--padding-top-3 mm-box--padding-right-3 mm-box--padding-bottom-4 mm-box--padding-left-3 mm-box--display-flex mm-box--align-items-center mm-box--rounded-md"
>
<div
class="box confirm-approve-content__custom-nonce-header box--flex-direction-row box--justify-content-flex-start box--display-flex"
class="mm-box custom-nonce__header mm-box--display-inline-flex mm-box--justify-content-flex-start mm-box--align-items-center"
>
<h6
class="box mm-text mm-text--body-sm box--flex-direction-row box--color-text-default"
>
Nonce
</h6>
<a
class="button btn-link confirm-approve-content__custom-nonce-edit"
role="button"
tabindex="0"
<button
class="box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md box--margin-left-3 box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-default box--background-color-transparent"
>
Edit
</a>
</button>
</div>
<h6
class="box mm-text confirm-approve-content__custom-nonce-value mm-text--body-sm-bold box--flex-direction-row box--color-text-default"
class="box mm-text custom-nonce__value mm-text--body-sm-bold box--flex-direction-row box--color-text-default"
>
2
</h6>
@ -316,26 +314,24 @@ exports[`ConfirmApproveContent Component should render Confirm approve page corr
class="confirm-approve-content__card-content"
>
<div
class="confirm-approve-content__custom-nonce-content"
class="mm-box custom-nonce__content mm-box--margin-bottom-4 mm-box--padding-top-3 mm-box--padding-right-3 mm-box--padding-bottom-4 mm-box--padding-left-3 mm-box--display-flex mm-box--align-items-center mm-box--rounded-md"
>
<div
class="box confirm-approve-content__custom-nonce-header box--flex-direction-row box--justify-content-flex-start box--display-flex"
class="mm-box custom-nonce__header mm-box--display-inline-flex mm-box--justify-content-flex-start mm-box--align-items-center"
>
<h6
class="box mm-text mm-text--body-sm box--flex-direction-row box--color-text-default"
>
Nonce
</h6>
<a
class="button btn-link confirm-approve-content__custom-nonce-edit"
role="button"
tabindex="0"
<button
class="box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md box--margin-left-3 box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-default box--background-color-transparent"
>
Edit
</a>
</button>
</div>
<h6
class="box mm-text confirm-approve-content__custom-nonce-value mm-text--body-sm-bold box--flex-direction-row box--color-text-default"
class="box mm-text custom-nonce__value mm-text--body-sm-bold box--flex-direction-row box--color-text-default"
>
2
</h6>
@ -503,26 +499,24 @@ exports[`ConfirmApproveContent Component should render Confirm approve page corr
class="confirm-approve-content__card-content"
>
<div
class="confirm-approve-content__custom-nonce-content"
class="mm-box custom-nonce__content mm-box--margin-bottom-4 mm-box--padding-top-3 mm-box--padding-right-3 mm-box--padding-bottom-4 mm-box--padding-left-3 mm-box--display-flex mm-box--align-items-center mm-box--rounded-md"
>
<div
class="box confirm-approve-content__custom-nonce-header box--flex-direction-row box--justify-content-flex-start box--display-flex"
class="mm-box custom-nonce__header mm-box--display-inline-flex mm-box--justify-content-flex-start mm-box--align-items-center"
>
<h6
class="box mm-text mm-text--body-sm box--flex-direction-row box--color-text-default"
>
Nonce
</h6>
<a
class="button btn-link confirm-approve-content__custom-nonce-edit"
role="button"
tabindex="0"
<button
class="box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md box--margin-left-3 box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-default box--background-color-transparent"
>
Edit
</a>
</button>
</div>
<h6
class="box mm-text confirm-approve-content__custom-nonce-value mm-text--body-sm-bold box--flex-direction-row box--color-text-default"
class="box mm-text custom-nonce__value mm-text--body-sm-bold box--flex-direction-row box--color-text-default"
>
2
</h6>
@ -690,26 +684,24 @@ exports[`ConfirmApproveContent Component should render Confirm approve page corr
class="confirm-approve-content__card-content"
>
<div
class="confirm-approve-content__custom-nonce-content"
class="mm-box custom-nonce__content mm-box--margin-bottom-4 mm-box--padding-top-3 mm-box--padding-right-3 mm-box--padding-bottom-4 mm-box--padding-left-3 mm-box--display-flex mm-box--align-items-center mm-box--rounded-md"
>
<div
class="box confirm-approve-content__custom-nonce-header box--flex-direction-row box--justify-content-flex-start box--display-flex"
class="mm-box custom-nonce__header mm-box--display-inline-flex mm-box--justify-content-flex-start mm-box--align-items-center"
>
<h6
class="box mm-text mm-text--body-sm box--flex-direction-row box--color-text-default"
>
Nonce
</h6>
<a
class="button btn-link confirm-approve-content__custom-nonce-edit"
role="button"
tabindex="0"
<button
class="box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md box--margin-left-3 box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-default box--background-color-transparent"
>
Edit
</a>
</button>
</div>
<h6
class="box mm-text confirm-approve-content__custom-nonce-value mm-text--body-sm-bold box--flex-direction-row box--color-text-default"
class="box mm-text custom-nonce__value mm-text--body-sm-bold box--flex-direction-row box--color-text-default"
>
2
</h6>

View File

@ -14,7 +14,6 @@ import MultiLayerFeeMessage from '../../../components/app/multilayer-fee-message
import SecurityProviderBannerMessage from '../../../components/app/security-provider-banner-message/security-provider-banner-message';
import {
BLOCK_SIZES,
JustifyContent,
DISPLAY,
TextColor,
IconColor,
@ -38,6 +37,7 @@ import TransactionDetailItem from '../../../components/app/transaction-detail-it
import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display';
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
import { ConfirmGasDisplay } from '../../../components/app/confirm-gas-display';
import CustomNonce from '../../../components/app/custom-nonce';
export default class ConfirmApproveContent extends Component {
static contextTypes = {
@ -351,55 +351,6 @@ export default class ConfirmApproveContent extends Component {
return null;
}
renderCustomNonceContent() {
const { t } = this.context;
const {
useNonceField,
customNonceValue,
updateCustomNonce,
getNextNonce,
nextNonce,
showCustomizeNonceModal,
} = this.props;
return (
<>
{useNonceField && (
<div className="confirm-approve-content__custom-nonce-content">
<Box
className="confirm-approve-content__custom-nonce-header"
justifyContent={JustifyContent.flexStart}
>
<Text variant={TextVariant.bodySm} as="h6">
{t('nonce')}
</Text>
<Button
type="link"
className="confirm-approve-content__custom-nonce-edit"
onClick={() =>
showCustomizeNonceModal({
nextNonce,
customNonceValue,
updateCustomNonce,
getNextNonce,
})
}
>
{t('edit')}
</Button>
</Box>
<Text
className="confirm-approve-content__custom-nonce-value"
variant={TextVariant.bodySmBold}
as="h6"
>
{customNonceValue || nextNonce}
</Text>
</div>
)}
</>
);
}
getTokenName() {
const { tokenId, assetName, assetStandard, tokenSymbol } = this.props;
const { t } = this.context;
@ -586,6 +537,11 @@ export default class ConfirmApproveContent extends Component {
userAcknowledgedGasMissing,
setUserAcknowledgedGasMissing,
renderSimulationFailureWarning,
nextNonce,
getNextNonce,
customNonceValue,
updateCustomNonce,
showCustomizeNonceModal,
} = this.props;
const { showFullTxDetails, setShowContractDetails } = this.state;
@ -709,7 +665,20 @@ export default class ConfirmApproveContent extends Component {
{useNonceField &&
this.renderApproveContentCard({
showHeader: false,
content: this.renderCustomNonceContent(),
content: (
<CustomNonce
nextNonce={nextNonce}
customNonceValue={customNonceValue}
showCustomizeNonceModal={() => {
showCustomizeNonceModal({
nextNonce,
customNonceValue,
updateCustomNonce,
getNextNonce,
});
}}
/>
),
useNonceField,
noBorder: !showFullTxDetails,
footer: (

View File

@ -385,33 +385,6 @@
width: 100%;
height: 30px;
}
&__custom-nonce-content {
display: flex;
height: 49px;
margin-top: 5px;
margin-bottom: 6px;
padding: 12px 12px 14px 12px;
border: 1px solid var(--color-border-muted);
box-sizing: border-box;
border-radius: 6px;
align-items: center;
}
&__custom-nonce-header {
flex: 1;
align-items: center;
}
&__custom-nonce-value {
flex: 0;
}
& &__custom-nonce-edit {
@include H7;
width: auto;
}
}
.confirm-approve-content--full {

View File

@ -205,6 +205,7 @@ export default function ConfirmApprove({
tokenSymbol={tokenSymbol}
decimals={decimals}
fromAddressIsLedger={fromAddressIsLedger}
warning={submitWarning}
/>
{showCustomizeGasPopover && !supportsEIP1559 && (
<EditGasPopover

View File

@ -190,6 +190,7 @@ exports[`TokenAllowancePage should match snapshot 1`] = `
</h6>
</div>
</div>
<div
class="box box--display-flex box--flex-direction-row box--justify-content-center"
>

View File

@ -38,4 +38,9 @@
&__full-tx-content {
max-width: 100%;
}
&__custom-nonce-warning {
width: 100%;
height: 30px;
}
}

View File

@ -1,4 +1,4 @@
import React, { useState, useContext } from 'react';
import React, { useState, useContext, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import PropTypes from 'prop-types';
@ -33,6 +33,8 @@ import {
getUnapprovedTransactions,
getUseCurrencyRateCheck,
getTargetAccountWithSendEtherInfo,
getCustomNonceValue,
getNextSuggestedNonce,
} from '../../selectors';
import { NETWORK_TO_NAME_MAP } from '../../../shared/constants/network';
import {
@ -40,6 +42,7 @@ import {
cancelTxs,
showModal,
updateAndApproveTx,
getNextNonce,
updateCustomNonce,
} from '../../store/actions';
import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck';
@ -63,6 +66,8 @@ import SimulationErrorMessage from '../../components/ui/simulation-error-message
import LedgerInstructionField from '../../components/app/ledger-instruction-field/ledger-instruction-field';
import SecurityProviderBannerMessage from '../../components/app/security-provider-banner-message/security-provider-banner-message';
import { Text, Icon, IconName } from '../../components/component-library';
import { ConfirmPageContainerWarning } from '../../components/app/confirm-page-container/confirm-page-container-content';
import CustomNonce from '../../components/app/custom-nonce';
const ALLOWED_HOSTS = ['portfolio.metamask.io'];
@ -91,6 +96,7 @@ export default function TokenAllowance({
toAddress,
tokenSymbol,
fromAddressIsLedger,
warning,
}) {
const t = useContext(I18nContext);
const dispatch = useDispatch();
@ -124,6 +130,8 @@ export default function TokenAllowance({
const unapprovedTxCount = useSelector(getUnapprovedTxCount);
const unapprovedTxs = useSelector(getUnapprovedTransactions);
const useCurrencyRateCheck = useSelector(getUseCurrencyRateCheck);
const nextNonce = useSelector(getNextSuggestedNonce);
const customNonceValue = useSelector(getCustomNonceValue);
const replaceCommaToDot = (inputValue) => {
return inputValue.replace(/,/gu, '.');
@ -175,7 +183,6 @@ export default function TokenAllowance({
const networkName =
NETWORK_TO_NAME_MAP[fullTxData.chainId] || networkIdentifier;
const customNonceValue = '';
const customNonceMerge = (transactionData) =>
customNonceValue
? {
@ -253,6 +260,39 @@ export default function TokenAllowance({
);
};
const handleNextNonce = () => {
dispatch(getNextNonce());
};
useEffect(() => {
handleNextNonce();
}, [dispatch]);
const handleUpdateCustomNonce = (value) => {
dispatch(updateCustomNonce(value));
};
const handleCustomizeNonceModal = (
/* eslint-disable no-shadow */
useNonceField,
nextNonce,
customNonceValue,
updateCustomNonce,
getNextNonce,
/* eslint-disable no-shadow */
) => {
dispatch(
showModal({
name: 'CUSTOMIZE_NONCE',
useNonceField,
nextNonce,
customNonceValue,
updateCustomNonce,
getNextNonce,
}),
);
};
const isEmpty = customSpendingCap === '';
const renderContractTokenValues = (
@ -317,6 +357,11 @@ export default function TokenAllowance({
accountAddress={userAddress}
chainId={fullTxData.chainId}
/>
{warning && (
<Box className="token-allowance-container__custom-nonce-warning">
<ConfirmPageContainerWarning warning={warning} />
</Box>
)}
<Box
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.ROW}
@ -459,6 +504,23 @@ export default function TokenAllowance({
/>
</Box>
)}
{useNonceField && (
<Box marginTop={4} marginRight={4} marginLeft={4}>
<CustomNonce
nextNonce={nextNonce}
customNonceValue={customNonceValue}
showCustomizeNonceModal={() =>
handleCustomizeNonceModal(
useNonceField,
nextNonce,
customNonceValue,
handleUpdateCustomNonce,
handleNextNonce,
)
}
/>
</Box>
)}
<Box
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.ROW}
@ -647,4 +709,8 @@ TokenAllowance.propTypes = {
* Whether the address sending the transaction is a ledger address
*/
fromAddressIsLedger: PropTypes.bool,
/**
* Customize nonce warning message
*/
warning: PropTypes.string,
};

View File

@ -1,6 +1,7 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import { act, fireEvent } from '@testing-library/react';
import thunk from 'redux-thunk';
import { renderWithProvider } from '../../../test/lib/render-helpers';
import { KeyringType } from '../../../shared/constants/keyring';
import TokenAllowance from './token-allowance';
@ -76,6 +77,8 @@ const state = {
accounts: ['0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'],
},
],
nextNonce: 1,
customNonceValue: '',
},
history: {
mostRecentOverviewPage: '/',
@ -88,6 +91,8 @@ const state = {
},
};
const mockShowModal = jest.fn();
jest.mock('../../store/actions', () => ({
disconnectGasFeeEstimatePoller: jest.fn(),
getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()),
@ -99,6 +104,8 @@ jest.mock('../../store/actions', () => ({
updateTransactionGasFees: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }),
updatePreviousGasParams: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }),
createTransactionEventFragment: jest.fn(),
getNextNonce: () => jest.fn(),
showModal: () => mockShowModal,
updateCustomNonce: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }),
estimateGas: jest.fn().mockImplementation(() => Promise.resolve()),
}));
@ -146,6 +153,8 @@ describe('TokenAllowancePage', () => {
currentTokenBalance: '10',
toAddress: '0x9bc5baf874d2da8d216ae9f137804184ee5afef4',
tokenSymbol: 'TST',
showCustomizeGasModal: jest.fn(),
warning: '',
txData: {
id: 3049568294499567,
time: 1664449552289,
@ -184,7 +193,7 @@ describe('TokenAllowancePage', () => {
let store;
beforeEach(() => {
store = configureMockStore()(state);
store = configureMockStore([thunk])(state);
});
it('should match snapshot', () => {
@ -212,6 +221,126 @@ describe('TokenAllowancePage', () => {
expect(onCloseBtn).toBeInTheDocument();
});
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);
});
it('should click View details and show function type', () => {
const { getByText } = renderWithProvider(
<TokenAllowance {...props} />,
@ -353,7 +482,7 @@ describe('TokenAllowancePage', () => {
selectedAddress: '0xc42edfcc21ed14dda456aa0756c153f7985d8813',
},
};
const newStore = configureMockStore()(newState);
const newStore = configureMockStore([thunk])(newState);
const { queryByText } = renderWithProvider(
<TokenAllowance {...props} />,
newStore,