diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 7c1644c7abda..bfb2ea999981 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1785,6 +1785,9 @@ "networkDetails": { "message": "Network Details" }, + "networkIsBusy": { + "message": "Network is busy. Gas prices are high and estimates are less accurate." + }, "networkName": { "message": "Network Name" }, diff --git a/shared/constants/gas.js b/shared/constants/gas.js index 6d92302af0e8..ae674e4a24e4 100644 --- a/shared/constants/gas.js +++ b/shared/constants/gas.js @@ -56,3 +56,15 @@ export const EDIT_GAS_MODES = { MODIFY_IN_PLACE: 'modify-in-place', SWAPS: 'swaps', }; + +/** + * Represents levels for `networkCongestion` (calculated along with gas fee + * estimates; represents a number between 0 and 1) that we use to render the + * network status slider on the send transaction screen and inform users when + * gas fees are high + */ +export const NETWORK_CONGESTION_THRESHOLDS = { + NOT_BUSY: 0, + STABLE: 0.33, + BUSY: 0.66, +}; diff --git a/ui/components/app/advanced-gas-controls/advanced-gas-controls.component.js b/ui/components/app/advanced-gas-controls/advanced-gas-controls.component.js index e13d293c86ac..62edad403a75 100644 --- a/ui/components/app/advanced-gas-controls/advanced-gas-controls.component.js +++ b/ui/components/app/advanced-gas-controls/advanced-gas-controls.component.js @@ -7,7 +7,7 @@ import FormField from '../../ui/form-field'; import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas'; import { getGasFormErrorText } from '../../../helpers/constants/gas'; import { getIsGasEstimatesLoading } from '../../../ducks/metamask/metamask'; -import { getNetworkSupportsSettingGasPrice } from '../../../selectors/selectors'; +import { getNetworkSupportsSettingGasPrice } from '../../../selectors'; export default function AdvancedGasControls({ gasEstimateType, diff --git a/ui/components/app/edit-gas-display/edit-gas-display.component.js b/ui/components/app/edit-gas-display/edit-gas-display.component.js index 99e7e17f6bff..c72d606c51f9 100644 --- a/ui/components/app/edit-gas-display/edit-gas-display.component.js +++ b/ui/components/app/edit-gas-display/edit-gas-display.component.js @@ -63,7 +63,6 @@ export default function EditGasDisplay({ estimatedMaximumFiat, dappSuggestedGasFeeAcknowledged, setDappSuggestedGasFeeAcknowledged, - warning, gasErrors, gasWarnings, onManualChange, @@ -72,6 +71,7 @@ export default function EditGasDisplay({ estimatesUnavailableWarning, hasGasErrors, txParamsHaveBeenCustomized, + isNetworkBusy, }) { const t = useContext(I18nContext); const scrollRef = useRef(null); @@ -93,7 +93,7 @@ export default function EditGasDisplay({ useLayoutEffect(() => { if (showAdvancedForm && scrollRef.current) { - scrollRef.current.scrollIntoView(); + scrollRef.current.scrollIntoView?.(); } }, [showAdvancedForm]); @@ -133,14 +133,6 @@ export default function EditGasDisplay({ return (
- {warning && !isGasEstimatesLoading && ( -
- -
- )} {showTopError && (
@@ -156,6 +148,16 @@ export default function EditGasDisplay({ />
)} + {isNetworkBusy ? ( +
+ +
+ ) : null} {mode === EDIT_GAS_MODES.SPEED_UP && (
, store); +} + +describe('EditGasDisplay', () => { + describe('if getIsNetworkBusy returns a truthy value', () => { + it('informs the user', () => { + render({ componentProps: { isNetworkBusy: true } }); + expect( + screen.getByText( + 'Network is busy. Gas prices are high and estimates are less accurate.', + ), + ).toBeInTheDocument(); + }); + }); + + describe('if getIsNetworkBusy does not return a truthy value', () => { + it('does not inform the user', () => { + render({ componentProps: { isNetworkBusy: false } }); + expect( + screen.queryByText( + 'Network is busy. Gas prices are high and estimates are less accurate.', + ), + ).not.toBeInTheDocument(); + }); + }); +}); diff --git a/ui/components/app/edit-gas-fee-popover/network-statistics/status-slider/status-slider.js b/ui/components/app/edit-gas-fee-popover/network-statistics/status-slider/status-slider.js index 7f4f34e77823..13987af3bc4d 100644 --- a/ui/components/app/edit-gas-fee-popover/network-statistics/status-slider/status-slider.js +++ b/ui/components/app/edit-gas-fee-popover/network-statistics/status-slider/status-slider.js @@ -1,5 +1,6 @@ import React from 'react'; +import { NETWORK_CONGESTION_THRESHOLDS } from '../../../../../../shared/constants/gas'; import { useGasFeeContext } from '../../../../../contexts/gasFee'; import I18nValue from '../../../../ui/i18n-value'; import { NetworkStabilityTooltip } from '../tooltips'; @@ -24,24 +25,24 @@ const determineStatusInfo = (givenNetworkCongestion) => { const color = GRADIENT_COLORS[colorIndex]; const sliderTickValue = colorIndex * 10; - if (networkCongestion <= 0.33) { + if (networkCongestion >= NETWORK_CONGESTION_THRESHOLDS.BUSY) { return { - statusLabel: 'notBusy', - tooltipLabel: 'lowLowercase', + statusLabel: 'busy', + tooltipLabel: 'highLowercase', color, sliderTickValue, }; - } else if (networkCongestion > 0.66) { + } else if (networkCongestion >= NETWORK_CONGESTION_THRESHOLDS.STABLE) { return { - statusLabel: 'busy', - tooltipLabel: 'highLowercase', + statusLabel: 'stable', + tooltipLabel: 'stableLowercase', color, sliderTickValue, }; } return { - statusLabel: 'stable', - tooltipLabel: 'stableLowercase', + statusLabel: 'notBusy', + tooltipLabel: 'lowLowercase', color, sliderTickValue, }; diff --git a/ui/components/app/edit-gas-fee-popover/network-statistics/status-slider/status-slider.test.js b/ui/components/app/edit-gas-fee-popover/network-statistics/status-slider/status-slider.test.js index 004838aa0d67..29940d981d88 100644 --- a/ui/components/app/edit-gas-fee-popover/network-statistics/status-slider/status-slider.test.js +++ b/ui/components/app/edit-gas-fee-popover/network-statistics/status-slider/status-slider.test.js @@ -20,31 +20,31 @@ const renderComponent = ({ networkCongestion }) => { describe('StatusSlider', () => { it('should show "Not busy" when networkCongestion is less than 0.33', () => { - const { queryByText } = renderComponent({ networkCongestion: 0.32 }); - expect(queryByText('Not busy')).toBeInTheDocument(); + const { getByText } = renderComponent({ networkCongestion: 0.32 }); + expect(getByText('Not busy')).toBeInTheDocument(); }); - it('should show "Not busy" when networkCongestion is 0.33', () => { - const { queryByText } = renderComponent({ networkCongestion: 0.33 }); - expect(queryByText('Not busy')).toBeInTheDocument(); + it('should show "Stable" when networkCongestion is 0.33', () => { + const { getByText } = renderComponent({ networkCongestion: 0.33 }); + expect(getByText('Stable')).toBeInTheDocument(); }); it('should show "Stable" when networkCongestion is between 0.33 and 0.66', () => { - const { queryByText } = renderComponent({ networkCongestion: 0.5 }); - expect(queryByText('Stable')).toBeInTheDocument(); + const { getByText } = renderComponent({ networkCongestion: 0.5 }); + expect(getByText('Stable')).toBeInTheDocument(); }); - it('should show "Stable" when networkCongestion is 0.66', () => { - const { queryByText } = renderComponent({ networkCongestion: 0.66 }); - expect(queryByText('Stable')).toBeInTheDocument(); + it('should show "Busy" when networkCongestion is 0.66', () => { + const { getByText } = renderComponent({ networkCongestion: 0.66 }); + expect(getByText('Busy')).toBeInTheDocument(); }); it('should show "Busy" when networkCongestion is greater than 0.66', () => { - const { queryByText } = renderComponent({ networkCongestion: 0.67 }); - expect(queryByText('Busy')).toBeInTheDocument(); + const { getByText } = renderComponent({ networkCongestion: 0.67 }); + expect(getByText('Busy')).toBeInTheDocument(); }); - it('should show "Stable" if networkCongestion has not been set yet', () => { + it('should show "Stable" if networkCongestion is not available yet', () => { const { getByText } = renderComponent({}); expect(getByText('Stable')).toBeInTheDocument(); }); diff --git a/ui/components/app/edit-gas-popover/edit-gas-popover.component.js b/ui/components/app/edit-gas-popover/edit-gas-popover.component.js index 314627cece19..d66f13af69d3 100644 --- a/ui/components/app/edit-gas-popover/edit-gas-popover.component.js +++ b/ui/components/app/edit-gas-popover/edit-gas-popover.component.js @@ -61,8 +61,6 @@ export default function EditGasPopover({ supportsEIP1559; const [showEducationContent, setShowEducationContent] = useState(false); - const [warning] = useState(null); - const [ dappSuggestedGasFeeAcknowledged, setDappSuggestedGasFeeAcknowledged, @@ -109,6 +107,7 @@ export default function EditGasPopover({ balanceError, estimatesUnavailableWarning, estimatedBaseFee, + isNetworkBusy, } = useGasFeeInputs( defaultEstimateToUse, updatedTransaction, @@ -264,7 +263,6 @@ export default function EditGasPopover({ {process.env.IN_TEST ? null : } diff --git a/ui/ducks/metamask/metamask.js b/ui/ducks/metamask/metamask.js index 99d9b7d9f75d..09635ae045f5 100644 --- a/ui/ducks/metamask/metamask.js +++ b/ui/ducks/metamask/metamask.js @@ -1,6 +1,10 @@ import { addHexPrefix, isHexString, stripHexPrefix } from 'ethereumjs-util'; import * as actionConstants from '../../store/actionConstants'; import { ALERT_TYPES } from '../../../shared/constants/alerts'; +import { + GAS_ESTIMATE_TYPES, + NETWORK_CONGESTION_THRESHOLDS, +} from '../../../shared/constants/gas'; import { NETWORK_TYPE_RPC } from '../../../shared/constants/network'; import { accountsWithSendEtherInfoSelector, @@ -11,7 +15,7 @@ import { updateTransaction } from '../../store/actions'; import { setCustomGasLimit, setCustomGasPrice } from '../gas/gas.duck'; import { decGWEIToHexWEI } from '../../helpers/utils/conversions.util'; import { isEqualCaseInsensitive } from '../../helpers/utils/util'; -import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas'; + import { KEYRING_TYPES } from '../../../shared/constants/hardware-wallets'; export default function reduceMetamask(state = {}, action) { @@ -361,6 +365,13 @@ export function getIsGasEstimatesLoading(state) { return isGasEstimatesLoading; } +export function getIsNetworkBusy(state) { + const gasFeeEstimates = getGasFeeEstimates(state); + return ( + gasFeeEstimates?.networkCongestion >= NETWORK_CONGESTION_THRESHOLDS.BUSY + ); +} + export function getCompletedOnboarding(state) { return state.metamask.completedOnboarding; } diff --git a/ui/ducks/metamask/metamask.test.js b/ui/ducks/metamask/metamask.test.js index 0c75e7f47ff7..b14c4ed5c199 100644 --- a/ui/ducks/metamask/metamask.test.js +++ b/ui/ducks/metamask/metamask.test.js @@ -3,6 +3,7 @@ import * as actionConstants from '../../store/actionConstants'; import reduceMetamask, { getBlockGasLimit, getConversionRate, + getIsNetworkBusy, getNativeCurrency, getSendHexDataFeatureFlagState, getSendToAccounts, @@ -414,4 +415,30 @@ describe('MetaMask Reducers', () => { ).toStrictEqual(false); }); }); + + describe('getIsNetworkBusy', () => { + it('should return true if state.metamask.gasFeeEstimates.networkCongestion is over the "busy" threshold', () => { + expect( + getIsNetworkBusy({ + metamask: { gasFeeEstimates: { networkCongestion: 0.67 } }, + }), + ).toBe(true); + }); + + it('should return true if state.metamask.gasFeeEstimates.networkCongestion is right at the "busy" threshold', () => { + expect( + getIsNetworkBusy({ + metamask: { gasFeeEstimates: { networkCongestion: 0.66 } }, + }), + ).toBe(true); + }); + + it('should return false if state.metamask.gasFeeEstimates.networkCongestion is not over the "busy" threshold', () => { + expect( + getIsNetworkBusy({ + metamask: { gasFeeEstimates: { networkCongestion: 0.65 } }, + }), + ).toBe(false); + }); + }); }); diff --git a/ui/hooks/gasFeeInput/useGasFeeInputs.js b/ui/hooks/gasFeeInput/useGasFeeInputs.js index ade4f0382e2d..63b5b3fe6406 100644 --- a/ui/hooks/gasFeeInput/useGasFeeInputs.js +++ b/ui/hooks/gasFeeInput/useGasFeeInputs.js @@ -111,6 +111,7 @@ export function useGasFeeInputs( gasFeeEstimates, isGasEstimatesLoading, estimatedGasFeeTimeBounds, + isNetworkBusy, } = useGasFeeEstimates(); const userPrefersAdvancedGas = useSelector(getAdvancedInlineGasShown); @@ -342,6 +343,7 @@ export function useGasFeeInputs( gasFeeEstimates, gasEstimateType, estimatedGasFeeTimeBounds, + isNetworkBusy, onManualChange, estimatedBaseFee, // error and warnings diff --git a/ui/hooks/useGasFeeEstimates.js b/ui/hooks/useGasFeeEstimates.js index 236e092d5e79..849d83871810 100644 --- a/ui/hooks/useGasFeeEstimates.js +++ b/ui/hooks/useGasFeeEstimates.js @@ -5,6 +5,7 @@ import { getGasEstimateType, getGasFeeEstimates, getIsGasEstimatesLoading, + getIsNetworkBusy, } from '../ducks/metamask/metamask'; import { useSafeGasEstimatePolling } from './useSafeGasEstimatePolling'; @@ -38,6 +39,7 @@ export function useGasFeeEstimates() { shallowEqual, ); const isGasEstimatesLoading = useSelector(getIsGasEstimatesLoading); + const isNetworkBusy = useSelector(getIsNetworkBusy); useSafeGasEstimatePolling(); return { @@ -45,5 +47,6 @@ export function useGasFeeEstimates() { gasEstimateType, estimatedGasFeeTimeBounds, isGasEstimatesLoading, + isNetworkBusy, }; } diff --git a/ui/pages/confirm-transaction-base/transaction-alerts/transaction-alerts.js b/ui/pages/confirm-transaction-base/transaction-alerts/transaction-alerts.js index f3eab09ba999..c998daa862b1 100644 --- a/ui/pages/confirm-transaction-base/transaction-alerts/transaction-alerts.js +++ b/ui/pages/confirm-transaction-base/transaction-alerts/transaction-alerts.js @@ -22,6 +22,7 @@ const TransactionAlerts = ({ estimateUsed, hasSimulationError, supportsEIP1559V2, + isNetworkBusy, } = useGasFeeContext(); const pendingTransactions = useSelector(submittedPendingTransactionsSelector); const t = useI18nContext(); @@ -107,6 +108,13 @@ const TransactionAlerts = ({ type="warning" /> )} + {isNetworkBusy ? ( + } + iconFillColor="#f8c000" + type="warning" + /> + ) : null}
); }; diff --git a/ui/pages/confirm-transaction-base/transaction-alerts/transaction-alerts.test.js b/ui/pages/confirm-transaction-base/transaction-alerts/transaction-alerts.test.js index 555a091a4f05..038e9c3d3a29 100644 --- a/ui/pages/confirm-transaction-base/transaction-alerts/transaction-alerts.test.js +++ b/ui/pages/confirm-transaction-base/transaction-alerts/transaction-alerts.test.js @@ -1,172 +1,307 @@ import React from 'react'; -import { fireEvent, screen } from '@testing-library/react'; - -import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas'; -import { - TRANSACTION_ENVELOPE_TYPES, - TRANSACTION_STATUSES, -} from '../../../../shared/constants/transaction'; -import { renderWithProvider } from '../../../../test/lib/render-helpers'; -import mockEstimates from '../../../../test/data/mock-estimates.json'; -import mockState from '../../../../test/data/mock-state.json'; -import { GasFeeContextProvider } from '../../../contexts/gasFee'; +import { fireEvent } from '@testing-library/react'; +import { renderWithProvider } from '../../../../test/jest'; +import { submittedPendingTransactionsSelector } from '../../../selectors/transactions'; +import { useGasFeeContext } from '../../../contexts/gasFee'; import configureStore from '../../../store/store'; - import TransactionAlerts from './transaction-alerts'; -jest.mock('../../../store/actions', () => ({ - disconnectGasFeeEstimatePoller: jest.fn(), - getGasFeeEstimatesAndStartPolling: jest - .fn() - .mockImplementation(() => Promise.resolve()), - addPollingTokenToAppState: jest.fn(), -})); - -const render = ({ componentProps, transactionProps, state }) => { - const store = configureStore({ - metamask: { - ...mockState.metamask, - accounts: { - [mockState.metamask.selectedAddress]: { - address: mockState.metamask.selectedAddress, - balance: '0x1F4', - }, - }, - gasFeeEstimates: mockEstimates[GAS_ESTIMATE_TYPES.FEE_MARKET], - ...state, - }, - }); +jest.mock('../../../selectors/transactions', () => { + return { + ...jest.requireActual('../../../selectors/transactions'), + submittedPendingTransactionsSelector: jest.fn(), + }; +}); - return renderWithProvider( - - - , - store, +jest.mock('../../../contexts/gasFee'); + +function render({ + componentProps = {}, + useGasFeeContextValue = {}, + submittedPendingTransactionsSelectorValue = null, +}) { + useGasFeeContext.mockReturnValue(useGasFeeContextValue); + submittedPendingTransactionsSelector.mockReturnValue( + submittedPendingTransactionsSelectorValue, ); -}; + const store = configureStore({}); + return renderWithProvider(, store); +} describe('TransactionAlerts', () => { - beforeEach(() => { - process.env.EIP_1559_V2 = true; - }); + describe('when supportsEIP1559V2 from useGasFeeContext is truthy', () => { + describe('if hasSimulationError from useGasFeeContext is true', () => { + it('informs the user that a simulation of the transaction failed', () => { + const { getByText } = render({ + useGasFeeContextValue: { + supportsEIP1559V2: true, + hasSimulationError: true, + }, + }); - afterEach(() => { - process.env.EIP_1559_V2 = false; - }); + expect( + getByText( + 'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.', + ), + ).toBeInTheDocument(); + }); - it('should returning warning message for low gas estimate', () => { - render({ transactionProps: { userFeeLevel: 'low' } }); - expect( - document.getElementsByClassName('actionable-message--warning'), - ).toHaveLength(1); - }); + describe('if the user has not acknowledged the failure', () => { + it('offers the user an option to bypass the warning', () => { + const { getByText } = render({ + useGasFeeContextValue: { + supportsEIP1559V2: true, + hasSimulationError: true, + }, + }); + expect(getByText('I want to proceed anyway')).toBeInTheDocument(); + }); - it('should return null for gas estimate other than low', () => { - render({ transactionProps: { userFeeLevel: 'high' } }); - expect( - document.getElementsByClassName('actionable-message--warning'), - ).toHaveLength(0); - }); + it('calls setUserAcknowledgedGasMissing if the user bypasses the warning', () => { + const setUserAcknowledgedGasMissing = jest.fn(); + const { getByText } = render({ + useGasFeeContextValue: { + supportsEIP1559V2: true, + hasSimulationError: true, + }, + componentProps: { setUserAcknowledgedGasMissing }, + }); + fireEvent.click(getByText('I want to proceed anyway')); + expect(setUserAcknowledgedGasMissing).toHaveBeenCalled(); + }); + }); - it('should not show insufficient balance message if transaction value is less than balance', () => { - render({ - transactionProps: { - userFeeLevel: 'high', - txParams: { value: '0x64' }, - }, + describe('if the user has already acknowledged the failure', () => { + it('does not offer the user an option to bypass the warning', () => { + const { queryByText } = render({ + useGasFeeContextValue: { + supportsEIP1559V2: true, + hasSimulationError: true, + }, + componentProps: { userAcknowledgedGasMissing: true }, + }); + expect( + queryByText('I want to proceed anyway'), + ).not.toBeInTheDocument(); + }); + }); }); - expect(screen.queryByText('Insufficient funds.')).not.toBeInTheDocument(); - }); - it('should show insufficient balance message if transaction value is more than balance', () => { - render({ - transactionProps: { - userFeeLevel: 'high', - txParams: { value: '0x5208' }, - }, + describe('if hasSimulationError from useGasFeeContext is falsy', () => { + it('does not inform the user that a simulation of the transaction failed', () => { + const { queryByText } = render({ + useGasFeeContextValue: { + supportsEIP1559V2: true, + }, + }); + expect( + queryByText( + 'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.', + ), + ).not.toBeInTheDocument(); + }); }); - expect(screen.queryByText('Insufficient funds.')).toBeInTheDocument(); - }); - it('should show pending transaction message if there are >= 1 pending transactions', () => { - render({ - state: { - currentNetworkTxList: [ - { - id: 0, - time: 0, - txParams: { - from: mockState.metamask.selectedAddress, - to: '0xRecipient', - }, - status: TRANSACTION_STATUSES.SUBMITTED, + describe('if the length of pendingTransactions is 1', () => { + it('informs the user that they have a pending transaction', () => { + const { getByText } = render({ + useGasFeeContextValue: { supportsEIP1559V2: true }, + submittedPendingTransactionsSelectorValue: [{ some: 'transaction' }], + }); + expect( + getByText('You have (1) pending transaction.'), + ).toBeInTheDocument(); + }); + }); + + describe('if the length of pendingTransactions is more than 1', () => { + it('informs the user that they have pending transactions', () => { + const { getByText } = render({ + useGasFeeContextValue: { supportsEIP1559V2: true }, + submittedPendingTransactionsSelectorValue: [ + { some: 'transaction' }, + { some: 'transaction' }, + ], + }); + expect( + getByText('You have (2) pending transactions.'), + ).toBeInTheDocument(); + }); + }); + + describe('if the length of pendingTransactions is 0', () => { + it('does not inform the user that they have pending transactions', () => { + const { queryByText } = render({ + useGasFeeContextValue: { supportsEIP1559V2: true }, + submittedPendingTransactionsSelectorValue: [], + }); + expect( + queryByText('You have (0) pending transactions.'), + ).not.toBeInTheDocument(); + }); + }); + + describe('if balanceError from useGasFeeContext is true', () => { + it('informs the user that they have insufficient funds', () => { + const { getByText } = render({ + useGasFeeContextValue: { + supportsEIP1559V2: true, + balanceError: true, + }, + }); + expect(getByText('Insufficient funds.')).toBeInTheDocument(); + }); + }); + + describe('if balanceError from useGasFeeContext is falsy', () => { + it('does not inform the user that they have insufficient funds', () => { + const { queryByText } = render({ + useGasFeeContextValue: { + supportsEIP1559V2: true, + balanceError: false, }, - ], - }, + }); + expect(queryByText('Insufficient funds.')).not.toBeInTheDocument(); + }); + }); + + describe('if estimateUsed from useGasFeeContext is "low"', () => { + it('informs the user that the current transaction is queued', () => { + const { getByText } = render({ + useGasFeeContextValue: { + supportsEIP1559V2: true, + estimateUsed: 'low', + }, + }); + expect( + getByText( + 'Future transactions will queue after this one. This price was last seen was some time ago.', + ), + ).toBeInTheDocument(); + }); + }); + + describe('if estimateUsed from useGasFeeContext is not "low"', () => { + it('does not inform the user that the current transaction is queued', () => { + const { queryByText } = render({ + useGasFeeContextValue: { + supportsEIP1559V2: true, + estimateUsed: 'something_else', + }, + }); + expect( + queryByText( + 'Future transactions will queue after this one. This price was last seen was some time ago.', + ), + ).not.toBeInTheDocument(); + }); + }); + + describe('if isNetworkBusy from useGasFeeContext is truthy', () => { + it('informs the user that the network is busy', () => { + const { getByText } = render({ + useGasFeeContextValue: { + supportsEIP1559V2: true, + isNetworkBusy: true, + }, + }); + expect( + getByText( + 'Network is busy. Gas prices are high and estimates are less accurate.', + ), + ).toBeInTheDocument(); + }); + }); + + describe('if isNetworkBusy from useGasFeeContext is falsy', () => { + it('does not inform the user that the network is busy', () => { + const { queryByText } = render({ + useGasFeeContextValue: { + supportsEIP1559V2: true, + isNetworkBusy: false, + }, + }); + expect( + queryByText( + 'Network is busy. Gas prices are high and estimates are less accurate.', + ), + ).not.toBeInTheDocument(); + }); }); - expect( - screen.queryByText('You have (1) pending transaction.'), - ).toBeInTheDocument(); }); - describe('SimulationError Message', () => { - it('should show simulation error message along with option to proceed anyway if transaction.simulationFails is true', () => { - render({ transactionProps: { simulationFails: true } }); - expect( - screen.queryByText( - 'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.', - ), - ).toBeInTheDocument(); - expect( - screen.queryByText('I want to proceed anyway'), - ).toBeInTheDocument(); - }); - - it('should not show options to acknowledge gas-missing warning if component prop userAcknowledgedGasMissing is already true', () => { - render({ - componentProps: { - userAcknowledgedGasMissing: true, - }, - transactionProps: { simulationFails: true }, - }); - expect( - screen.queryByText( - 'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.', - ), - ).toBeInTheDocument(); - expect( - screen.queryByText('I want to proceed anyway'), - ).not.toBeInTheDocument(); - }); - - it('should call prop setUserAcknowledgedGasMissing if option to acknowledge gas-missing warning is clicked', () => { - const setUserAcknowledgedGasMissing = jest.fn(); - render({ - componentProps: { - setUserAcknowledgedGasMissing, - }, - transactionProps: { simulationFails: true }, - }); - fireEvent.click(screen.queryByText('I want to proceed anyway')); - expect(setUserAcknowledgedGasMissing).toHaveBeenCalledTimes(1); - }); - - it('should return null for legacy transactions', () => { - const { container } = render({ - transactionProps: { - txParams: { - type: TRANSACTION_ENVELOPE_TYPES.LEGACY, + describe('when supportsEIP1559V2 from useGasFeeContext is falsy', () => { + describe('if hasSimulationError from useGasFeeContext is true', () => { + it('does not inform the user that a simulation of the transaction failed', () => { + const { queryByText } = render({ + useGasFeeContextValue: { + supportsEIP1559V2: false, + hasSimulationError: true, + }, + }); + + expect( + queryByText( + 'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.', + ), + ).not.toBeInTheDocument(); + }); + }); + + describe('if the length of pendingTransactions is at least 1', () => { + it('informs the user that they have a pending transaction', () => { + const { queryByText } = render({ + useGasFeeContextValue: { supportsEIP1559V2: false }, + submittedPendingTransactionsSelectorValue: [{ some: 'transaction' }], + }); + expect( + queryByText('You have (1) pending transaction.'), + ).not.toBeInTheDocument(); + }); + }); + + describe('if balanceError from useGasFeeContext is true', () => { + it('informs the user that they have insufficient funds', () => { + const { queryByText } = render({ + useGasFeeContextValue: { + supportsEIP1559V2: false, + balanceError: true, + }, + }); + expect(queryByText('Insufficient funds.')).not.toBeInTheDocument(); + }); + }); + + describe('if estimateUsed from useGasFeeContext is "low"', () => { + it('informs the user that the current transaction is queued', () => { + const { queryByText } = render({ + useGasFeeContextValue: { + supportsEIP1559V2: false, + estimateUsed: 'low', + }, + }); + expect( + queryByText( + 'Future transactions will queue after this one. This price was last seen was some time ago.', + ), + ).not.toBeInTheDocument(); + }); + }); + + describe('if isNetworkBusy from useGasFeeContext is truthy', () => { + it('does not inform the user that the network is busy', () => { + const { queryByText } = render({ + useGasFeeContextValue: { + supportsEIP1559V2: false, + isNetworkBusy: true, }, - }, + }); + expect( + queryByText( + 'Network is busy. Gas prices are high and estimates are less accurate.', + ), + ).not.toBeInTheDocument(); }); - expect(container.firstChild).toBeNull(); }); }); }); diff --git a/ui/selectors/transactions.js b/ui/selectors/transactions.js index 55cb4d67597c..77980087baec 100644 --- a/ui/selectors/transactions.js +++ b/ui/selectors/transactions.js @@ -10,8 +10,11 @@ import { TRANSACTION_TYPES, } from '../../shared/constants/transaction'; import { transactionMatchesNetwork } from '../../shared/modules/transaction.utils'; -import { getCurrentChainId, deprecatedGetCurrentNetworkId } from './selectors'; -import { getSelectedAddress } from '.'; +import { + getCurrentChainId, + deprecatedGetCurrentNetworkId, + getSelectedAddress, +} from './selectors'; export const incomingTxListSelector = (state) => { const { showIncomingTransactions } = state.metamask.featureFlags;