feature/default_network_editable
parent
ba98edf604
commit
673371d013
@ -0,0 +1,149 @@ |
|||||||
|
import assert from 'assert' |
||||||
|
import React from 'react' |
||||||
|
import { shallow } from 'enzyme' |
||||||
|
import { Provider } from 'react-redux' |
||||||
|
import configureMockStore from 'redux-mock-store' |
||||||
|
import ViewQuotePriceDifference from '../view-quote-price-difference' |
||||||
|
|
||||||
|
describe('View Price Quote Difference', function () { |
||||||
|
const t = (key) => `translate ${key}` |
||||||
|
|
||||||
|
const state = { |
||||||
|
metamask: { |
||||||
|
tokens: [], |
||||||
|
provider: { type: 'rpc', nickname: '', rpcUrl: '' }, |
||||||
|
preferences: { showFiatInTestnets: true }, |
||||||
|
currentCurrency: 'usd', |
||||||
|
conversionRate: 600.0, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
const store = configureMockStore()(state) |
||||||
|
|
||||||
|
// Sample transaction is 1 $ETH to ~42.880915 $LINK
|
||||||
|
const DEFAULT_PROPS = { |
||||||
|
usedQuote: { |
||||||
|
trade: { |
||||||
|
data: |
||||||
|
'0x5f575529000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000007756e69737761700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca0000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000024855454cb32d335f0000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000005fc7b7100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f161421c8e0000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca', |
||||||
|
from: '0xd7440fdcb70a9fba55dfe06942ddbc17679c90ac', |
||||||
|
value: '0xde0b6b3a7640000', |
||||||
|
gas: '0xbbfd0', |
||||||
|
to: '0x881D40237659C251811CEC9c364ef91dC08D300C', |
||||||
|
}, |
||||||
|
sourceAmount: '1000000000000000000', |
||||||
|
destinationAmount: '42947749216634160067', |
||||||
|
error: null, |
||||||
|
sourceToken: '0x0000000000000000000000000000000000000000', |
||||||
|
destinationToken: '0x514910771af9ca656af840dff83e8264ecf986ca', |
||||||
|
approvalNeeded: null, |
||||||
|
maxGas: 770000, |
||||||
|
averageGas: 210546, |
||||||
|
estimatedRefund: 80000, |
||||||
|
fetchTime: 647, |
||||||
|
aggregator: 'uniswap', |
||||||
|
aggType: 'DEX', |
||||||
|
fee: 0.875, |
||||||
|
gasMultiplier: 1.5, |
||||||
|
priceSlippage: { |
||||||
|
ratio: 1.007876641534847, |
||||||
|
calculationError: '', |
||||||
|
bucket: 'low', |
||||||
|
sourceAmountInETH: 1, |
||||||
|
destinationAmountInEth: 0.9921849150875727, |
||||||
|
}, |
||||||
|
slippage: 2, |
||||||
|
sourceTokenInfo: { |
||||||
|
symbol: 'ETH', |
||||||
|
name: 'Ether', |
||||||
|
address: '0x0000000000000000000000000000000000000000', |
||||||
|
decimals: 18, |
||||||
|
iconUrl: 'images/black-eth-logo.svg', |
||||||
|
}, |
||||||
|
destinationTokenInfo: { |
||||||
|
address: '0x514910771af9ca656af840dff83e8264ecf986ca', |
||||||
|
symbol: 'LINK', |
||||||
|
decimals: 18, |
||||||
|
occurances: 12, |
||||||
|
iconUrl: |
||||||
|
'https://cloudflare-ipfs.com/ipfs/QmQhZAdcZvW9T2tPm516yHqbGkfhyZwTZmLixW9MXJudTA', |
||||||
|
}, |
||||||
|
ethFee: '0.011791', |
||||||
|
ethValueOfTokens: '0.99220724791716534441', |
||||||
|
overallValueOfQuote: '0.98041624791716534441', |
||||||
|
metaMaskFeeInEth: '0.00875844985551091729', |
||||||
|
isBestQuote: true, |
||||||
|
savings: { |
||||||
|
performance: '0.00207907025112527799', |
||||||
|
fee: '0.005581', |
||||||
|
metaMaskFee: '0.00875844985551091729', |
||||||
|
total: '-0.0010983796043856393', |
||||||
|
medianMetaMaskFee: '0.00874009740688812165', |
||||||
|
}, |
||||||
|
}, |
||||||
|
sourceTokenValue: '1', |
||||||
|
destinationTokenValue: '42.947749', |
||||||
|
} |
||||||
|
|
||||||
|
let component |
||||||
|
function renderComponent(props) { |
||||||
|
component = shallow( |
||||||
|
<Provider store={store}> |
||||||
|
<ViewQuotePriceDifference {...props} /> |
||||||
|
</Provider>, |
||||||
|
{ |
||||||
|
context: { t }, |
||||||
|
}, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
afterEach(function () { |
||||||
|
component.unmount() |
||||||
|
}) |
||||||
|
|
||||||
|
it('does not render when there is no quote', function () { |
||||||
|
const props = { ...DEFAULT_PROPS, usedQuote: null } |
||||||
|
renderComponent(props) |
||||||
|
|
||||||
|
const wrappingDiv = component.find( |
||||||
|
'.view-quote__price-difference-warning-wrapper', |
||||||
|
) |
||||||
|
assert.strictEqual(wrappingDiv.length, 0) |
||||||
|
}) |
||||||
|
|
||||||
|
it('does not render when the item is in the low bucket', function () { |
||||||
|
const props = { ...DEFAULT_PROPS } |
||||||
|
props.usedQuote.priceSlippage.bucket = 'low' |
||||||
|
|
||||||
|
renderComponent(props) |
||||||
|
const wrappingDiv = component.find( |
||||||
|
'.view-quote__price-difference-warning-wrapper', |
||||||
|
) |
||||||
|
assert.strictEqual(wrappingDiv.length, 0) |
||||||
|
}) |
||||||
|
|
||||||
|
it('displays an error when in medium bucket', function () { |
||||||
|
const props = { ...DEFAULT_PROPS } |
||||||
|
props.usedQuote.priceSlippage.bucket = 'medium' |
||||||
|
|
||||||
|
renderComponent(props) |
||||||
|
assert.strictEqual(component.html().includes('medium'), true) |
||||||
|
}) |
||||||
|
|
||||||
|
it('displays an error when in high bucket', function () { |
||||||
|
const props = { ...DEFAULT_PROPS } |
||||||
|
props.usedQuote.priceSlippage.bucket = 'high' |
||||||
|
|
||||||
|
renderComponent(props) |
||||||
|
assert.strictEqual(component.html().includes('high'), true) |
||||||
|
}) |
||||||
|
|
||||||
|
it('displays a fiat error when calculationError is present', function () { |
||||||
|
const props = { ...DEFAULT_PROPS } |
||||||
|
props.usedQuote.priceSlippage.calculationError = |
||||||
|
'Could not determine price.' |
||||||
|
|
||||||
|
renderComponent(props) |
||||||
|
assert.strictEqual(component.html().includes('fiat-error'), true) |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,114 @@ |
|||||||
|
import React, { useContext } from 'react' |
||||||
|
|
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import classnames from 'classnames' |
||||||
|
import BigNumber from 'bignumber.js' |
||||||
|
import { useEthFiatAmount } from '../../../hooks/useEthFiatAmount' |
||||||
|
import { I18nContext } from '../../../contexts/i18n' |
||||||
|
|
||||||
|
import ActionableMessage from '../actionable-message' |
||||||
|
import Tooltip from '../../../components/ui/tooltip' |
||||||
|
|
||||||
|
export default function ViewQuotePriceDifference(props) { |
||||||
|
const { usedQuote, sourceTokenValue, destinationTokenValue } = props |
||||||
|
|
||||||
|
const t = useContext(I18nContext) |
||||||
|
|
||||||
|
const priceSlippageFromSource = useEthFiatAmount( |
||||||
|
usedQuote?.priceSlippage?.sourceAmountInETH || 0, |
||||||
|
) |
||||||
|
const priceSlippageFromDestination = useEthFiatAmount( |
||||||
|
usedQuote?.priceSlippage?.destinationAmountInEth || 0, |
||||||
|
) |
||||||
|
|
||||||
|
if (!usedQuote || !usedQuote.priceSlippage) { |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
const { priceSlippage } = usedQuote |
||||||
|
|
||||||
|
// We cannot present fiat value if there is a calculation error or no slippage
|
||||||
|
// from source or destination
|
||||||
|
const priceSlippageUnknownFiatValue = |
||||||
|
!priceSlippageFromSource || |
||||||
|
!priceSlippageFromDestination || |
||||||
|
priceSlippage.calculationError |
||||||
|
|
||||||
|
let priceDifferencePercentage = 0 |
||||||
|
if (priceSlippage.ratio) { |
||||||
|
priceDifferencePercentage = parseFloat( |
||||||
|
new BigNumber(priceSlippage.ratio, 10) |
||||||
|
.minus(1, 10) |
||||||
|
.times(100, 10) |
||||||
|
.toFixed(2), |
||||||
|
10, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
const shouldShowPriceDifferenceWarning = |
||||||
|
['high', 'medium'].includes(priceSlippage.bucket) || |
||||||
|
priceSlippageUnknownFiatValue |
||||||
|
|
||||||
|
if (!shouldShowPriceDifferenceWarning) { |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
let priceDifferenceTitle = '' |
||||||
|
let priceDifferenceMessage = '' |
||||||
|
let priceDifferenceClass = '' |
||||||
|
if (priceSlippageUnknownFiatValue) { |
||||||
|
// A calculation error signals we cannot determine dollar value
|
||||||
|
priceDifferenceMessage = t('swapPriceDifferenceUnavailable') |
||||||
|
priceDifferenceClass = 'fiat-error' |
||||||
|
} else { |
||||||
|
priceDifferenceTitle = t('swapPriceDifferenceTitle', [ |
||||||
|
priceDifferencePercentage, |
||||||
|
]) |
||||||
|
priceDifferenceMessage = t('swapPriceDifference', [ |
||||||
|
sourceTokenValue, // Number of source token to swap
|
||||||
|
usedQuote.sourceTokenInfo.symbol, // Source token symbol
|
||||||
|
priceSlippageFromSource, // Source tokens total value
|
||||||
|
destinationTokenValue, // Number of destination tokens in return
|
||||||
|
usedQuote.destinationTokenInfo.symbol, // Destination token symbol,
|
||||||
|
priceSlippageFromDestination, // Destination tokens total value
|
||||||
|
]) |
||||||
|
priceDifferenceClass = priceSlippage.bucket |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div |
||||||
|
className={classnames( |
||||||
|
'view-quote__price-difference-warning-wrapper', |
||||||
|
priceDifferenceClass, |
||||||
|
)} |
||||||
|
> |
||||||
|
<ActionableMessage |
||||||
|
message={ |
||||||
|
<div className="view-quote__price-difference-warning-contents"> |
||||||
|
<div className="view-quote__price-difference-warning-contents-text"> |
||||||
|
{priceDifferenceTitle && ( |
||||||
|
<div className="view-quote__price-difference-warning-contents-title"> |
||||||
|
{priceDifferenceTitle} |
||||||
|
</div> |
||||||
|
)} |
||||||
|
{priceDifferenceMessage} |
||||||
|
</div> |
||||||
|
<Tooltip |
||||||
|
position="bottom" |
||||||
|
theme="white" |
||||||
|
title={t('swapPriceDifferenceTooltip')} |
||||||
|
> |
||||||
|
<i className="fa fa-info-circle" /> |
||||||
|
</Tooltip> |
||||||
|
</div> |
||||||
|
} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
ViewQuotePriceDifference.propTypes = { |
||||||
|
usedQuote: PropTypes.object, |
||||||
|
sourceTokenValue: PropTypes.string, |
||||||
|
destinationTokenValue: PropTypes.string, |
||||||
|
} |
Loading…
Reference in new issue