mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2026-09-15 15:21:39 -04:00
MOB-1347: tests
This commit is contained in:
1 parent
64e3218af6
commit
bfec7984df
6 files changed
+362
-3
No files matched your search
@@ -36,7 +36,7 @@ const EMPTY_TABS: Tab[] = [];
|
||||
|
||||
// first two tabs need to occupy less than 100% of the screen width
|
||||
// so the user can be cued by a third option peeking out
|
||||
const SCROLLABLE_TAB_WIDTH_RATIO = 0.42;
|
||||
export const SCROLLABLE_TAB_WIDTH_RATIO = 0.42;
|
||||
|
||||
const SCROLL_STYLE = { flexGrow: 0 };
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { screen, userEvent } from "@testing-library/react-native";
|
||||
import ExploreV2Tabs from "components/Explore/ExploreV2/components/ExploreV2Tabs";
|
||||
import { ExploreV2Provider, initialExploreV2State } from "providers/ExploreV2Context";
|
||||
import React from "react";
|
||||
import useStore from "stores/useStore";
|
||||
import { renderComponent } from "tests/helpers/render";
|
||||
|
||||
const actor = userEvent.setup( );
|
||||
@@ -12,7 +13,14 @@ const renderTabs = props => renderComponent(
|
||||
</ExploreV2Provider>,
|
||||
);
|
||||
|
||||
const setAdvancedSearchMode = advancedSearchMode => useStore
|
||||
.getState( ).exploreV2AdvancedSearch.setAdvancedSearchMode( advancedSearchMode );
|
||||
|
||||
describe( "ExploreV2Tabs", () => {
|
||||
beforeEach( () => {
|
||||
setAdvancedSearchMode( false );
|
||||
} );
|
||||
|
||||
it( "renders both the observations and species tabs", () => {
|
||||
renderTabs();
|
||||
|
||||
@@ -62,4 +70,49 @@ describe( "ExploreV2Tabs", () => {
|
||||
|
||||
expect( screen.getAllByText( "--" ) ).toHaveLength( 2 );
|
||||
} );
|
||||
|
||||
it( "does not render the observers and identifiers tabs in default search mode", () => {
|
||||
renderTabs();
|
||||
|
||||
expect( screen.queryByTestId( "ExploreV2Tabs.observers" ) ).toBeNull();
|
||||
expect( screen.queryByTestId( "ExploreV2Tabs.identifiers" ) ).toBeNull();
|
||||
} );
|
||||
|
||||
describe( "in advanced search mode", () => {
|
||||
beforeEach( () => {
|
||||
setAdvancedSearchMode( true );
|
||||
} );
|
||||
|
||||
it( "renders all four tabs", () => {
|
||||
renderTabs();
|
||||
|
||||
expect( screen.getByTestId( "ExploreV2Tabs.observations" ) ).toBeTruthy();
|
||||
expect( screen.getByTestId( "ExploreV2Tabs.species" ) ).toBeTruthy();
|
||||
expect( screen.getByTestId( "ExploreV2Tabs.observers" ) ).toBeTruthy();
|
||||
expect( screen.getByTestId( "ExploreV2Tabs.identifiers" ) ).toBeTruthy();
|
||||
} );
|
||||
|
||||
it( "renders the provided observer and identifier counts", () => {
|
||||
renderTabs( { observersCount: 12, identifiersCount: 3 } );
|
||||
|
||||
expect( screen.getByText( "12" ) ).toBeTruthy();
|
||||
expect( screen.getByText( "3" ) ).toBeTruthy();
|
||||
} );
|
||||
|
||||
it.each( ["observers", "identifiers"] )(
|
||||
"switches the active tab when the %s tab is pressed",
|
||||
async tab => {
|
||||
renderTabs();
|
||||
|
||||
await actor.press( screen.getByTestId( `ExploreV2Tabs.${tab}` ) );
|
||||
|
||||
expect(
|
||||
screen.getByTestId( `ExploreV2Tabs.${tab}` ).props.accessibilityState.selected,
|
||||
).toBe( true );
|
||||
expect(
|
||||
screen.getByTestId( "ExploreV2Tabs.observations" ).props.accessibilityState.selected,
|
||||
).toBe( false );
|
||||
},
|
||||
);
|
||||
} );
|
||||
} );
|
||||
@@ -0,0 +1,51 @@
|
||||
import { act, waitFor } from "@testing-library/react-native";
|
||||
import useUserTabCounts from "components/Explore/ExploreV2/hooks/useUserTabCounts";
|
||||
import { renderHookInApp } from "tests/helpers/render";
|
||||
|
||||
jest.mock( "api/observations" );
|
||||
const { fetchIdentifiers, fetchObservers } = require( "api/observations" );
|
||||
|
||||
const PARAMS = { taxon_id: 12, place_id: 1 };
|
||||
|
||||
beforeEach( ( ) => {
|
||||
fetchObservers.mockReset( );
|
||||
fetchIdentifiers.mockReset( );
|
||||
fetchObservers.mockResolvedValue( { total_results: 8, results: [] } );
|
||||
fetchIdentifiers.mockResolvedValue( { total_results: 5, results: [] } );
|
||||
} );
|
||||
|
||||
describe( "useUserTabCounts", ( ) => {
|
||||
it( "returns the observer and identifier totals", async ( ) => {
|
||||
const { result } = renderHookInApp( ( ) => useUserTabCounts( PARAMS ) );
|
||||
|
||||
await waitFor( ( ) => expect( result.current.observersCount ).toBe( 8 ) );
|
||||
expect( result.current.identifiersCount ).toBe( 5 );
|
||||
} );
|
||||
|
||||
it( "asks for no results, only the totals", async ( ) => {
|
||||
renderHookInApp( ( ) => useUserTabCounts( PARAMS ) );
|
||||
|
||||
await waitFor( ( ) => expect( fetchObservers ).toHaveBeenCalled( ) );
|
||||
const [observersParams] = fetchObservers.mock.calls.at( -1 );
|
||||
expect( observersParams.per_page ).toBe( 0 );
|
||||
expect( observersParams.taxon_id ).toBe( 12 );
|
||||
const [identifiersParams] = fetchIdentifiers.mock.calls.at( -1 );
|
||||
expect( identifiersParams.per_page ).toBe( 0 );
|
||||
} );
|
||||
|
||||
it( "returns null counts and does not fetch when disabled", async ( ) => {
|
||||
const { result } = renderHookInApp(
|
||||
( ) => useUserTabCounts( { taxon_id: 999 }, { enabled: false } ),
|
||||
);
|
||||
|
||||
await act( async ( ) => {
|
||||
await new Promise( resolve => {
|
||||
setTimeout( resolve, 0 );
|
||||
} );
|
||||
} );
|
||||
expect( fetchObservers ).not.toHaveBeenCalled( );
|
||||
expect( fetchIdentifiers ).not.toHaveBeenCalled( );
|
||||
expect( result.current.observersCount ).toBeNull( );
|
||||
expect( result.current.identifiersCount ).toBeNull( );
|
||||
} );
|
||||
} );
|
||||
@@ -2,7 +2,9 @@ import { refresh, useNetInfo } from "@react-native-community/netinfo";
|
||||
import {
|
||||
act, screen, userEvent, waitFor,
|
||||
} from "@testing-library/react-native";
|
||||
import { SPECIES_TAB } from "appConstants/tabs";
|
||||
import {
|
||||
IDENTIFIERS_TAB, OBSERVATIONS_TAB, OBSERVERS_TAB, SPECIES_TAB,
|
||||
} from "appConstants/tabs";
|
||||
import ExploreResults from "components/Explore/ExploreV2/screens/ExploreResults";
|
||||
import initI18next from "i18n/initI18next";
|
||||
import {
|
||||
@@ -12,6 +14,7 @@ import {
|
||||
} from "providers/ExploreV2Context";
|
||||
import React from "react";
|
||||
import { SPECIES_SORT } from "sharedHelpers/speciesSort";
|
||||
import useStore from "stores/useStore";
|
||||
import { renderComponent } from "tests/helpers/render";
|
||||
|
||||
jest.mock( "@react-navigation/native", ( ) => {
|
||||
@@ -62,6 +65,24 @@ jest.mock( "components/Explore/ExploreV2/screens/ExploreV2SpeciesView", ( ) => (
|
||||
default: props => mockExploreV2SpeciesView( props ),
|
||||
} ) );
|
||||
|
||||
const mockExploreV2UsersView = jest.fn( ( ) => null );
|
||||
jest.mock( "components/Explore/ExploreV2/screens/ExploreV2UsersView", ( ) => ( {
|
||||
__esModule: true,
|
||||
default: props => mockExploreV2UsersView( props ),
|
||||
} ) );
|
||||
|
||||
const lastUsersViewTab = ( ) => mockExploreV2UsersView.mock.calls.at( -1 )?.[0]?.tab;
|
||||
|
||||
let mockUserTabCounts = { observersCount: null, identifiersCount: null };
|
||||
const mockUseUserTabCounts = jest.fn( );
|
||||
jest.mock( "components/Explore/ExploreV2/hooks/useUserTabCounts", ( ) => ( {
|
||||
__esModule: true,
|
||||
default: ( ...args ) => {
|
||||
mockUseUserTabCounts( ...args );
|
||||
return mockUserTabCounts;
|
||||
},
|
||||
} ) );
|
||||
|
||||
let mockLayout = "map";
|
||||
const mockWriteLayoutToStorage = jest.fn( );
|
||||
jest.mock( "sharedHooks/useStoredLayout", ( ) => ( {
|
||||
@@ -91,6 +112,10 @@ beforeEach( ( ) => {
|
||||
mockDispatch.mockClear( );
|
||||
mockRequestPermissions.mockClear( );
|
||||
mockExploreV2SpeciesView.mockClear( );
|
||||
mockExploreV2UsersView.mockClear( );
|
||||
mockUseUserTabCounts.mockClear( );
|
||||
mockUserTabCounts = { observersCount: null, identifiersCount: null };
|
||||
useStore.getState( ).exploreV2AdvancedSearch.setAdvancedSearchMode( false );
|
||||
fetchCoarseUserLocation.mockReset( );
|
||||
mockUseInfiniteExploreScroll.mockReset( );
|
||||
mockUseInfiniteExploreScroll.mockReturnValue( {
|
||||
@@ -448,3 +473,72 @@ describe( "ExploreResults observations view", ( ) => {
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( "ExploreResults observers and identifiers tabs", ( ) => {
|
||||
const advancedState = tab => ( {
|
||||
...mockState( { placeMode: EXPLORE_V2_PLACE_MODE.WORLDWIDE } ),
|
||||
activeTab: tab,
|
||||
} );
|
||||
|
||||
beforeEach( ( ) => {
|
||||
useStore.getState( ).exploreV2AdvancedSearch.setAdvancedSearchMode( true );
|
||||
} );
|
||||
|
||||
it.each( [OBSERVERS_TAB, IDENTIFIERS_TAB] )(
|
||||
"shows the users list on the %s tab",
|
||||
async tab => {
|
||||
useExploreV2.mockReturnValue( {
|
||||
state: advancedState( tab ),
|
||||
dispatch: mockDispatch,
|
||||
} );
|
||||
|
||||
renderComponent( <ExploreResults /> );
|
||||
|
||||
await waitFor( ( ) => {
|
||||
expect( lastUsersViewTab( ) ).toBe( tab );
|
||||
} );
|
||||
},
|
||||
);
|
||||
|
||||
it( "shows the observer and identifier counts in the tabs", async ( ) => {
|
||||
mockUserTabCounts = { observersCount: 8, identifiersCount: 5 };
|
||||
useExploreV2.mockReturnValue( {
|
||||
state: advancedState( OBSERVERS_TAB ),
|
||||
dispatch: mockDispatch,
|
||||
} );
|
||||
|
||||
renderComponent( <ExploreResults /> );
|
||||
|
||||
expect( await screen.findByText( "8" ) ).toBeVisible( );
|
||||
expect( screen.getByText( "5" ) ).toBeVisible( );
|
||||
} );
|
||||
|
||||
it( "offers no sort on the observers tab", async ( ) => {
|
||||
useExploreV2.mockReturnValue( {
|
||||
state: advancedState( OBSERVERS_TAB ),
|
||||
dispatch: mockDispatch,
|
||||
} );
|
||||
|
||||
renderComponent( <ExploreResults /> );
|
||||
|
||||
await waitFor( ( ) => {
|
||||
expect( mockExploreV2UsersView ).toHaveBeenCalled( );
|
||||
} );
|
||||
expect( screen.queryByLabelText( "Change observations sort order" ) ).toBeNull( );
|
||||
} );
|
||||
|
||||
it( "does not count observers or identifiers outside of advanced search mode", async ( ) => {
|
||||
useStore.getState( ).exploreV2AdvancedSearch.setAdvancedSearchMode( false );
|
||||
useExploreV2.mockReturnValue( {
|
||||
state: advancedState( OBSERVATIONS_TAB ),
|
||||
dispatch: mockDispatch,
|
||||
} );
|
||||
|
||||
renderComponent( <ExploreResults /> );
|
||||
|
||||
await waitFor( ( ) => {
|
||||
expect( mockUseUserTabCounts ).toHaveBeenCalled( );
|
||||
} );
|
||||
expect( mockUseUserTabCounts.mock.calls.at( -1 )[1] ).toEqual( { enabled: false } );
|
||||
} );
|
||||
} );
|
||||
@@ -0,0 +1,126 @@
|
||||
import { screen, userEvent } from "@testing-library/react-native";
|
||||
import { fetchIdentifiers, fetchObservers } from "api/observations";
|
||||
import { IDENTIFIERS_TAB, OBSERVERS_TAB } from "appConstants/tabs";
|
||||
import ExploreV2UsersView
|
||||
from "components/Explore/ExploreV2/screens/ExploreV2UsersView";
|
||||
import initI18next from "i18n/initI18next";
|
||||
import React from "react";
|
||||
import factory from "tests/factory";
|
||||
import { renderComponent } from "tests/helpers/render";
|
||||
|
||||
const mockObservers = [
|
||||
{ user: factory( "RemoteUser", { id: 1, login: "carrieseltzer" } ), observation_count: 22 },
|
||||
{ user: factory( "RemoteUser", { id: 2, login: "kueda" } ), observation_count: 11 },
|
||||
];
|
||||
|
||||
const mockIdentifiers = [
|
||||
{ user: factory( "RemoteUser", { id: 3, login: "loarie" } ), count: 9 },
|
||||
{ user: factory( "RemoteUser", { id: 4, login: "tiwane" } ), count: 4 },
|
||||
];
|
||||
|
||||
const mockedNavigate = jest.fn( );
|
||||
jest.mock( "@react-navigation/native", () => {
|
||||
const actualNav = jest.requireActual( "@react-navigation/native" );
|
||||
return {
|
||||
...actualNav,
|
||||
useNavigation: () => ( { navigate: mockedNavigate } ),
|
||||
};
|
||||
} );
|
||||
|
||||
const mockUseInfiniteScroll = jest.fn( );
|
||||
jest.mock( "sharedHooks/useInfiniteScroll", () => ( {
|
||||
__esModule: true,
|
||||
default: ( ...args ) => mockUseInfiniteScroll( ...args ),
|
||||
} ) );
|
||||
|
||||
const PARAMS = { taxon_id: 12, place_id: 1 };
|
||||
|
||||
const renderUsers = ( props = {} ) => renderComponent(
|
||||
<ExploreV2UsersView
|
||||
enabled
|
||||
isConnected
|
||||
params={PARAMS}
|
||||
tab={OBSERVERS_TAB}
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
|
||||
const mockResults = results => mockUseInfiniteScroll.mockReturnValue( {
|
||||
data: results,
|
||||
fetchNextPage: jest.fn( ),
|
||||
isFetchingNextPage: false,
|
||||
totalResults: results.length,
|
||||
} );
|
||||
|
||||
beforeAll( async ( ) => {
|
||||
await initI18next( );
|
||||
} );
|
||||
|
||||
beforeEach( ( ) => {
|
||||
mockedNavigate.mockClear( );
|
||||
mockUseInfiniteScroll.mockReset( );
|
||||
mockResults( mockObservers );
|
||||
} );
|
||||
|
||||
describe( "ExploreV2UsersView", ( ) => {
|
||||
describe( "observers tab", ( ) => {
|
||||
it( "lists observers with their observation counts", ( ) => {
|
||||
renderUsers( );
|
||||
|
||||
expect( screen.getByText( "carrieseltzer" ) ).toBeVisible( );
|
||||
expect( screen.getByText( "22 Observations" ) ).toBeVisible( );
|
||||
expect( screen.getByText( "kueda" ) ).toBeVisible( );
|
||||
} );
|
||||
|
||||
it( "navigates to a user profile when an observer is pressed", async ( ) => {
|
||||
const actor = userEvent.setup( );
|
||||
renderUsers( );
|
||||
|
||||
await actor.press( screen.getByTestId( "UserProfile.1" ) );
|
||||
|
||||
expect( mockedNavigate ).toHaveBeenCalledWith( "UserProfile", { userId: 1 } );
|
||||
} );
|
||||
|
||||
it( "queries observers, sorted by observation count, with the search params", ( ) => {
|
||||
renderUsers( );
|
||||
|
||||
const [queryKey, fetchUsers, params] = mockUseInfiniteScroll.mock.calls.at( -1 );
|
||||
expect( queryKey ).toBe( "exploreV2Observers" );
|
||||
expect( fetchUsers ).toBe( fetchObservers );
|
||||
expect( params.order_by ).toBe( "observation_count" );
|
||||
expect( params.taxon_id ).toBe( 12 );
|
||||
expect( params.place_id ).toBe( 1 );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( "identifiers tab", ( ) => {
|
||||
beforeEach( ( ) => {
|
||||
mockResults( mockIdentifiers );
|
||||
} );
|
||||
|
||||
it( "lists identifiers with their identification counts", ( ) => {
|
||||
renderUsers( { tab: IDENTIFIERS_TAB } );
|
||||
|
||||
expect( screen.getByText( "loarie" ) ).toBeVisible( );
|
||||
expect( screen.getByText( "9 Identifications" ) ).toBeVisible( );
|
||||
expect( screen.getByText( "tiwane" ) ).toBeVisible( );
|
||||
} );
|
||||
|
||||
it( "queries identifiers, without an observers sort, with the search params", ( ) => {
|
||||
renderUsers( { tab: IDENTIFIERS_TAB } );
|
||||
|
||||
const [queryKey, fetchUsers, params] = mockUseInfiniteScroll.mock.calls.at( -1 );
|
||||
expect( queryKey ).toBe( "exploreV2Identifiers" );
|
||||
expect( fetchUsers ).toBe( fetchIdentifiers );
|
||||
expect( params.order_by ).toBeUndefined( );
|
||||
expect( params.taxon_id ).toBe( 12 );
|
||||
expect( params.place_id ).toBe( 1 );
|
||||
} );
|
||||
} );
|
||||
|
||||
it( "passes the disabled flag through to the query", ( ) => {
|
||||
renderUsers( { enabled: false } );
|
||||
|
||||
expect( mockUseInfiniteScroll.mock.calls.at( -1 )[3] ).toEqual( { enabled: false } );
|
||||
} );
|
||||
} );
|
||||
@@ -1,9 +1,12 @@
|
||||
import { fireEvent, render, screen } from "@testing-library/react-native";
|
||||
import { Tabs } from "components/SharedComponents";
|
||||
import Tabs, { SCROLLABLE_TAB_WIDTH_RATIO } from "components/SharedComponents/Tabs/Tabs";
|
||||
import React from "react";
|
||||
import { Dimensions, ScrollView } from "react-native";
|
||||
|
||||
const TAB_1 = "TAB_1";
|
||||
const TAB_2 = "TAB_2";
|
||||
const TAB_3 = "TAB_3";
|
||||
const TAB_4 = "TAB_4";
|
||||
const tab1Click = jest.fn();
|
||||
const tab2Click = jest.fn();
|
||||
|
||||
@@ -63,3 +66,35 @@ describe( "Tabs", () => {
|
||||
expect( tab2Click ).toHaveBeenCalled();
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( "scrollable Tabs", () => {
|
||||
const { width } = Dimensions.get( "window" );
|
||||
const tabWidth = width * SCROLLABLE_TAB_WIDTH_RATIO;
|
||||
const scrollableTabs = [TAB_1, TAB_2, TAB_3, TAB_4].map( id => ( {
|
||||
id,
|
||||
text: id,
|
||||
onPress: jest.fn( ),
|
||||
} ) );
|
||||
|
||||
beforeEach( () => {
|
||||
ScrollView.prototype.scrollTo.mockClear( );
|
||||
} );
|
||||
|
||||
it( "should keep the strip at the start when the first tab is active", () => {
|
||||
render( <Tabs tabs={scrollableTabs} activeId={TAB_1} scrollable /> );
|
||||
|
||||
expect( ScrollView.prototype.scrollTo ).toHaveBeenCalledWith(
|
||||
{ x: 0, animated: false },
|
||||
);
|
||||
} );
|
||||
|
||||
it( "should scroll the newly active tab into view when the user changes tabs", () => {
|
||||
render( <Tabs tabs={scrollableTabs} activeId={TAB_1} scrollable /> );
|
||||
ScrollView.prototype.scrollTo.mockClear( );
|
||||
screen.update( <Tabs tabs={scrollableTabs} activeId={TAB_3} scrollable /> );
|
||||
|
||||
expect( ScrollView.prototype.scrollTo ).toHaveBeenCalledWith(
|
||||
{ x: 2.5 * tabWidth - width / 2, animated: true },
|
||||
);
|
||||
} );
|
||||
} );
|
||||
Reference in new issue
Block a user