From 0aa41e397ea8a4e54b196d69f89102eeef7988a8 Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Tue, 12 May 2020 14:07:35 -0500 Subject: [PATCH] factor out containers for currency components (#8543) --- package.json | 1 + .../index.js | 2 +- ...erenced-currency-display.component.test.js | 11 + ...erenced-currency-display.container.test.js | 202 ------------------ ...-preferenced-currency-display.component.js | 63 +++--- ...-preferenced-currency-display.container.js | 67 ------ .../currency-display.component.js | 92 +++++--- .../currency-display.container.js | 68 ------ .../components/ui/currency-display/index.js | 2 +- .../tests/currency-display.component.test.js | 15 ++ .../tests/currency-display.container.test.js | 145 ------------- .../token-currency-display.component.js | 73 ++----- ui/app/hooks/tests/useCurrencyDisplay.test.js | 121 +++++++++++ .../hooks/tests/useTokenDisplayValue.test.js | 136 ++++++++++++ .../tests/useUserPreferencedCurrency.test.js | 143 +++++++++++++ ui/app/hooks/useCurrencyDisplay.js | 69 ++++++ ui/app/hooks/useTokenDisplayValue.js | 38 ++++ ui/app/hooks/useUserPreferencedCurrency.js | 52 +++++ .../choose-account.component.js | 1 - ui/app/selectors/selectors.js | 6 + yarn.lock | 35 +++ 21 files changed, 735 insertions(+), 607 deletions(-) delete mode 100644 ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js delete mode 100644 ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js delete mode 100644 ui/app/components/ui/currency-display/currency-display.container.js delete mode 100644 ui/app/components/ui/currency-display/tests/currency-display.container.test.js create mode 100644 ui/app/hooks/tests/useCurrencyDisplay.test.js create mode 100644 ui/app/hooks/tests/useTokenDisplayValue.test.js create mode 100644 ui/app/hooks/tests/useUserPreferencedCurrency.test.js create mode 100644 ui/app/hooks/useCurrencyDisplay.js create mode 100644 ui/app/hooks/useTokenDisplayValue.js create mode 100644 ui/app/hooks/useUserPreferencedCurrency.js diff --git a/package.json b/package.json index e68a621af8ce..a0e2fe745924 100644 --- a/package.json +++ b/package.json @@ -196,6 +196,7 @@ "@storybook/core": "^5.3.14", "@storybook/react": "^5.3.14", "@storybook/storybook-deployer": "^2.8.1", + "@testing-library/react-hooks": "^3.2.1", "addons-linter": "1.14.0", "babel-eslint": "^10.0.2", "babel-loader": "^8.0.6", diff --git a/ui/app/components/app/user-preferenced-currency-display/index.js b/ui/app/components/app/user-preferenced-currency-display/index.js index 0deddaecff6e..5bc2d4246534 100644 --- a/ui/app/components/app/user-preferenced-currency-display/index.js +++ b/ui/app/components/app/user-preferenced-currency-display/index.js @@ -1 +1 @@ -export { default } from './user-preferenced-currency-display.container' +export { default } from './user-preferenced-currency-display.component' diff --git a/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js b/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js index c8678e82b6ff..d6145aae8839 100644 --- a/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js +++ b/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js @@ -3,9 +3,17 @@ import assert from 'assert' import { shallow } from 'enzyme' import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display.component' import CurrencyDisplay from '../../../ui/currency-display' +import * as currencyHook from '../../../../hooks/useCurrencyDisplay' +import * as currencyPrefHook from '../../../../hooks/useUserPreferencedCurrency' +import sinon from 'sinon' + describe('UserPreferencedCurrencyDisplay Component', function () { describe('rendering', function () { + beforeEach(function () { + sinon.stub(currencyHook, 'useCurrencyDisplay').returns(['1', {}]) + sinon.stub(currencyPrefHook, 'useUserPreferencedCurrency').returns({ currency: 'ETH', decimals: 6 }) + }) it('should render properly', function () { const wrapper = shallow( @@ -30,5 +38,8 @@ describe('UserPreferencedCurrencyDisplay Component', function () { assert.equal(wrapper.find(CurrencyDisplay).props().prop2, 'test') assert.equal(wrapper.find(CurrencyDisplay).props().prop3, 1) }) + afterEach(function () { + sinon.restore() + }) }) }) diff --git a/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js b/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js deleted file mode 100644 index b0587e0b0e85..000000000000 --- a/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js +++ /dev/null @@ -1,202 +0,0 @@ -import assert from 'assert' -import proxyquire from 'proxyquire' - -let mapStateToProps, mergeProps - -proxyquire('../user-preferenced-currency-display.container.js', { - 'react-redux': { - connect: (ms, _, mp) => { - mapStateToProps = ms - mergeProps = mp - return () => ({}) - }, - }, -}) - -describe('UserPreferencedCurrencyDisplay container', function () { - describe('mapStateToProps()', function () { - it('should return the correct props', function () { - const mockState = { - metamask: { - nativeCurrency: 'ETH', - preferences: { - useNativeCurrencyAsPrimaryCurrency: true, - showFiatInTestnets: false, - }, - provider: { - type: 'mainnet', - }, - }, - } - - assert.deepEqual(mapStateToProps(mockState), { - nativeCurrency: 'ETH', - useNativeCurrencyAsPrimaryCurrency: true, - isMainnet: true, - showFiatInTestnets: false, - }) - }) - - it('should return the correct props when not in mainnet and showFiatInTestnets is true', function () { - const mockState = { - metamask: { - nativeCurrency: 'ETH', - preferences: { - useNativeCurrencyAsPrimaryCurrency: true, - showFiatInTestnets: true, - }, - provider: { - type: 'rinkeby', - }, - }, - } - - assert.deepEqual(mapStateToProps(mockState), { - nativeCurrency: 'ETH', - useNativeCurrencyAsPrimaryCurrency: true, - isMainnet: false, - showFiatInTestnets: true, - }) - }) - }) - - describe('mergeProps()', function () { - it('should return the correct props', function () { - const mockDispatchProps = {} - - const tests = [ - { - stateProps: { - useNativeCurrencyAsPrimaryCurrency: true, - nativeCurrency: 'ETH', - isMainnet: true, - showFiatInTestnets: false, - }, - ownProps: { - type: 'PRIMARY', - }, - result: { - currency: 'ETH', - nativeCurrency: 'ETH', - numberOfDecimals: 6, - prefix: undefined, - }, - }, - { - stateProps: { - useNativeCurrencyAsPrimaryCurrency: false, - nativeCurrency: 'ETH', - isMainnet: true, - showFiatInTestnets: false, - }, - ownProps: { - type: 'PRIMARY', - }, - result: { - currency: undefined, - nativeCurrency: 'ETH', - numberOfDecimals: 2, - prefix: undefined, - }, - }, - { - stateProps: { - useNativeCurrencyAsPrimaryCurrency: true, - nativeCurrency: 'ETH', - isMainnet: true, - showFiatInTestnets: false, - }, - ownProps: { - type: 'SECONDARY', - fiatNumberOfDecimals: 4, - fiatPrefix: '-', - }, - result: { - nativeCurrency: 'ETH', - currency: undefined, - numberOfDecimals: 4, - prefix: '-', - }, - }, - { - stateProps: { - useNativeCurrencyAsPrimaryCurrency: false, - nativeCurrency: 'ETH', - isMainnet: true, - showFiatInTestnets: false, - }, - ownProps: { - type: 'SECONDARY', - fiatNumberOfDecimals: 4, - numberOfDecimals: 3, - fiatPrefix: 'a', - prefix: 'b', - }, - result: { - currency: 'ETH', - nativeCurrency: 'ETH', - numberOfDecimals: 3, - prefix: 'b', - }, - }, - { - stateProps: { - useNativeCurrencyAsPrimaryCurrency: false, - nativeCurrency: 'ETH', - isMainnet: false, - showFiatInTestnets: false, - }, - ownProps: { - type: 'PRIMARY', - }, - result: { - currency: 'ETH', - nativeCurrency: 'ETH', - numberOfDecimals: 6, - prefix: undefined, - }, - }, - { - stateProps: { - useNativeCurrencyAsPrimaryCurrency: false, - nativeCurrency: 'ETH', - isMainnet: false, - showFiatInTestnets: true, - }, - ownProps: { - type: 'PRIMARY', - }, - result: { - currency: undefined, - nativeCurrency: 'ETH', - numberOfDecimals: 2, - prefix: undefined, - }, - }, - { - stateProps: { - useNativeCurrencyAsPrimaryCurrency: false, - nativeCurrency: 'ETH', - isMainnet: true, - showFiatInTestnets: true, - }, - ownProps: { - type: 'PRIMARY', - }, - result: { - currency: undefined, - nativeCurrency: 'ETH', - numberOfDecimals: 2, - prefix: undefined, - }, - }, - ] - - tests.forEach(({ stateProps, ownProps, result }) => { - assert.deepEqual(mergeProps({ ...stateProps }, mockDispatchProps, { ...ownProps }), { - ...result, - }) - }) - }) - }) -}) diff --git a/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js b/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js index 4b64b26c0221..b26f1eef4e9b 100644 --- a/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js +++ b/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js @@ -1,47 +1,42 @@ -import React, { PureComponent } from 'react' +import React, { useMemo } from 'react' import PropTypes from 'prop-types' import { PRIMARY, SECONDARY, ETH } from '../../../helpers/constants/common' import CurrencyDisplay from '../../ui/currency-display' +import { useUserPreferencedCurrency } from '../../../hooks/useUserPreferencedCurrency' -export default class UserPreferencedCurrencyDisplay extends PureComponent { - static propTypes = { - className: PropTypes.string, - prefix: PropTypes.string, - value: PropTypes.string, - numberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - hideLabel: PropTypes.bool, - hideTitle: PropTypes.bool, - style: PropTypes.object, - showEthLogo: PropTypes.bool, - ethLogoHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - // Used in container - type: PropTypes.oneOf([PRIMARY, SECONDARY]), - ethNumberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - fiatNumberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - ethPrefix: PropTypes.string, - fiatPrefix: PropTypes.string, - // From container - currency: PropTypes.string, - nativeCurrency: PropTypes.string, - } - - renderEthLogo () { - const { currency, showEthLogo, ethLogoHeight = 12 } = this.props +export default function UserPreferencedCurrencyDisplay ({ type, showEthLogo, ethLogoHeight = 12, ethNumberOfDecimals, fiatNumberOfDecimals, numberOfDecimals: propsNumberOfDecimals, ...restProps }) { + const { currency, numberOfDecimals } = useUserPreferencedCurrency(type, { ethNumberOfDecimals, fiatNumberOfDecimals, numberOfDecimals: propsNumberOfDecimals }) + const prefixComponent = useMemo(() => { return currency === ETH && showEthLogo && ( ) - } + }, [currency, showEthLogo, ethLogoHeight]) - render () { - return ( - - ) - } + return ( + + ) +} + +UserPreferencedCurrencyDisplay.propTypes = { + className: PropTypes.string, + prefix: PropTypes.string, + value: PropTypes.string, + numberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + hideLabel: PropTypes.bool, + hideTitle: PropTypes.bool, + style: PropTypes.object, + showEthLogo: PropTypes.bool, + ethLogoHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + type: PropTypes.oneOf([PRIMARY, SECONDARY]), + ethNumberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + fiatNumberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), } diff --git a/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js b/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js deleted file mode 100644 index b94bec6d1b1d..000000000000 --- a/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js +++ /dev/null @@ -1,67 +0,0 @@ -import { connect } from 'react-redux' -import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display.component' -import { preferencesSelector, getIsMainnet } from '../../../selectors' -import { ETH, PRIMARY, SECONDARY } from '../../../helpers/constants/common' - -const mapStateToProps = (state) => { - const { - useNativeCurrencyAsPrimaryCurrency, - showFiatInTestnets, - } = preferencesSelector(state) - - const isMainnet = getIsMainnet(state) - - return { - useNativeCurrencyAsPrimaryCurrency, - showFiatInTestnets, - isMainnet, - nativeCurrency: state.metamask.nativeCurrency, - } -} - -const mergeProps = (stateProps, dispatchProps, ownProps) => { - const { useNativeCurrencyAsPrimaryCurrency, showFiatInTestnets, isMainnet, nativeCurrency, ...restStateProps } = stateProps - const { - type, - numberOfDecimals: propsNumberOfDecimals, - ethNumberOfDecimals, - fiatNumberOfDecimals, - ethPrefix, - fiatPrefix, - prefix: propsPrefix, - ...restOwnProps - } = ownProps - - let currency, numberOfDecimals, prefix - - if ((type === PRIMARY && useNativeCurrencyAsPrimaryCurrency) || - (type === SECONDARY && !useNativeCurrencyAsPrimaryCurrency)) { - // Display ETH - currency = nativeCurrency || ETH - numberOfDecimals = propsNumberOfDecimals || ethNumberOfDecimals || 6 - prefix = propsPrefix || ethPrefix - } else if ((type === SECONDARY && useNativeCurrencyAsPrimaryCurrency) || - (type === PRIMARY && !useNativeCurrencyAsPrimaryCurrency)) { - // Display Fiat - numberOfDecimals = propsNumberOfDecimals || fiatNumberOfDecimals || 2 - prefix = propsPrefix || fiatPrefix - } - - if (!isMainnet && !showFiatInTestnets) { - currency = nativeCurrency || ETH - numberOfDecimals = propsNumberOfDecimals || ethNumberOfDecimals || 6 - prefix = propsPrefix || ethPrefix - } - - return { - ...restStateProps, - ...dispatchProps, - ...restOwnProps, - nativeCurrency, - currency, - numberOfDecimals, - prefix, - } -} - -export default connect(mapStateToProps, null, mergeProps)(UserPreferencedCurrencyDisplay) diff --git a/ui/app/components/ui/currency-display/currency-display.component.js b/ui/app/components/ui/currency-display/currency-display.component.js index da70191c1e5e..5b6311b6a569 100644 --- a/ui/app/components/ui/currency-display/currency-display.component.js +++ b/ui/app/components/ui/currency-display/currency-display.component.js @@ -1,40 +1,62 @@ -import React, { PureComponent } from 'react' +import React from 'react' import PropTypes from 'prop-types' import classnames from 'classnames' +import { GWEI } from '../../../helpers/constants/common' +import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay' +export default function CurrencyDisplay ({ + value, + displayValue, + style, + className, + prefix, + prefixComponent, + hideLabel, + hideTitle, + numberOfDecimals, + denomination, + currency, + suffix, +}) { + const [title, parts] = useCurrencyDisplay(value, { + displayValue, + prefix, + numberOfDecimals, + hideLabel, + denomination, + currency, + suffix, + }) + return ( +
+ { prefixComponent } + { parts.prefix }{ parts.value } + { + parts.suffix && ( + + { parts.suffix } + + ) + } +
+ ) +} -export default class CurrencyDisplay extends PureComponent { - static propTypes = { - className: PropTypes.string, - displayValue: PropTypes.string, - prefix: PropTypes.string, - prefixComponent: PropTypes.node, - style: PropTypes.object, - suffix: PropTypes.string, - hideTitle: PropTypes.bool, - } - - render () { - const { className, displayValue, prefix, prefixComponent, style, suffix, hideTitle } = this.props - const text = `${prefix || ''}${displayValue}` - const title = suffix ? `${text} ${suffix}` : text - - return ( -
- { prefixComponent } - { text } - { - suffix && ( - - { suffix } - - ) - } -
- ) - } +CurrencyDisplay.propTypes = { + className: PropTypes.string, + currency: PropTypes.string, + denomination: PropTypes.oneOf([GWEI]), + displayValue: PropTypes.string, + hideLabel: PropTypes.bool, + hideTitle: PropTypes.bool, + numberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + prefix: PropTypes.string, + prefixComponent: PropTypes.node, + style: PropTypes.object, + suffix: PropTypes.string, + value: PropTypes.string, } diff --git a/ui/app/components/ui/currency-display/currency-display.container.js b/ui/app/components/ui/currency-display/currency-display.container.js deleted file mode 100644 index 30055e31b540..000000000000 --- a/ui/app/components/ui/currency-display/currency-display.container.js +++ /dev/null @@ -1,68 +0,0 @@ -import { connect } from 'react-redux' -import PropTypes from 'prop-types' -import CurrencyDisplay from './currency-display.component' -import { getValueFromWeiHex, formatCurrency } from '../../../helpers/utils/confirm-tx.util' -import { GWEI } from '../../../helpers/constants/common' - -const mapStateToProps = (state) => { - const { metamask: { nativeCurrency, currentCurrency, conversionRate } } = state - - return { - currentCurrency, - conversionRate, - nativeCurrency, - } -} - -const mergeProps = (stateProps, _, ownProps) => { - const { nativeCurrency, currentCurrency, conversionRate } = stateProps - const { - value, - numberOfDecimals = 2, - currency, - denomination, - hideLabel, - displayValue: propsDisplayValue, - suffix: propsSuffix, - ...restOwnProps - } = ownProps - - const toCurrency = currency || currentCurrency - - const displayValue = propsDisplayValue || formatCurrency( - getValueFromWeiHex({ - value, - fromCurrency: nativeCurrency, - toCurrency, conversionRate, - numberOfDecimals, - toDenomination: denomination, - }), - toCurrency - ) - const suffix = propsSuffix || (hideLabel ? undefined : toCurrency.toUpperCase()) - - return { - ...restOwnProps, - displayValue, - suffix, - } -} - -const CurrencyDisplayContainer = connect(mapStateToProps, null, mergeProps)(CurrencyDisplay) - -CurrencyDisplayContainer.propTypes = { - className: PropTypes.string, - currency: PropTypes.string, - denomination: PropTypes.oneOf([GWEI]), - displayValue: PropTypes.string, - hideLabel: PropTypes.bool, - hideTitle: PropTypes.bool, - numberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - prefix: PropTypes.string, - prefixComponent: PropTypes.node, - style: PropTypes.object, - suffix: PropTypes.string, - value: PropTypes.string, -} - -export default CurrencyDisplayContainer diff --git a/ui/app/components/ui/currency-display/index.js b/ui/app/components/ui/currency-display/index.js index 38f08765fb51..f861bb91a3d1 100644 --- a/ui/app/components/ui/currency-display/index.js +++ b/ui/app/components/ui/currency-display/index.js @@ -1 +1 @@ -export { default } from './currency-display.container' +export { default } from './currency-display.component' diff --git a/ui/app/components/ui/currency-display/tests/currency-display.component.test.js b/ui/app/components/ui/currency-display/tests/currency-display.component.test.js index fd16401d7aff..862c160f8caf 100644 --- a/ui/app/components/ui/currency-display/tests/currency-display.component.test.js +++ b/ui/app/components/ui/currency-display/tests/currency-display.component.test.js @@ -2,13 +2,24 @@ import React from 'react' import assert from 'assert' import { shallow } from 'enzyme' import CurrencyDisplay from '../currency-display.component' +import sinon from 'sinon' +import * as reactRedux from 'react-redux' describe('CurrencyDisplay Component', function () { + beforeEach(function () { + const stub = sinon.stub(reactRedux, 'useSelector') + stub.callsFake(() => ({ + currentCurrency: 'usd', + nativeCurrency: 'ETH', + conversionRate: 280.45, + })) + }) it('should render text with a className', function () { const wrapper = shallow(( )) @@ -22,10 +33,14 @@ describe('CurrencyDisplay Component', function () { displayValue="$123.45" className="currency-display" prefix="-" + hideLabel /> )) assert.ok(wrapper.hasClass('currency-display')) assert.equal(wrapper.text(), '-$123.45') }) + afterEach(function () { + sinon.restore() + }) }) diff --git a/ui/app/components/ui/currency-display/tests/currency-display.container.test.js b/ui/app/components/ui/currency-display/tests/currency-display.container.test.js deleted file mode 100644 index 905f04108f89..000000000000 --- a/ui/app/components/ui/currency-display/tests/currency-display.container.test.js +++ /dev/null @@ -1,145 +0,0 @@ -import assert from 'assert' -import proxyquire from 'proxyquire' - -let mapStateToProps, mergeProps - -proxyquire('../currency-display.container.js', { - 'react-redux': { - connect: (ms, _, mp) => { - mapStateToProps = ms - mergeProps = mp - return () => ({}) - }, - }, -}) - -describe('CurrencyDisplay container', function () { - describe('mapStateToProps()', function () { - it('should return the correct props', function () { - const mockState = { - metamask: { - conversionRate: 280.45, - currentCurrency: 'usd', - nativeCurrency: 'ETH', - }, - } - - assert.deepEqual(mapStateToProps(mockState), { - conversionRate: 280.45, - currentCurrency: 'usd', - nativeCurrency: 'ETH', - }) - }) - }) - - describe('mergeProps()', function () { - it('should return the correct props', function () { - const mockStateProps = { - conversionRate: 280.45, - currentCurrency: 'usd', - nativeCurrency: 'ETH', - } - - const tests = [ - { - props: { - value: '0x2386f26fc10000', - numberOfDecimals: 2, - currency: 'usd', - nativeCurrency: 'ETH', - }, - result: { - displayValue: '$2.80', - suffix: 'USD', - nativeCurrency: 'ETH', - }, - }, - { - props: { - value: '0x2386f26fc10000', - currency: 'usd', - nativeCurrency: 'ETH', - }, - result: { - displayValue: '$2.80', - suffix: 'USD', - nativeCurrency: 'ETH', - }, - }, - { - props: { - value: '0x1193461d01595930', - currency: 'ETH', - nativeCurrency: 'ETH', - numberOfDecimals: 3, - }, - result: { - displayValue: '1.266', - suffix: 'ETH', - nativeCurrency: 'ETH', - }, - }, - { - props: { - value: '0x1193461d01595930', - currency: 'ETH', - nativeCurrency: 'ETH', - numberOfDecimals: 3, - hideLabel: true, - }, - result: { - nativeCurrency: 'ETH', - displayValue: '1.266', - suffix: undefined, - }, - }, - { - props: { - value: '0x3b9aca00', - currency: 'ETH', - nativeCurrency: 'ETH', - denomination: 'GWEI', - hideLabel: true, - }, - result: { - nativeCurrency: 'ETH', - displayValue: '1', - suffix: undefined, - }, - }, - { - props: { - value: '0x3b9aca00', - currency: 'ETH', - nativeCurrency: 'ETH', - denomination: 'WEI', - hideLabel: true, - }, - result: { - nativeCurrency: 'ETH', - displayValue: '1000000000', - suffix: undefined, - }, - }, - { - props: { - value: '0x3b9aca00', - currency: 'ETH', - nativeCurrency: 'ETH', - numberOfDecimals: 100, - hideLabel: true, - }, - result: { - nativeCurrency: 'ETH', - displayValue: '0.000000001', - suffix: undefined, - }, - }, - ] - - tests.forEach(({ props, result }) => { - assert.deepEqual(mergeProps(mockStateProps, {}, { ...props }), result) - }) - }) - }) -}) diff --git a/ui/app/components/ui/token-currency-display/token-currency-display.component.js b/ui/app/components/ui/token-currency-display/token-currency-display.component.js index 3c2722b365f5..b8e46614a6e8 100644 --- a/ui/app/components/ui/token-currency-display/token-currency-display.component.js +++ b/ui/app/components/ui/token-currency-display/token-currency-display.component.js @@ -1,57 +1,24 @@ -import React, { PureComponent } from 'react' +import React from 'react' import PropTypes from 'prop-types' import CurrencyDisplay from '../currency-display' -import { getTokenData } from '../../../helpers/utils/transactions.util' -import { getTokenValue, calcTokenAmount } from '../../../helpers/utils/token-util' - -export default class TokenCurrencyDisplay extends PureComponent { - static propTypes = { - transactionData: PropTypes.string, - token: PropTypes.object, - } - - state = { - displayValue: '', - suffix: '', - } - - componentDidMount () { - this.setDisplayValue() - } - - componentDidUpdate (prevProps) { - const { transactionData } = this.props - const { transactionData: prevTransactionData } = prevProps - - if (transactionData !== prevTransactionData) { - this.setDisplayValue() - } - } - - setDisplayValue () { - const { transactionData: data, token } = this.props - const { decimals = '', symbol: suffix = '' } = token - const tokenData = getTokenData(data) - - let displayValue - - if (tokenData && tokenData.params && tokenData.params.length) { - const tokenValue = getTokenValue(tokenData.params) - displayValue = calcTokenAmount(tokenValue, decimals).toString() - } - - this.setState({ displayValue, suffix }) - } - - render () { - const { displayValue, suffix } = this.state +import { useTokenDisplayValue } from '../../../hooks/useTokenDisplayValue' + +export default function TokenCurrencyDisplay ({ className, transactionData, token, prefix }) { + const displayValue = useTokenDisplayValue(transactionData, token) + + return ( + + ) +} - return ( - - ) - } +TokenCurrencyDisplay.propTypes = { + className: PropTypes.string, + transactionData: PropTypes.string, + token: PropTypes.object, + prefix: PropTypes.string, } diff --git a/ui/app/hooks/tests/useCurrencyDisplay.test.js b/ui/app/hooks/tests/useCurrencyDisplay.test.js new file mode 100644 index 000000000000..98116fa3d0a1 --- /dev/null +++ b/ui/app/hooks/tests/useCurrencyDisplay.test.js @@ -0,0 +1,121 @@ +import assert from 'assert' +import { renderHook } from '@testing-library/react-hooks' +import * as reactRedux from 'react-redux' +import { useCurrencyDisplay } from '../useCurrencyDisplay' +import sinon from 'sinon' + +const tests = [ + { + input: { + value: '0x2386f26fc10000', + numberOfDecimals: 2, + currency: 'usd', + }, + result: { + value: '$2.80', + suffix: 'USD', + displayValue: '$2.80 USD', + }, + }, + { + input: { + value: '0x2386f26fc10000', + currency: 'usd', + }, + result: { + value: '$2.80', + suffix: 'USD', + displayValue: '$2.80 USD', + }, + }, + { + input: { + value: '0x1193461d01595930', + currency: 'ETH', + numberOfDecimals: 3, + }, + result: { + value: '1.266', + suffix: 'ETH', + displayValue: '1.266 ETH', + }, + }, + { + input: { + value: '0x1193461d01595930', + currency: 'ETH', + numberOfDecimals: 3, + hideLabel: true, + }, + result: { + value: '1.266', + suffix: undefined, + displayValue: '1.266', + }, + }, + { + input: { + value: '0x3b9aca00', + currency: 'ETH', + denomination: 'GWEI', + hideLabel: true, + }, + result: { + value: '1', + suffix: undefined, + displayValue: '1', + }, + }, + { + input: { + value: '0x3b9aca00', + currency: 'ETH', + denomination: 'WEI', + hideLabel: true, + }, + result: { + value: '1000000000', + suffix: undefined, + displayValue: '1000000000', + }, + }, + { + input: { + value: '0x3b9aca00', + currency: 'ETH', + numberOfDecimals: 100, + hideLabel: true, + }, + result: { + value: '0.000000001', + suffix: undefined, + displayValue: '0.000000001', + }, + }, +] + + +describe('useCurrencyDisplay', function () { + tests.forEach(({ input: { value, ...restProps }, result }) => { + describe(`when input is { value: ${value}, decimals: ${restProps.numberOfDecimals}, denomation: ${restProps.denomination} }`, function () { + const stub = sinon.stub(reactRedux, 'useSelector') + stub.callsFake(() => ({ + currentCurrency: 'usd', + nativeCurrency: 'ETH', + conversionRate: 280.45, + })) + const hookReturn = renderHook(() => useCurrencyDisplay(value, restProps)) + const [ displayValue, parts ] = hookReturn.result.current + stub.restore() + it(`should return ${result.displayValue} as displayValue`, function () { + assert.equal(displayValue, result.displayValue) + }) + it(`should return ${result.value} as value`, function () { + assert.equal(parts.value, result.value) + }) + it(`should return ${result.suffix} as suffix`, function () { + assert.equal(parts.suffix, result.suffix) + }) + }) + }) +}) diff --git a/ui/app/hooks/tests/useTokenDisplayValue.test.js b/ui/app/hooks/tests/useTokenDisplayValue.test.js new file mode 100644 index 000000000000..bb8464f8a28c --- /dev/null +++ b/ui/app/hooks/tests/useTokenDisplayValue.test.js @@ -0,0 +1,136 @@ +import assert from 'assert' +import { renderHook } from '@testing-library/react-hooks' +import * as tokenUtil from '../../helpers/utils/token-util' +import * as txUtil from '../../helpers/utils/transactions.util' +import { useTokenDisplayValue } from '../useTokenDisplayValue' +import sinon from 'sinon' + +const tests = [ + { + token: { + symbol: 'DAI', + decimals: 18, + }, + tokenData: { + params: 'decoded-params1', + }, + tokenValue: '1000000000000000000', + displayValue: '1', + }, + { + token: { + symbol: 'DAI', + decimals: 18, + }, + tokenData: { + params: 'decoded-params2', + }, + tokenValue: '10000000000000000000', + displayValue: '10', + }, + { + token: { + symbol: 'DAI', + decimals: 18, + }, + tokenData: { + params: 'decoded-params3', + }, + tokenValue: '1500000000000000000', + displayValue: '1.5', + }, + { + token: { + symbol: 'DAI', + decimals: 18, + }, + tokenData: { + params: 'decoded-params4', + }, + tokenValue: '1756000000000000000', + displayValue: '1.756', + }, + { + token: { + symbol: 'DAI', + decimals: 18, + }, + tokenData: { + params: 'decoded-params5', + }, + tokenValue: '25500000000000000000', + displayValue: '25.5', + }, + { + token: { + symbol: 'USDC', + decimals: 6, + }, + tokenData: { + params: 'decoded-params6', + }, + tokenValue: '1000000', + displayValue: '1', + }, + { + token: { + symbol: 'USDC', + decimals: 6, + }, + tokenData: { + params: 'decoded-params7', + }, + tokenValue: '10000000', + displayValue: '10', + }, + { + token: { + symbol: 'USDC', + decimals: 6, + }, + tokenData: { + params: 'decoded-params8', + }, + tokenValue: '1500000', + displayValue: '1.5', + }, + { + token: { + symbol: 'USDC', + decimals: 6, + }, + tokenData: { + params: 'decoded-params9', + }, + tokenValue: '1756000', + displayValue: '1.756', + }, + { + token: { + symbol: 'USDC', + decimals: 6, + }, + tokenData: { + params: 'decoded-params10', + }, + tokenValue: '25500000', + displayValue: '25.5', + }, +] + + +describe('useTokenDisplayValue', function () { + tests.forEach((test, idx) => { + describe(`when input is decimals: ${test.token.decimals} and value: ${test.tokenValue}`, function () { + it(`should return ${test.displayValue} as displayValue`, function () { + const getTokenValueStub = sinon.stub(tokenUtil, 'getTokenValue') + const getTokenDataStub = sinon.stub(txUtil, 'getTokenData') + getTokenDataStub.callsFake(() => test.tokenData) + getTokenValueStub.callsFake(() => test.tokenValue) + const { result } = renderHook(() => useTokenDisplayValue(`${idx}-fakestring`, test.token)) + sinon.restore() + assert.equal(result.current, test.displayValue) + }) + }) + }) +}) diff --git a/ui/app/hooks/tests/useUserPreferencedCurrency.test.js b/ui/app/hooks/tests/useUserPreferencedCurrency.test.js new file mode 100644 index 000000000000..c984fa0d869d --- /dev/null +++ b/ui/app/hooks/tests/useUserPreferencedCurrency.test.js @@ -0,0 +1,143 @@ +import assert from 'assert' +import { renderHook } from '@testing-library/react-hooks' +import { useUserPreferencedCurrency } from '../useUserPreferencedCurrency' +import * as reactRedux from 'react-redux' +import { preferencesSelector, getShouldShowFiat } from '../../selectors' +import sinon from 'sinon' + +const tests = [ + { + state: { + useNativeCurrencyAsPrimaryCurrency: true, + nativeCurrency: 'ETH', + showFiat: true, + }, + params: { + type: 'PRIMARY', + }, + result: { + currency: 'ETH', + numberOfDecimals: 6, + }, + }, + { + state: { + useNativeCurrencyAsPrimaryCurrency: false, + nativeCurrency: 'ETH', + showFiat: true, + }, + params: { + type: 'PRIMARY', + }, + result: { + currency: undefined, + numberOfDecimals: 2, + }, + }, + { + state: { + useNativeCurrencyAsPrimaryCurrency: true, + nativeCurrency: 'ETH', + showFiat: true, + }, + params: { + type: 'SECONDARY', + fiatNumberOfDecimals: 4, + fiatPrefix: '-', + }, + result: { + currency: undefined, + numberOfDecimals: 4, + }, + }, + { + state: { + useNativeCurrencyAsPrimaryCurrency: false, + nativeCurrency: 'ETH', + showFiat: true, + }, + params: { + type: 'SECONDARY', + fiatNumberOfDecimals: 4, + numberOfDecimals: 3, + fiatPrefix: 'a', + }, + result: { + currency: 'ETH', + numberOfDecimals: 3, + }, + }, + { + state: { + useNativeCurrencyAsPrimaryCurrency: false, + nativeCurrency: 'ETH', + showFiat: false, + }, + params: { + type: 'PRIMARY', + }, + result: { + currency: 'ETH', + numberOfDecimals: 6, + }, + }, + { + state: { + useNativeCurrencyAsPrimaryCurrency: false, + nativeCurrency: 'ETH', + showFiat: true, + }, + params: { + type: 'PRIMARY', + }, + result: { + currency: undefined, + numberOfDecimals: 2, + }, + }, + { + state: { + useNativeCurrencyAsPrimaryCurrency: false, + nativeCurrency: 'ETH', + showFiat: true, + }, + params: { + type: 'PRIMARY', + }, + result: { + currency: undefined, + numberOfDecimals: 2, + }, + }, +] + +function getFakeUseSelector (state) { + return (selector) => { + if (selector === preferencesSelector) { + return state + } else if (selector === getShouldShowFiat) { + return state.showFiat + } else { + return state.nativeCurrency + } + } +} + + +describe('useUserPreferencedCurrency', function () { + tests.forEach(({ params: { type, ...otherParams }, state, result }) => { + describe(`when showFiat is ${state.showFiat}, useNativeCurrencyAsPrimary is ${state.useNativeCurrencyAsPrimaryCurrency} and type is ${type}`, function () { + const stub = sinon.stub(reactRedux, 'useSelector') + stub.callsFake(getFakeUseSelector(state)) + + const { result: hookResult } = renderHook(() => useUserPreferencedCurrency(type, otherParams)) + stub.restore() + it(`should return currency as ${result.currency || 'not modified by user preferences'}`, function () { + assert.equal(hookResult.current.currency, result.currency) + }) + it(`should return decimals as ${result.numberOfDecimals || 'not modified by user preferences'}`, function () { + assert.equal(hookResult.current.numberOfDecimals, result.numberOfDecimals) + }) + }) + }) +}) diff --git a/ui/app/hooks/useCurrencyDisplay.js b/ui/app/hooks/useCurrencyDisplay.js new file mode 100644 index 000000000000..e00d328a6d5d --- /dev/null +++ b/ui/app/hooks/useCurrencyDisplay.js @@ -0,0 +1,69 @@ +import { useMemo } from 'react' +import { useSelector } from 'react-redux' +import { formatCurrency, getValueFromWeiHex } from '../helpers/utils/confirm-tx.util' + +/** + * Defines the shape of the options parameter for useCurrencyDisplay + * @typedef {Object} UseCurrencyOptions + * @property {string} [displayValue] - When present is used in lieu of formatting the inputValue + * @property {string} [prefix] - String to prepend to the final result + * @property {number} [numberOfDecimals] - Number of significant decimals to display + * @property {string} [denomination] - Denomination (wei, gwei) to convert to for display + * @property {string} [currency] - Currency type to convert to. Will override nativeCurrency + */ + +/** + * Defines the return shape of the second value in the tuple + * @typedef {Object} CurrencyDisplayParts + * @property {string} [prefix] - string to prepend to the value for display + * @property {string} value - string representing the value, formatted for display + * @property {string} [suffix] - string to append to the value for display + */ + +/** + * useCurrencyDisplay hook + * + * Given a hexadecimal encoded value string and an object of parameters used for formatting the + * display, produce both a fully formed string and the pieces of that string used for displaying + * the currency to the user + * @param {string} inputValue - The value to format for display + * @param {UseCurrencyOptions} opts - An object for options to format the inputValue + * @return {[string, CurrencyDisplayParts]} + */ +export function useCurrencyDisplay (inputValue, { displayValue, prefix, numberOfDecimals, denomination, currency, ...opts }) { + const { currentCurrency, nativeCurrency, conversionRate } = useSelector( + ({ metamask: { currentCurrency, nativeCurrency, conversionRate } }) => ({ + currentCurrency, + nativeCurrency, + conversionRate, + }) + ) + + const toCurrency = currency || currentCurrency + + const value = useMemo(() => { + if (displayValue) { + return displayValue + } + return formatCurrency( + getValueFromWeiHex({ + value: inputValue, + fromCurrency: nativeCurrency, + toCurrency, + conversionRate, + numberOfDecimals: numberOfDecimals || 2, + toDenomination: denomination, + }), + toCurrency + ) + }, [inputValue, nativeCurrency, conversionRate, displayValue, numberOfDecimals, denomination, toCurrency]) + + let suffix + + if (!opts.hideLabel) { + suffix = opts.suffix || toCurrency.toUpperCase() + } + + + return [`${prefix || ''}${value}${suffix ? ' ' + suffix : ''}`, { prefix, value, suffix }] +} diff --git a/ui/app/hooks/useTokenDisplayValue.js b/ui/app/hooks/useTokenDisplayValue.js new file mode 100644 index 000000000000..eea71f68a5bd --- /dev/null +++ b/ui/app/hooks/useTokenDisplayValue.js @@ -0,0 +1,38 @@ +import { getTokenValue, calcTokenAmount } from '../helpers/utils/token-util' +import { getTokenData } from '../helpers/utils/transactions.util' +import { useMemo } from 'react' + +/** + * Defines the shape for the Token input parameter for useTokenDisplayValue + * @typedef {Object} Token + * @property {string} symbol - The string to use as a suffix for the token (eg. DAI) + * @property {number} decimals - The number of decimals to show when displaying this type of token + */ + +/** + * useTokenDisplayValue + * Given the data string from txParams and a token object with symbol and decimals, return + * a displayValue that represents a string representing that token amount as a string. Also + * return a tokenData object for downstream usage and the suffix for the token to use as props + * for other hooks and/or components + * @param {string} transactionData + * @param {Token} token + * @return {string} - The computed displayValue of the provided transactionData and token + */ +export function useTokenDisplayValue (transactionData, token) { + if (!transactionData || !token) { + return null + } + const tokenData = useMemo(() => getTokenData(transactionData), [transactionData]) + if (!tokenData?.params?.length) { + return null + } + const { decimals } = token + + const displayValue = useMemo(() => { + const tokenValue = getTokenValue(tokenData.params) + return calcTokenAmount(tokenValue, decimals).toString() + }, [tokenData, decimals]) + + return displayValue +} diff --git a/ui/app/hooks/useUserPreferencedCurrency.js b/ui/app/hooks/useUserPreferencedCurrency.js new file mode 100644 index 000000000000..8bd7f0e463ff --- /dev/null +++ b/ui/app/hooks/useUserPreferencedCurrency.js @@ -0,0 +1,52 @@ +import { preferencesSelector, getShouldShowFiat } from '../selectors' +import { useSelector } from 'react-redux' +import { PRIMARY, SECONDARY, ETH } from '../helpers/constants/common' + +/** + * Defines the shape of the options parameter for useUserPreferencedCurrency + * @typedef {Object} UseUserPreferencedCurrencyOptions + * @property {number} [numberOfDecimals] - Number of significant decimals to display + * @property {number} [ethNumberOfDecimals] - Number of significant decimals to display + * when using ETH + * @property {number} [fiatNumberOfDecimals] - Number of significant decimals to display + * when using fiat + */ + +/** + * Defines the return shape of useUserPreferencedCurrency + * @typedef {Object} UserPreferredCurrency + * @property {string} currency - the currency type to use (eg: 'ETH', 'usd') + * @property {number} numberOfDecimals - Number of significant decimals to display + */ + +/** + * useUserPreferencedCurrency + * + * returns an object that contains what currency to use for displaying values based + * on the user's preference settings, as well as the significant number of decimals + * to display based on the currency + * @param {"PRIMARY" | "SECONDARY"} type - what display type is being rendered + * @param {UseUserPreferencedCurrencyOptions} opts - options to override default values + * @return {UserPreferredCurrency} + */ +export function useUserPreferencedCurrency (type, opts = {}) { + const nativeCurrency = useSelector((state) => state.metamask.nativeCurrency) + const { + useNativeCurrencyAsPrimaryCurrency, + } = useSelector(preferencesSelector) + const showFiat = useSelector(getShouldShowFiat) + + let currency, numberOfDecimals + if (!showFiat || (type === PRIMARY && useNativeCurrencyAsPrimaryCurrency) || + (type === SECONDARY && !useNativeCurrencyAsPrimaryCurrency)) { + // Display ETH + currency = nativeCurrency || ETH + numberOfDecimals = opts.numberOfDecimals || opts.ethNumberOfDecimals || 6 + } else if ((type === SECONDARY && useNativeCurrencyAsPrimaryCurrency) || + (type === PRIMARY && !useNativeCurrencyAsPrimaryCurrency)) { + // Display Fiat + numberOfDecimals = opts.numberOfDecimals || opts.fiatNumberOfDecimals || 2 + } + + return { currency, numberOfDecimals } +} diff --git a/ui/app/pages/permissions-connect/choose-account/choose-account.component.js b/ui/app/pages/permissions-connect/choose-account/choose-account.component.js index 3758d556ba2a..51591430464f 100644 --- a/ui/app/pages/permissions-connect/choose-account/choose-account.component.js +++ b/ui/app/pages/permissions-connect/choose-account/choose-account.component.js @@ -104,7 +104,6 @@ export default class ChooseAccount extends Component { value={balance} style={{ color: '#6A737D' }} suffix={nativeCurrency} - hideLabel /> diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js index 952a5a94ba18..891414e46d51 100644 --- a/ui/app/selectors/selectors.js +++ b/ui/app/selectors/selectors.js @@ -285,6 +285,12 @@ export function preferencesSelector ({ metamask }) { return metamask.preferences } +export function getShouldShowFiat (state) { + const isMainNet = getIsMainnet(state) + const { showFiatInTestnets } = preferencesSelector(state) + return isMainNet || showFiatInTestnets +} + export function getAdvancedInlineGasShown (state) { return Boolean(state.metamask.featureFlags.advancedInlineGas) } diff --git a/yarn.lock b/yarn.lock index 262c67bccee4..14249d9a1c9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1088,6 +1088,13 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.5.4": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f" + integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" @@ -2653,6 +2660,14 @@ dependencies: defer-to-connect "^1.0.1" +"@testing-library/react-hooks@^3.2.1": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-3.2.1.tgz#19b6caa048ef15faa69d439c469033873ea01294" + integrity sha512-1OB6Ksvlk6BCJA1xpj8/WWz0XVd1qRcgqdaFAq+xeC6l61Ucj0P6QpA5u+Db/x9gU4DCX8ziR5b66Mlfg0M2RA== + dependencies: + "@babel/runtime" "^7.5.4" + "@types/testing-library__react-hooks" "^3.0.0" + "@types/babel__core@^7.1.0": version "7.1.3" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.3.tgz#e441ea7df63cd080dfcd02ab199e6d16a735fc30" @@ -2839,6 +2854,13 @@ dependencies: "@types/react" "*" +"@types/react-test-renderer@*": + version "16.9.2" + resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.2.tgz#e1c408831e8183e5ad748fdece02214a7c2ab6c5" + integrity sha512-4eJr1JFLIAlWhzDkBCkhrOIWOvOxcCAfQh+jiKg7l/nNZcCIL2MHl2dZhogIFKyHzedVWHaVP1Yydq/Ruu4agw== + dependencies: + "@types/react" "*" + "@types/react-textarea-autosize@^4.3.3": version "4.3.5" resolved "https://registry.yarnpkg.com/@types/react-textarea-autosize/-/react-textarea-autosize-4.3.5.tgz#6c4d2753fa1864c98c0b2b517f67bb1f6e4c46de" @@ -2872,6 +2894,14 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/testing-library__react-hooks@^3.0.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@types/testing-library__react-hooks/-/testing-library__react-hooks-3.2.0.tgz#52f3a109bef06080e3b1e3ae7ea1c014ce859897" + integrity sha512-dE8iMTuR5lzB+MqnxlzORlXzXyCL0EKfzH0w/lau20OpkHD37EaWjZDz0iNG8b71iEtxT4XKGmSKAGVEqk46mw== + dependencies: + "@types/react" "*" + "@types/react-test-renderer" "*" + "@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" @@ -23991,6 +24021,11 @@ regenerator-runtime@^0.13.2: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447" integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA== +regenerator-runtime@^0.13.4: + version "0.13.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" + integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== + regenerator-transform@^0.10.0: version "0.10.1" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd"