mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-26 12:29:06 +01:00
Restore heartbeat to transaction confirmation, use isGasEstimatesLoading more broadly (#11781)
This commit is contained in:
parent
99fecbf6d2
commit
0377e2c64e
@ -234,6 +234,7 @@ export const createSwapsMockStore = () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
gasLoadingAnimationIsShowing: false,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -7,6 +7,8 @@ import FormField from '../../ui/form-field';
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas';
|
||||
import { getGasFormErrorText } from '../../../helpers/constants/gas';
|
||||
import { checkNetworkAndAccountSupports1559 } from '../../../selectors';
|
||||
import { getIsGasEstimatesLoading } from '../../../ducks/metamask/metamask';
|
||||
import { getGasLoadingAnimationIsShowing } from '../../../ducks/app/app';
|
||||
|
||||
export default function AdvancedGasControls({
|
||||
gasEstimateType,
|
||||
@ -28,6 +30,12 @@ export default function AdvancedGasControls({
|
||||
const networkAndAccountSupport1559 = useSelector(
|
||||
checkNetworkAndAccountSupports1559,
|
||||
);
|
||||
const isGasEstimatesLoading = useSelector(getIsGasEstimatesLoading);
|
||||
const isGasLoadingAnimationIsShowing = useSelector(
|
||||
getGasLoadingAnimationIsShowing,
|
||||
);
|
||||
const disableFormFields =
|
||||
isGasEstimatesLoading || isGasLoadingAnimationIsShowing;
|
||||
|
||||
const showFeeMarketFields =
|
||||
networkAndAccountSupport1559 &&
|
||||
@ -71,6 +79,7 @@ export default function AdvancedGasControls({
|
||||
? getGasFormErrorText(gasErrors.maxPriorityFee, t)
|
||||
: null
|
||||
}
|
||||
disabled={disableFormFields}
|
||||
/>
|
||||
<FormField
|
||||
titleText={t('maxFee')}
|
||||
@ -88,6 +97,7 @@ export default function AdvancedGasControls({
|
||||
? getGasFormErrorText(gasErrors.maxFee, t)
|
||||
: null
|
||||
}
|
||||
disabled={disableFormFields}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
@ -107,6 +117,7 @@ export default function AdvancedGasControls({
|
||||
? getGasFormErrorText(gasErrors.gasPrice, t)
|
||||
: null
|
||||
}
|
||||
disabled={disableFormFields}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
@ -2,7 +2,7 @@ import React, { useCallback, useContext, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useGasFeeInputs } from '../../../hooks/useGasFeeInputs';
|
||||
import { useShouldAnimateGasEstimations } from '../../../hooks/useShouldAnimateGasEstimations';
|
||||
import { getGasLoadingAnimationIsShowing } from '../../../ducks/app/app';
|
||||
|
||||
import { EDIT_GAS_MODES, GAS_LIMITS } from '../../../../shared/constants/gas';
|
||||
|
||||
@ -45,8 +45,9 @@ export default function EditGasPopover({
|
||||
const networkAndAccountSupport1559 = useSelector(
|
||||
checkNetworkAndAccountSupports1559,
|
||||
);
|
||||
|
||||
const shouldAnimate = useShouldAnimateGasEstimations();
|
||||
const gasLoadingAnimationIsShowing = useSelector(
|
||||
getGasLoadingAnimationIsShowing,
|
||||
);
|
||||
|
||||
const showEducationButton =
|
||||
(mode === EDIT_GAS_MODES.MODIFY_IN_PLACE ||
|
||||
@ -192,7 +193,12 @@ export default function EditGasPopover({
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={onSubmit}
|
||||
disabled={hasGasErrors || isGasEstimatesLoading || balanceError}
|
||||
disabled={
|
||||
hasGasErrors ||
|
||||
isGasEstimatesLoading ||
|
||||
balanceError ||
|
||||
gasLoadingAnimationIsShowing
|
||||
}
|
||||
>
|
||||
{footerButtonText}
|
||||
</Button>
|
||||
@ -205,9 +211,7 @@ export default function EditGasPopover({
|
||||
<EditGasDisplayEducation />
|
||||
) : (
|
||||
<>
|
||||
{process.env.IN_TEST === 'true' ? null : (
|
||||
<LoadingHeartBeat active={shouldAnimate} />
|
||||
)}
|
||||
{process.env.IN_TEST === 'true' ? null : <LoadingHeartBeat />}
|
||||
<EditGasDisplay
|
||||
showEducationButton={showEducationButton}
|
||||
warning={warning}
|
||||
|
@ -1,14 +1,20 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas';
|
||||
|
||||
import { useGasFeeEstimates } from '../../../hooks/useGasFeeEstimates';
|
||||
import { usePrevious } from '../../../hooks/usePrevious';
|
||||
import { I18nContext } from '../../../contexts/i18n';
|
||||
|
||||
import {
|
||||
getGasEstimateType,
|
||||
getGasFeeEstimates,
|
||||
getIsGasEstimatesLoading,
|
||||
} from '../../../ducks/metamask/metamask';
|
||||
|
||||
import Typography from '../../ui/typography/typography';
|
||||
import {
|
||||
TYPOGRAPHY,
|
||||
@ -33,11 +39,9 @@ export default function GasTiming({
|
||||
maxFeePerGas = 0,
|
||||
maxPriorityFeePerGas = 0,
|
||||
}) {
|
||||
const {
|
||||
gasFeeEstimates,
|
||||
isGasEstimatesLoading,
|
||||
gasEstimateType,
|
||||
} = useGasFeeEstimates();
|
||||
const gasEstimateType = useSelector(getGasEstimateType);
|
||||
const gasFeeEstimates = useSelector(getGasFeeEstimates);
|
||||
const isGasEstimatesLoading = useSelector(getIsGasEstimatesLoading);
|
||||
|
||||
const [customEstimatedTime, setCustomEstimatedTime] = useState(null);
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { shallow } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
|
||||
@ -9,7 +10,13 @@ import messages from '../../../../app/_locales/en/messages.json';
|
||||
import { getMessage } from '../../../helpers/utils/i18n-helper';
|
||||
|
||||
import * as i18nhooks from '../../../hooks/useI18nContext';
|
||||
import * as useGasFeeEstimatesExport from '../../../hooks/useGasFeeEstimates';
|
||||
|
||||
import { checkNetworkAndAccountSupports1559 } from '../../../selectors';
|
||||
import {
|
||||
getGasEstimateType,
|
||||
getGasFeeEstimates,
|
||||
getIsGasEstimatesLoading,
|
||||
} from '../../../ducks/metamask/metamask';
|
||||
|
||||
import GasTiming from '.';
|
||||
|
||||
@ -35,58 +42,103 @@ const MOCK_FEE_ESTIMATE = {
|
||||
estimatedBaseFee: '50',
|
||||
};
|
||||
|
||||
jest.mock('react-redux', () => {
|
||||
const actual = jest.requireActual('react-redux');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
useSelector: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const DEFAULT_OPTS = {
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
|
||||
gasFeeEstimates: {
|
||||
low: '10',
|
||||
medium: '20',
|
||||
high: '30',
|
||||
},
|
||||
isGasEstimatesLoading: true,
|
||||
};
|
||||
|
||||
const generateUseSelectorRouter = (opts = DEFAULT_OPTS) => (selector) => {
|
||||
if (selector === checkNetworkAndAccountSupports1559) {
|
||||
return true;
|
||||
}
|
||||
if (selector === getGasEstimateType) {
|
||||
return opts.gasEstimateType ?? DEFAULT_OPTS.gasEstimateType;
|
||||
}
|
||||
if (selector === getGasFeeEstimates) {
|
||||
return opts.gasFeeEstimates ?? DEFAULT_OPTS.gasFeeEstimates;
|
||||
}
|
||||
if (selector === getIsGasEstimatesLoading) {
|
||||
return opts.isGasEstimatesLoading ?? DEFAULT_OPTS.isGasEstimatesLoading;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
describe('Gas timing', () => {
|
||||
beforeEach(() => {
|
||||
const useI18nContext = sinon.stub(i18nhooks, 'useI18nContext');
|
||||
useI18nContext.returns((key, variables) =>
|
||||
getMessage('en', messages, key, variables),
|
||||
);
|
||||
jest.clearAllMocks();
|
||||
useSelector.mockImplementation(generateUseSelectorRouter());
|
||||
});
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('renders nothing when gas is loading', () => {
|
||||
sinon.stub(useGasFeeEstimatesExport, 'useGasFeeEstimates').returns({
|
||||
isGasEstimatesLoading: true,
|
||||
gasFeeEstimates: null,
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
|
||||
});
|
||||
useSelector.mockImplementation(
|
||||
generateUseSelectorRouter({
|
||||
isGasEstimatesLoading: true,
|
||||
gasFeeEstimates: null,
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
|
||||
}),
|
||||
);
|
||||
|
||||
const wrapper = shallow(<GasTiming />);
|
||||
expect(wrapper.html()).toBeNull();
|
||||
});
|
||||
|
||||
it('renders "very likely" when high estimate is chosen', () => {
|
||||
sinon.stub(useGasFeeEstimatesExport, 'useGasFeeEstimates').returns({
|
||||
isGasEstimatesLoading: false,
|
||||
gasFeeEstimates: MOCK_FEE_ESTIMATE,
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
|
||||
});
|
||||
useSelector.mockImplementation(
|
||||
generateUseSelectorRouter({
|
||||
isGasEstimatesLoading: false,
|
||||
gasFeeEstimates: MOCK_FEE_ESTIMATE,
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
|
||||
}),
|
||||
);
|
||||
|
||||
const wrapper = shallow(<GasTiming maxPriorityFeePerGas={10} />);
|
||||
const wrapper = shallow(<GasTiming maxPriorityFeePerGas="10" />);
|
||||
expect(wrapper.html()).toContain('gasTimingVeryPositive');
|
||||
});
|
||||
|
||||
it('renders "likely" when medium estimate is chosen', () => {
|
||||
sinon.stub(useGasFeeEstimatesExport, 'useGasFeeEstimates').returns({
|
||||
isGasEstimatesLoading: false,
|
||||
gasFeeEstimates: MOCK_FEE_ESTIMATE,
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
|
||||
});
|
||||
useSelector.mockImplementation(
|
||||
generateUseSelectorRouter({
|
||||
isGasEstimatesLoading: false,
|
||||
gasFeeEstimates: MOCK_FEE_ESTIMATE,
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
|
||||
}),
|
||||
);
|
||||
|
||||
const wrapper = shallow(<GasTiming maxPriorityFeePerGas={8} />);
|
||||
const wrapper = shallow(<GasTiming maxPriorityFeePerGas="8" />);
|
||||
expect(wrapper.html()).toContain('gasTimingPositive');
|
||||
});
|
||||
|
||||
it('renders "maybe" when low estimate is chosen', () => {
|
||||
sinon.stub(useGasFeeEstimatesExport, 'useGasFeeEstimates').returns({
|
||||
isGasEstimatesLoading: false,
|
||||
gasFeeEstimates: MOCK_FEE_ESTIMATE,
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
|
||||
});
|
||||
useSelector.mockImplementation(
|
||||
generateUseSelectorRouter({
|
||||
isGasEstimatesLoading: false,
|
||||
gasFeeEstimates: MOCK_FEE_ESTIMATE,
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
|
||||
}),
|
||||
);
|
||||
|
||||
const wrapper = shallow(<GasTiming maxPriorityFeePerGas={3} />);
|
||||
const wrapper = shallow(<GasTiming maxPriorityFeePerGas="3" />);
|
||||
expect(wrapper.html()).toContain('gasTimingNegative');
|
||||
});
|
||||
});
|
||||
|
@ -1,18 +1,33 @@
|
||||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { getIsGasEstimatesLoading } from '../../../ducks/metamask/metamask';
|
||||
import { getGasLoadingAnimationIsShowing } from '../../../ducks/app/app';
|
||||
|
||||
import { I18nContext } from '../../../contexts/i18n';
|
||||
|
||||
import TransactionDetailItem from '../transaction-detail-item/transaction-detail-item.component';
|
||||
import LoadingHeartBeat from '../../ui/loading-heartbeat';
|
||||
|
||||
export default function TransactionDetail({ rows = [], onEdit }) {
|
||||
const t = useContext(I18nContext);
|
||||
const isGasEstimatesLoading = useSelector(getIsGasEstimatesLoading);
|
||||
const gasLoadingAnimationIsShowing = useSelector(
|
||||
getGasLoadingAnimationIsShowing,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="transaction-detail">
|
||||
{process.env.IN_TEST === 'true' ? null : <LoadingHeartBeat />}
|
||||
{onEdit && (
|
||||
<div className="transaction-detail-edit">
|
||||
<button onClick={onEdit}>{t('edit')}</button>
|
||||
<button
|
||||
onClick={onEdit}
|
||||
disabled={isGasEstimatesLoading || gasLoadingAnimationIsShowing}
|
||||
>
|
||||
{t('edit')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="transaction-detail-rows">{rows}</div>
|
||||
|
@ -28,6 +28,7 @@ export default function FormField({
|
||||
autoFocus,
|
||||
password,
|
||||
allowDecimals,
|
||||
disabled,
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
@ -81,6 +82,7 @@ export default function FormField({
|
||||
detailText={detailText}
|
||||
autoFocus={autoFocus}
|
||||
allowDecimals={allowDecimals}
|
||||
disabled={disabled}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
@ -91,6 +93,7 @@ export default function FormField({
|
||||
value={value}
|
||||
type={password ? 'password' : 'text'}
|
||||
autoFocus={autoFocus}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)}
|
||||
{error && (
|
||||
@ -120,6 +123,7 @@ FormField.propTypes = {
|
||||
numeric: PropTypes.bool,
|
||||
password: PropTypes.bool,
|
||||
allowDecimals: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
FormField.defaultProps = {
|
||||
@ -135,4 +139,5 @@ FormField.defaultProps = {
|
||||
numeric: false,
|
||||
password: false,
|
||||
allowDecimals: true,
|
||||
disabled: false,
|
||||
};
|
||||
|
@ -1,36 +1,25 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { getGasLoadingAnimationIsShowing } from '../../../ducks/app/app';
|
||||
import { useShouldAnimateGasEstimations } from '../../../hooks/useShouldAnimateGasEstimations';
|
||||
|
||||
export default function LoadingHeartBeat({ active }) {
|
||||
const heartNode = useRef(null);
|
||||
const BASE_CLASS = 'loading-heartbeat';
|
||||
const LOADING_CLASS = `${BASE_CLASS}--active`;
|
||||
|
||||
const LOADING_CLASS = 'loading-heartbeat--active';
|
||||
|
||||
// When the loading animation completes, remove the className to disappear again
|
||||
useEffect(() => {
|
||||
const eventName = 'animationend';
|
||||
const node = heartNode?.current;
|
||||
const eventHandler = () => {
|
||||
node?.classList.remove(LOADING_CLASS);
|
||||
};
|
||||
|
||||
node?.addEventListener(eventName, eventHandler);
|
||||
return () => {
|
||||
node?.removeEventListener(eventName, eventHandler);
|
||||
};
|
||||
}, [heartNode]);
|
||||
export default function LoadingHeartBeat() {
|
||||
useShouldAnimateGasEstimations();
|
||||
const active = useSelector(getGasLoadingAnimationIsShowing);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('loading-heartbeat', {
|
||||
[LOADING_CLASS]: active,
|
||||
})}
|
||||
ref={heartNode}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
|
||||
LoadingHeartBeat.propTypes = {
|
||||
active: PropTypes.bool,
|
||||
};
|
||||
|
@ -7,10 +7,14 @@
|
||||
opacity: 0;
|
||||
background: #fff;
|
||||
display: none;
|
||||
pointer-events: none;
|
||||
|
||||
&--active {
|
||||
display: block;
|
||||
animation: heartbeat 2s ease-in-out;
|
||||
animation-name: heartbeat;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ export default function NumericInput({
|
||||
error = '',
|
||||
autoFocus = false,
|
||||
allowDecimals = true,
|
||||
disabled = false,
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
@ -29,6 +30,7 @@ export default function NumericInput({
|
||||
}}
|
||||
min="0"
|
||||
autoFocus={autoFocus}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{detailText && (
|
||||
<Typography color={COLORS.UI4} variant={TYPOGRAPHY.H7} tag="span">
|
||||
@ -46,4 +48,5 @@ NumericInput.propTypes = {
|
||||
error: PropTypes.string,
|
||||
autoFocus: PropTypes.bool,
|
||||
allowDecimals: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
@ -53,6 +53,7 @@ export default function reduceApp(state = {}, action) {
|
||||
singleExceptions: {
|
||||
testKey: null,
|
||||
},
|
||||
gasLoadingAnimationIsShowing: false,
|
||||
...state,
|
||||
};
|
||||
|
||||
@ -358,6 +359,12 @@ export default function reduceApp(state = {}, action) {
|
||||
},
|
||||
};
|
||||
|
||||
case actionConstants.TOGGLE_GAS_LOADING_ANIMATION:
|
||||
return {
|
||||
...appState,
|
||||
gasLoadingAnimationIsShowing: action.value,
|
||||
};
|
||||
|
||||
default:
|
||||
return appState;
|
||||
}
|
||||
@ -377,7 +384,15 @@ export function hideWhatsNewPopup() {
|
||||
};
|
||||
}
|
||||
|
||||
export function toggleGasLoadingAnimation(value) {
|
||||
return { type: actionConstants.TOGGLE_GAS_LOADING_ANIMATION, value };
|
||||
}
|
||||
|
||||
// Selectors
|
||||
export function getQrCodeData(state) {
|
||||
return state.appState.qrCodeData;
|
||||
}
|
||||
|
||||
export function getGasLoadingAnimationIsShowing(state) {
|
||||
return state.appState.gasLoadingAnimationIsShowing;
|
||||
}
|
||||
|
@ -4,11 +4,13 @@ import { ALERT_TYPES } from '../../../shared/constants/alerts';
|
||||
import { NETWORK_TYPE_RPC } from '../../../shared/constants/network';
|
||||
import {
|
||||
accountsWithSendEtherInfoSelector,
|
||||
checkNetworkAndAccountSupports1559,
|
||||
getAddressBook,
|
||||
} from '../../selectors';
|
||||
import { updateTransaction } from '../../store/actions';
|
||||
import { setCustomGasLimit, setCustomGasPrice } from '../gas/gas.duck';
|
||||
import { decGWEIToHexWEI } from '../../helpers/utils/conversions.util';
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas';
|
||||
|
||||
export default function reduceMetamask(state = {}, action) {
|
||||
const metamaskState = {
|
||||
@ -299,3 +301,24 @@ export function getGasFeeEstimates(state) {
|
||||
export function getEstimatedGasFeeTimeBounds(state) {
|
||||
return state.metamask.estimatedGasFeeTimeBounds;
|
||||
}
|
||||
|
||||
export function getIsGasEstimatesLoading(state) {
|
||||
const networkAndAccountSupports1559 = checkNetworkAndAccountSupports1559(
|
||||
state,
|
||||
);
|
||||
const gasEstimateType = getGasEstimateType(state);
|
||||
|
||||
// We consider the gas estimate to be loading if the gasEstimateType is
|
||||
// 'NONE' or if the current gasEstimateType cannot be supported by the current
|
||||
// network
|
||||
const isEIP1559TolerableEstimateType =
|
||||
gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET ||
|
||||
gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE;
|
||||
const isGasEstimatesLoading =
|
||||
gasEstimateType === GAS_ESTIMATE_TYPES.NONE ||
|
||||
(networkAndAccountSupports1559 && !isEIP1559TolerableEstimateType) ||
|
||||
(!networkAndAccountSupports1559 &&
|
||||
gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET);
|
||||
|
||||
return isGasEstimatesLoading;
|
||||
}
|
||||
|
@ -1,17 +1,12 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { GAS_ESTIMATE_TYPES } from '../../shared/constants/gas';
|
||||
import {
|
||||
getEstimatedGasFeeTimeBounds,
|
||||
getGasEstimateType,
|
||||
getGasFeeEstimates,
|
||||
getIsGasEstimatesLoading,
|
||||
} from '../ducks/metamask/metamask';
|
||||
import { checkNetworkAndAccountSupports1559 } from '../selectors';
|
||||
import { useSafeGasEstimatePolling } from './useSafeGasEstimatePolling';
|
||||
|
||||
/**
|
||||
* @typedef {keyof typeof GAS_ESTIMATE_TYPES} GasEstimateTypes
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} GasEstimates
|
||||
* @property {GasEstimateTypes} gasEstimateType - The type of estimate provided
|
||||
@ -35,26 +30,12 @@ import { useSafeGasEstimatePolling } from './useSafeGasEstimatePolling';
|
||||
* @returns {GasFeeEstimates} - GasFeeEstimates object
|
||||
*/
|
||||
export function useGasFeeEstimates() {
|
||||
const networkAndAccountSupports1559 = useSelector(
|
||||
checkNetworkAndAccountSupports1559,
|
||||
);
|
||||
const gasEstimateType = useSelector(getGasEstimateType);
|
||||
const gasFeeEstimates = useSelector(getGasFeeEstimates);
|
||||
const estimatedGasFeeTimeBounds = useSelector(getEstimatedGasFeeTimeBounds);
|
||||
const isGasEstimatesLoading = useSelector(getIsGasEstimatesLoading);
|
||||
useSafeGasEstimatePolling();
|
||||
|
||||
// We consider the gas estimate to be loading if the gasEstimateType is
|
||||
// 'NONE' or if the current gasEstimateType cannot be supported by the current
|
||||
// network
|
||||
const isEIP1559TolerableEstimateType =
|
||||
gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET ||
|
||||
gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE;
|
||||
const isGasEstimatesLoading =
|
||||
gasEstimateType === GAS_ESTIMATE_TYPES.NONE ||
|
||||
(networkAndAccountSupports1559 && !isEIP1559TolerableEstimateType) ||
|
||||
(!networkAndAccountSupports1559 &&
|
||||
gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET);
|
||||
|
||||
return {
|
||||
gasFeeEstimates,
|
||||
gasEstimateType,
|
||||
|
@ -5,12 +5,14 @@ import createRandomId from '../../shared/modules/random-id';
|
||||
import {
|
||||
getGasEstimateType,
|
||||
getGasFeeEstimates,
|
||||
getIsGasEstimatesLoading,
|
||||
} from '../ducks/metamask/metamask';
|
||||
import { checkNetworkAndAccountSupports1559 } from '../selectors';
|
||||
import {
|
||||
disconnectGasFeeEstimatePoller,
|
||||
getGasFeeEstimatesAndStartPolling,
|
||||
} from '../store/actions';
|
||||
|
||||
import { useGasFeeEstimates } from './useGasFeeEstimates';
|
||||
|
||||
jest.mock('../store/actions', () => ({
|
||||
@ -37,6 +39,7 @@ const DEFAULT_OPTS = {
|
||||
medium: '20',
|
||||
high: '30',
|
||||
},
|
||||
isGasEstimatesLoading: true,
|
||||
};
|
||||
|
||||
const generateUseSelectorRouter = (opts = DEFAULT_OPTS) => (selector) => {
|
||||
@ -52,6 +55,9 @@ const generateUseSelectorRouter = (opts = DEFAULT_OPTS) => (selector) => {
|
||||
if (selector === getGasFeeEstimates) {
|
||||
return opts.gasFeeEstimates ?? DEFAULT_OPTS.gasFeeEstimates;
|
||||
}
|
||||
if (selector === getIsGasEstimatesLoading) {
|
||||
return opts.isGasEstimatesLoading ?? DEFAULT_OPTS.isGasEstimatesLoading;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
@ -68,15 +74,16 @@ describe('useGasFeeEstimates', () => {
|
||||
disconnectGasFeeEstimatePoller.mockImplementation((token) => {
|
||||
tokens = tokens.filter((tkn) => tkn !== token);
|
||||
});
|
||||
useSelector.mockImplementation(generateUseSelectorRouter());
|
||||
});
|
||||
|
||||
it('registers with the controller', () => {
|
||||
useSelector.mockImplementation(generateUseSelectorRouter());
|
||||
renderHook(() => useGasFeeEstimates());
|
||||
expect(tokens).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('clears token with the controller on unmount', async () => {
|
||||
useSelector.mockImplementation(generateUseSelectorRouter());
|
||||
renderHook(() => useGasFeeEstimates());
|
||||
expect(tokens).toHaveLength(1);
|
||||
const expectedToken = tokens[0];
|
||||
@ -87,6 +94,11 @@ describe('useGasFeeEstimates', () => {
|
||||
});
|
||||
|
||||
it('works with LEGACY gas prices', () => {
|
||||
useSelector.mockImplementation(
|
||||
generateUseSelectorRouter({
|
||||
isGasEstimatesLoading: false,
|
||||
}),
|
||||
);
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useGasFeeEstimates());
|
||||
@ -104,6 +116,7 @@ describe('useGasFeeEstimates', () => {
|
||||
generateUseSelectorRouter({
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.ETH_GASPRICE,
|
||||
gasFeeEstimates,
|
||||
isGasEstimatesLoading: false,
|
||||
}),
|
||||
);
|
||||
|
||||
@ -145,6 +158,7 @@ describe('useGasFeeEstimates', () => {
|
||||
checkNetworkAndAccountSupports1559: true,
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
|
||||
gasFeeEstimates,
|
||||
isGasEstimatesLoading: false,
|
||||
}),
|
||||
);
|
||||
|
||||
|
@ -1,10 +1,20 @@
|
||||
import { useRef } from 'react';
|
||||
import { useRef, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import {
|
||||
getGasLoadingAnimationIsShowing,
|
||||
toggleGasLoadingAnimation,
|
||||
} from '../ducks/app/app';
|
||||
import { useGasFeeEstimates } from './useGasFeeEstimates';
|
||||
|
||||
export function useShouldAnimateGasEstimations() {
|
||||
const { isGasEstimatesLoading, gasFeeEstimates } = useGasFeeEstimates();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isGasLoadingAnimationActive = useSelector(
|
||||
getGasLoadingAnimationIsShowing,
|
||||
);
|
||||
|
||||
// Do the animation only when gas prices have changed...
|
||||
const lastGasEstimates = useRef(gasFeeEstimates);
|
||||
@ -24,5 +34,17 @@ export function useShouldAnimateGasEstimations() {
|
||||
const showLoadingAnimation =
|
||||
isGasEstimatesLoading || (gasEstimatesChanged && !gasJustLoaded);
|
||||
|
||||
return showLoadingAnimation;
|
||||
useEffect(() => {
|
||||
if (
|
||||
isGasLoadingAnimationActive === false &&
|
||||
showLoadingAnimation === true
|
||||
) {
|
||||
dispatch(toggleGasLoadingAnimation(true));
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('Killing the toggleGasLoadingAnimation to false');
|
||||
dispatch(toggleGasLoadingAnimation(false));
|
||||
}, 2000);
|
||||
}
|
||||
}, [dispatch, isGasLoadingAnimationActive, showLoadingAnimation]);
|
||||
}
|
||||
|
@ -107,6 +107,7 @@ export default class ConfirmTransactionBase extends Component {
|
||||
setDefaultHomeActiveTabName: PropTypes.func,
|
||||
primaryTotalTextOverride: PropTypes.string,
|
||||
secondaryTotalTextOverride: PropTypes.string,
|
||||
gasIsLoading: PropTypes.bool,
|
||||
};
|
||||
|
||||
state = {
|
||||
@ -772,6 +773,7 @@ export default class ConfirmTransactionBase extends Component {
|
||||
hideSenderToRecipient,
|
||||
showAccountInHeader,
|
||||
txData,
|
||||
gasIsLoading,
|
||||
} = this.props;
|
||||
const {
|
||||
submitting,
|
||||
@ -838,7 +840,7 @@ export default class ConfirmTransactionBase extends Component {
|
||||
lastTx={lastTx}
|
||||
ofText={ofText}
|
||||
requestsWaitingText={requestsWaitingText}
|
||||
disabled={!valid || submitting}
|
||||
disabled={!valid || submitting || gasIsLoading}
|
||||
onEdit={() => this.handleEdit()}
|
||||
onCancelAll={() => this.handleCancelAll()}
|
||||
onCancel={() => this.handleCancel()}
|
||||
|
@ -32,7 +32,11 @@ import {
|
||||
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
||||
import { transactionMatchesNetwork } from '../../../shared/modules/transaction.utils';
|
||||
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils';
|
||||
import { updateTransactionGasFees } from '../../ducks/metamask/metamask';
|
||||
import {
|
||||
updateTransactionGasFees,
|
||||
getIsGasEstimatesLoading,
|
||||
} from '../../ducks/metamask/metamask';
|
||||
import { getGasLoadingAnimationIsShowing } from '../../ducks/app/app';
|
||||
import ConfirmTransactionBase from './confirm-transaction-base.component';
|
||||
|
||||
const casedContractMap = Object.keys(contractMap).reduce((acc, base) => {
|
||||
@ -60,6 +64,10 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const { id: paramsTransactionId } = params;
|
||||
const isMainnet = getIsMainnet(state);
|
||||
const supportsEIP1599 = checkNetworkAndAccountSupports1559(state);
|
||||
|
||||
const isGasEstimatesLoading = getIsGasEstimatesLoading(state);
|
||||
const gasLoadingAnimationIsShowing = getGasLoadingAnimationIsShowing(state);
|
||||
|
||||
const { confirmTransaction, metamask } = state;
|
||||
const {
|
||||
ensResolutionsByAddress,
|
||||
@ -185,6 +193,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
isEthGasPrice,
|
||||
noGasPrice,
|
||||
supportsEIP1599,
|
||||
gasIsLoading: isGasEstimatesLoading || gasLoadingAnimationIsShowing,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,14 @@
|
||||
import React from 'react';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { checkNetworkAndAccountSupports1559 } from '../../../selectors';
|
||||
import {
|
||||
getGasEstimateType,
|
||||
getGasFeeEstimates,
|
||||
getIsGasEstimatesLoading,
|
||||
} from '../../../ducks/metamask/metamask';
|
||||
|
||||
import {
|
||||
renderWithProvider,
|
||||
@ -13,21 +21,34 @@ import FeeCard from '.';
|
||||
|
||||
const middleware = [thunk];
|
||||
|
||||
jest.mock('../../../hooks/useGasFeeEstimates', () => {
|
||||
jest.mock('react-redux', () => {
|
||||
const actual = jest.requireActual('react-redux');
|
||||
|
||||
return {
|
||||
useGasFeeEstimates: () => {
|
||||
return {
|
||||
gasFeeEstimates: MOCKS.createGasFeeEstimatesForFeeMarket(),
|
||||
gasEstimateType: 'fee-market',
|
||||
estimatedGasFeeTimeBounds: undefined,
|
||||
isGasEstimatesLoading: false,
|
||||
};
|
||||
},
|
||||
...actual,
|
||||
useSelector: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const generateUseSelectorRouter = () => (selector) => {
|
||||
if (selector === checkNetworkAndAccountSupports1559) {
|
||||
return true;
|
||||
}
|
||||
if (selector === getGasEstimateType) {
|
||||
return 'fee-market';
|
||||
}
|
||||
if (selector === getGasFeeEstimates) {
|
||||
return MOCKS.createGasFeeEstimatesForFeeMarket();
|
||||
}
|
||||
if (selector === getIsGasEstimatesLoading) {
|
||||
return false;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
setBackgroundConnection({
|
||||
getGasFeeTimeEstimate: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest.fn(),
|
||||
});
|
||||
|
||||
const createProps = (customProps = {}) => {
|
||||
@ -65,6 +86,7 @@ const createProps = (customProps = {}) => {
|
||||
|
||||
describe('FeeCard', () => {
|
||||
it('renders the component with initial props', () => {
|
||||
useSelector.mockImplementation(generateUseSelectorRouter());
|
||||
const props = createProps();
|
||||
const { getByText } = renderWithProvider(<FeeCard {...props} />);
|
||||
expect(getByText('Using the best quote')).toBeInTheDocument();
|
||||
|
@ -102,3 +102,5 @@ export const SET_OPEN_METAMASK_TAB_IDS = 'SET_OPEN_METAMASK_TAB_IDS';
|
||||
|
||||
// Home Screen
|
||||
export const HIDE_WHATS_NEW_POPUP = 'HIDE_WHATS_NEW_POPUP';
|
||||
|
||||
export const TOGGLE_GAS_LOADING_ANIMATION = 'TOGGLE_GAS_LOADING_ANIMATION';
|
||||
|
Loading…
Reference in New Issue
Block a user