import { fireEvent, render, waitFor } from '@testing-library/react-native' import { FetchMock } from 'jest-fetch-mock/types' import * as React from 'react' import { Provider } from 'react-redux' import AppAnalytics from 'src/analytics/AppAnalytics' import { PointsEvents } from 'src/analytics/Events' import PointsHistoryBottomSheet from 'src/points/PointsHistoryBottomSheet' import { getHistoryStarted } from 'src/points/slice' import { GetHistoryResponse } from 'src/points/types' import { RootState } from 'src/redux/reducers' import { RecursivePartial, createMockStore } from 'test/utils' jest.mock('src/statsig', () => ({ getMultichainFeatures: jest.fn().mockReturnValue({ showSwap: ['celo-alfajores'], }), })) const MOCK_RESPONSE_NO_NEXT_PAGE: GetHistoryResponse = { data: [ { activityId: 'deposit-earn', pointsAmount: 10, createdAt: '2024-03-05T20:26:25.000Z', metadata: { tokenId: 'celo-alfajores:0x874069fa1eb16d44d622f2e0ca25eea172369bc1', }, }, { activityId: 'create-live-link', pointsAmount: 10, createdAt: '2024-03-05T19:26:25.000Z', metadata: { liveLinkType: 'erc721', }, }, { activityId: 'create-live-link', pointsAmount: 20, createdAt: '2024-03-05T19:26:25.000Z', metadata: { liveLinkType: 'erc20', tokenId: 'celo-alfajores:native', }, }, { activityId: 'swap', pointsAmount: 20, createdAt: '2024-03-05T19:26:25.000Z', metadata: { to: 'celo-alfajores:native', from: 'celo-alfajores:0x874069fa1eb16d44d622f2e0ca25eea172369bc1', }, }, { activityId: 'swap', pointsAmount: 20, createdAt: '2024-03-05T19:26:25.000Z', metadata: { to: 'celo-alfajores:0x874069fa1eb16d44d622f2e0ca25eea172369bc1', from: 'celo-alfajores:native', }, }, { activityId: 'create-wallet', pointsAmount: 20, createdAt: '2023-12-04T19:26:25.000Z', }, ], hasNextPage: false, nextPageUrl: '', } describe(PointsHistoryBottomSheet, () => { const mockFetch = fetch as FetchMock beforeEach(() => { jest.clearAllMocks() mockFetch.resetMocks() }) function renderScreen(storeOverrides: RecursivePartial = {}) { const store = createMockStore({ ...storeOverrides, }) const dispatch = jest.spyOn(store, 'dispatch') const tree = render( ) return { dispatch, store, ...tree, } } it('shows empty state when no history', async () => { const tree = renderScreen() expect(tree.queryByTestId('PointsHistoryBottomSheet/Error')).toBeNull() expect(tree.queryByTestId('PointsHistoryBottomSheet/Error')).toBeNull() expect(tree.getByTestId('PointsHistoryBottomSheet/Empty')).toBeTruthy() }) it('displays content while loading', async () => { const tree = renderScreen({ points: { pointsHistory: MOCK_RESPONSE_NO_NEXT_PAGE.data, getHistoryStatus: 'loading' }, }) await waitFor(() => expect(tree.getByTestId('PointsHistoryList').props.data.length).toBe(2)) expect( tree.getByText('points.history.cards.depositEarn.subtitle, {"network":"Celo Alfajores"}') ).toBeTruthy() expect( tree.getByText('points.history.cards.createLiveLink.subtitle.erc20, {"tokenSymbol":"CELO"}') ).toBeTruthy() expect(tree.getByText('points.history.cards.createLiveLink.subtitle.erc721')).toBeTruthy() expect( tree.getByText('points.history.cards.swap.subtitle, {"fromToken":"CELO","toToken":"cUSD"}') ).toBeTruthy() expect( tree.getByText('points.history.cards.swap.subtitle, {"fromToken":"cUSD","toToken":"CELO"}') ).toBeTruthy() expect(tree.getByText('points.history.cards.createWallet.subtitle')).toBeTruthy() expect(tree.getByText('March', { exact: false })).toBeTruthy() expect(tree.getByText('December 2023')).toBeTruthy() expect(tree.getByTestId('PointsHistoryBottomSheet/Loading')).toBeTruthy() }) it('fetches more content when bottom is visible', async () => { const { dispatch, getByTestId } = renderScreen({ points: { pointsHistory: MOCK_RESPONSE_NO_NEXT_PAGE.data, getHistoryStatus: 'idle', nextPageUrl: 'foo', }, }) // Trigger end reached to fetch more getByTestId('PointsHistoryList').props.onEndReached() expect(dispatch).toHaveBeenCalledWith(getHistoryStarted({ getNextPage: true })) }) it('shows error screen if fetch fails', async () => { const tree = renderScreen({ points: { getHistoryStatus: 'errorFirstPage' } }) expect(tree.getByTestId('PointsHistoryBottomSheet/Error')).toBeTruthy() }) it('dispatches an action when try again is pressed', async () => { const { dispatch, getByText } = renderScreen({ points: { getHistoryStatus: 'errorFirstPage' } }) fireEvent.press(getByText('points.history.error.tryAgain')) await waitFor(() => expect(AppAnalytics.track).toHaveBeenCalledWith( PointsEvents.points_screen_activity_try_again_press, { getNextPage: false, } ) ) expect(dispatch).toHaveBeenCalledWith(getHistoryStarted({ getNextPage: false })) }) it('shows empty screen if no info after fetch', async () => { const tree = renderScreen({ points: { getHistoryStatus: 'idle', pointsHistory: [] } }) expect(tree.getByTestId('PointsHistoryBottomSheet/Empty')).toBeTruthy() }) it('closes bottom sheet if got it is pressed', async () => { const { getByText } = renderScreen({ points: { getHistoryStatus: 'idle', pointsHistory: [] } }) fireEvent.press(getByText('points.history.empty.gotIt')) await waitFor(() => expect(AppAnalytics.track).toHaveBeenCalledWith( PointsEvents.points_screen_activity_learn_more_press ) ) }) it('shows inline error if failure while fetching subsequent page', async () => { const tree = renderScreen({ points: { getHistoryStatus: 'errorNextPage', pointsHistory: MOCK_RESPONSE_NO_NEXT_PAGE.data }, }) expect(tree.getByTestId('PointsHistoryBottomSheet/ErrorBanner')).toBeTruthy() }) it('refreshes if error banner CTA is pressed', async () => { const { dispatch, getByText } = renderScreen({ points: { getHistoryStatus: 'errorNextPage', pointsHistory: MOCK_RESPONSE_NO_NEXT_PAGE.data }, }) fireEvent.press(getByText('points.history.pageError.refresh')) await waitFor(() => expect(AppAnalytics.track).toHaveBeenCalledWith( PointsEvents.points_screen_activity_try_again_press, { getNextPage: true, } ) ) expect(dispatch).toHaveBeenCalledWith(getHistoryStarted({ getNextPage: true })) }) })