diff --git a/web/cashtab/src/components/Send/Send.js b/web/cashtab/src/components/Send/Send.js --- a/web/cashtab/src/components/Send/Send.js +++ b/web/cashtab/src/components/Send/Send.js @@ -22,6 +22,7 @@ toLegacy, } from '@components/Common/Ticker.js'; import { Event } from '@utils/GoogleAnalytics'; +import { shouldRejectAmountInput } from '@utils/validation'; export const BalanceHeader = styled.div` p { color: #777; @@ -332,27 +333,13 @@ const handleBchAmountChange = e => { const { value, name } = e.target; - let error = false; let bchValue = value; - - if (selectedCurrency === 'USD') { - bchValue = (value / fiatPrice).toFixed(8); - } - - // Validate value for > 0 - if (isNaN(bchValue)) { - error = 'Amount must be a number'; - } else if (bchValue <= 0) { - error = 'Amount must be greater than 0'; - } else if (bchValue < currency.dust) { - error = `Send amount must be at least ${currency.dust} ${currency.ticker}`; - } else if (bchValue > balances.totalBalance) { - error = `Amount cannot exceed your ${currency.ticker} balance`; - } else if (!isNaN(bchValue) && bchValue.toString().includes('.')) { - if (bchValue.toString().split('.')[1].length > 8) { - error = `${currency.ticker} transactions do not support more than 8 decimal places`; - } - } + const error = shouldRejectAmountInput( + bchValue, + selectedCurrency, + fiatPrice, + balances.totalBalance, + ); setSendBchAmountError(error); setFormData(p => ({ ...p, [name]: value })); diff --git a/web/cashtab/src/utils/__tests__/validation.test.js b/web/cashtab/src/utils/__tests__/validation.test.js new file mode 100644 --- /dev/null +++ b/web/cashtab/src/utils/__tests__/validation.test.js @@ -0,0 +1,74 @@ +import { shouldRejectAmountInput } from '../validation'; +import { currency } from '@components/Common/Ticker.js'; + +describe('Validation utils', () => { + it(`Returns 'false' if ${currency.ticker} send amount is a valid send amount`, () => { + expect(shouldRejectAmountInput('10', currency.ticker, 20.0, 300)).toBe( + false, + ); + }); + it(`Returns 'false' if ${currency.ticker} send amount is a valid send amount in USD`, () => { + // Here, user is trying to send $170 USD, where 1 BCHA = $20 USD, and the user has a balance of 15 BCHA or $300 + expect(shouldRejectAmountInput('170', 'USD', 20.0, 15)).toBe(false); + }); + it(`Returns not a number if ${currency.ticker} send amount is not a number`, () => { + const expectedValidationError = `Amount must be a number`; + expect( + shouldRejectAmountInput('Not a number', currency.ticker, 20.0, 3), + ).toBe(expectedValidationError); + }); + it(`Returns amount must be greater than 0 if ${currency.ticker} send amount is 0`, () => { + const expectedValidationError = `Amount must be greater than 0`; + expect(shouldRejectAmountInput('0', currency.ticker, 20.0, 3)).toBe( + expectedValidationError, + ); + }); + it(`Returns amount must be greater than 0 if ${currency.ticker} send amount is less than 0`, () => { + const expectedValidationError = `Amount must be greater than 0`; + expect( + shouldRejectAmountInput('-0.031', currency.ticker, 20.0, 3), + ).toBe(expectedValidationError); + }); + it(`Returns balance error if ${currency.ticker} send amount is greater than user balance`, () => { + const expectedValidationError = `Amount cannot exceed your ${currency.ticker} balance`; + expect(shouldRejectAmountInput('17', currency.ticker, 20.0, 3)).toBe( + expectedValidationError, + ); + }); + it(`Returns balance error if ${currency.ticker} send amount is greater than user balance`, () => { + const expectedValidationError = `Amount cannot exceed your ${currency.ticker} balance`; + expect(shouldRejectAmountInput('17', currency.ticker, 20.0, 3)).toBe( + expectedValidationError, + ); + }); + it(`Returns error if ${currency.ticker} send amount is less than ${currency.dust} minimum`, () => { + const expectedValidationError = `Send amount must be at least ${currency.dust} ${currency.ticker}`; + expect( + shouldRejectAmountInput( + (currency.dust - 0.00000001).toString(), + currency.ticker, + 20.0, + 3, + ), + ).toBe(expectedValidationError); + }); + it(`Returns error if ${currency.ticker} send amount is less than ${currency.dust} minimum in fiat currency`, () => { + const expectedValidationError = `Send amount must be at least ${currency.dust} ${currency.ticker}`; + expect( + shouldRejectAmountInput('0.0000005', 'USD', 14.63, 0.52574662), + ).toBe(expectedValidationError); + }); + it(`Returns balance error if ${currency.ticker} send amount is greater than user balance with fiat currency selected`, () => { + const expectedValidationError = `Amount cannot exceed your ${currency.ticker} balance`; + // Here, user is trying to send $170 USD, where 1 BCHA = $20 USD, and the user has a balance of 5 BCHA or $100 + expect(shouldRejectAmountInput('170', 'USD', 20.0, 5)).toBe( + expectedValidationError, + ); + }); + it(`Returns precision error if ${currency.ticker} send amount has more than 8 decimal places`, () => { + const expectedValidationError = `${currency.ticker} transactions do not support more than 8 decimal places`; + expect( + shouldRejectAmountInput('17.123456789', currency.ticker, 20.0, 35), + ).toBe(expectedValidationError); + }); +}); diff --git a/web/cashtab/src/utils/validation.js b/web/cashtab/src/utils/validation.js new file mode 100644 --- /dev/null +++ b/web/cashtab/src/utils/validation.js @@ -0,0 +1,34 @@ +import { currency } from '@components/Common/Ticker.js'; + +// Validate cash amount +export const shouldRejectAmountInput = ( + cashAmount, + selectedCurrency, + fiatPrice, + totalCashBalance, +) => { + // Take cashAmount as input, a string from form input + let error = false; + let testedAmount = cashAmount; + + if (selectedCurrency === 'USD') { + testedAmount = (cashAmount / fiatPrice).toFixed(8); + } + + // Validate value for > 0 + if (isNaN(testedAmount)) { + error = 'Amount must be a number'; + } else if (testedAmount <= 0) { + error = 'Amount must be greater than 0'; + } else if (testedAmount < currency.dust) { + error = `Send amount must be at least ${currency.dust} ${currency.ticker}`; + } else if (testedAmount > totalCashBalance) { + error = `Amount cannot exceed your ${currency.ticker} balance`; + } else if (!isNaN(testedAmount) && testedAmount.toString().includes('.')) { + if (testedAmount.toString().split('.')[1].length > 8) { + error = `${currency.ticker} transactions do not support more than 8 decimal places`; + } + } + // return false if no error, or string error msg if error + return error; +};