import { fireEvent, render, waitFor } from '@testing-library/react-native'
import React from 'react'
import { Provider } from 'react-redux'
import AppAnalytics from 'src/analytics/AppAnalytics'
import { CICOFlow } from 'src/fiatExchanges/types'
import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import { Price } from 'src/priceHistory/slice'
import { getFeatureGate } from 'src/statsig'
import TokenDetailsScreen from 'src/tokens/TokenDetails'
import { NetworkId } from 'src/transactions/types'
import { ONE_DAY_IN_MILLIS } from 'src/utils/time'
import MockedNavigator from 'test/MockedNavigator'
import { createMockStore } from 'test/utils'
import {
mockCeloTokenId,
mockPoofTokenId,
mockTestTokenTokenId,
mockTokenBalances,
} from 'test/values'
jest.mock('src/statsig', () => ({
getMultichainFeatures: jest.fn(() => {
return {
showCico: ['celo-alfajores', 'ethereum-sepolia'],
showSend: ['celo-alfajores', 'ethereum-sepolia'],
showSwap: ['celo-alfajores', 'ethereum-sepolia'],
}
}),
getFeatureGate: jest.fn().mockReturnValue(true),
}))
describe('TokenDetails', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('renders title, balance and token balance item', () => {
const store = createMockStore({
tokens: {
tokenBalances: {
[mockPoofTokenId]: mockTokenBalances[mockPoofTokenId],
},
},
})
const { getByTestId, getByText, queryByTestId } = render(
)
expect(getByTestId('TokenDetails/TitleImage')).toBeTruthy()
expect(getByTestId('TokenDetails/Title')).toHaveTextContent('Poof Governance Token')
expect(getByTestId('TokenDetails/AssetValue')).toHaveTextContent('₱0.13')
expect(getByText('tokenDetails.yourBalance')).toBeTruthy()
expect(getByTestId('TokenBalanceItem')).toBeTruthy()
expect(queryByTestId('TokenDetails/LearnMore')).toBeFalsy()
expect(queryByTestId('TokenDetails/Chart')).toBeFalsy()
})
it('renders learn more if token has infoUrl', () => {
const store = createMockStore({
tokens: {
tokenBalances: {
[mockPoofTokenId]: {
...mockTokenBalances[mockPoofTokenId],
infoUrl: 'https://poofToken',
},
},
},
})
const { getByTestId } = render(
)
expect(getByTestId('TokenDetails/LearnMore')).toBeTruthy()
fireEvent.press(getByTestId('TokenDetails/LearnMore'))
expect(navigate).toHaveBeenCalledWith(Screens.WebViewScreen, { uri: 'https://poofToken' })
})
it('renders price unavailable if token price is not present', () => {
const store = createMockStore({
tokens: {
tokenBalances: {
[mockPoofTokenId]: {
...mockTokenBalances[mockPoofTokenId],
priceUsd: undefined,
},
},
},
})
const { queryByTestId, getByText, getByTestId } = render(
)
expect(queryByTestId('TokenDetails/PriceDelta')).toBeFalsy()
expect(getByText('tokenDetails.priceUnavailable')).toBeTruthy()
expect(getByTestId('TokenDetails/AssetValue')).toHaveTextContent('₱ --')
})
it('renders no price info if historical price info is not available', () => {
const store = createMockStore({
tokens: {
tokenBalances: {
[mockPoofTokenId]: mockTokenBalances[mockPoofTokenId],
},
},
})
const { queryByTestId, queryByText } = render(
)
expect(queryByTestId('TokenDetails/PriceDelta')).toBeFalsy()
expect(queryByText('tokenDetails.priceUnavailable')).toBeFalsy()
})
it('renders no price info if historical price info is out of date', () => {
const store = createMockStore({
tokens: {
tokenBalances: {
[mockPoofTokenId]: {
...mockTokenBalances[mockPoofTokenId],
historicalPricesUsd: {
lastDay: {
at: Date.now() - 2 * ONE_DAY_IN_MILLIS,
price: 1,
},
},
},
},
},
})
const { queryByTestId, queryByText } = render(
)
expect(queryByTestId('TokenDetails/PriceDelta')).toBeFalsy()
expect(queryByText('tokenDetails.priceUnavailable')).toBeFalsy()
})
it('renders price delta if historical price is available and one day old', () => {
const store = createMockStore({
tokens: {
tokenBalances: {
[mockPoofTokenId]: {
...mockTokenBalances[mockPoofTokenId],
historicalPricesUsd: {
lastDay: {
at: Date.now() - ONE_DAY_IN_MILLIS,
price: 1,
},
},
},
},
},
})
const { getByTestId, queryByText } = render(
)
expect(getByTestId('TokenDetails/PriceDelta')).toBeTruthy()
expect(queryByText('tokenDetails.priceUnavailable')).toBeFalsy()
})
it('renders chart loader using blockchain API', () => {
const store = createMockStore({
priceHistory: {
[mockCeloTokenId]: {
status: 'loading',
},
},
})
const { getByTestId } = render(
)
expect(getByTestId(`PriceHistoryChart/Loader`)).toBeTruthy()
})
it('renders chart and celo news using blockchain API', () => {
jest.mocked(getFeatureGate).mockReturnValue(true) // Use new prices from blockchain API
const store = createMockStore({
priceHistory: {
[mockCeloTokenId]: {
status: 'success',
prices: [
{
priceFetchedAt: 1700378258000,
priceUsd: '0.97',
},
{
priceFetchedAt: 1701659858000,
priceUsd: '1.2',
},
{
priceFetchedAt: 1702941458000,
priceUsd: '1.4',
},
] as Price[],
},
},
})
const { getByTestId } = render(
)
expect(getByTestId(`TokenDetails/Chart/${mockCeloTokenId}`)).toBeTruthy()
})
it('renders celo news when using blockchain API', () => {
jest.mocked(getFeatureGate).mockReturnValue(true) // Use new prices from blockchain API
const store = createMockStore({
priceHistory: {
[mockCeloTokenId]: {
status: 'success',
prices: [
{
priceFetchedAt: 1700378258000,
priceUsd: '0.97',
},
{
priceFetchedAt: 1701659858000,
priceUsd: '1.2',
},
{
priceFetchedAt: 1702941458000,
priceUsd: '1.4',
},
] as Price[],
},
},
})
const { queryByText } = render(
)
expect(queryByText('celoNews.headerTitle')).toBeTruthy()
})
it('does not render chart if no prices are found and error status', () => {
jest.mocked(getFeatureGate).mockReturnValue(true) // Use new prices from blockchain API
const store = createMockStore({
tokens: {
tokenBalances: {
[mockCeloTokenId]: mockTokenBalances[mockCeloTokenId],
},
},
priceHistory: {
[mockCeloTokenId]: {
status: 'error',
prices: [],
},
},
})
const { queryByTestId } = render(
)
expect(queryByTestId(`TokenDetails/Chart/${mockCeloTokenId}`)).toBeFalsy()
expect(queryByTestId(`PriceHistoryChart/Loader`)).toBeFalsy()
})
it('renders send and swap action only if token has balance, and not a CICO token', () => {
const store = createMockStore({
tokens: {
tokenBalances: {
[mockPoofTokenId]: mockTokenBalances[mockPoofTokenId],
},
},
app: {
showSwapMenuInDrawerMenu: true,
},
})
const { getByTestId, queryByTestId } = render(
)
expect(getByTestId('TokenDetails/Action/Send')).toBeTruthy()
expect(getByTestId('TokenDetails/Action/Swap')).toBeTruthy()
expect(queryByTestId('TokenDetails/Action/Add')).toBeFalsy()
expect(queryByTestId('TokenDetails/Action/Withdraw')).toBeFalsy()
expect(queryByTestId('TokenDetails/Action/More')).toBeFalsy()
})
it('renders send and swap action only if token has balance, is swappable and not a CICO token', () => {
const store = createMockStore({
tokens: {
tokenBalances: {
[mockPoofTokenId]: { ...mockTokenBalances[mockPoofTokenId], isSwappable: true },
},
},
app: {
showSwapMenuInDrawerMenu: true,
},
})
const { getByTestId, queryByTestId } = render(
)
expect(getByTestId('TokenDetails/Action/Send')).toBeTruthy()
expect(getByTestId('TokenDetails/Action/Swap')).toBeTruthy()
expect(queryByTestId('TokenDetails/Action/Add')).toBeFalsy()
expect(queryByTestId('TokenDetails/Action/Withdraw')).toBeFalsy()
expect(queryByTestId('TokenDetails/Action/More')).toBeFalsy()
})
it('renders send, swap and more if token has balance, is swappable and a CICO token', () => {
const store = createMockStore({
tokens: {
tokenBalances: {
[mockCeloTokenId]: {
...mockTokenBalances[mockCeloTokenId],
balance: '10',
isSwappable: true,
},
},
},
app: {
showSwapMenuInDrawerMenu: true,
},
})
const { getByTestId, queryByTestId } = render(
)
expect(getByTestId('TokenDetails/Action/Send')).toBeTruthy()
expect(getByTestId('TokenDetails/Action/Swap')).toBeTruthy()
expect(queryByTestId('TokenDetails/Action/Add')).toBeFalsy()
expect(queryByTestId('TokenDetails/Action/Withdraw')).toBeFalsy()
expect(getByTestId('TokenDetails/Action/More')).toBeTruthy()
})
it('renders the default actions for the CICO token with 0 balance', () => {
const store = createMockStore({
tokens: {
tokenBalances: {
[mockCeloTokenId]: {
...mockTokenBalances[mockCeloTokenId],
isSwappable: true,
},
},
},
app: {
showSwapMenuInDrawerMenu: true,
},
})
const { getByTestId, queryByTestId } = render(
)
expect(queryByTestId('TokenDetails/Action/Send')).toBeFalsy()
expect(getByTestId('TokenDetails/Action/Swap')).toBeTruthy()
expect(getByTestId('TokenDetails/Action/Add')).toBeTruthy()
expect(queryByTestId('TokenDetails/Action/Withdraw')).toBeFalsy()
expect(queryByTestId('TokenDetails/Action/More')).toBeFalsy()
})
it('hides swap action and shows more action if token is swappable, has balance and CICO token but swapfeature gate is false', () => {
const store = createMockStore({
tokens: {
tokenBalances: {
[mockCeloTokenId]: {
...mockTokenBalances[mockCeloTokenId],
balance: '10',
isSwappable: true,
},
},
},
app: {
showSwapMenuInDrawerMenu: false,
},
})
const { getByTestId, queryByTestId } = render(
)
expect(getByTestId('TokenDetails/Action/Send')).toBeTruthy()
expect(queryByTestId('TokenDetails/Action/Swap')).toBeFalsy()
expect(getByTestId('TokenDetails/Action/Add')).toBeTruthy()
expect(getByTestId('TokenDetails/Action/More')).toBeTruthy()
expect(queryByTestId('TokenDetails/Action/Withdraw')).toBeFalsy()
})
it('actions navigate to appropriate screens', async () => {
jest.mocked(getFeatureGate).mockReturnValue(false) // Use old send flow
const store = createMockStore({
tokens: {
tokenBalances: {
[mockCeloTokenId]: {
...mockTokenBalances[mockCeloTokenId],
balance: '10',
isSwappable: true,
},
},
},
app: {
showSwapMenuInDrawerMenu: true,
},
})
const { getByTestId } = render(
)
fireEvent.press(getByTestId('TokenDetails/Action/Send'))
expect(navigate).toHaveBeenCalledWith(Screens.SendSelectRecipient, {
defaultTokenIdOverride: mockCeloTokenId,
})
fireEvent.press(getByTestId('TokenDetails/Action/Swap'))
expect(navigate).toHaveBeenCalledWith(Screens.SwapScreenWithBack, {
fromTokenId: mockCeloTokenId,
})
fireEvent.press(getByTestId('TokenDetails/Action/More'))
await waitFor(() => expect(getByTestId('TokenDetailsMoreActions')).toBeTruthy())
fireEvent.press(getByTestId('TokenDetailsMoreActions/Add'))
expect(navigate).toHaveBeenCalledWith(Screens.FiatExchangeAmount, {
tokenId: mockCeloTokenId,
flow: CICOFlow.CashIn,
tokenSymbol: 'CELO',
})
fireEvent.press(getByTestId('TokenDetailsMoreActions/Withdraw'))
expect(navigate).toHaveBeenCalledWith(Screens.WithdrawSpend)
expect(AppAnalytics.track).toHaveBeenCalledTimes(6) // 4 actions + 1 more action + 1 celo news
})
it('renders the send and swap actions for the imported tokens with balance', () => {
const store = createMockStore({
tokens: {
tokenBalances: {
[mockTestTokenTokenId]: {
tokenId: mockTestTokenTokenId,
balance: '10',
isManuallyImported: true,
networkId: NetworkId['celo-alfajores'],
symbol: 'TT',
},
},
},
app: {
showSwapMenuInDrawerMenu: true,
},
})
const { getByTestId, queryByTestId } = render(
)
expect(getByTestId('TokenDetails/Action/Send')).toBeTruthy()
expect(getByTestId('TokenDetails/Action/Swap')).toBeTruthy()
expect(queryByTestId('TokenDetails/Action/Add')).toBeFalsy()
expect(queryByTestId('TokenDetails/Action/Withdraw')).toBeFalsy()
expect(queryByTestId('TokenDetails/Action/More')).toBeFalsy()
})
})