import { fireEvent, render, waitFor, within } from '@testing-library/react-native' import React from 'react' import { Provider } from 'react-redux' import AppAnalytics from 'src/analytics/AppAnalytics' import { EarnEvents } from 'src/analytics/Events' import EarnPoolInfoScreen from 'src/earn/poolInfoScreen/EarnPoolInfoScreen' import { navigate } from 'src/navigator/NavigationService' import { Screens } from 'src/navigator/Screens' import { EarnPosition } from 'src/positions/types' import { getFeatureGate, getMultichainFeatures } from 'src/statsig' import { StatsigFeatureGates } from 'src/statsig/types' import { NetworkId } from 'src/transactions/types' import { navigateToURI } from 'src/utils/linking' import MockedNavigator from 'test/MockedNavigator' import { createMockStore } from 'test/utils' import { mockArbEthTokenId, mockArbUsdcTokenId, mockEarnPositions, mockPositions, mockRewardsPositions, mockTokenBalances, } from 'test/values' jest.mock('src/statsig', () => ({ getMultichainFeatures: jest.fn(), getFeatureGate: jest.fn(), })) const mockPoolTokenId = mockEarnPositions[0].dataProps.depositTokenId function getStore({ balance = '0', includeSameChainToken = false, includeRewardPositions = true, }: { balance?: string includeSameChainToken?: boolean includeRewardPositions?: boolean } = {}) { const sameChainToken = includeSameChainToken ? { [mockArbEthTokenId]: { ...mockTokenBalances[mockArbEthTokenId], balance: '1' } } : {} return createMockStore({ tokens: { tokenBalances: { ...sameChainToken, [mockPoolTokenId]: { ...mockTokenBalances[mockArbUsdcTokenId], balance, isCashInEligible: true, }, }, }, positions: { positions: includeRewardPositions ? [...mockPositions, ...mockRewardsPositions] : [], }, app: { showSwapMenuInDrawerMenu: true }, }) } const renderEarnPoolInfoScreen = (pool: EarnPosition) => render( ) describe('EarnPoolInfoScreen', () => { beforeEach(() => { jest.clearAllMocks() jest.mocked(getMultichainFeatures).mockReturnValue({ showCico: [NetworkId['arbitrum-sepolia']], showSwap: [NetworkId['celo-alfajores'], NetworkId['arbitrum-sepolia']], }) jest .mocked(getFeatureGate) .mockImplementation((gate) => gate === StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAPS) jest.useFakeTimers({ now: new Date('2024-08-15T00:00:00.000Z'), }) }) it('renders correctly when not deposited in pool', () => { const { getByTestId, queryByTestId } = renderEarnPoolInfoScreen(mockEarnPositions[0]) expect(queryByTestId('DepositAndEarningsCard')).toBeFalsy() expect( within(getByTestId('TitleSection')).getByText('earnFlow.poolInfoScreen.chainName') ).toBeTruthy() expect( within(getByTestId('TitleSection')).getByText('earnFlow.poolInfoScreen.protocolName') ).toBeTruthy() expect( within(getByTestId('YieldCard')).getAllByText( 'earnFlow.poolInfoScreen.ratePercent, {"rate":"1.92"}' ) ).toBeTruthy() expect(within(getByTestId('TvlCard')).getByText('earnFlow.poolInfoScreen.tvl')).toBeTruthy() expect(within(getByTestId('TvlCard')).getByText('₱1,808,800.00')).toBeTruthy() expect( within(getByTestId('AgeCard')).getByText('duration, {"context":"month","count":5}') ).toBeTruthy() expect( within(getByTestId('ActionButtons')).queryByText('earnFlow.poolInfoScreen.withdraw') ).toBeFalsy() expect( within(getByTestId('ActionButtons')).getByText('earnFlow.poolInfoScreen.deposit') ).toBeTruthy() }) it('renders deposit and earnings card when user has deposited in pool', () => { const mockPool = { ...mockEarnPositions[0], balance: '100', dataProps: { ...mockEarnPositions[0].dataProps, earningItems: [ { amount: '15', label: 'Earnings', tokenId: mockArbUsdcTokenId }, { amount: '1', label: 'Reward', tokenId: mockArbUsdcTokenId, includedInPoolBalance: false, }, ], }, } const { getByTestId, getAllByTestId } = renderEarnPoolInfoScreen(mockPool) expect( within(getByTestId('DepositAndEarningsCard')).getByText( 'earnFlow.poolInfoScreen.totalDepositAndEarnings' ) ).toBeTruthy() expect( within(getByTestId('DepositAndEarningsCard')).getByText( ' earnFlow.poolInfoScreen.titleLocalAmountDisplay, {"localCurrencySymbol":"₱","localCurrencyAmount":"180.88"}' ) ).toBeTruthy() expect( within(getByTestId('DepositAndEarningsCard')).getByText( 'earnFlow.poolInfoScreen.lineItemAmountDisplay, {"localCurrencySymbol":"₱","localCurrencyAmount":"146.30","cryptoAmount":"110.00","cryptoSymbol":"USDC"}' ) ).toBeTruthy() expect(getAllByTestId('EarningItemLineItem')).toHaveLength(2) expect( within(getAllByTestId('EarningItemLineItem')[0]).getByText( 'earnFlow.poolInfoScreen.lineItemAmountDisplay, {"localCurrencySymbol":"₱","localCurrencyAmount":"19.95","cryptoAmount":"15.00","cryptoSymbol":"USDC"}' ) ).toBeTruthy() expect( within(getAllByTestId('EarningItemLineItem')[1]).getByText( 'earnFlow.poolInfoScreen.lineItemAmountDisplay, {"localCurrencySymbol":"₱","localCurrencyAmount":"1.33","cryptoAmount":"1.00","cryptoSymbol":"USDC"}' ) ).toBeTruthy() }) it('renders correctly when compounded interest cannot be separated', () => { const mockPool = { ...mockEarnPositions[0], balance: '100', dataProps: { ...mockEarnPositions[0].dataProps, cantSeparateCompoundedInterest: true, }, } const { getByTestId } = renderEarnPoolInfoScreen(mockPool) expect( within(getByTestId('DepositAndEarningsCard')).getByText( 'earnFlow.poolInfoScreen.depositAndEarnings' ) ).toBeTruthy() expect( within(getByTestId('DepositAndEarningsCard')).getByText( ' earnFlow.poolInfoScreen.titleLocalAmountDisplay, {"localCurrencySymbol":"₱","localCurrencyAmount":"159.60"}' ) ).toBeTruthy() expect( within(getByTestId('DepositAndEarningsCard')).getByText( 'earnFlow.poolInfoScreen.lineItemAmountDisplay, {"localCurrencySymbol":"₱","localCurrencyAmount":"146.30","cryptoAmount":"110.00","cryptoSymbol":"USDC"}' ) ).toBeTruthy() }) it('renders correctly when includedInPoolBalance is true for an earning item', () => { const mockPool = { ...mockEarnPositions[0], balance: '100', dataProps: { ...mockEarnPositions[0].dataProps, earningItems: [ { amount: '15', label: 'Earnings', tokenId: mockArbUsdcTokenId }, { amount: '1', label: 'Reward', tokenId: mockArbUsdcTokenId, includedInPoolBalance: true, }, ], }, } const { getByTestId, getAllByTestId } = renderEarnPoolInfoScreen(mockPool) expect( within(getByTestId('DepositAndEarningsCard')).getByText( 'earnFlow.poolInfoScreen.totalDepositAndEarnings' ) ).toBeTruthy() expect( within(getByTestId('DepositAndEarningsCard')).getByText( 'earnFlow.poolInfoScreen.titleLocalAmountDisplay, {"localCurrencySymbol":"₱","localCurrencyAmount":"179.55"}' ) ).toBeTruthy() expect( within(getByTestId('DepositAndEarningsCard')).getByText( 'earnFlow.poolInfoScreen.lineItemAmountDisplay, {"localCurrencySymbol":"₱","localCurrencyAmount":"144.97","cryptoAmount":"109.00","cryptoSymbol":"USDC"}' ) ).toBeTruthy() expect(getAllByTestId('EarningItemLineItem')).toHaveLength(2) expect( within(getAllByTestId('EarningItemLineItem')[0]).getByText( 'earnFlow.poolInfoScreen.lineItemAmountDisplay, {"localCurrencySymbol":"₱","localCurrencyAmount":"19.95","cryptoAmount":"15.00","cryptoSymbol":"USDC"}' ) ).toBeTruthy() }) it('renders correctly when includedInPoolBalance is true for an earning item and earning item is a different token', () => { const mockPool = { ...mockEarnPositions[0], balance: '100', dataProps: { ...mockEarnPositions[0].dataProps, earningItems: [ { amount: '15', label: 'Earnings', tokenId: mockArbUsdcTokenId }, { amount: '0.001', label: 'Reward', tokenId: mockArbEthTokenId, includedInPoolBalance: true, }, ], }, } const { getByTestId, getAllByTestId } = render( ) expect( within(getByTestId('DepositAndEarningsCard')).getByText( 'earnFlow.poolInfoScreen.totalDepositAndEarnings' ) ).toBeTruthy() expect( within(getByTestId('DepositAndEarningsCard')).getByText( 'earnFlow.poolInfoScreen.titleLocalAmountDisplay, {"localCurrencySymbol":"₱","localCurrencyAmount":"179.55"}' ) ).toBeTruthy() expect( within(getByTestId('DepositAndEarningsCard')).getByText( 'earnFlow.poolInfoScreen.lineItemAmountDisplay, {"localCurrencySymbol":"₱","localCurrencyAmount":"144.31","cryptoAmount":"108.50","cryptoSymbol":"USDC"}' ) ).toBeTruthy() expect(getAllByTestId('EarningItemLineItem')).toHaveLength(2) expect( within(getAllByTestId('EarningItemLineItem')[0]).getByText( 'earnFlow.poolInfoScreen.lineItemAmountDisplay, {"localCurrencySymbol":"₱","localCurrencyAmount":"19.95","cryptoAmount":"15.00","cryptoSymbol":"USDC"}' ) ).toBeTruthy() expect( within(getAllByTestId('EarningItemLineItem')[1]).getByText( 'earnFlow.poolInfoScreen.lineItemAmountDisplay, {"localCurrencySymbol":"₱","localCurrencyAmount":"2.00","cryptoAmount":"0.001","cryptoSymbol":"ETH"}' ) ).toBeTruthy() }) it('renders safety card when safety is provided', () => { const mockPool = { ...mockEarnPositions[0], balance: '100', dataProps: { ...mockEarnPositions[0].dataProps, safety: { level: 'high' as const, risks: [ { isPositive: false, title: 'Risk 1', category: 'Category 1' }, { isPositive: true, title: 'Risk 2', category: 'Category 2' }, ], }, }, } const { getByTestId } = renderEarnPoolInfoScreen(mockPool) expect(getByTestId('SafetyCard')).toBeTruthy() }) it('navigates to external URI when "View Pool on Provider" is tapped', () => { const { getByText } = renderEarnPoolInfoScreen(mockEarnPositions[0]) fireEvent.press( getByText('earnFlow.poolInfoScreen.learnMoreOnProvider, {"providerName":"Aave"}') ) expect(navigateToURI).toHaveBeenCalledWith('https://app.aave.com/?marketName=proto_arbitrum_v3') expect(AppAnalytics.track).toHaveBeenCalledWith(EarnEvents.earn_pool_info_view_pool, { providerId: 'aave', poolId: 'arbitrum-sepolia:0x460b97bd498e1157530aeb3086301d5225b91216', networkId: 'arbitrum-sepolia', depositTokenId: mockEarnPositions[0].dataProps.depositTokenId, }) }) it.each([ { testId: 'DepositInfoBottomSheet', infoIconTestId: 'DepositInfoIcon', type: 'deposit', }, { testId: 'TvlInfoBottomSheet', infoIconTestId: 'TvlInfoIcon', type: 'tvl', }, { testId: 'AgeInfoBottomSheet', infoIconTestId: 'AgeInfoIcon', type: 'age', }, { testId: 'YieldRateInfoBottomSheet', infoIconTestId: 'YieldRateInfoIcon', type: 'yieldRate', }, { testId: 'SafetyScoreInfoBottomSheet', infoIconTestId: 'SafetyCardInfoIcon', type: 'safetyScore', }, ])('opens $testId and track analytics event', async ({ testId, infoIconTestId, type }) => { const mockPool = { ...mockEarnPositions[0], balance: '100', dataProps: { ...mockEarnPositions[0].dataProps, earningItems: [ { amount: '15', label: 'Earnings', tokenId: mockArbUsdcTokenId }, { amount: '1', label: 'Reward', tokenId: mockArbUsdcTokenId, includedInPoolBalance: false, }, ], safety: { level: 'high' as const, risks: [ { isPositive: false, title: 'Risk 1', category: 'Category 1' }, { isPositive: true, title: 'Risk 2', category: 'Category 2' }, ], }, }, } const { getByTestId } = renderEarnPoolInfoScreen(mockPool) fireEvent.press(getByTestId(infoIconTestId)) await waitFor(() => expect(getByTestId(testId)).toBeVisible()) expect(AppAnalytics.track).toHaveBeenCalledWith(EarnEvents.earn_pool_info_tap_info_icon, { providerId: 'aave', poolId: 'arbitrum-sepolia:0x460b97bd498e1157530aeb3086301d5225b91216', type, networkId: 'arbitrum-sepolia', depositTokenId: mockEarnPositions[0].dataProps.depositTokenId, }) }) it('Show bottom sheet when Deposit button is tapped and depositTokenId has a balance', () => { const { getByText, getByTestId } = render( ) fireEvent.press(getByText('earnFlow.poolInfoScreen.deposit')) expect(AppAnalytics.track).toHaveBeenCalledWith(EarnEvents.earn_pool_info_tap_deposit, { providerId: 'aave', poolId: 'arbitrum-sepolia:0x460b97bd498e1157530aeb3086301d5225b91216', networkId: 'arbitrum-sepolia', depositTokenId: mockEarnPositions[0].dataProps.depositTokenId, hasDepositToken: true, hasTokensOnSameNetwork: false, hasTokensOnOtherNetworks: false, }) expect(getByTestId('Earn/BeforeDepositBottomSheet')).toBeVisible() }) it('Show bottom sheet when Deposit button is tapped and depositTokenId does not have a balance', () => { const { getByText, getByTestId } = render( ) fireEvent.press(getByText('earnFlow.poolInfoScreen.deposit')) expect(AppAnalytics.track).toHaveBeenCalledWith(EarnEvents.earn_pool_info_tap_deposit, { providerId: 'aave', poolId: 'arbitrum-sepolia:0x460b97bd498e1157530aeb3086301d5225b91216', networkId: 'arbitrum-sepolia', depositTokenId: mockEarnPositions[0].dataProps.depositTokenId, hasDepositToken: false, hasTokensOnSameNetwork: false, hasTokensOnOtherNetworks: false, }) expect(getByTestId('Earn/BeforeDepositBottomSheet')).toBeVisible() }) it('navigate to EarnConfirmationScreen when Withdraw button is tapped, no rewards and cannot partial withdraw', () => { jest .mocked(getFeatureGate) .mockImplementation( (gateName: StatsigFeatureGates) => gateName === StatsigFeatureGates.SHOW_POSITIONS ) const { getByTestId } = render( ) fireEvent.press(getByTestId('WithdrawButton')) expect(AppAnalytics.track).toHaveBeenCalledWith(EarnEvents.earn_pool_info_tap_withdraw, { providerId: 'aave', poolId: 'arbitrum-sepolia:0x460b97bd498e1157530aeb3086301d5225b91216', poolAmount: '100', networkId: 'arbitrum-sepolia', depositTokenId: mockEarnPositions[0].dataProps.depositTokenId, }) expect(navigate).toHaveBeenCalledWith(Screens.EarnConfirmationScreen, { pool: { ...mockEarnPositions[0], balance: '100' }, mode: 'exit', useMax: true, }) }) it('open WithdrawBottomSheet when Withdraw button pressed, check that expected options exist', () => { jest .mocked(getFeatureGate) .mockImplementation( (gate) => gate === StatsigFeatureGates.ALLOW_EARN_PARTIAL_WITHDRAWAL || gate === StatsigFeatureGates.SHOW_POSITIONS ) const { getByTestId } = render( ) fireEvent.press(getByTestId('WithdrawButton')) expect(getByTestId('Earn/WithdrawBottomSheet')).toBeVisible() expect(getByTestId('Earn/ActionCard/withdraw')).toBeTruthy() expect(getByTestId('Earn/ActionCard/claim-rewards')).toBeTruthy() expect(getByTestId('Earn/ActionCard/exit')).toBeTruthy() }) it('shows the daily yield rate when it is available', () => { const { getByTestId } = renderEarnPoolInfoScreen({ ...mockEarnPositions[0], dataProps: { ...mockEarnPositions[0].dataProps, dailyYieldRatePercentage: 0.0452483, }, }) expect( within(getByTestId('DailyYieldRateCard')).getAllByText( 'earnFlow.poolInfoScreen.ratePercent, {"rate":"0.0452"}' ) ).toBeTruthy() }) it.each([0, undefined])('does not show the daily yield rate when it is %s', (dailyYieldRate) => { const { queryByTestId } = renderEarnPoolInfoScreen({ ...mockEarnPositions[0], dataProps: { ...mockEarnPositions[0].dataProps, dailyYieldRatePercentage: dailyYieldRate, }, }) expect(queryByTestId('DailyYieldRateCard')).toBeFalsy() }) })