import { fireEvent, render } from '@testing-library/react-native' import BigNumber from 'bignumber.js' import React from 'react' import { Provider } from 'react-redux' import AppAnalytics from 'src/analytics/AppAnalytics' import { EarnEvents } from 'src/analytics/Events' import { openUrl } from 'src/app/actions' import EarnDepositBottomSheet from 'src/earn/EarnDepositBottomSheet' import { depositStart } from 'src/earn/slice' import { EarnActiveMode } from 'src/earn/types' import * as earnUtils from 'src/earn/utils' import { NetworkId } from 'src/transactions/types' import { PreparedTransactionsPossible } from 'src/viem/prepareTransactions' import { getSerializablePreparedTransactions } from 'src/viem/preparedTransactionSerialization' import { createMockStore } from 'test/utils' import { mockAccount, mockArbEthTokenId, mockArbUsdcTokenId, mockCeloTokenId, mockEarnPositions, mockTokenBalances, mockUSDCAddress, } from 'test/values' const mockPreparedTransaction: PreparedTransactionsPossible = { type: 'possible' as const, transactions: [ { from: '0xfrom', to: '0xto', data: '0xdata', gas: BigInt(5e12), _baseFeePerGas: BigInt(1), maxFeePerGas: BigInt(1), maxPriorityFeePerGas: undefined, }, { from: '0xfrom', to: '0xto', data: '0xdata', gas: BigInt(1e12), _baseFeePerGas: BigInt(1), maxFeePerGas: BigInt(1), maxPriorityFeePerGas: undefined, }, ], feeCurrency: { ...mockTokenBalances[mockArbEthTokenId], isNative: true, balance: new BigNumber(10), priceUsd: new BigNumber(1), lastKnownPriceUsd: new BigNumber(1), }, } const mockDepositProps = { forwardedRef: { current: null }, inputAmount: new BigNumber(100), preparedTransaction: mockPreparedTransaction, pool: mockEarnPositions[0], mode: 'deposit' as EarnActiveMode, inputTokenId: mockArbUsdcTokenId, } const mockSwapDepositProps = { ...mockDepositProps, mode: 'swap-deposit' as EarnActiveMode, inputTokenId: mockArbEthTokenId, inputAmount: new BigNumber(0.041), swapTransaction: { swapType: 'same-chain' as const, chainId: 42161, price: '2439', guaranteedPrice: '2377', appFeePercentageIncludedInPrice: '0.6', sellTokenAddress: '0xEeeeeeE', buyTokenAddress: mockUSDCAddress, sellAmount: '41000000000000000', buyAmount: '99999000', allowanceTarget: '0x0000000000000000000000000000000000000123', from: mockAccount, to: '0x0000000000000000000000000000000000000123', value: '0', data: '0x0', gas: '1800000', estimatedGasUse: undefined, estimatedPriceImpact: '0.1', }, } const mockCrossChainProps = { ...mockSwapDepositProps, preparedTransaction: { ...mockPreparedTransaction, feeCurrency: { ...mockTokenBalances[mockCeloTokenId], isNative: true, balance: new BigNumber(10), priceUsd: new BigNumber(1), lastKnownPriceUsd: new BigNumber(1), }, }, inputTokenId: mockCeloTokenId, swapTransaction: { ...mockSwapDepositProps.swapTransaction, swapType: 'cross-chain' as const, estimatedDuration: 300, maxCrossChainFee: '0.1', estimatedCrossChainFee: '0.05', }, } describe('EarnDepositBottomSheet', () => { const commonAnalyticsProperties = { depositTokenId: mockArbUsdcTokenId, depositTokenAmount: '100', networkId: NetworkId['arbitrum-sepolia'], providerId: mockEarnPositions[0].appId, poolId: mockEarnPositions[0].positionId, fromNetworkId: NetworkId['arbitrum-sepolia'], } beforeEach(() => { jest.clearAllMocks() jest.spyOn(earnUtils, 'isGasSubsidizedForNetwork').mockReturnValue(false) }) it('renders all elements for deposit', () => { const { getByTestId, queryByTestId, getByText } = render( ) expect(getByText('earnFlow.depositBottomSheet.title')).toBeTruthy() expect( getByText('earnFlow.depositBottomSheet.descriptionV1_93, {"providerName":"Aave"}') ).toBeTruthy() expect(queryByTestId('EarnDeposit/GasSubsidized')).toBeFalsy() expect(getByText('earnFlow.depositBottomSheet.yieldRate')).toBeTruthy() expect(getByText('earnFlow.depositBottomSheet.apy, {"apy":"1.92"}')).toBeTruthy() expect(getByText('earnFlow.depositBottomSheet.amount')).toBeTruthy() expect(getByTestId('EarnDeposit/Amount')).toHaveTextContent('100.00 USDC(₱133.00)') expect(getByText('earnFlow.depositBottomSheet.fee')).toBeTruthy() expect(getByTestId('EarnDeposit/Fee')).toHaveTextContent('₱0.012(0.000006 ETH)') expect(getByText('earnFlow.depositBottomSheet.provider')).toBeTruthy() expect(getByText('Aave')).toBeTruthy() expect(getByTestId('EarnDeposit/ProviderInfo')).toBeTruthy() expect(getByText('earnFlow.depositBottomSheet.network')).toBeTruthy() expect(getByText('Arbitrum Sepolia')).toBeTruthy() expect( getByText('earnFlow.depositBottomSheet.footerV1_93, {"providerName":"Aave"}') ).toBeTruthy() expect(getByTestId('EarnDeposit/PrimaryCta')).toBeTruthy() expect(getByTestId('EarnDeposit/SecondaryCta')).toBeTruthy() }) it.each([ { swapType: 'same-chain', props: mockSwapDepositProps, fromSymbol: 'ETH', fiatFee: '0.012' }, { swapType: 'cross-chain', props: mockCrossChainProps, fromSymbol: 'CELO', fiatFee: '0.00011' }, ])('renders all elements for $swapType swap-deposit', ({ props, fromSymbol, fiatFee }) => { const { getByTestId, queryByTestId, getByText } = render( ) expect(getByText('earnFlow.depositBottomSheet.title')).toBeTruthy() expect( getByText('earnFlow.depositBottomSheet.descriptionV1_93, {"providerName":"Aave"}') ).toBeTruthy() expect(queryByTestId('EarnDeposit/GasSubsidized')).toBeFalsy() expect(getByText('earnFlow.depositBottomSheet.yieldRate')).toBeTruthy() expect(getByText('earnFlow.depositBottomSheet.apy, {"apy":"1.92"}')).toBeTruthy() expect(getByText('earnFlow.depositBottomSheet.amount')).toBeTruthy() expect(getByTestId('EarnDeposit/Amount')).toHaveTextContent('100.00 USDC(₱133.00)') expect(getByTestId('EarnDeposit/Swap/From')).toHaveTextContent(`0.041 ${fromSymbol}`) expect(getByTestId('EarnDeposit/Swap/To')).toHaveTextContent('100.00 USDC') expect(getByText('earnFlow.depositBottomSheet.fee')).toBeTruthy() expect(getByTestId('EarnDeposit/Fee')).toHaveTextContent(`₱${fiatFee}(0.000006 ${fromSymbol})`) expect(getByText('earnFlow.depositBottomSheet.provider')).toBeTruthy() expect(getByText('Aave')).toBeTruthy() expect(getByTestId('EarnDeposit/ProviderInfo')).toBeTruthy() expect(getByText('earnFlow.depositBottomSheet.network')).toBeTruthy() expect(getByText('Arbitrum Sepolia')).toBeTruthy() expect( getByText('earnFlow.depositBottomSheet.footerV1_93, {"providerName":"Aave"}') ).toBeTruthy() expect(getByTestId('EarnDeposit/PrimaryCta')).toBeTruthy() expect(getByTestId('EarnDeposit/SecondaryCta')).toBeTruthy() }) describe.each([ { testName: 'deposit', mode: 'deposit', props: mockDepositProps, fromTokenAmount: '100', fromTokenId: mockArbUsdcTokenId, depositTokenAmount: '100', swapType: undefined, }, { testName: 'same chain swap & deposit', mode: 'swap-deposit', props: mockSwapDepositProps, fromTokenAmount: '0.041', fromTokenId: mockArbEthTokenId, depositTokenAmount: '99.999', swapType: 'same-chain', }, { testName: 'cross chain swap & deposit', mode: 'swap-deposit', props: mockCrossChainProps, fromTokenAmount: '0.041', fromTokenId: mockCeloTokenId, depositTokenAmount: '99.999', swapType: 'cross-chain', }, ])('$testName', ({ mode, props, fromTokenAmount, fromTokenId, depositTokenAmount, swapType }) => { const fromNetworkId = swapType === 'cross-chain' ? NetworkId['celo-alfajores'] : NetworkId['arbitrum-sepolia'] const expectedAnalyticsProperties = { ...commonAnalyticsProperties, mode, fromTokenAmount, fromTokenId, depositTokenAmount, swapType, fromNetworkId, } it('pressing complete submits action and fires analytics event', () => { const store = createMockStore({ tokens: { tokenBalances: mockTokenBalances } }) const { getByTestId } = render( ) fireEvent.press(getByTestId('EarnDeposit/PrimaryCta')) expect(AppAnalytics.track).toHaveBeenCalledWith( EarnEvents.earn_deposit_complete, expectedAnalyticsProperties ) expect(store.getActions()).toEqual([ { type: depositStart.type, payload: { amount: depositTokenAmount, pool: mockEarnPositions[0], preparedTransactions: getSerializablePreparedTransactions( mockPreparedTransaction.transactions ), mode, fromTokenAmount, fromTokenId, }, }, ]) }) it('pressing cancel fires analytics event', () => { const { getByTestId } = render( ) fireEvent.press(getByTestId('EarnDeposit/SecondaryCta')) expect(AppAnalytics.track).toHaveBeenCalledWith( EarnEvents.earn_deposit_cancel, expectedAnalyticsProperties ) }) it('pressing provider info opens the terms and conditions', () => { const store = createMockStore({ tokens: { tokenBalances: mockTokenBalances } }) const { getByTestId } = render( ) fireEvent.press(getByTestId('EarnDeposit/ProviderInfo')) expect(AppAnalytics.track).toHaveBeenCalledWith( EarnEvents.earn_deposit_provider_info_press, expectedAnalyticsProperties ) expect(store.getActions()).toEqual([openUrl('termsUrl', true)]) }) it('pressing terms and conditions opens the terms and conditions', () => { const store = createMockStore({ tokens: { tokenBalances: mockTokenBalances } }) const { getByTestId } = render( ) fireEvent.press(getByTestId('EarnDeposit/TermsAndConditions')) expect(AppAnalytics.track).toHaveBeenCalledWith( EarnEvents.earn_deposit_terms_and_conditions_press, { type: 'providerTermsAndConditions', ...expectedAnalyticsProperties } ) expect(store.getActions()).toEqual([openUrl('termsUrl', true)]) }) it('pressing provider docs opens the providers doc URL (when provider does not have terms and conditions)', () => { const store = createMockStore({ tokens: { tokenBalances: mockTokenBalances } }) const { getByTestId } = render( ) fireEvent.press(getByTestId('EarnDeposit/ProviderDocuments')) expect(AppAnalytics.track).toHaveBeenCalledWith( EarnEvents.earn_deposit_terms_and_conditions_press, { type: 'providerDocuments', ...expectedAnalyticsProperties, providerId: 'beefy' } ) expect(store.getActions()).toEqual([openUrl('https://docs.beefy.finance/', true)]) }) it('pressing app terms and conditions opens the app T&C URL (when provider does not have terms and conditions)', () => { const store = createMockStore({ tokens: { tokenBalances: mockTokenBalances } }) const { getByTestId } = render( ) fireEvent.press(getByTestId('EarnDeposit/AppTermsAndConditions')) expect(AppAnalytics.track).toHaveBeenCalledWith( EarnEvents.earn_deposit_terms_and_conditions_press, { type: 'appTermsAndConditions', ...expectedAnalyticsProperties, providerId: 'beefy' } ) expect(store.getActions()).toEqual([openUrl('https://valora.xyz/terms', true)]) }) it('shows loading state and buttons are disabled when deposit is submitted', () => { const store = createMockStore({ tokens: { tokenBalances: mockTokenBalances }, earn: { depositStatus: 'loading' }, }) const { getByTestId } = render( ) expect(getByTestId('EarnDeposit/PrimaryCta')).toBeDisabled() expect(getByTestId('EarnDeposit/SecondaryCta')).toBeDisabled() expect(getByTestId('EarnDeposit/PrimaryCta')).toContainElement(getByTestId('Button/Loading')) }) it('shows gas subsidized copy if feature gate is set', () => { jest.spyOn(earnUtils, 'isGasSubsidizedForNetwork').mockReturnValue(true) const { getByTestId } = render( ) expect(getByTestId('EarnDeposit/GasSubsidized')).toBeTruthy() expect(earnUtils.isGasSubsidizedForNetwork).toHaveBeenCalledWith(fromNetworkId) }) }) })