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( );