From 829fa70f34ec5aefa4bc82bca8bd3a8af4279781 Mon Sep 17 00:00:00 2001
From: Amanda Bullington <35536439+albullington@users.noreply.github.com>
Date: Tue, 4 Jun 2024 15:07:15 -0700
Subject: [PATCH] Make explore view sticky; default to species view (#1647)
* Remove tab bar when sharing photos into app; closes #1645
* Make explore view sticky with species as default view; closes #1614
* Fix Explore integration test; use default species view
---
.../Explore/RootExploreContainer.js | 9 +----
src/components/Explore/hooks/useParams.js | 6 ---
.../MyObservations/ToolbarContainer.js | 16 +++++---
.../ProjectDetails/ProjectDetails.js | 19 ++++++----
src/components/TaxonDetails/TaxonDetails.js | 23 ++++++-----
src/components/UserProfile/UserProfile.js | 33 +++++++++-------
src/stores/createExploreSlice.ts | 10 +++--
tests/integration/Explore.test.js | 38 ++++++++++++++-----
tests/integration/navigation/Explore.test.js | 4 +-
9 files changed, 96 insertions(+), 62 deletions(-)
diff --git a/src/components/Explore/RootExploreContainer.js b/src/components/Explore/RootExploreContainer.js
index ffb3a35f3..cd6f5f2eb 100644
--- a/src/components/Explore/RootExploreContainer.js
+++ b/src/components/Explore/RootExploreContainer.js
@@ -23,7 +23,6 @@ const RootExploreContainerWithContext = ( ): Node => {
const currentUser = useCurrentUser( );
const storedParams = useStore( state => state.storedParams );
const setStoredParams = useStore( state => state.setStoredParams );
- const setExploreView = useStore( state => state.setExploreView );
const worldwidePlaceText = t( "Worldwide" );
@@ -33,10 +32,6 @@ const RootExploreContainerWithContext = ( ): Node => {
const [showFiltersModal, setShowFiltersModal] = useState( false );
- useEffect( ( ) => {
- setExploreView( "species" );
- }, [setExploreView] );
-
const updateTaxon = ( taxon: Object ) => {
dispatch( {
type: EXPLORE_ACTION.CHANGE_TAXON,
@@ -167,10 +162,10 @@ const RootExploreContainerWithContext = ( ): Node => {
);
};
-const ExploreContainer = (): Node => (
+const RootExploreContainer = (): Node => (
);
-export default ExploreContainer;
+export default RootExploreContainer;
diff --git a/src/components/Explore/hooks/useParams.js b/src/components/Explore/hooks/useParams.js
index 700071b0e..dc390f740 100644
--- a/src/components/Explore/hooks/useParams.js
+++ b/src/components/Explore/hooks/useParams.js
@@ -14,15 +14,10 @@ const useParams = ( ): Object => {
const { params } = useRoute( );
const { dispatch, setExploreLocation } = useExplore( );
const storedParams = useStore( state => state.storedParams );
- const setExploreView = useStore( state => state.setExploreView );
const worldwidePlaceText = t( "Worldwide" );
const updateContextWithParams = useCallback( async ( storedState = { } ) => {
- if ( params?.viewSpecies ) {
- setExploreView( "species" );
- }
-
const setWorldwide = ( ) => {
dispatch( {
type: EXPLORE_ACTION.SET_PLACE,
@@ -81,7 +76,6 @@ const useParams = ( ): Object => {
dispatch,
params,
setExploreLocation,
- setExploreView,
worldwidePlaceText
] );
diff --git a/src/components/MyObservations/ToolbarContainer.js b/src/components/MyObservations/ToolbarContainer.js
index bf39706b8..c43b58e70 100644
--- a/src/components/MyObservations/ToolbarContainer.js
+++ b/src/components/MyObservations/ToolbarContainer.js
@@ -28,6 +28,7 @@ const ToolbarContainer = ( {
syncInProgress,
toggleLayout
}: Props ): Node => {
+ const setExploreView = useStore( state => state.setExploreView );
const currentUser = useCurrentUser( );
const navigation = useNavigation( );
const deletions = useStore( state => state.deletions );
@@ -69,12 +70,15 @@ const ToolbarContainer = ( {
] );
const navToExplore = useCallback(
- ( ) => navigation.navigate( "Explore", {
- user: currentUser,
- worldwide: true,
- resetStoredParams: true
- } ),
- [navigation, currentUser]
+ ( ) => {
+ setExploreView( "observations" );
+ navigation.navigate( "Explore", {
+ user: currentUser,
+ worldwide: true,
+ resetStoredParams: true
+ } );
+ },
+ [navigation, currentUser, setExploreView]
);
const { t } = useTranslation( );
diff --git a/src/components/ProjectDetails/ProjectDetails.js b/src/components/ProjectDetails/ProjectDetails.js
index 64bf95480..c4e2e52a2 100644
--- a/src/components/ProjectDetails/ProjectDetails.js
+++ b/src/components/ProjectDetails/ProjectDetails.js
@@ -13,6 +13,7 @@ import {
import type { Node } from "react";
import React, { useCallback } from "react";
import { useTranslation } from "sharedHooks";
+import useStore from "stores/useStore";
import AboutProjectType from "./AboutProjectType";
@@ -26,6 +27,8 @@ type Props = {
const ProjectDetails = ( {
project, joinProject, leaveProject, loadingProjectMembership
}: Props ): Node => {
+ const setExploreView = useStore( state => state.setExploreView );
+
const { t } = useTranslation( );
const navigation = useNavigation( );
@@ -39,13 +42,15 @@ const ProjectDetails = ( {
);
const onSpeciesPressed = useCallback(
- ( ) => navigation.navigate( "Explore", {
- project,
- worldwide: true,
- viewSpecies: true,
- resetStoredParams: true
- } ),
- [navigation, project]
+ ( ) => {
+ setExploreView( "species" );
+ navigation.navigate( "Explore", {
+ project,
+ worldwide: true,
+ resetStoredParams: true
+ } );
+ },
+ [navigation, project, setExploreView]
);
if ( !project ) {
diff --git a/src/components/TaxonDetails/TaxonDetails.js b/src/components/TaxonDetails/TaxonDetails.js
index c4a6ec472..985d8f9db 100644
--- a/src/components/TaxonDetails/TaxonDetails.js
+++ b/src/components/TaxonDetails/TaxonDetails.js
@@ -30,6 +30,7 @@ import DeviceInfo from "react-native-device-info";
import { useTheme } from "react-native-paper";
import { log } from "sharedHelpers/logger";
import { useAuthenticatedQuery, useTranslation, useUserMe } from "sharedHooks";
+import useStore from "stores/useStore";
import EstablishmentMeans from "./EstablishmentMeans";
import TaxonDetailsMediaViewerHeader from "./TaxonDetailsMediaViewerHeader";
@@ -48,6 +49,7 @@ const TAXON_URL = "https://www.inaturalist.org/taxa";
const isTablet = DeviceInfo.isTablet();
const TaxonDetails = ( ): Node => {
+ const setExploreView = useStore( state => state.setExploreView );
const theme = useTheme( );
const navigation = useNavigation( );
const { params } = useRoute( );
@@ -229,17 +231,20 @@ const TaxonDetails = ( ): Node => {
navigation.navigate( "TabNavigator", {
- screen: "TabStackNavigator",
- params: {
- screen: "Explore",
+ onPress={( ) => {
+ setExploreView( "observations" );
+ navigation.navigate( "TabNavigator", {
+ screen: "TabStackNavigator",
params: {
- taxon,
- worldwide: true,
- resetStoredParams: true
+ screen: "Explore",
+ params: {
+ taxon,
+ worldwide: true,
+ resetStoredParams: true
+ }
}
- }
- } )}
+ } );
+ }}
accessibilityLabel={t( "See-observations-of-this-taxon-in-explore" )}
accessibilityHint={t( "Navigates-to-explore" )}
size={30}
diff --git a/src/components/UserProfile/UserProfile.js b/src/components/UserProfile/UserProfile.js
index cbf8f1c84..7765ce098 100644
--- a/src/components/UserProfile/UserProfile.js
+++ b/src/components/UserProfile/UserProfile.js
@@ -25,11 +25,13 @@ import {
useCurrentUser,
useIsConnected
} from "sharedHooks";
+import useStore from "stores/useStore";
import FollowButtonContainer from "./FollowButtonContainer";
import UnfollowSheet from "./UnfollowSheet";
const UserProfile = ( ): Node => {
+ const setExploreView = useStore( state => state.setExploreView );
const navigation = useNavigation( );
const currentUser = useCurrentUser( );
const { params } = useRoute( );
@@ -79,22 +81,27 @@ const UserProfile = ( ): Node => {
// }, [navigation, user, currentUser] );
const onObservationPressed = useCallback(
- ( ) => navigation.navigate( "Explore", {
- user,
- worldwide: true,
- resetStoredParams: true
- } ),
- [navigation, user]
+ ( ) => {
+ setExploreView( "observations" );
+ navigation.navigate( "Explore", {
+ user,
+ worldwide: true,
+ resetStoredParams: true
+ } );
+ },
+ [navigation, user, setExploreView]
);
const onSpeciesPressed = useCallback(
- ( ) => navigation.navigate( "Explore", {
- user,
- worldwide: true,
- viewSpecies: true,
- resetStoredParams: true
- } ),
- [navigation, user]
+ ( ) => {
+ setExploreView( "species" );
+ navigation.navigate( "Explore", {
+ user,
+ worldwide: true,
+ resetStoredParams: true
+ } );
+ },
+ [navigation, user, setExploreView]
);
if ( !user ) {
diff --git a/src/stores/createExploreSlice.ts b/src/stores/createExploreSlice.ts
index dcc2b9969..39ce454f4 100644
--- a/src/stores/createExploreSlice.ts
+++ b/src/stores/createExploreSlice.ts
@@ -9,6 +9,12 @@ export const initialMapRegion = {
longitudeDelta: DELTA
};
+const DEFAULT_STATE = {
+ storedParams: {},
+ exploreView: "species",
+ mapRegion: initialMapRegion
+};
+
interface MapRegion {
latitude: number,
longitude: number,
@@ -26,11 +32,9 @@ interface ExploreSlice {
}
const createExploreSlice: StateCreator = set => ( {
- storedParams: {},
+ ...DEFAULT_STATE,
setStoredParams: params => set( ( ) => ( { storedParams: params } ) ),
- exploreView: "observations",
setExploreView: exploreView => set( ( ) => ( { exploreView } ) ),
- mapRegion: initialMapRegion,
setMapRegion: region => set( ( ) => ( { mapRegion: region } ) )
} );
diff --git a/tests/integration/Explore.test.js b/tests/integration/Explore.test.js
index aff35840a..0cdc441ed 100644
--- a/tests/integration/Explore.test.js
+++ b/tests/integration/Explore.test.js
@@ -1,4 +1,4 @@
-import { fireEvent, screen } from "@testing-library/react-native";
+import { fireEvent, screen, userEvent } from "@testing-library/react-native";
import ExploreContainer from "components/Explore/ExploreContainer";
import inatjs from "inaturalistjs";
import React from "react";
@@ -17,12 +17,34 @@ const mockRemoteObservation = factory( "RemoteObservation", {
taxon: factory.states( "genus" )( "RemoteTaxon" )
} );
+const mockTaxon = factory( "LocalTaxon" );
+
+const actor = userEvent.setup( );
+
+beforeAll( ( ) => {
+ inatjs.observations.speciesCounts.mockResolvedValue( makeResponse( [{
+ count: 1,
+ taxon: mockTaxon
+ }] ) );
+ inatjs.observations.search.mockResolvedValue( makeResponse( [mockRemoteObservation] ) );
+} );
+
+const switchToObservationsView = async ( ) => {
+ const speciesViewIcon = await screen.findByLabelText( /Species View/ );
+ expect( speciesViewIcon ).toBeVisible( );
+ await actor.press( speciesViewIcon );
+ const observationsRadioButton = await screen.findByText( "Observations" );
+ await actor.press( observationsRadioButton );
+ const confirmButton = await screen.findByText( /EXPLORE OBSERVATIONS/ );
+ await actor.press( confirmButton );
+ const obsTaxonNameElt = await screen.findByText( mockRemoteObservation.taxon.name );
+ expect( obsTaxonNameElt ).toBeTruthy( );
+};
+
describe( "Explore", ( ) => {
- it( "should render", async ( ) => {
- inatjs.observations.search.mockResolvedValue( makeResponse( [mockRemoteObservation] ) );
+ it( "should render species view and switch to observations view list correctly", async ( ) => {
renderAppWithComponent( );
- const obsTaxonNameElt = await screen.findByText( mockRemoteObservation.taxon.name );
- expect( obsTaxonNameElt ).toBeTruthy( );
+ await switchToObservationsView( );
expect(
await screen.findByTestId( `ObsStatus.${mockRemoteObservation.uuid}` )
).toBeTruthy( );
@@ -31,11 +53,9 @@ describe( "Explore", ( ) => {
).toBeFalsy( );
} );
- it( "should display grid item correctly", async ( ) => {
- inatjs.observations.search.mockResolvedValue( makeResponse( [mockRemoteObservation] ) );
+ it( "should display observations view grid correctly", async ( ) => {
renderAppWithComponent( );
- const obsTaxonNameElt = await screen.findByText( mockRemoteObservation.taxon.name );
- expect( obsTaxonNameElt ).toBeTruthy( );
+ await switchToObservationsView( );
expect(
await screen.findByTestId( "SegmentedButton.grid" )
).toBeTruthy( );
diff --git a/tests/integration/navigation/Explore.test.js b/tests/integration/navigation/Explore.test.js
index 1117eebfb..070b05bd7 100644
--- a/tests/integration/navigation/Explore.test.js
+++ b/tests/integration/navigation/Explore.test.js
@@ -233,8 +233,8 @@ describe( "logged in", ( ) => {
await actor.press( taxonDetailsExploreButton );
const defaultGlobalLocation = await screen.findByText( /Worldwide/ );
expect( defaultGlobalLocation ).toBeVisible( );
- const speciesIcon = await screen.findByLabelText( /Species View/ );
- expect( speciesIcon ).toBeVisible( );
+ const observationsIcon = await screen.findByLabelText( /Observations View/ );
+ expect( observationsIcon ).toBeVisible( );
const backButton = screen.queryByTestId( "Explore.BackButton" );
await actor.press( backButton );
expect( taxonDetailsExploreButton ).toBeVisible( );