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 @@ -71,14 +71,26 @@ } `; -const SendBCH = ({ filledAddress, callbackTxId }) => { - const { - wallet, - fiatPrice, - balances, - slpBalancesAndUtxos, - apiError, - } = React.useContext(WalletContext); +// Note jestBCH is only used for unit tests; BCHJS must be mocked for jest +const SendBCH = ({ jestBCH, filledAddress, callbackTxId }) => { + // use balance parameters from wallet.state object and not legacy balances parameter from walletState, if user has migrated wallet + // this handles edge case of user with old wallet who has not opened latest Cashtab version yet + + // If the wallet object from ContextValue has a `state key`, then check which keys are in the wallet object + // Else set it as blank + const ContextValue = React.useContext(WalletContext); + const { wallet, fiatPrice, slpBalancesAndUtxos, apiError } = ContextValue; + let balances; + const paramsInWalletState = wallet.state ? Object.keys(wallet.state) : []; + // If wallet.state includes balances and parsedTxHistory params, use these + // These are saved in indexedDb in the latest version of the app, hence accessible more quickly + if (paramsInWalletState.includes('balances')) { + balances = wallet.state.balances; + } else { + // If balances and parsedTxHistory are not in the wallet.state object, load them from Context + // This is how the app used to work + balances = ContextValue.balances; + } // Get device window width // If this is less than 769, the page will open with QR scanner open @@ -117,7 +129,9 @@ }; const { getBCH, getRestUrl, sendBch, calcFee } = useBCH(); - const BCH = getBCH(); + + // jestBCH is only ever specified for unit tests, otherwise app will use getBCH(); + const BCH = jestBCH ? jestBCH : getBCH(); // If the balance has changed, unlock the UI // This is redundant, if backend has refreshed in 1.75s timeout below, UI will already be unlocked @@ -154,7 +168,10 @@ function populateFormsFromUrl(txInfo) { if (txInfo && txInfo.address && txInfo.value) { - setFormData({ address: txInfo.address, value: txInfo.value }); + setFormData({ + address: txInfo.address, + value: txInfo.value, + }); } } @@ -311,7 +328,12 @@ setSelectedCurrency(currency.ticker); // Use this object to mimic user input and get validation for the value - let amountObj = { target: { name: 'value', value: amount } }; + let amountObj = { + target: { + name: 'value', + value: amount, + }, + }; handleBchAmountChange(amountObj); setFormData({ ...formData, @@ -329,7 +351,10 @@ const handleSelectedCurrencyChange = e => { setSelectedCurrency(e); // Clear input field to prevent accidentally sending 1 BCH instead of 1 USD - setFormData(p => ({ ...p, value: '' })); + setFormData(p => ({ + ...p, + value: '', + })); }; const handleBchAmountChange = e => { @@ -343,7 +368,10 @@ ); setSendBchAmountError(error); - setFormData(p => ({ ...p, [name]: value })); + setFormData(p => ({ + ...p, + [name]: value, + })); }; const onMax = async () => { @@ -428,7 +456,11 @@ -
+ = {fiatPriceString} -
+
{!balances.totalBalance || apiError || sendBchAmountError || @@ -514,7 +550,11 @@ {apiError && ( <> -

+

An error occured on our end. Reconnecting... diff --git a/web/cashtab/src/components/Send/__tests__/Send.test.js b/web/cashtab/src/components/Send/__tests__/Send.test.js new file mode 100644 --- /dev/null +++ b/web/cashtab/src/components/Send/__tests__/Send.test.js @@ -0,0 +1,130 @@ +import React from 'react'; +import renderer from 'react-test-renderer'; +import { ThemeProvider } from 'styled-components'; +import { theme } from '@assets/styles/theme'; +import Send from '@components/Send/Send'; +import BCHJS from '@psf/bch-js'; +import { + walletWithBalancesAndTokens, + walletWithBalancesMock, + walletWithoutBalancesMock, + walletWithBalancesAndTokensWithCorrectState, + walletWithBalancesAndTokensWithEmptyState, +} from '../../Wallet/__mocks__/walletAndBalancesMock'; +import { BrowserRouter as Router } from 'react-router-dom'; + +let realUseContext; +let useContextMock; + +beforeEach(() => { + realUseContext = React.useContext; + useContextMock = React.useContext = jest.fn(); + + // Mock method not implemented in JSDOM + // See reference at https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), + }); +}); + +afterEach(() => { + React.useContext = realUseContext; +}); + +test('Wallet without BCH balance', () => { + useContextMock.mockReturnValue(walletWithoutBalancesMock); + const testBCH = new BCHJS(); + const component = renderer.create( + + + + + , + ); + let tree = component.toJSON(); + expect(tree).toMatchSnapshot(); +}); + +test('Wallet with BCH balances', () => { + useContextMock.mockReturnValue(walletWithBalancesMock); + const testBCH = new BCHJS(); + const component = renderer.create( + + + + + , + ); + let tree = component.toJSON(); + expect(tree).toMatchSnapshot(); +}); + +test('Wallet with BCH balances and tokens', () => { + useContextMock.mockReturnValue(walletWithBalancesAndTokens); + const testBCH = new BCHJS(); + const component = renderer.create( + + + + + , + ); + let tree = component.toJSON(); + expect(tree).toMatchSnapshot(); +}); + +test('Wallet with BCH balances and tokens and state field', () => { + useContextMock.mockReturnValue(walletWithBalancesAndTokensWithCorrectState); + const testBCH = new BCHJS(); + const component = renderer.create( + + + + + , + ); + let tree = component.toJSON(); + expect(tree).toMatchSnapshot(); +}); + +test('Wallet with BCH balances and tokens and state field, but no params in state', () => { + useContextMock.mockReturnValue(walletWithBalancesAndTokensWithEmptyState); + const testBCH = new BCHJS(); + const component = renderer.create( + + + + + , + ); + let tree = component.toJSON(); + expect(tree).toMatchSnapshot(); +}); + +test('Without wallet defined', () => { + useContextMock.mockReturnValue({ + wallet: {}, + balances: { totalBalance: 0 }, + loading: false, + }); + const testBCH = new BCHJS(); + const component = renderer.create( + + + + + , + ); + let tree = component.toJSON(); + expect(tree).toMatchSnapshot(); +}); diff --git a/web/cashtab/src/components/Send/__tests__/__snapshots__/Send.test.js.snap b/web/cashtab/src/components/Send/__tests__/__snapshots__/Send.test.js.snap new file mode 100644 --- /dev/null +++ b/web/cashtab/src/components/Send/__tests__/__snapshots__/Send.test.js.snap @@ -0,0 +1,2141 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP @generated + +exports[`Wallet with BCH balances 1`] = ` +Array [ +

+

+ Available balance +

+

+ 0.06047469 + + BCHA +

+
, +
+ $ + NaN + + USD +
, +
+
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ + + + + + + +
+
+ + + + + BCHA + +
+ + + + + +
+ + max + +
+
+
+
+
+ +
+
+
+
+
+
+ = + $ NaN USD +
+
+ +
+ +
+
+
+
, +] +`; + +exports[`Wallet with BCH balances and tokens 1`] = ` +Array [ +
+

+ Available balance +

+

+ 0.06047469 + + BCHA +

+
, +
+ $ + NaN + + USD +
, +
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ + + + + + + +
+
+ + + + + BCHA + +
+ + + + + +
+ + max + +
+
+
+
+
+ +
+
+
+
+
+
+ = + $ NaN USD +
+
+ +
+
+
+
+
+
, +] +`; + +exports[`Wallet with BCH balances and tokens and state field 1`] = ` +Array [ +
+

+ Available balance +

+

+ 0.06047469 + + BCHA +

+
, +
+ $ + NaN + + USD +
, +
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ + + + + + + +
+
+ + + + + BCHA + +
+ + + + + +
+ + max + +
+
+
+
+
+ +
+
+
+
+
+
+ = + $ NaN USD +
+
+ +
+
+
+
+
+
, +] +`; + +exports[`Wallet with BCH balances and tokens and state field, but no params in state 1`] = ` +Array [ +
+

+ Available balance +

+

+ 0.06047469 + + BCHA +

+
, +
+ $ + NaN + + USD +
, +
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ + + + + + + +
+
+ + + + + BCHA + +
+ + + + + +
+ + max + +
+
+
+
+
+ +
+
+
+
+
+
+ = + $ NaN USD +
+
+ +
+
+
+
+
+
, +] +`; + +exports[`Wallet without BCH balance 1`] = ` +Array [ +
+ You currently have 0 + BCHA +
+ Deposit some funds to use this feature +
, +
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ + + + + + + +
+
+ + + + + BCHA + +
+ + + + + +
+ + max + +
+
+
+
+
+ +
+
+
+
+
+
+ = + $ NaN USD +
+
+ +
+
+
+
+
+
, +] +`; + +exports[`Without wallet defined 1`] = ` +Array [ +
+ You currently have 0 + BCHA +
+ Deposit some funds to use this feature +
, +
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ + + + + + + +
+
+ + + + + BCHA + +
+ + + + + +
+ + max + +
+
+
+
+
+ +
+
+
+
+
+
+ = + $ NaN USD +
+
+ +
+
+
+
+
+
, +] +`; diff --git a/web/cashtab/src/components/Wallet/__mocks__/walletAndBalancesMock.js b/web/cashtab/src/components/Wallet/__mocks__/walletAndBalancesMock.js --- a/web/cashtab/src/components/Wallet/__mocks__/walletAndBalancesMock.js +++ b/web/cashtab/src/components/Wallet/__mocks__/walletAndBalancesMock.js @@ -38,6 +38,7 @@ totalBalanceInSatoshis: 6047469, totalBalance: 0.06047469, }, + loading: false, }; export const walletWithoutBalancesMock = { @@ -78,6 +79,7 @@ balances: { totalBalance: 0, }, + loading: false, }; export const walletWithBalancesAndTokens = { @@ -151,6 +153,7 @@ hasBaton: false, }, ], + loading: false, }; export const walletWithBalancesAndTokensWithCorrectState = { @@ -264,6 +267,7 @@ hasBaton: false, }, ], + loading: false, }; export const walletWithBalancesAndTokensWithEmptyState = { @@ -338,4 +342,5 @@ hasBaton: false, }, ], + loading: false, };