import { FiatAccountSchema, FiatAccountType, FiatType } from '@fiatconnect/fiatconnect-types' import { fireEvent, render } from '@testing-library/react-native' import * as React from 'react' import { Provider } from 'react-redux' import { showError } from 'src/alert/actions' import { ErrorMessages } from 'src/app/ErrorMessages' import FiatExchangeAmount from 'src/fiatExchanges/FiatExchangeAmount' import { CICOFlow } from 'src/fiatExchanges/types' import { attemptReturnUserFlow } from 'src/fiatconnect/slice' import { LocalCurrencyCode } from 'src/localCurrency/consts' import { navigate } from 'src/navigator/NavigationService' import { Screens } from 'src/navigator/Screens' import { getFeatureGate } from 'src/statsig' import { NetworkId } from 'src/transactions/types' import { CiCoCurrency } from 'src/utils/currencies' import { createMockStore, getMockStackScreenProps } from 'test/utils' import { mockCeloAddress, mockCeloTokenId, mockCeurAddress, mockCeurTokenId, mockCusdAddress, mockCusdTokenId, mockEthTokenId, } from 'test/values' jest.mock('src/statsig', () => ({ getFeatureGate: jest.fn(), })) jest.mock('src/web3/networkConfig', () => { const originalModule = jest.requireActual('src/web3/networkConfig') return { __esModule: true, ...originalModule, default: { ...originalModule.default, networkToNetworkId: { celo: 'celo-alfajores', ethereum: 'ethereum-sepolia', }, defaultNetworkId: 'celo-alfajores', }, } }) const usdToUsdRate = '1' const usdToEurRate = '0.862' const usdToPhpRate = '50' const mockTokens = { tokenBalances: { [mockCusdTokenId]: { address: mockCusdAddress, tokenId: mockCusdTokenId, networkId: NetworkId['celo-alfajores'], symbol: 'cUSD', balance: '1000', priceUsd: '1', isFeeCurrency: true, priceFetchedAt: Date.now(), }, [mockCeurTokenId]: { address: mockCeurAddress, tokenId: mockCeurTokenId, networkId: NetworkId['celo-alfajores'], symbol: 'cEUR', balance: '100', priceUsd: '1.2', isFeeCurrency: true, priceFetchedAt: Date.now(), }, [mockCeloTokenId]: { address: mockCeloAddress, tokenId: mockCeloTokenId, networkId: NetworkId['celo-alfajores'], symbol: 'CELO', balance: '200', priceUsd: '5', isFeeCurrency: true, priceFetchedAt: Date.now(), }, [mockEthTokenId]: { address: undefined, tokenId: mockEthTokenId, networkId: NetworkId['ethereum-sepolia'], symbol: 'ETH', balance: '1', priceUsd: '1000', isFeeCurrency: true, priceFetchedAt: Date.now(), }, }, } const storeWithUSD = createMockStore({ localCurrency: { fetchedCurrencyCode: LocalCurrencyCode.USD, preferredCurrencyCode: LocalCurrencyCode.USD, usdToLocalRate: usdToUsdRate, }, tokens: mockTokens, }) const storeWithEUR = createMockStore({ localCurrency: { fetchedCurrencyCode: LocalCurrencyCode.EUR, preferredCurrencyCode: LocalCurrencyCode.EUR, usdToLocalRate: usdToEurRate, }, tokens: mockTokens, }) const storeWithPHP = createMockStore({ localCurrency: { fetchedCurrencyCode: LocalCurrencyCode.PHP, preferredCurrencyCode: LocalCurrencyCode.PHP, usdToLocalRate: usdToPhpRate, }, tokens: mockTokens, }) describe('FiatExchangeAmount cashIn', () => { beforeEach(() => { jest.clearAllMocks() storeWithUSD.clearActions() storeWithPHP.clearActions() }) it.each([ { currency: 'cUSD', tokenId: mockCusdTokenId }, { currency: 'cEUR', tokenId: mockCeurTokenId }, { currency: 'ETH', tokenId: mockEthTokenId }, ])(`disables the next button if the $currency amount is 0`, ({ tokenId, currency }) => { const mockScreenProps = getMockStackScreenProps(Screens.FiatExchangeAmount, { tokenId, flow: CICOFlow.CashIn, tokenSymbol: currency, }) const tree = render( ) fireEvent.changeText(tree.getByTestId('FiatExchangeInput'), '0') expect(tree.getByTestId('FiatExchangeNextButton')).toBeDisabled() }) it.each([ { currency: 'cUSD', tokenId: mockCusdTokenId, store: storeWithUSD, }, { currency: 'cEUR', tokenId: mockCeurTokenId, store: storeWithPHP, }, { currency: 'ETH', tokenId: mockEthTokenId, store: storeWithUSD, }, ])( `enables the next button if the $currency amount is greater than 0`, ({ tokenId, currency, store }) => { const mockScreenProps = getMockStackScreenProps(Screens.FiatExchangeAmount, { tokenId, flow: CICOFlow.CashIn, tokenSymbol: currency, }) const tree = render( ) fireEvent.changeText(tree.getByTestId('FiatExchangeInput'), '5') expect(tree.getByTestId('FiatExchangeNextButton')).not.toBeDisabled() } ) it('renders correctly with USD as app currency', () => { const mockScreenProps = getMockStackScreenProps(Screens.FiatExchangeAmount, { tokenId: mockCusdTokenId, flow: CICOFlow.CashIn, tokenSymbol: 'cUSD', }) const tree = render( ) expect(tree).toMatchSnapshot() }) it('renders correctly with EUR as app currency', () => { const mockScreenProps = getMockStackScreenProps(Screens.FiatExchangeAmount, { tokenId: mockCusdTokenId, flow: CICOFlow.CashIn, tokenSymbol: 'cUSD', }) const tree = render( ) expect(tree).toMatchSnapshot() }) }) describe('FiatExchangeAmount cashOut', () => { const mockScreenProps = getMockStackScreenProps(Screens.FiatExchangeAmount, { tokenId: mockCusdTokenId, flow: CICOFlow.CashOut, tokenSymbol: 'cUSD', }) const mockScreenPropsEuro = getMockStackScreenProps(Screens.FiatExchangeAmount, { tokenId: mockCeurTokenId, flow: CICOFlow.CashOut, tokenSymbol: 'cEUR', }) const mockScreenPropsCelo = getMockStackScreenProps(Screens.FiatExchangeAmount, { tokenId: mockCeloTokenId, flow: CICOFlow.CashOut, tokenSymbol: 'CELO', }) beforeEach(() => { jest.clearAllMocks() storeWithUSD.clearActions() storeWithPHP.clearActions() jest.mocked(getFeatureGate).mockReturnValue(false) }) it('displays correctly for cUSD when local currency is USD', () => { const { getByText } = render( ) expect(getByText('amount (cUSD)')).toBeTruthy() }) it('displays correctly for cEUR when local currency is USD', () => { const { getByText } = render( ) expect(getByText('amount (cEUR)')).toBeTruthy() }) it('displays correctly for CELO when local currency is USD', () => { const { getByText } = render( ) expect(getByText('amount (CELO)')).toBeTruthy() }) it('disables the next button if the cUSD amount is 0', () => { const tree = render( ) fireEvent.changeText(tree.getByTestId('FiatExchangeInput'), '0') expect(tree.getByTestId('FiatExchangeNextButton')).toBeDisabled() }) it('shows an error banner if the user balance is less than the requested cash-out amount', () => { const tree = render( ) fireEvent.changeText(tree.getByTestId('FiatExchangeInput'), '1001') fireEvent.press(tree.getByTestId('FiatExchangeNextButton')) expect(storeWithUSD.getActions()).toEqual( expect.arrayContaining([ showError(ErrorMessages.CASH_OUT_LIMIT_EXCEEDED, undefined, { balance: '1000.00', // matches the balance of the cUSD token in storeWithUSD currency: 'cUSD', }), ]) ) }) it('navigates to the SelectProvider if the user balance is greater than the requested cash-out amount', () => { const tree = render( ) fireEvent.changeText(tree.getByTestId('FiatExchangeInput'), '750') fireEvent.press(tree.getByTestId('FiatExchangeNextButton')) expect(navigate).toHaveBeenCalledWith(Screens.SelectProvider, { flow: CICOFlow.CashOut, amount: { fiat: 750, crypto: 750, }, tokenId: mockCusdTokenId, }) }) it('calls dispatch attemptReturnUserFlow when there is a previously linked fiatconnect account', () => { const store = createMockStore({ tokens: mockTokens, localCurrency: { fetchedCurrencyCode: LocalCurrencyCode.USD, preferredCurrencyCode: LocalCurrencyCode.USD, usdToLocalRate: usdToUsdRate, }, fiatConnect: { cachedFiatAccountUses: [ { providerId: 'provider-two', fiatAccountId: '123', fiatAccountType: FiatAccountType.BankAccount, flow: CICOFlow.CashOut, cryptoType: CiCoCurrency.cUSD, fiatType: FiatType.USD, fiatAccountSchema: FiatAccountSchema.AccountNumber, }, ], }, }) store.dispatch = jest.fn() const screenProps = getMockStackScreenProps(Screens.FiatExchangeAmount, { tokenId: mockCusdTokenId, flow: CICOFlow.CashOut, tokenSymbol: 'cUSD', }) const tree = render( ) fireEvent.changeText(tree.getByTestId('FiatExchangeInput'), '750') fireEvent.press(tree.getByTestId('FiatExchangeNextButton')) expect(store.dispatch).toHaveBeenLastCalledWith( attemptReturnUserFlow({ flow: CICOFlow.CashOut, selectedCrypto: 'cUSD', amount: { crypto: 750, fiat: 750, }, providerId: 'provider-two', fiatAccountId: '123', fiatAccountType: FiatAccountType.BankAccount, fiatAccountSchema: FiatAccountSchema.AccountNumber, tokenId: mockCusdTokenId, }) ) }) it('calls dispatch attemptReturnUserFlow when there is a previously linked fiatconnect account that used a different flow', () => { const store = createMockStore({ tokens: mockTokens, localCurrency: { fetchedCurrencyCode: LocalCurrencyCode.USD, preferredCurrencyCode: LocalCurrencyCode.USD, usdToLocalRate: usdToUsdRate, }, fiatConnect: { cachedFiatAccountUses: [ { providerId: 'provider-two', fiatAccountId: '123', fiatAccountType: FiatAccountType.BankAccount, flow: CICOFlow.CashIn, cryptoType: CiCoCurrency.cUSD, fiatType: FiatType.USD, fiatAccountSchema: FiatAccountSchema.AccountNumber, }, ], }, }) store.dispatch = jest.fn() const screenProps = getMockStackScreenProps(Screens.FiatExchangeAmount, { tokenId: mockCusdTokenId, flow: CICOFlow.CashOut, tokenSymbol: 'cUSD', }) const tree = render( ) fireEvent.changeText(tree.getByTestId('FiatExchangeInput'), '750') fireEvent.press(tree.getByTestId('FiatExchangeNextButton')) expect(store.dispatch).toHaveBeenLastCalledWith( attemptReturnUserFlow({ flow: CICOFlow.CashOut, selectedCrypto: 'cUSD', amount: { crypto: 750, fiat: 750, }, providerId: 'provider-two', fiatAccountId: '123', fiatAccountType: FiatAccountType.BankAccount, fiatAccountSchema: FiatAccountSchema.AccountNumber, tokenId: mockCusdTokenId, }) ) }) })