diff --git a/app/components/SetAmountButton.test.tsx b/app/components/SetAmountButton.test.tsx new file mode 100644 index 0000000000..4edd1f5068 --- /dev/null +++ b/app/components/SetAmountButton.test.tsx @@ -0,0 +1,20 @@ +import { render } from "@testing-library/react-native"; +import BigNumber from "bignumber.js"; +import * as React from "react"; +import { SetAmountButton } from "./SetAmountButton"; + +const buttonType: Array<'half' | 'max'> = ['half', 'max'] +const buttonAmount = new BigNumber(10); + +describe('set amount button', () => { + buttonType.forEach(type => { + it(`should match styling of set amount button type ${type}`, () => { + const onPress = jest.fn() + const component = ( + + ) + const rendered = render(component) + expect(rendered.toJSON()).toMatchSnapshot() + }) + }) +}) diff --git a/app/components/SetAmountButton.tsx b/app/components/SetAmountButton.tsx new file mode 100644 index 0000000000..7b725e36d1 --- /dev/null +++ b/app/components/SetAmountButton.tsx @@ -0,0 +1,35 @@ +import React from 'react' +import { TouchableOpacity } from 'react-native' +import { tailwind } from '../tailwind' +import { Text } from './Text' +import { translate } from '../translations' +import BigNumber from 'bignumber.js' + +export enum AmountButtonTypes { + half = '50%', + max = 'MAX' +} +interface SetAmountButtonProps { + type: AmountButtonTypes + onPress: (amount: string) => void + amount: BigNumber +} + +export function SetAmountButton (props: SetAmountButtonProps): JSX.Element { + const decimalPlace = 8 + + return ( + { + props.onPress(props.type === AmountButtonTypes.half ? props.amount.div(2).toFixed(decimalPlace) : props.amount.toFixed(decimalPlace)) + }} + > + {translate('components/max', props.type)} + + ) +} diff --git a/app/components/__snapshots__/SetAmountButton.test.tsx.snap b/app/components/__snapshots__/SetAmountButton.test.tsx.snap new file mode 100644 index 0000000000..2fdc2026cc --- /dev/null +++ b/app/components/__snapshots__/SetAmountButton.test.tsx.snap @@ -0,0 +1,117 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`set amount button should match styling of set amount button type half 1`] = ` + + + half + + +`; + +exports[`set amount button should match styling of set amount button type max 1`] = ` + + + max + + +`; diff --git a/app/screens/AppNavigator/screens/Balances/ConvertScreen.tsx b/app/screens/AppNavigator/screens/Balances/ConvertScreen.tsx index f5ab0703d7..73bc0310cf 100644 --- a/app/screens/AppNavigator/screens/Balances/ConvertScreen.tsx +++ b/app/screens/AppNavigator/screens/Balances/ConvertScreen.tsx @@ -15,6 +15,7 @@ import { Logging } from '../../../../api' import { Text, TextInput, View } from '../../../../components' import { Button } from '../../../../components/Button' import { getTokenIcon } from '../../../../components/icons/tokens/_index' +import { AmountButtonTypes, SetAmountButton } from '../../../../components/SetAmountButton' import { SectionTitle } from '../../../../components/SectionTitle' import { useTokensAPI } from '../../../../hooks/wallet/TokensAPI' import { RootState } from '../../../../store' @@ -131,27 +132,8 @@ function ConversionIOCard (props: { style?: StyleProp, mode: 'input' const iconType = props.unit === 'UTXO' ? '_UTXO' : 'DFI' const titlePrefix = props.mode === 'input' ? 'CONVERT' : 'TO' const title = `${translate('screens/Convert', titlePrefix)} ${props.unit}` - const DFIIcon = getTokenIcon(iconType) - const MaxButton = (): JSX.Element | null => { - if (props.mode === 'output') { - return null - } - return ( - { - if (props.onChange !== undefined) { - props.onChange(props.balance.toString()) - } - }} - > - {translate('components/max', 'MAX')} - - ) - } return ( @@ -170,15 +152,16 @@ function ConversionIOCard (props: { style?: StyleProp, mode: 'input' /> - - + + {translate('screens/Convert', 'Balance')}: {value}} /> - {MaxButton()} + {props.mode === 'input' && props.onChange && } + {props.mode === 'input' && props.onChange && } ) diff --git a/app/screens/AppNavigator/screens/Balances/screens/SendScreen.tsx b/app/screens/AppNavigator/screens/Balances/screens/SendScreen.tsx index 1d6b0eb616..e0ea93638a 100644 --- a/app/screens/AppNavigator/screens/Balances/screens/SendScreen.tsx +++ b/app/screens/AppNavigator/screens/Balances/screens/SendScreen.tsx @@ -17,6 +17,7 @@ import { Text, TextInput } from '../../../../../components' import { Button } from '../../../../../components/Button' import { getTokenIcon } from '../../../../../components/icons/tokens/_index' import { SectionTitle } from '../../../../../components/SectionTitle' +import { AmountButtonTypes, SetAmountButton } from '../../../../../components/SetAmountButton' import { useNetworkContext } from '../../../../../contexts/NetworkContext' import { useWhaleApiClient } from '../../../../../contexts/WhaleContext' import { RootState } from '../../../../../store' @@ -112,7 +113,9 @@ export function SendScreen ({ route, navigation }: Props): JSX.Element { /> { + token={token} + control={control} + onAmountButtonPress={async (amount) => { setValue('amount', amount) await trigger('amount') }} @@ -189,11 +192,11 @@ function AddressRow ({ interface AmountForm { control: Control token: WalletToken - onMaxPress: (amount: string) => void + onAmountButtonPress: (amount: string) => void fee: BigNumber } -function AmountRow ({ token, control, onMaxPress, fee }: AmountForm): JSX.Element { +function AmountRow ({ token, control, onAmountButtonPress, fee }: AmountForm): JSX.Element { const Icon = getTokenIcon(token.avatarSymbol) let maxAmount = token.symbol === 'DFI' ? new BigNumber(token.amount).minus(fee).toFixed(8) : token.amount maxAmount = BigNumber.max(maxAmount, 0).toFixed(8) @@ -234,20 +237,16 @@ function AmountRow ({ token, control, onMaxPress, fee }: AmountForm): JSX.Elemen name='amount' defaultValue='' /> - - + + {translate('screens/SendScreen', 'Balance: ')} {value}} /> - onMaxPress(maxAmount)}> - {translate('screens/SendScreen', 'MAX')} - - + + ) diff --git a/app/screens/AppNavigator/screens/Dex/DexAddLiquidity.tsx b/app/screens/AppNavigator/screens/Dex/DexAddLiquidity.tsx index a8819d30f7..1b09c4b272 100644 --- a/app/screens/AppNavigator/screens/Dex/DexAddLiquidity.tsx +++ b/app/screens/AppNavigator/screens/Dex/DexAddLiquidity.tsx @@ -4,11 +4,12 @@ import { StackScreenProps } from '@react-navigation/stack' import BigNumber from 'bignumber.js' import * as React from 'react' import { useCallback, useEffect, useState } from 'react' -import { ScrollView, TouchableOpacity } from 'react-native' +import { ScrollView } from 'react-native' import NumberFormat from 'react-number-format' import { Text, TextInput, View } from '../../../../components' import { Button } from '../../../../components/Button' import { getTokenIcon } from '../../../../components/icons/tokens/_index' +import { AmountButtonTypes, SetAmountButton } from '../../../../components/SetAmountButton' import { SectionTitle } from '../../../../components/SectionTitle' import { useTokensAPI } from '../../../../hooks/wallet/TokensAPI' import { tailwind } from '../../../../tailwind' @@ -45,11 +46,11 @@ export function AddLiquidityScreen (props: Props): JSX.Element { if (pair === undefined) return if (ref === 'primary') { setTokenAAmount(amountString) - setTokenBAmount(refAmount.times(pair.aToBRate).toString()) + setTokenBAmount(refAmount.times(pair.aToBRate).toFixed(8)) setSharePercentage(refAmount.div(pair.tokenA.reserve)) } else { setTokenBAmount(amountString) - setTokenAAmount(refAmount.times(pair.bToARate).toString()) + setTokenAAmount(refAmount.times(pair.bToARate).toFixed(8)) setSharePercentage(refAmount.div(pair.tokenB.reserve)) } }, [pair]) @@ -150,8 +151,8 @@ function TokenInput (props: { symbol: string, balance: BigNumber, current: strin {props.symbol} - - + + {translate('screens/AddLiquidity', 'Balance')}: - props.onChange(props.balance.toString())} - > - {translate('screens/AddLiquidity', 'MAX')} - - + + diff --git a/app/screens/AppNavigator/screens/Dex/PoolSwap/PoolSwapScreen.tsx b/app/screens/AppNavigator/screens/Dex/PoolSwap/PoolSwapScreen.tsx index 99b9841671..9a0f3e9bed 100644 --- a/app/screens/AppNavigator/screens/Dex/PoolSwap/PoolSwapScreen.tsx +++ b/app/screens/AppNavigator/screens/Dex/PoolSwap/PoolSwapScreen.tsx @@ -14,6 +14,7 @@ import { Logging } from '../../../../../api' import { Text, TextInput } from '../../../../../components' import { Button } from '../../../../../components/Button' import { getTokenIcon } from '../../../../../components/icons/tokens/_index' +import { AmountButtonTypes, SetAmountButton } from '../../../../../components/SetAmountButton' import { SectionTitle } from '../../../../../components/SectionTitle' import { useWallet } from '../../../../../contexts/WalletContext' import { useTokensAPI } from '../../../../../hooks/wallet/TokensAPI' @@ -198,8 +199,8 @@ function TokenRow (form: TokenForm): JSX.Element { name={controlName} defaultValue='' /> - - + + {translate('screens/PoolSwapScreen', 'Balance: ')} { (enableMaxButton != null && onChangeFromAmount !== undefined) && ( - onChangeFromAmount(token.amount)}> - {translate('screens/PoolSwapScreen', 'MAX')} - - + <> + + + ) } diff --git a/cypress/integration/functional/balances/convert/convert.spec.ts b/cypress/integration/functional/balances/convert/convert.spec.ts index cad66e1d01..f3bb9251c8 100644 --- a/cypress/integration/functional/balances/convert/convert.spec.ts +++ b/cypress/integration/functional/balances/convert/convert.spec.ts @@ -24,9 +24,24 @@ context('wallet/balances/convert - bi-direction success case', () => { // cy.getByTestID('button_continue_convert').should('not.be.enabled') }) + it('utxosToToken: should build summary correctly with max amount button', function () { + cy.getByTestID('MAX_amount_button').click() + cy.getByTestID('text_preview_input_value').contains('0 DFI') + cy.getByTestID('text_preview_output_value').contains('10 DFI') + cy.getByTestID('button_continue_convert').should('not.be.disabled') + }) + + it('utxosToToken: should build summary correctly with half amount button', function () { + cy.getByTestID('50%_amount_button').click() + cy.getByTestID('text_preview_input_value').contains('5 DFI') + cy.getByTestID('text_preview_output_value').contains('5 DFI') + cy.getByTestID('button_continue_convert').should('not.be.disabled') + }) + it('utxosToToken: should build summary correctly', function () { // https://github.com/cypress-io/cypress/issues/1171#issuecomment-364059485 cy.getByTestID('text_input_convert_from_input') + .clear() .invoke('attr', 'type', 'text') // cypress issue with numeric/decimal input, must cast .type('1.23') @@ -71,8 +86,23 @@ context('wallet/balances/convert - bi-direction success case', () => { cy.getByTestID('text_preview_output_value').contains('8.769').contains('DFI') }) + it('tokenToUtxos: should build summary correctly with max amount button', function () { + cy.getByTestID('MAX_amount_button').click() + cy.getByTestID('text_preview_input_value').should('contain', '0 DFI') + cy.getByTestID('text_preview_output_value').should('contain', '9.999').contains('DFI') + cy.getByTestID('button_continue_convert').should('not.be.disabled') + }) + + it('tokenToUtxos: should build summary correctly with half amount button', function () { + cy.getByTestID('50%_amount_button').click() + cy.getByTestID('text_preview_input_value').should('contain', '0.615 DFI') + cy.getByTestID('text_preview_output_value').should('contain', '9.384').contains('DFI') + cy.getByTestID('button_continue_convert').should('not.be.disabled') + }) + it('tokenToUtxos: should build summary correctly', function () { cy.getByTestID('text_input_convert_from_input') + .clear() .invoke('attr', 'type', 'text') // cypress issue with numeric/decimal input, must cast .type('0.4') diff --git a/cypress/integration/functional/balances/convert/convert_account_to_utxos.spec.ts b/cypress/integration/functional/balances/convert/convert_account_to_utxos.spec.ts index 5a6580f327..be5c485176 100644 --- a/cypress/integration/functional/balances/convert/convert_account_to_utxos.spec.ts +++ b/cypress/integration/functional/balances/convert/convert_account_to_utxos.spec.ts @@ -36,8 +36,16 @@ context('wallet/balances/convert - accountToUtxos', () => { cy.getByTestID('button_continue_convert').should('not.be.enabled') }) + it('should insert 50% of balance as amount on 50% pressed', function () { + cy.getByTestID('50%_amount_button').click() + + cy.getByTestID('text_preview_input_value').contains('5 DFI') + cy.getByTestID('text_preview_output_value').contains('15 DFI') + cy.getByTestID('button_continue_convert').should('not.be.disabled') + }) + it('should insert balance as amount on MAX pressed', function () { - cy.getByTestID('button_max_convert_from').click() + cy.getByTestID('MAX_amount_button').click() cy.getByTestID('text_preview_input_value').contains('0 DFI') cy.getByTestID('text_preview_output_value').contains('20 DFI') diff --git a/cypress/integration/functional/balances/convert/convert_utxos_to_account.spec.ts b/cypress/integration/functional/balances/convert/convert_utxos_to_account.spec.ts index f0f878c7ab..335fa5096f 100644 --- a/cypress/integration/functional/balances/convert/convert_utxos_to_account.spec.ts +++ b/cypress/integration/functional/balances/convert/convert_utxos_to_account.spec.ts @@ -34,8 +34,16 @@ context('wallet/balances/convert - utxosToAccount', () => { cy.getByTestID('button_continue_convert').should('not.be.enabled') }) + it('should insert balance as amount on 50% pressed', function () { + cy.getByTestID('50%_amount_button').click() + + cy.getByTestID('text_preview_input_value').contains('5 DFI') + cy.getByTestID('text_preview_output_value').contains('5 DFI') + cy.getByTestID('button_continue_convert').should('not.be.disabled') + }) + it('should insert balance as amount on MAX pressed', function () { - cy.getByTestID('button_max_convert_from').click() + cy.getByTestID('MAX_amount_button').click() cy.getByTestID('text_preview_input_value').contains('0 DFI') cy.getByTestID('text_preview_output_value').contains('10 DFI') diff --git a/cypress/integration/functional/balances/send.spec.ts b/cypress/integration/functional/balances/send.spec.ts index 5499b73050..0392a28772 100644 --- a/cypress/integration/functional/balances/send.spec.ts +++ b/cypress/integration/functional/balances/send.spec.ts @@ -50,13 +50,28 @@ context('wallet/send', () => { const maxValue = $txt[0].textContent.replace(' DFI', '') expect(new BigNumber(transactionFee).plus(maxValue).toFixed(0)).eq('10') cy.getByTestID('amount_input').clear() - cy.getByTestID('max_button').click() + cy.getByTestID('MAX_amount_button').click() cy.getByTestID('amount_input').should('have.value', maxValue) cy.getByTestID('send_submit_button').should('not.have.attr', 'disabled') }) }) }) + it('should be able to compute half of max values', function () { + cy.getByTestID('transaction_fee').then(($txt: any) => { + const transactionFee = $txt[0].textContent.replace(' DFI', '') + cy.getByTestID('max_value').then(($txt: any) => { + const maxValue = $txt[0].textContent.replace(' DFI', '') + const halfValue = new BigNumber(maxValue).div(2) + expect(new BigNumber(halfValue).multipliedBy(2).plus(transactionFee).toFixed(0)).eq('10') + cy.getByTestID('amount_input').clear() + cy.getByTestID('50%_amount_button').click() + cy.getByTestID('amount_input').should('have.value', halfValue.toFixed(8)) + cy.getByTestID('send_submit_button').should('not.have.attr', 'disabled') + }) + }) + }) + addresses.forEach((address) => { it(`should be able to send to address ${address}`, function () { cy.getByTestID('address_input').clear().type(address) @@ -98,7 +113,7 @@ context('wallet/send', () => { cy.getByTestID('balances_row_1_amount').contains(10).click() cy.getByTestID('send_button').click() cy.getByTestID('address_input').type(address) - cy.getByTestID('max_button').click() + cy.getByTestID('MAX_amount_button').click() cy.getByTestID('send_submit_button').click() cy.wait(5000).getByTestID('oceanInterface_close').click().wait(5000) cy.getByTestID('playground_wallet_fetch_balances').click() diff --git a/cypress/integration/functional/dex/add_liquidity.spec.ts b/cypress/integration/functional/dex/add_liquidity.spec.ts index 9c1fdcd123..74589407b8 100644 --- a/cypress/integration/functional/dex/add_liquidity.spec.ts +++ b/cypress/integration/functional/dex/add_liquidity.spec.ts @@ -1,12 +1,44 @@ context('app/dex/addLiquidity', () => { - beforeEach(function () { + before(function () { cy.createEmptyWallet(true) + cy.getByTestID('bottom_tab_dex').click() + cy.sendDFItoWallet() + .sendDFITokentoWallet() + .sendTokenToWallet(['BTC']).wait(10000) + cy.getByTestID('playground_wallet_fetch_balances').click() + cy.getByTestID('bottom_tab_dex').click() cy.getByTestID('pool_pair_add_DFI-BTC').click() + cy.wait(100) + cy.getByTestID('token_balance_primary').contains('10') + cy.getByTestID('token_balance_secondary').contains('10') + }) + + it('should update both token and build summary when click on max amount button', function () { + cy.getByTestID('MAX_amount_button').first().click() + cy.getByTestID('token_input_primary').should('have.value', '10.00000000') + cy.getByTestID('token_input_secondary').should('have.value', '10.00000000') + cy.getByTestID('a_per_b_price').contains('1') + cy.getByTestID('a_per_b_unit').contains('DFI per BTC') + cy.getByTestID('b_per_a_price').contains('1') + cy.getByTestID('b_per_a_unit').contains('BTC per DFI') + cy.getByTestID('share_of_pool').contains('1') + }) + + it('should update both token and build summary when click on half amount button', function () { + cy.getByTestID('token_input_primary').clear() + cy.getByTestID('50%_amount_button').first().click() + cy.getByTestID('token_input_primary').should('have.value', '5.00000000') + cy.getByTestID('token_input_secondary').should('have.value', '5.00000000') + cy.getByTestID('a_per_b_price').contains('1') + cy.getByTestID('a_per_b_unit').contains('DFI per BTC') + cy.getByTestID('b_per_a_price').contains('1') + cy.getByTestID('b_per_a_unit').contains('BTC per DFI') + cy.getByTestID('share_of_pool').contains('0.5') }) it('should update both token and build summary base on primary token input', function () { - cy.getByTestID('token_input_primary').invoke('val').should(text => expect(text).to.contain('0')) + cy.getByTestID('token_input_primary').clear().invoke('val').should(text => expect(text).to.contain('')) cy.getByTestID('token_input_secondary').invoke('val').should(text => expect(text).to.contain('0')) cy.getByTestID('token_input_primary').invoke('attr', 'type', 'text').type('1.23').trigger('change') @@ -22,7 +54,7 @@ context('app/dex/addLiquidity', () => { }) it('should update both token and build summary base on secondary token input', function () { - cy.getByTestID('token_input_secondary').invoke('attr', 'type', 'text').type('7.8').trigger('change') + cy.getByTestID('token_input_secondary').clear().invoke('attr', 'type', 'text').type('7.8').trigger('change') cy.getByTestID('token_input_primary').invoke('val').should(text => expect(text).to.contain('7.8')) cy.getByTestID('token_input_secondary').invoke('val').should(text => expect(text).to.contain('7.8')) diff --git a/cypress/integration/functional/dex/poolswap.spec.ts b/cypress/integration/functional/dex/poolswap.spec.ts index 14b9f70975..dcb81ded2d 100644 --- a/cypress/integration/functional/dex/poolswap.spec.ts +++ b/cypress/integration/functional/dex/poolswap.spec.ts @@ -54,7 +54,7 @@ context('poolswap with values', () => { }) it('should be able to click max', function () { - cy.getByTestID('max_button_token_a').click().wait(3000) + cy.getByTestID('MAX_amount_button').click().wait(3000) cy.getByTestID('text_input_tokenA').should('have.value', '10.00000000') cy.getByTestID('text_price_row_minimum_0').then(($txt: any) => { const tokenValue = $txt[0].textContent.replace(' LTC', '').replace(',', '') @@ -62,6 +62,15 @@ context('poolswap with values', () => { }) }) + it('should be able to click half', function () { + cy.getByTestID('50%_amount_button').click().wait(3000) + cy.getByTestID('text_input_tokenA').should('have.value', '5.00000000') + cy.getByTestID('text_price_row_minimum_0').then(($txt: any) => { + const tokenValue = $txt[0].textContent.replace(' LTC', '').replace(',', '') + cy.getByTestID('text_input_tokenB').should('have.value', new BigNumber(tokenValue).div(2).toFixed(8)) + }) + }) + it('should be able to swap', function () { cy.getByTestID('text_price_row_minimum_0').then(() => { // const tokenValue = $txt[0].textContent.replace(' LTC', '').replace(',', '') diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 57afeacbbf..cac72a7196 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -64,7 +64,7 @@ declare global { } Cypress.Commands.add('getByTestID', (selector, ...args) => { - return cy.get(`[data-testid=${selector}]`, ...args) + return cy.get(`[data-testid=${Cypress.$.escapeSelector(selector)}]`, ...args) }) Cypress.Commands.add('createEmptyWallet', (isRandom: boolean = false) => {