-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(earn): Add Crypto bottom sheet (#5376)
### Description Creates new component for the Earn "Add Crypto" bottom sheet. This component should be used in the same way as the TokenDetailsMoreActions component [is used](https://github.com/valora-inc/wallet/blob/6df3859e7271db5e643aba11eda8cde19151135d/src/tokens/TokenDetails.tsx#L119). Decided to make a new component as to avoid adding extra props to decide copy, choose actions, and add an amount prop which would not be used in the TokenDetailsMoreActions case. Talked with product and Nitya and confirmed that: - On the QR code page, have title be "Deposit Crypto" like in the cash-in exchange case - Use "Arbitrum One" as the network name in copy Note: Price is not passed to the swap component, there is not support for that and based on discussion in quick sync it seems hard. ### Test plan Unit tests added. **Manual testing:** Bottom sheet: ![bottom-sheet](https://github.com/valora-inc/wallet/assets/140328381/0a029234-6bba-46dd-8512-6a73a12b660a) Tapping Buy: https://github.com/valora-inc/wallet/assets/140328381/2a8985ca-9ae2-41e1-b0f7-abed4071b723 Tapping Transfer: https://github.com/valora-inc/wallet/assets/140328381/61e38183-d641-43c8-9d2d-39af4236215d Tapping Swap: https://github.com/valora-inc/wallet/assets/140328381/393d00a6-41d7-49fc-a0f7-162e1ed15e71 Tapping ### Related issues - Fixes #ACT-1177 ### Backwards compatibility Yes, new component so not changing past stuff ### Network scalability If a new NetworkId and/or Network are added in the future, the changes in this PR will: - [X] Continue to work without code changes, OR trigger a compilation error (guaranteeing we find it when a new network is added)
- Loading branch information
1 parent
d5e7740
commit 7d421cf
Showing
10 changed files
with
395 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
import { fireEvent, render } from '@testing-library/react-native' | ||
import BigNumber from 'bignumber.js' | ||
import React from 'react' | ||
import { Provider } from 'react-redux' | ||
import { EarnEvents } from 'src/analytics/Events' | ||
import ValoraAnalytics from 'src/analytics/ValoraAnalytics' | ||
import EarnAddCryptoBottomSheet from 'src/earn/EarnAddCryptoBottomSheet' | ||
import { navigate } from 'src/navigator/NavigationService' | ||
import { Screens } from 'src/navigator/Screens' | ||
import { getDynamicConfigParams } from 'src/statsig' | ||
import { StoredTokenBalance, TokenBalance } from 'src/tokens/slice' | ||
import { TokenActionName } from 'src/tokens/types' | ||
import { NetworkId } from 'src/transactions/types' | ||
import { createMockStore } from 'test/utils' | ||
|
||
jest.mock('src/statsig', () => ({ | ||
getDynamicConfigParams: jest.fn(), | ||
getFeatureGate: jest.fn().mockReturnValue(false), | ||
})) | ||
|
||
const mockStoredArbitrumUsdcTokenBalance: StoredTokenBalance = { | ||
tokenId: 'arbitrum-sepolia:0x123', | ||
priceUsd: '1.16', | ||
address: '0x123', | ||
isNative: false, | ||
symbol: 'USDC', | ||
imageUrl: | ||
'https://raw.githubusercontent.com/ubeswap/default-token-list/master/assets/asset_CELO.png', | ||
name: 'USDC', | ||
decimals: 6, | ||
balance: '5', | ||
isFeeCurrency: true, | ||
canTransferWithComment: false, | ||
priceFetchedAt: Date.now(), | ||
networkId: NetworkId['arbitrum-sepolia'], | ||
isSwappable: true, | ||
isCashInEligible: true, | ||
isCashOutEligible: true, | ||
} | ||
|
||
const mockArbitrumUsdcBalance: TokenBalance = { | ||
...mockStoredArbitrumUsdcTokenBalance, | ||
balance: new BigNumber(mockStoredArbitrumUsdcTokenBalance.balance!), | ||
lastKnownPriceUsd: new BigNumber(mockStoredArbitrumUsdcTokenBalance.priceUsd!), | ||
priceUsd: new BigNumber(mockStoredArbitrumUsdcTokenBalance.priceUsd!), | ||
} | ||
|
||
const store = createMockStore({ | ||
tokens: { | ||
tokenBalances: { | ||
['arbitrum-sepolia:0x123']: { | ||
...mockStoredArbitrumUsdcTokenBalance, | ||
balance: `${mockStoredArbitrumUsdcTokenBalance.balance!}`, | ||
}, | ||
['arbitrum-sepolia:0x456']: { | ||
...mockStoredArbitrumUsdcTokenBalance, | ||
address: '0x456', | ||
tokenId: 'arbitrum-sepolia:0x456', | ||
balance: `${mockStoredArbitrumUsdcTokenBalance.balance!}`, | ||
}, | ||
}, | ||
}, | ||
app: { | ||
showSwapMenuInDrawerMenu: true, | ||
}, | ||
}) | ||
|
||
describe('EarnAddCryptoBottomSheet', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks() | ||
jest.mocked(getDynamicConfigParams).mockReturnValue({ | ||
showCico: ['arbitrum-sepolia'], | ||
showSwap: ['arbitrum-sepolia'], | ||
}) | ||
}) | ||
it('Renders all actions', () => { | ||
const { getByText } = render( | ||
<Provider store={store}> | ||
<EarnAddCryptoBottomSheet | ||
forwardedRef={{ current: null }} | ||
token={mockArbitrumUsdcBalance} | ||
tokenAmount={new BigNumber(100)} | ||
/> | ||
</Provider> | ||
) | ||
|
||
expect(getByText('earnFlow.addCryptoBottomSheet.actions.transfer')).toBeTruthy() | ||
expect(getByText('earnFlow.addCryptoBottomSheet.actions.swap')).toBeTruthy() | ||
expect(getByText('earnFlow.addCryptoBottomSheet.actions.add')).toBeTruthy() | ||
}) | ||
|
||
it('Does not render swap action when no tokens available to swap', () => { | ||
const mockStore = createMockStore({ | ||
tokens: { | ||
tokenBalances: { | ||
['arbitrum-sepolia:0x123']: { | ||
...mockStoredArbitrumUsdcTokenBalance, | ||
balance: `${mockStoredArbitrumUsdcTokenBalance.balance!}`, | ||
}, | ||
}, | ||
}, | ||
app: { | ||
showSwapMenuInDrawerMenu: true, | ||
}, | ||
}) | ||
const { getByText, queryByText } = render( | ||
<Provider store={mockStore}> | ||
<EarnAddCryptoBottomSheet | ||
forwardedRef={{ current: null }} | ||
token={mockArbitrumUsdcBalance} | ||
tokenAmount={new BigNumber(100)} | ||
/> | ||
</Provider> | ||
) | ||
|
||
expect(getByText('earnFlow.addCryptoBottomSheet.actions.transfer')).toBeTruthy() | ||
expect(queryByText('earnFlow.addCryptoBottomSheet.actions.swap')).toBeFalsy() | ||
expect(getByText('earnFlow.addCryptoBottomSheet.actions.add')).toBeTruthy() | ||
}) | ||
|
||
it('Does not render swap or add when network is not in dynamic config', () => { | ||
jest.mocked(getDynamicConfigParams).mockReturnValue({ | ||
showCico: ['ethereum-sepolia'], | ||
showSwap: ['ethereum-sepolia'], | ||
}) | ||
const { getByText, queryByText } = render( | ||
<Provider store={store}> | ||
<EarnAddCryptoBottomSheet | ||
forwardedRef={{ current: null }} | ||
token={mockArbitrumUsdcBalance} | ||
tokenAmount={new BigNumber(100)} | ||
/> | ||
</Provider> | ||
) | ||
|
||
expect(getByText('earnFlow.addCryptoBottomSheet.actions.transfer')).toBeTruthy() | ||
expect(queryByText('earnFlow.addCryptoBottomSheet.actions.swap')).toBeFalsy() | ||
expect(queryByText('earnFlow.addCryptoBottomSheet.actions.add')).toBeFalsy() | ||
}) | ||
|
||
it.each([ | ||
{ | ||
actionName: TokenActionName.Add, | ||
actionTitle: 'earnFlow.addCryptoBottomSheet.actions.add', | ||
navigateScreen: Screens.SelectProvider, | ||
navigateProps: { | ||
amount: { crypto: 100, fiat: 154 }, | ||
flow: 'CashIn', | ||
tokenId: 'arbitrum-sepolia:0x123', | ||
}, | ||
}, | ||
{ | ||
actionName: TokenActionName.Transfer, | ||
actionTitle: 'earnFlow.addCryptoBottomSheet.actions.transfer', | ||
navigateScreen: Screens.ExchangeQR, | ||
navigateProps: { exchanges: [], flow: 'CashIn' }, | ||
}, | ||
{ | ||
actionName: TokenActionName.Swap, | ||
actionTitle: 'earnFlow.addCryptoBottomSheet.actions.swap', | ||
navigateScreen: Screens.SwapScreenWithBack, | ||
navigateProps: { toTokenId: 'arbitrum-sepolia:0x123' }, | ||
}, | ||
])( | ||
'triggers the correct analytics and navigation for $actionName', | ||
async ({ actionName, actionTitle, navigateScreen, navigateProps }) => { | ||
const { getByText } = render( | ||
<Provider store={store}> | ||
<EarnAddCryptoBottomSheet | ||
forwardedRef={{ current: null }} | ||
token={mockArbitrumUsdcBalance} | ||
tokenAmount={new BigNumber(100)} | ||
/> | ||
</Provider> | ||
) | ||
|
||
fireEvent.press(getByText(actionTitle)) | ||
expect(ValoraAnalytics.track).toHaveBeenCalledWith(EarnEvents.earn_add_crypto_action_press, { | ||
action: actionName, | ||
address: '0x123', | ||
balanceUsd: 5.8, | ||
networkId: mockArbitrumUsdcBalance.networkId, | ||
symbol: mockArbitrumUsdcBalance.symbol, | ||
tokenId: 'arbitrum-sepolia:0x123', | ||
}) | ||
|
||
expect(navigate).toHaveBeenCalledWith(navigateScreen, navigateProps) | ||
} | ||
) | ||
}) |
Oops, something went wrong.