import { FiatAccountSchema, FiatAccountType } from '@fiatconnect/fiatconnect-types' import { fireEvent, render } from '@testing-library/react-native' import React from 'react' import { Provider } from 'react-redux' import { act } from 'react-test-renderer' import AppAnalytics from 'src/analytics/AppAnalytics' import { FiatExchangeEvents } from 'src/analytics/Events' import FiatConnectQuote from 'src/fiatExchanges/quotes/FiatConnectQuote' import { SettlementEstimation, SettlementTime } from 'src/fiatExchanges/quotes/constants' import { CICOFlow } from 'src/fiatExchanges/types' import { FiatConnectQuoteSuccess } from 'src/fiatconnect' import TransferStatusScreen from 'src/fiatconnect/TransferStatusScreen' import { FiatConnectTransfer, SendingTransferStatus } from 'src/fiatconnect/slice' import { navigate, navigateHome } from 'src/navigator/NavigationService' import { Screens } from 'src/navigator/Screens' import appTheme from 'src/styles/appTheme' import networkConfig, { blockExplorerUrls } from 'src/web3/networkConfig' import { createMockStore, getMockStackScreenProps } from 'test/utils' import { mockCusdTokenId, mockFiatConnectQuotes } from 'test/values' jest.mock('src/analytics/AppAnalytics') const mockTxHash = '0xc7a9b0f4354e6279cb476d4c91d5bbc5db6ad29aa8611408de7aee6d2e7fe7c72' const mockAddress = '0x123' describe('TransferStatusScreen', () => { const mockStore = (fcTransferOverrides: Partial = {}) => { const store = createMockStore({ fiatConnect: { transfer: { flow: CICOFlow.CashIn, quoteId: 'quote_id', status: SendingTransferStatus.Completed, txHash: null, ...fcTransferOverrides, }, }, web3: { account: mockAddress, }, }) store.dispatch = jest.fn() return store } const getQuote = (flow: CICOFlow) => new FiatConnectQuote({ flow, fiatAccountType: FiatAccountType.BankAccount, quote: mockFiatConnectQuotes[1] as FiatConnectQuoteSuccess, tokenId: mockCusdTokenId, }) const mockScreenProps = (flow: CICOFlow) => getMockStackScreenProps(Screens.FiatConnectTransferStatus, { flow, fiatAccount: { fiatAccountId: 'some-fiat-account-id', accountName: 'some-friendly-name', institutionName: 'some-bank', fiatAccountType: FiatAccountType.BankAccount, fiatAccountSchema: FiatAccountSchema.AccountNumber, providerId: 'provider-two', }, normalizedQuote: getQuote(flow), }) beforeEach(() => { jest.clearAllMocks() }) describe('success view', () => { it('sets header options', () => { const store = mockStore() const mockProps = mockScreenProps(CICOFlow.CashOut) render( ) expect(mockProps.navigation.setOptions).toBeCalledWith( expect.objectContaining({ headerTitle: 'fiatConnectStatusScreen.success.header', }) ) }) it('navigates home on success for transfer outs', () => { const store = mockStore({ flow: CICOFlow.CashOut, txHash: mockTxHash }) const { queryByTestId, getByTestId } = render( ) expect(queryByTestId('Continue')).toBeTruthy() fireEvent.press(getByTestId('Continue')) expect(navigateHome).toHaveBeenCalledWith() expect(AppAnalytics.track).toHaveBeenCalledTimes(1) expect(AppAnalytics.track).toHaveBeenCalledWith( FiatExchangeEvents.cico_fc_transfer_success_complete, { provider: 'provider-two', flow: CICOFlow.CashOut, txHash: mockTxHash, } ) }) const lessThanOneHour: SettlementEstimation = { settlementTime: SettlementTime.LESS_THAN_ONE_HOUR, } const lessThan24Hours: SettlementEstimation = { settlementTime: SettlementTime.LESS_THAN_X_HOURS, upperBound: 24, } const oneToThreeDays: SettlementEstimation = { settlementTime: SettlementTime.X_TO_Y_DAYS, lowerBound: 1, upperBound: 3, } it.each([ [lessThanOneHour, 'descriptionWithin1Hour', ''], [lessThan24Hours, 'descriptionWithinXHours', '{"upperBound":24}'], [oneToThreeDays, 'descriptionXtoYDays', '{"lowerBound":1,"upperBound":3}'], ])( 'shows appropriate description for settlement time %s', (settlementTime, stringSuffix, stringArgs) => { const store = mockStore({ flow: CICOFlow.CashOut, txHash: mockTxHash }) const props = mockScreenProps(CICOFlow.CashOut) jest .spyOn(props.route.params.normalizedQuote, 'getTimeEstimation') .mockReturnValue(settlementTime) const { getByText } = render( ) const translationKey = `fiatConnectStatusScreen.success.${stringSuffix}` const translationKeyAndArgs = stringArgs ? `${translationKey}, ${stringArgs}` : translationKey expect(getByText(translationKeyAndArgs)).toBeTruthy() } ) it('shows TX details on Celo Explorer on success for transfer outs', () => { const store = mockStore({ flow: CICOFlow.CashOut, txHash: mockTxHash }) const { queryByTestId, getByTestId } = render( ) expect(queryByTestId('txDetails')).toBeTruthy() fireEvent.press(getByTestId('txDetails')) expect(navigate).toHaveBeenCalledWith(Screens.WebViewScreen, { uri: new URL( mockTxHash, blockExplorerUrls[networkConfig.defaultNetworkId].baseTxUrl ).toString(), }) expect(AppAnalytics.track).toHaveBeenCalledTimes(1) expect(AppAnalytics.track).toHaveBeenCalledWith( FiatExchangeEvents.cico_fc_transfer_success_view_tx, { provider: 'provider-two', flow: CICOFlow.CashOut, txHash: mockTxHash, } ) }) it('does not show tx details and navigates home on success for transfer ins', () => { const store = mockStore() const { queryByTestId, getByTestId } = render( ) expect(queryByTestId('Continue')).toBeTruthy() fireEvent.press(getByTestId('Continue')) expect(navigateHome).toHaveBeenCalledWith() expect(AppAnalytics.track).toHaveBeenCalledTimes(1) expect(AppAnalytics.track).toHaveBeenCalledWith( FiatExchangeEvents.cico_fc_transfer_success_complete, { provider: 'provider-two', flow: CICOFlow.CashIn, txHash: null, } ) expect(queryByTestId('txDetails')).toBeFalsy() }) }) describe('tx processing view', () => { it('navigates home on clicking continue for transfer outs', () => { const store = mockStore({ status: SendingTransferStatus.TxProcessing, flow: CICOFlow.CashOut, }) const { queryByTestId, getByTestId } = render( ) expect(queryByTestId('Continue')).toBeTruthy() fireEvent.press(getByTestId('Continue')) expect(navigateHome).toHaveBeenCalledWith() expect(AppAnalytics.track).toHaveBeenCalledTimes(1) expect(AppAnalytics.track).toHaveBeenCalledWith( FiatExchangeEvents.cico_fc_transfer_processing_continue, { provider: 'provider-two', flow: CICOFlow.CashOut, txHash: null, } ) }) it('shows address page on Celo Explorer for transfer outs', () => { const store = mockStore({ status: SendingTransferStatus.TxProcessing, flow: CICOFlow.CashOut, }) const { queryByTestId, getByTestId } = render( ) expect(queryByTestId('txDetails')).toBeTruthy() fireEvent.press(getByTestId('txDetails')) expect(navigate).toHaveBeenCalledWith(Screens.WebViewScreen, { uri: new URL( mockAddress, blockExplorerUrls[networkConfig.defaultNetworkId].baseAddressUrl ).toString(), }) expect(AppAnalytics.track).toHaveBeenCalledTimes(1) expect(AppAnalytics.track).toHaveBeenCalledWith( FiatExchangeEvents.cico_fc_transfer_processing_view_tx, { provider: 'provider-two', flow: CICOFlow.CashOut, txHash: null, } ) }) it('does not show celo explorer link and navigates home on continue for transfer ins', () => { const store = mockStore({ status: SendingTransferStatus.TxProcessing }) const { queryByTestId, getByTestId } = render( ) expect(queryByTestId('Continue')).toBeTruthy() fireEvent.press(getByTestId('Continue')) expect(navigateHome).toHaveBeenCalledWith() expect(AppAnalytics.track).toHaveBeenCalledTimes(1) expect(AppAnalytics.track).toHaveBeenCalledWith( FiatExchangeEvents.cico_fc_transfer_processing_continue, { provider: 'provider-two', flow: CICOFlow.CashIn, txHash: null, } ) expect(queryByTestId('txDetails')).toBeFalsy() }) }) describe('failure view', () => { it.each([ [CICOFlow.CashIn, 'cashIn'], [CICOFlow.CashOut, 'cashOut'], ])('sets header options for %s', (flow, header) => { const store = mockStore({ status: SendingTransferStatus.Failed, flow }) const mockProps = mockScreenProps(flow) let headerRightButton: React.ReactNode ;(mockProps.navigation.setOptions as jest.Mock).mockImplementation((options) => { headerRightButton = options.headerRight() }) render( ) expect(mockProps.navigation.setOptions).toBeCalledWith( expect.objectContaining({ headerLeft: expect.any(Function), headerRight: expect.any(Function), }) ) const { getByText, getByTestId } = render( {headerRightButton} ) expect(getByText(`fiatConnectStatusScreen.${header}.cancel`)).toBeTruthy() fireEvent.press(getByTestId('Cancel')) expect(navigateHome).toHaveBeenCalledWith() expect(AppAnalytics.track).toHaveBeenCalledTimes(1) expect(AppAnalytics.track).toHaveBeenCalledWith( FiatExchangeEvents.cico_fc_transfer_error_cancel, { provider: 'provider-two', flow, } ) }) it('navigates to review screen when try again button is pressed on failure', () => { const store = mockStore({ status: SendingTransferStatus.Failed }) const { queryByTestId, getByTestId } = render( ) expect(queryByTestId('TryAgain')).toBeTruthy() fireEvent.press(getByTestId('TryAgain')) expect(navigate).toHaveBeenCalledWith(Screens.FiatConnectReview, { flow: CICOFlow.CashOut, fiatAccount: { fiatAccountId: 'some-fiat-account-id', accountName: 'some-friendly-name', institutionName: 'some-bank', fiatAccountType: FiatAccountType.BankAccount, fiatAccountSchema: FiatAccountSchema.AccountNumber, providerId: 'provider-two', }, normalizedQuote: getQuote(CICOFlow.CashOut), shouldRefetchQuote: true, }) expect(AppAnalytics.track).toHaveBeenCalledTimes(1) expect(AppAnalytics.track).toHaveBeenCalledWith( FiatExchangeEvents.cico_fc_transfer_error_retry, { provider: 'provider-two', flow: CICOFlow.CashOut, } ) }) it.each([ [CICOFlow.CashIn, 'cashIn'], [CICOFlow.CashOut, 'cashOut'], ])( 'navigates to support page when contact support button is pressed on failure for %s', (flow, text) => { const store = mockStore({ status: SendingTransferStatus.Failed, flow }) const { queryByTestId, getByTestId } = render( ) expect(queryByTestId('SupportContactLink')).toBeTruthy() fireEvent.press(getByTestId('SupportContactLink')) expect(navigate).toHaveBeenCalledWith(Screens.SupportContact, { prefilledText: `fiatConnectStatusScreen.${text}.contactSupportPrefill`, }) expect(AppAnalytics.track).toHaveBeenCalledTimes(1) expect(AppAnalytics.track).toHaveBeenCalledWith( FiatExchangeEvents.cico_fc_transfer_error_contact_support, { provider: 'provider-two', flow, } ) } ) }) describe('loading view', () => { it('shows loading screen with no headers', () => { const mockProps = mockScreenProps(CICOFlow.CashIn) const store = mockStore({ status: SendingTransferStatus.Sending }) const { getByTestId } = render( ) expect(getByTestId('loadingTransferStatus')).toBeTruthy() expect(mockProps.navigation.setOptions).not.toBeCalled() }) it('Shows explanation if taking a while', async () => { const mockProps = mockScreenProps(CICOFlow.CashIn) const store = mockStore({ status: SendingTransferStatus.Sending }) const { getByTestId } = render( ) expect(getByTestId('loadingTransferStatus')).toBeTruthy() expect(getByTestId('loadingDescription')).toHaveStyle({ color: appTheme.colors.background, }) await act(async () => { jest.runOnlyPendingTimers() }) expect(getByTestId('loadingDescription')).toHaveTextContent( 'fiatConnectStatusScreen.stillProcessing' ) expect(getByTestId('loadingDescription')).toHaveStyle({ color: appTheme.colors.text }) }) }) })