import { act, fireEvent, render, waitFor } from '@testing-library/react-native' import React from 'react' import { Provider } from 'react-redux' import DappShortcutTransactionRequest from 'src/dapps/DappShortcutTransactionRequest' import { RawShortcutTransaction, denyExecuteShortcut, executeShortcut } from 'src/positions/slice' import { ShortcutStatus } from 'src/positions/types' import { NetworkId } from 'src/transactions/types' import { prepareTransactions } from 'src/viem/prepareTransactions' import { getSerializablePreparedTransactions } from 'src/viem/preparedTransactionSerialization' import MockedNavigator from 'test/MockedNavigator' import { createMockStore } from 'test/utils' import { mockCeloTokenBalance, mockPositions, mockShortcuts } from 'test/values' import { parseGwei } from 'viem' jest.mock('src/statsig', () => ({ getFeatureGate: jest.fn(() => true), })) jest.mock('src/viem/prepareTransactions') const rewardId = 'claim-reward-0xda7f463c27ec862cfbf2369f3f74c364d050d93f-0.010209368244703615' function getMockStoreWithShortcutStatus( status: ShortcutStatus, transactions: RawShortcutTransaction[] ) { return createMockStore({ positions: { positions: mockPositions, shortcuts: mockShortcuts, triggeredShortcutsStatus: { [rewardId]: { status, transactions, appName: 'some app name', appImage: 'https://some.app/image.png', }, }, }, }) } describe('DappShortcutTransactionRequest', () => { const transactions = [ { from: '0x2b8441ef13333ffa955c9ea5ab5b3692da95260d', networkId: NetworkId['celo-alfajores'], data: '0x3d18b912', to: '0xda7f463c27ec862cfbf2369f3f74c364d050d93f', } as const, ] const gas = BigInt(100_000) const maxFeePerGas = parseGwei('5') const _baseFeePerGas = parseGwei('1') beforeEach(() => { jest.clearAllMocks() jest.mocked(prepareTransactions).mockImplementation(async ({ baseTransactions }) => ({ transactions: baseTransactions.map((tx) => ({ ...tx, gas, maxFeePerGas, _baseFeePerGas, })), type: 'possible' as const, feeCurrency: mockCeloTokenBalance, })) }) it('should a loader when the shortcut transaction has not been fetched yet', async () => { const { queryByText, getByTestId } = render( ) await waitFor(() => expect(getByTestId('DappShortcutTransactionRequest/Loading')).toBeTruthy()) expect(queryByText('confirmTransaction')).toBeFalsy() }) it('should display a dismiss-only bottom sheet if the user has insufficient gas funds', async () => { const store = getMockStoreWithShortcutStatus('pendingAccept', transactions) jest.mocked(prepareTransactions).mockImplementation(async ({ baseTransactions }) => ({ type: 'not-enough-balance-for-gas' as const, feeCurrencies: [mockCeloTokenBalance], })) const { getByText, queryByText } = render( ) await waitFor(() => expect(getByText('walletConnectRequest.notEnoughBalanceForGas.title')).toBeTruthy() ) expect( getByText( 'walletConnectRequest.notEnoughBalanceForGas.description, {"feeCurrencies":"CELO, cEUR, cUSD"}' ) ).toBeTruthy() expect(queryByText('allow')).toBeFalsy() fireEvent.press(getByText('dismiss')) expect(store.getActions()).toEqual([denyExecuteShortcut(rewardId)]) }) it("should display a dismiss-only bottom sheet if the transaction couldn't be prepared", async () => { const store = getMockStoreWithShortcutStatus('pendingAccept', transactions) jest.mocked(prepareTransactions).mockRejectedValue(new Error('some error')) const { getByText, queryByText } = render( ) await waitFor(() => expect(getByText('walletConnectRequest.failedToPrepareTransaction.title')).toBeTruthy() ) expect( getByText( 'walletConnectRequest.failedToPrepareTransaction.description, {"errorMessage":"some error"}' ) ).toBeTruthy() expect(queryByText('allow')).toBeFalsy() fireEvent.press(getByText('dismiss')) expect(store.getActions()).toEqual([denyExecuteShortcut(rewardId)]) }) it('should accept the transaction request and handle dismiss the bottom sheet', async () => { const store = getMockStoreWithShortcutStatus('pendingAccept', transactions) const { getByText, queryByTestId, getByTestId, unmount } = render( ) await waitFor(() => expect(queryByTestId('EstimatedNetworkFee/Loading')).toBeFalsy()) expect(queryByTestId('DappShortcutTransactionRequest/Loading')).toBeFalsy() expect(getByText('confirmTransaction')).toBeTruthy() const expectedPreparedTransactions = getSerializablePreparedTransactions( transactions.map((tx) => ({ from: tx.from, to: tx.to, value: undefined, data: tx.data, gas, maxFeePerGas, _baseFeePerGas, _estimatedGasUse: undefined, })) ) expect(getByTestId('DappShortcutTransactionRequest/RewardTransactionData')).toHaveTextContent( JSON.stringify(expectedPreparedTransactions) ) fireEvent.press(getByText('allow')) expect(store.getActions()).toStrictEqual([ executeShortcut({ id: rewardId, preparedTransactions: expectedPreparedTransactions }), ]) store.clearActions() unmount() // should not deny the transaction on dismiss bottom sheet if transaction is approved expect(store.getActions()).toStrictEqual([]) }) it('should deny the transaction request on dismiss bottom sheet', async () => { const store = getMockStoreWithShortcutStatus('pendingAccept', transactions) const { unmount } = render( ) await act(() => { unmount() }) expect(store.getActions()).toEqual([denyExecuteShortcut(rewardId)]) }) })