import { fireEvent, render, within } from '@testing-library/react-native' import * as React from 'react' import { Provider } from 'react-redux' import AppAnalytics from 'src/analytics/AppAnalytics' import { DappExplorerEvents } from 'src/analytics/Events' import DappsScreen from 'src/dapps/DappsScreen' import { dappSelected, favoriteDapp, fetchDappsList, unfavoriteDapp } from 'src/dapps/slice' import { DappCategory, DappSection } from 'src/dapps/types' import { getFeatureGate } from 'src/statsig' import { StatsigFeatureGates } from 'src/statsig/types' import MockedNavigator from 'test/MockedNavigator' import { createMockStore } from 'test/utils' import { mockDappListWithCategoryNames } from 'test/values' jest.mock('src/analytics/AppAnalytics') jest.mock('src/statsig', () => ({ getExperimentParams: jest.fn(() => ({ dappsFilterEnabled: true, dappsSearchEnabled: true, })), getFeatureGate: jest.fn(), })) const dappsList = mockDappListWithCategoryNames const dappsCategories: DappCategory[] = [ { id: '1', name: 'Swap', backgroundColor: '#DEF8EA', fontColor: '#1AB775', }, { id: '2', name: 'Lend, Borrow & Earn', backgroundColor: '#DEF8F7', fontColor: '#07A0AE', }, ] const defaultStore = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories }, }) describe('DappsScreen', () => { beforeEach(() => { defaultStore.clearActions() jest.clearAllMocks() }) it('renders correctly and fires the correct actions on press dapp', () => { const { getByText, queryByText } = render( ) expect(defaultStore.getActions()).toEqual([fetchDappsList()]) expect(queryByText('featuredDapp')).toBeFalsy() expect(getByText('Dapp 1')).toBeTruthy() expect(getByText('Dapp 2')).toBeTruthy() expect(queryByText('dappsScreen.disclaimer_UK')).toBeFalsy() fireEvent.press(getByText('Dapp 1')) expect(defaultStore.getActions()).toEqual([ fetchDappsList(), dappSelected({ dapp: { ...dappsList[0], openedFrom: DappSection.All } }), ]) }) it('renders the disclaimer for UK compliance', () => { jest .mocked(getFeatureGate) .mockImplementation((gate) => gate === StatsigFeatureGates.SHOW_UK_COMPLIANT_VARIANT) const { getByText } = render( ) expect(getByText('dappsScreen.disclaimer_UK')).toBeTruthy() }) it('renders correctly and fires the correct actions on press deep linked dapp', () => { const { getByText, queryByText } = render( ) expect(defaultStore.getActions()).toEqual([fetchDappsList()]) expect(getByText('Dapp 1')).toBeTruthy() expect(getByText('Dapp 2')).toBeTruthy() expect(queryByText('featuredDapp')).toBeFalsy() fireEvent.press(getByText('Dapp 2')) expect(defaultStore.getActions()).toEqual([ fetchDappsList(), dappSelected({ dapp: { ...dappsList[1], openedFrom: DappSection.All } }), ]) }) it('opens dapps directly', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, }, }) const { getByText } = render( ) expect(getByText('dappsDisclaimerAllDapps')).toBeTruthy() fireEvent.press(getByText('Dapp 1')) expect(store.getActions()).toEqual([ fetchDappsList(), dappSelected({ dapp: { ...dappsList[0], openedFrom: DappSection.All } }), ]) }) it('renders error message when fetching dapps fails', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList: [], dappsCategories: [], dappsListError: 'Error fetching dapps', }, }) const { getByText, getByTestId } = render( ) expect(getByText('dappsScreen.errorMessage')).toBeTruthy() // asserts whether categories.length (0) isn't rendered, which causes a // crash in the app expect(getByTestId('DappsScreen')).not.toHaveTextContent('0') }) describe('favorite dapps', () => { it('renders correctly when there are no favorite dapps', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, favoriteDappIds: [], }, }) const { getByText, queryByText } = render( ) expect(queryByText('dappsScreen.noFavorites.title')).toBeNull() expect(queryByText('dappsScreen.noFavorites.description')).toBeNull() expect(getByText('Dapp 1')).toBeTruthy() expect(getByText('Dapp 2')).toBeTruthy() }) it('renders correctly when there are favourited dapps', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, favoriteDappIds: ['dapp2'], }, }) const { queryByText, getAllByTestId } = render( ) const favoritesSectionResults = getAllByTestId('DappsScreen/FavoritesSection/DappCard') expect(favoritesSectionResults.length).toBe(1) expect(favoritesSectionResults[0]).toHaveTextContent(dappsList[1].name) expect(favoritesSectionResults[0]).toHaveTextContent(dappsList[1].description) expect(queryByText('dappsScreen.favoritedDappToast.message')).toBeFalsy() }) it('triggers the events when favoriting', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, favoriteDappIds: ['dapp1'], }, }) const { getByTestId, getByText } = render( ) // don't include events dispatched on screen load jest.clearAllMocks() const allDappsSection = getByTestId('DappsScreen/DappsList') fireEvent.press(within(allDappsSection).getByTestId('Dapp/Favorite/dapp2')) // favorited dapp confirmation toast expect(getByText('dappsScreen.favoritedDappToast.message')).toBeTruthy() expect(getByText('dappsScreen.favoritedDappToast.labelCTA')).toBeTruthy() expect(AppAnalytics.track).toHaveBeenCalledTimes(1) expect(AppAnalytics.track).toHaveBeenCalledWith('dapp_favorite', { categories: ['2'], dappId: 'dapp2', dappName: 'Dapp 2', section: 'all', }) expect(store.getActions()).toEqual([fetchDappsList(), favoriteDapp({ dappId: 'dapp2' })]) }) it('triggers the events when unfavoriting', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, favoriteDappIds: ['dapp2'], }, }) const { getByTestId, getAllByTestId, queryByText } = render( ) // don't include events dispatched on screen load jest.clearAllMocks() const selectedDappCards = getAllByTestId('Dapp/dapp2') // should only appear once, in the favorites section expect(selectedDappCards).toHaveLength(1) fireEvent.press(getByTestId('Dapp/Favorite/dapp2')) expect(queryByText('dappsScreen.favoritedDappToast.message')).toBeFalsy() expect(AppAnalytics.track).toHaveBeenCalledTimes(1) expect(AppAnalytics.track).toHaveBeenCalledWith('dapp_unfavorite', { categories: ['2'], dappId: 'dapp2', dappName: 'Dapp 2', section: 'all', }) expect(store.getActions()).toEqual([fetchDappsList(), unfavoriteDapp({ dappId: 'dapp2' })]) }) }) describe('searching dapps', () => { it('renders correctly when there are no search results', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, favoriteDappIds: [], }, }) const { getByTestId, queryByTestId } = render( ) fireEvent.changeText(getByTestId('SearchInput'), 'iDoNotExist') // Should the no results within the all section expect(queryByTestId('DappsScreen/FavoritesSection/NoResults')).toBeNull() expect(getByTestId('DappsScreen/AllSection/NoResults')).toBeTruthy() }) it('renders correctly when there are search results in both sections', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, favoriteDappIds: ['dapp2'], }, }) const { getByTestId, queryByTestId, getAllByTestId } = render( ) fireEvent.changeText(getByTestId('SearchInput'), 'dapp') // Should display the correct sections const favoritesSectionResults = getAllByTestId('DappsScreen/FavoritesSection/DappCard') expect(favoritesSectionResults.length).toBe(1) expect(favoritesSectionResults[0]).toHaveTextContent(dappsList[1].name) const allSectionResults = getAllByTestId('DappsScreen/AllSection/DappCard') expect(allSectionResults.length).toBe(2) expect(allSectionResults[0]).toHaveTextContent(dappsList[0].name) expect(allSectionResults[1]).toHaveTextContent(dappsList[2].name) // No results sections should not be displayed expect(queryByTestId('DappsScreen/FavoritesSection/NoResults')).toBeNull() expect(queryByTestId('DappsScreen/AllSection/NoResults')).toBeNull() }) it('clearing search input should show all dapps', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, favoriteDappIds: ['dapp2'], }, }) const { getByTestId, getAllByTestId } = render( ) // Type in search that should have no results fireEvent.press(getByTestId('SearchInput')) fireEvent.changeText(getByTestId('SearchInput'), 'iDoNotExist') // Clear search field - onPress is tested in src/components/CircleButton.test.tsx fireEvent.changeText(getByTestId('SearchInput'), '') // Dapps displayed in the correct sections const favoritesSectionResults = getAllByTestId('DappsScreen/FavoritesSection/DappCard') expect(favoritesSectionResults.length).toBe(1) expect(favoritesSectionResults[0]).toHaveTextContent(dappsList[1].name) const allSectionResults = getAllByTestId('DappsScreen/AllSection/DappCard') expect(allSectionResults.length).toBe(2) expect(allSectionResults[0]).toHaveTextContent(dappsList[0].name) expect(allSectionResults[1]).toHaveTextContent(dappsList[2].name) }) }) describe('filter dapps', () => { it('renders correctly when there are no filters applied', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, favoriteDappIds: ['dapp1'], }, }) const { getByText, queryByText, getAllByTestId } = render( ) // All Filter Chips is not displayed expect(queryByText('dappsScreen.allDapps')).toBeFalsy() // Category Filter Chips displayed expect(getByText(dappsCategories[0].name)).toBeTruthy() expect(getByText(dappsCategories[1].name)).toBeTruthy() const favoritesSectionResults = getAllByTestId('DappsScreen/FavoritesSection/DappCard') expect(favoritesSectionResults.length).toBe(1) expect(favoritesSectionResults[0]).toHaveTextContent(dappsList[0].name) const allSectionResults = getAllByTestId('DappsScreen/AllSection/DappCard') expect(allSectionResults.length).toBe(2) expect(allSectionResults[0]).toHaveTextContent(dappsList[1].name) expect(allSectionResults[1]).toHaveTextContent(dappsList[2].name) }) it('renders correctly when there are filters applied', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, favoriteDappIds: ['dapp1'], }, }) const { getByTestId, getByText, queryByTestId, getAllByTestId } = render( ) // Tap on category 2 filter fireEvent.press(getByText(dappsCategories[1].name)) // Favorite Section should not show results expect(queryByTestId('DappsScreen/FavoritesSection/DappCard')).toBeNull() // All Section should show only 'dapp 2' const allDappsSection = getByTestId('DappsScreen/DappsList') // queryByText returns null if not found expect(within(allDappsSection).queryByText(dappsList[0].name)).toBeFalsy() expect(within(allDappsSection).queryByText(dappsList[0].description)).toBeFalsy() expect(within(allDappsSection).getByText(dappsList[1].name)).toBeTruthy() expect(within(allDappsSection).getByText(dappsList[1].description)).toBeTruthy() const allSectionResults = getAllByTestId('DappsScreen/AllSection/DappCard') expect(allSectionResults.length).toBe(1) expect(allSectionResults[0]).toHaveTextContent(dappsList[1].name) }) it('triggers event when filtering', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, favoriteDappIds: ['dapp1'], }, }) const { getByText } = render( ) // don't include events dispatched on screen load jest.clearAllMocks() // Tap on category 2 filter fireEvent.press(getByText(dappsCategories[1].name)) expect(AppAnalytics.track).toHaveBeenCalledTimes(1) expect(AppAnalytics.track).toHaveBeenCalledWith(DappExplorerEvents.dapp_filter, { filterId: '2', remove: false, }) }) it('triggers events when toggling a category filter', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, favoriteDappIds: ['dapp1'], }, }) const { getByText } = render( ) // don't include events dispatched on screen load jest.clearAllMocks() // Tap on category 2 filter fireEvent.press(getByText(dappsCategories[1].name)) // Tap on category 2 filter again to remove it fireEvent.press(getByText(dappsCategories[1].name)) expect(AppAnalytics.track).toHaveBeenCalledTimes(2) expect(AppAnalytics.track).toHaveBeenNthCalledWith(1, DappExplorerEvents.dapp_filter, { filterId: '2', remove: false, }) expect(AppAnalytics.track).toHaveBeenNthCalledWith(2, DappExplorerEvents.dapp_filter, { filterId: '2', remove: true, }) }) it('triggers event when clearing filters from category section', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, favoriteDappIds: ['dapp2'], }, }) const { getByTestId, getByText } = render( ) // don't include events dispatched on screen load jest.clearAllMocks() // Tap on category 2 filter fireEvent.press(getByText(dappsCategories[1].name)) // Tap on remove filters from all section fireEvent.press(getByTestId('DappsScreen/AllSection/NoResults/RemoveFilter')) // Assert correct analytics are fired expect(AppAnalytics.track).toHaveBeenCalledTimes(2) expect(AppAnalytics.track).toHaveBeenCalledWith(DappExplorerEvents.dapp_filter, { filterId: '2', remove: false, }) expect(AppAnalytics.track).toHaveBeenCalledWith(DappExplorerEvents.dapp_filter, { filterId: '2', remove: true, }) }) it('triggers event when clearing filters from favorite section', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, favoriteDappIds: ['dapp1'], }, }) const { getByText, queryByTestId } = render( ) // don't include events dispatched on screen load jest.clearAllMocks() // Tap on category 2 filter fireEvent.press(getByText(dappsCategories[1].name)) // Should not render favorites section expect(queryByTestId('DappsScreen/FavoritesSection/Title')).toBeNull() expect(queryByTestId('DappsScreen/FavoritesSection/DappCard')).toBeNull() // Assert correct analytics are fired expect(AppAnalytics.track).toHaveBeenCalledTimes(1) expect(AppAnalytics.track).toHaveBeenCalledWith(DappExplorerEvents.dapp_filter, { filterId: '2', remove: false, }) }) }) describe('searching and filtering', () => { it('renders correctly when there are results in all and favorites sections', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, favoriteDappIds: ['dapp1'], }, }) const { getByTestId, getAllByTestId } = render( ) // Search for 'tokens' fireEvent.changeText(getByTestId('SearchInput'), 'tokens') // Favorites section displays correctly const favoritesSectionResults = getAllByTestId('DappsScreen/FavoritesSection/DappCard') expect(favoritesSectionResults.length).toBe(1) expect(favoritesSectionResults[0]).toHaveTextContent(dappsList[0].name) // All section displays correctly const allSectionResults = getAllByTestId('DappsScreen/AllSection/DappCard') expect(allSectionResults.length).toBe(1) expect(allSectionResults[0]).toHaveTextContent(dappsList[1].name) }) it('renders correctly when there are results in favorites and no results in all', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, favoriteDappIds: ['dapp1'], }, }) const { getByTestId, getByText, getAllByTestId, queryByTestId } = render( ) // Searching for tokens should return both dapps fireEvent.changeText(getByTestId('SearchInput'), 'tokens') // Filtering by category 1 should return only dapp 1 fireEvent.press(getByText(dappsCategories[0].name)) // Favorite Section should show only 'dapp 1' const favoritesSectionResults = getAllByTestId('DappsScreen/FavoritesSection/DappCard') expect(favoritesSectionResults.length).toBe(1) expect(favoritesSectionResults[0]).toHaveTextContent(dappsList[0].name) // All Section should show no results expect(queryByTestId('DappsScreen/AllSection/DappCard')).toBeFalsy() expect(getByTestId('DappsScreen/AllSection/NoResults')).toBeTruthy() }) it('renders correctly when there are no results in favorites and results in all', () => { const store = createMockStore({ dapps: { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, favoriteDappIds: ['dapp2'], }, }) const { getByTestId, getByText, queryByTestId } = render( ) // Searching for tokens should return both dapps fireEvent.changeText(getByTestId('SearchInput'), 'tokens') // Filtering by category 1 should return only dapp 1 fireEvent.press(getByText(dappsCategories[0].name)) // Favorite Section should not show expect(queryByTestId('FavoriteDappsSection')).toBeNull() // All Section should show only 'dapp 1' const allDappsSection = getByTestId('DappsScreen/DappsList') expect(within(allDappsSection).getByText(dappsList[0].name)).toBeTruthy() expect(within(allDappsSection).getByText(dappsList[0].description)).toBeTruthy() expect(within(allDappsSection).queryByText(dappsList[1].name)).toBeFalsy() expect(within(allDappsSection).queryByText(dappsList[1].description)).toBeFalsy() }) }) describe('dapp open analytics event properties', () => { const defaultStoreProps = { dappListApiUrl: 'http://url.com', dappsList, dappsCategories, favoriteDappIds: [], } const defaultExpectedDappOpenProps = { activeFilter: 'all', activeSearchTerm: '', categories: ['1'], dappId: 'dapp3', dappName: 'Dapp 3', position: 3, section: 'all', fromScreen: 'DappsScreen', } it('should dispatch with no filter or search, from the normal list with no confirmation bottom sheet', () => { const store = createMockStore({ dapps: defaultStoreProps, }) const { getByText } = render( ) fireEvent.press(getByText('Dapp 3')) expect(AppAnalytics.track).toHaveBeenLastCalledWith( DappExplorerEvents.dapp_open, defaultExpectedDappOpenProps ) }) it('should dispatch with no filter or search, with favourites', () => { const store = createMockStore({ dapps: { ...defaultStoreProps, favoriteDappIds: ['dapp1'], }, }) const { getByText } = render( ) fireEvent.press(getByText('Dapp 3')) expect(AppAnalytics.track).toHaveBeenLastCalledWith(DappExplorerEvents.dapp_open, { ...defaultExpectedDappOpenProps, position: 2, // note the position explicitly does not take into account the number of favorites }) }) it('should dispatch with filter and search', () => { const store = createMockStore({ dapps: defaultStoreProps, }) const { getByText, getByTestId, getAllByTestId } = render( ) fireEvent.changeText(getByTestId('SearchInput'), 'cool') fireEvent.press(getByText(dappsCategories[0].name)) expect(getAllByTestId('DappsScreen/AllSection/DappCard')).toHaveLength(1) fireEvent.press(getByText('Dapp 3')) expect(AppAnalytics.track).toHaveBeenLastCalledWith(DappExplorerEvents.dapp_open, { ...defaultExpectedDappOpenProps, activeFilter: '1', activeSearchTerm: 'cool', position: 1, }) }) }) })