From ae2f9eeddcabaadb5e487f2ab8fa21bf102851ec Mon Sep 17 00:00:00 2001 From: Ken-ichi Date: Fri, 28 Jun 2024 10:13:51 -0700 Subject: [PATCH] Fix missing map tiles when taxon chosen on Explore (#1722) * IconicTaxonChooser no longer takes a taxon or manages its own state * Setting iconic taxon filter to Unknown is now its own action * Unsetting a taxon can be done with the CHOOSE_TAXON action by setting taxon to null or undefined * Omitted some extraneous params from tile requests --- ios/Podfile.lock | 4 +- src/components/Developer/UiLibrary/Misc.js | 6 +- src/components/Explore/Explore.js | 3 + src/components/Explore/ExploreContainer.js | 21 +- src/components/Explore/Header/Header.js | 5 +- src/components/Explore/MapView.js | 10 +- .../Explore/Modals/ExploreFiltersModal.js | 3 + src/components/Explore/Modals/FilterModal.tsx | 58 +++--- .../Explore/RootExploreContainer.js | 21 +- .../ObsEdit/IdentificationSection.js | 49 +++-- .../SharedComponents/IconicTaxonChooser.js | 55 ++---- src/providers/ExploreContext.tsx | 186 ++++++++++-------- .../IconicTaxonChooser.test.js | 4 +- tests/unit/providers/ExploreContext.test.js | 31 +++ 14 files changed, 251 insertions(+), 205 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 8cb75d971..7997a9b79 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1477,12 +1477,12 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: d3f49c53809116a5d38da093a8aa78bf551aed09 BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3 - DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 + DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 FasterImage: 60d0750ddbcefff0070c4c17309c2d1d6cc650f0 FBLazyVector: 9f533d5a4c75ca77c8ed774aced1a91a0701781e FBReactNativeSpec: 40b791f4a1df779e7e4aa12c000319f4f216d40a fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b + glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 hermes-engine: 39589e9c297d024e90fe68f6830ff86c4e01498a libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 MMKV: 506311d0494023c2f7e0b62cc1f31b7370fa3cfb diff --git a/src/components/Developer/UiLibrary/Misc.js b/src/components/Developer/UiLibrary/Misc.js index 4d53f5c23..d57cb8a62 100644 --- a/src/components/Developer/UiLibrary/Misc.js +++ b/src/components/Developer/UiLibrary/Misc.js @@ -344,11 +344,7 @@ const Misc = (): Node => { Iconic Taxon Chooser } onTaxonChosen={taxon => console.log( "taxon selected:", taxon )} /> diff --git a/src/components/Explore/Explore.js b/src/components/Explore/Explore.js index 357c1d3db..b7f2bd919 100644 --- a/src/components/Explore/Explore.js +++ b/src/components/Explore/Explore.js @@ -45,6 +45,7 @@ const exploreViewIcon = { type Props = { closeFiltersModal: Function, count: Object, + filterByIconicTaxonUnknown: Function, hideBackButton: boolean, isOnline: boolean, loadingStatus: boolean, @@ -61,6 +62,7 @@ type Props = { const Explore = ( { closeFiltersModal, count, + filterByIconicTaxonUnknown, hideBackButton, isOnline, loadingStatus, @@ -256,6 +258,7 @@ const Explore = ( { { useParams( ); - const updateTaxon = ( taxon: Object ) => { - if ( !taxon ) { - dispatch( { - type: EXPLORE_ACTION.CHANGE_TAXON_NONE, - taxon: null - } ); - } else { - dispatch( { - type: EXPLORE_ACTION.CHANGE_TAXON, - taxon, - taxonId: taxon?.id, - taxonName: taxon?.preferred_common_name || taxon?.name - } ); - } - }; - const updateLocation = ( place: Object ) => { if ( place === "worldwide" ) { dispatch( { @@ -113,13 +97,16 @@ const ExploreContainerWithContext = ( ): Node => { closeFiltersModal={closeFiltersModal} count={count} hideBackButton={false} + filterByIconicTaxonUnknown={ + () => dispatch( { type: EXPLORE_ACTION.FILTER_BY_ICONIC_TAXON_UNKNOWN } ) + } isOnline={isOnline} loadingStatus={loadingStatus} openFiltersModal={openFiltersModal} queryParams={queryParams} showFiltersModal={showFiltersModal} updateCount={updateCount} - updateTaxon={updateTaxon} + updateTaxon={taxon => dispatch( { type: EXPLORE_ACTION.CHANGE_TAXON, taxon } )} updateLocation={updateLocation} updateUser={updateUser} updateProject={updateProject} diff --git a/src/components/Explore/Header/Header.js b/src/components/Explore/Header/Header.js index 355280fa1..604d8e451 100644 --- a/src/components/Explore/Header/Header.js +++ b/src/components/Explore/Header/Header.js @@ -48,6 +48,7 @@ const Header = ( { const theme = useTheme( ); const { state, numberOfFilters } = useExplore( ); const { taxon } = state; + const iconicTaxonNames = state.iconic_taxa || []; const placeGuess = state.place_guess; const [showTaxonSearch, setShowTaxonSearch] = useState( false ); const [showLocationSearch, setShowLocationSearch] = useState( false ); @@ -71,11 +72,11 @@ const Header = ( { /> ) } - {taxon + {( taxon || iconicTaxonNames.indexOf( "unknown" ) >= 0 ) ? ( setShowTaxonSearch( true )} diff --git a/src/components/Explore/MapView.js b/src/components/Explore/MapView.js index 5601a3ce0..406adf71c 100644 --- a/src/components/Explore/MapView.js +++ b/src/components/Explore/MapView.js @@ -28,7 +28,7 @@ type Props = { const MapView = ( { observations, - queryParams: tileMapParams + queryParams }: Props ): Node => { const { t } = useTranslation( ); const { isDebug } = useDebugMode( ); @@ -48,6 +48,14 @@ const MapView = ( { updateMapBoundaries } = useMapLocation( ); + const tileMapParams = { + ...queryParams + }; + // Tile queries never need these params + delete tileMapParams.return_bounds; + delete tileMapParams.order; + delete tileMapParams.orderBy; + return ( diff --git a/src/components/Explore/Modals/ExploreFiltersModal.js b/src/components/Explore/Modals/ExploreFiltersModal.js index 632031c9e..03a45076d 100644 --- a/src/components/Explore/Modals/ExploreFiltersModal.js +++ b/src/components/Explore/Modals/ExploreFiltersModal.js @@ -9,6 +9,7 @@ import FilterModal from "./FilterModal"; type Props = { showModal: boolean, closeModal: Function, + filterByIconicTaxonUnknown: Function, updateTaxon: Function, updateLocation: Function, updateUser: Function, @@ -18,6 +19,7 @@ type Props = { const ExploreFiltersModal = ( { showModal, closeModal, + filterByIconicTaxonUnknown, updateTaxon, updateLocation, updateUser, @@ -31,6 +33,7 @@ const ExploreFiltersModal = ( { modal={( {t( "TAXON" )} - {taxon + {( taxon || ( iconicTaxonNames || [] ).indexOf( "unknown" ) >= 0 ) ? ( - + ) @@ -704,16 +707,21 @@ const FilterModal = ( { { if ( taxonName === "unknown" ) { - updateTaxon( ); + if ( ( iconicTaxonNames || [] ).indexOf( taxonName ) >= 0 ) { + updateTaxon( null ); + } else { + filterByIconicTaxonUnknown(); + } + } else if ( taxon?.name?.toLowerCase() === taxonName ) { + updateTaxon( null ); } else { const selectedTaxon = realm ?.objects( "Taxon" ) .filtered( "name CONTAINS[c] $0", taxonName ); - const iconicTaxon - = selectedTaxon.length > 0 + const iconicTaxon = selectedTaxon.length > 0 ? selectedTaxon[0] : null; updateTaxon( iconicTaxon ); diff --git a/src/components/Explore/RootExploreContainer.js b/src/components/Explore/RootExploreContainer.js index 3489416f4..5a06cb87d 100644 --- a/src/components/Explore/RootExploreContainer.js +++ b/src/components/Explore/RootExploreContainer.js @@ -36,22 +36,6 @@ const RootExploreContainerWithContext = ( ): Node => { const [showFiltersModal, setShowFiltersModal] = useState( false ); - const updateTaxon = ( taxon: Object ) => { - if ( !taxon ) { - dispatch( { - type: EXPLORE_ACTION.CHANGE_TAXON_NONE, - taxon: null - } ); - } else { - dispatch( { - type: EXPLORE_ACTION.CHANGE_TAXON, - taxon, - taxonId: taxon?.id, - taxonName: taxon?.preferred_common_name || taxon?.name - } ); - } - }; - const updateLocation = ( place: Object ) => { if ( place === "worldwide" ) { dispatch( { @@ -144,13 +128,16 @@ const RootExploreContainerWithContext = ( ): Node => { closeFiltersModal={closeFiltersModal} count={count} hideBackButton + filterByIconicTaxonUnknown={ + () => dispatch( { type: EXPLORE_ACTION.FILTER_BY_ICONIC_TAXON_UNKNOWN } ) + } isOnline={isOnline} loadingStatus={loadingStatus} openFiltersModal={openFiltersModal} queryParams={queryParams} showFiltersModal={showFiltersModal} updateCount={updateCount} - updateTaxon={updateTaxon} + updateTaxon={taxon => dispatch( { type: EXPLORE_ACTION.CHANGE_TAXON, taxon } )} updateLocation={updateLocation} updateUser={updateUser} updateProject={updateProject} diff --git a/src/components/ObsEdit/IdentificationSection.js b/src/components/ObsEdit/IdentificationSection.js index 7a3ab1a2b..00b14059a 100644 --- a/src/components/ObsEdit/IdentificationSection.js +++ b/src/components/ObsEdit/IdentificationSection.js @@ -10,6 +10,7 @@ import { TaxonResult } from "components/SharedComponents"; import { View } from "components/styledComponents"; +import { capitalize } from "lodash"; import { RealmContext } from "providers/contexts"; import type { Node } from "react"; import React, { useCallback, useEffect } from "react"; @@ -40,19 +41,15 @@ const IdentificationSection = ( { const navigation = useNavigation( ); const realm = useRealm( ); - const identification = currentObservation?.taxon; + const identTaxon = currentObservation?.taxon; const hasPhotos = currentObservation?.observationPhotos?.length > 0; - const hasIdentification = identification && identification.rank_level !== 100; + const hasIdentification = identTaxon && identTaxon.rank_level !== 100; - const showIconicTaxonChooser = !identification - || identification.name === identification.iconic_taxon_name - || identification.isIconic - || identification.name === "Life"; - - const onTaxonChosen = taxonName => updateObservationKeys( { - taxon: realm?.objects( "Taxon" ).filtered( "name CONTAINS[c] $0", taxonName )[0] - } ); + const showIconicTaxonChooser = !identTaxon + || identTaxon.name === identTaxon.iconic_taxon_name + || identTaxon.isIconic + || identTaxon.name === "Life"; const navToSuggestions = useCallback( ( ) => { if ( hasPhotos ) { @@ -83,20 +80,20 @@ const IdentificationSection = ( { @@ -104,8 +101,26 @@ const IdentificationSection = ( { accessibilityLabel={t( "View-suggestions" )} /> )} - taxon={identification} - onTaxonChosen={onTaxonChosen} + chosen={ + identTaxon + ? [identTaxon.name.toLowerCase()] + : [] + } + onTaxonChosen={taxonName => { + const capitalizedTaxonName = capitalize( taxonName ); + if ( + // user chose unknown + taxonName === "unknown" + // user tapped the selected iconic taxon to unselect + || identTaxon?.name === capitalizedTaxonName + ) { + updateObservationKeys( { taxon: undefined } ); + } else { + const newTaxon = realm?.objects( "Taxon" ) + .filtered( "name = $0", capitalizedTaxonName )[0]; + updateObservationKeys( { taxon: newTaxon } ); + } + }} /> ); @@ -120,14 +135,14 @@ const IdentificationSection = ( { )} - {identification && ( + {identTaxon && ( { const { t } = useTranslation( ); - const [selectedIcon, setSelectedIcon] = useState( null ); - const iconicTaxa = useIconicTaxa( { reload: false } ); - const isIconic = taxon?.id && iconicTaxa.filtered( `id = ${taxon?.id}` ); - - useEffect( ( ) => { - if ( !isIconic ) { return; } - setSelectedIcon( taxon.name.toLowerCase( ) ); - }, [isIconic, taxon] ); - - useEffect( ( ) => { - // reset selectedIcon state when navigating multiple observations - // on ObsEdit screen - if ( !taxon && selectedIcon ) { - setSelectedIcon( null ); - } - }, [taxon, selectedIcon] ); - - const renderIcon = useCallback( ( { item } ) => { - const isSelected = selectedIcon === item; + const renderIcon = useCallback( ( { item: iconicTaxonName } ) => { + const isSelected = chosen.indexOf( iconicTaxonName ) >= 0; return ( { - setSelectedIcon( item ); - onTaxonChosen( item ); + onTaxonChosen( iconicTaxonName ); }} color={isSelected && colors.white} accessibilityLabel={ - t( "Iconic-taxon-name", { iconicTaxon: item } ) + t( "Iconic-taxon-name", { iconicTaxon: iconicTaxonName } ) } accessibilityHint={ - t( "Selects-iconic-taxon-X-for-identification", { iconicTaxon: item } ) + t( "Selects-iconic-taxon-X-for-identification", { iconicTaxon: iconicTaxonName } ) } /> ); - }, [onTaxonChosen, t, selectedIcon] ); + }, [ + chosen, + onTaxonChosen, + t + // selectedIcon + ] ); const renderHeader = useCallback( ( ) => { if ( before ) { diff --git a/src/providers/ExploreContext.tsx b/src/providers/ExploreContext.tsx index 1cd0b4e24..56ec2e563 100644 --- a/src/providers/ExploreContext.tsx +++ b/src/providers/ExploreContext.tsx @@ -1,6 +1,7 @@ /* eslint-disable no-unused-vars */ /* eslint-disable no-shadow */ import { t } from "i18next"; +import { isEqual } from "lodash"; import * as React from "react"; import { LatLng } from "react-native-maps"; @@ -12,6 +13,7 @@ export enum EXPLORE_ACTION { CHANGE_SORT_BY = "CHANGE_SORT_BY", CHANGE_TAXON = "CHANGE_TAXON", DISCARD = "DISCARD", + FILTER_BY_ICONIC_TAXON_UNKNOWN = "FILTER_BY_ICONIC_TAXON_UNKNOWN", RESET = "RESET", SET_DATE_OBSERVED_ALL = "SET_DATE_OBSERVED_ALL", SET_DATE_OBSERVED_EXACT = "SET_DATE_OBSERVED_EXACT", @@ -160,65 +162,68 @@ interface PLACE { type ExploreProviderProps = {children: React.ReactNode} type State = { - verifiable: boolean, + casual: boolean, + created_d1: string | null | undefined, + created_d2: string | null | undefined, + created_on: string | null | undefined, + d1: string | null | undefined, + d2: string | null | undefined, + dateObserved: DATE_OBSERVED, + dateUploaded: DATE_UPLOADED, + establishmentMean: ESTABLISHMENT_MEAN, + hrank: TAXONOMIC_RANK | undefined | null, + iconic_taxa: string[] | undefined, + lat?: number, + lng?: number, + lrank: TAXONOMIC_RANK | undefined | null, + mapBoundaries: MapBoundaries | undefined, + media: MEDIA, + months: number[] | null | undefined, + needsID: boolean, + nelat?: number, + nelng?: number, + observed_on: string | null | undefined, + photoLicense: PHOTO_LICENSE, + place: PLACE | null | undefined, + place_guess: string, + place_id: number | null | undefined, + // TODO: technically this is not any Object but a "Project" + // and should be typed as such (e.g., in realm model) + project: Object | undefined, + project_id: number | undefined, + radius?: number, + researchGrade: boolean, return_bounds: boolean, + reviewedFilter: REVIEWED, + sortBy: SORT_BY, + swlat?: number, + swlng?: number, // TODO: technically this is not any Object but a "Taxon" // and should be typed as such (e.g., in realm model) taxon: Object | undefined, taxon_id: number | undefined, - place: PLACE | null | undefined, - place_id: number | null | undefined, - place_guess: string, - user_id: number | undefined, // TODO: technically this is not any Object but a "User" // and should be typed as such (e.g., in realm model) user: Object | undefined, - project_id: number | undefined, - // TODO: technically this is not any Object but a "Project" - // and should be typed as such (e.g., in realm model) - project: Object | undefined, - sortBy: SORT_BY, - researchGrade: boolean, - needsID: boolean, - casual: boolean, - hrank: TAXONOMIC_RANK | undefined | null, - lrank: TAXONOMIC_RANK | undefined | null, - dateObserved: DATE_OBSERVED, - observed_on: string | null | undefined, - d1: string | null | undefined, - d2: string | null | undefined, - months: number[] | null | undefined, - dateUploaded: DATE_UPLOADED, - created_on: string | null | undefined, - created_d1: string | null | undefined, - created_d2: string | null | undefined, - media: MEDIA, - establishmentMean: ESTABLISHMENT_MEAN, - wildStatus: WILD_STATUS, - reviewedFilter: REVIEWED, - photoLicense: PHOTO_LICENSE, - mapBoundaries: MapBoundaries + user_id: number | undefined, + verifiable: boolean, + wildStatus: WILD_STATUS } type Action = {type: EXPLORE_ACTION.RESET} | {type: EXPLORE_ACTION.DISCARD, snapshot: State} | {type: EXPLORE_ACTION.SET_USER, user: Object, userId: number, storedState: State} | { type: EXPLORE_ACTION.CHANGE_TAXON, - taxon: Object, - taxonId: number, - taxonName: string, + taxon: { id: number }, storedState: State } - | { - type: EXPLORE_ACTION.CHANGE_TAXON_NONE, - iconic_taxa: Object - } + | { type: EXPLORE_ACTION.FILTER_BY_ICONIC_TAXON_UNKNOWN } | {type: EXPLORE_ACTION.SET_EXPLORE_LOCATION, exploreLocation: Object} | { type: EXPLORE_ACTION.SET_PLACE, place: PLACE, placeId: number, - placeName: string, + placeGuess: string, lat: number, lng: number, radius: number, @@ -272,26 +277,29 @@ const calculatedFilters = { // Sort by: is NOT a filter criteria, but should return to default state when reset is pressed const defaultFilters = { ...calculatedFilters, - user: undefined, - project: undefined, - sortBy: SORT_BY.DATE_UPLOADED_NEWEST, - observed_on: undefined, + created_d1: undefined, + created_d2: undefined, + created_on: undefined, d1: undefined, d2: undefined, + iconic_taxa: undefined, months: undefined, - created_on: undefined, - created_d1: undefined, - created_d2: undefined + observed_on: undefined, + project: undefined, + sortBy: SORT_BY.DATE_UPLOADED_NEWEST, + user: undefined }; -const initialState = { +const initialState: State = { ...defaultFilters, + mapBoundaries: undefined, + place: undefined, + place_guess: "", + place_id: undefined, + return_bounds: true, taxon: undefined, taxon_id: undefined, - place_id: undefined, - place_guess: "", - verifiable: true, - return_bounds: true + verifiable: true }; // Checks if the date is in the format XXXX-XX-XX @@ -315,6 +323,9 @@ async function defaultExploreLocation( ) { }; } +// Note: if an action needs to remove a value from state, do not `delete` it. +// Instead, set it to undefined. This helps us detect changes to the default +// state function exploreReducer( state: State, action: Action ) { switch ( action.type ) { case EXPLORE_ACTION.RESET: @@ -324,43 +335,56 @@ function exploreReducer( state: State, action: Action ) { }; case EXPLORE_ACTION.DISCARD: return action.snapshot; - case EXPLORE_ACTION.CHANGE_TAXON: - return { + case EXPLORE_ACTION.CHANGE_TAXON: { + const newState = { ...state, ...action.storedState, - taxon: action.taxon, - taxon_id: action.taxonId, - iconic_taxa: [] + iconic_taxa: undefined }; - case EXPLORE_ACTION.CHANGE_TAXON_NONE: - return { + if ( action.taxon ) { + newState.taxon = action.taxon; + newState.taxon_id = action.taxon.id; + } else { + newState.taxon = undefined; + newState.taxon_id = undefined; + } + return newState; + } + // Every iconic taxon filter is essentially a taxon filter... except + // "unknown", which is a search for observations not associated with an + // iconic taxon (either they have no taxon or their taxon is not a + // descendant of an iconic taxon), so it needs its own special action. + // We could also redo this so all iconic taxon filters remove the taxon + // and add iconic_taxa. + case EXPLORE_ACTION.FILTER_BY_ICONIC_TAXON_UNKNOWN: { + const newState = { ...state, - taxon: "Unknown", - taxon_id: null, - iconic_taxa: ["unknown"] + iconic_taxa: ["unknown"], + taxon: undefined, + taxon_id: undefined }; + return newState; + } case EXPLORE_ACTION.SET_EXPLORE_LOCATION: return { ...state, ...action.exploreLocation }; case EXPLORE_ACTION.SET_PLACE: - // eslint-disable-next-line no-case-declarations - const placeState = { + return { ...state, ...action.storedState, - place: action.place, - place_id: action.placeId, - place_guess: action.placeGuess, lat: action.lat, lng: action.lng, - radius: action.radius + nelat: undefined, + nelng: undefined, + place: action.place, + place_guess: action.placeGuess, + place_id: action.placeId, + radius: action.radius, + swlat: undefined, + swlng: undefined }; - delete placeState.swlat; - delete placeState.swlng; - delete placeState.nelat; - delete placeState.nelng; - return placeState; case EXPLORE_ACTION.SET_USER: return { ...state, @@ -512,15 +536,14 @@ function exploreReducer( state: State, action: Action ) { reviewedFilter: action.reviewedFilter }; case EXPLORE_ACTION.SET_MAP_BOUNDARIES: { - const newState = { + return { ...state, - ...action.mapBoundaries + ...action.mapBoundaries, + lat: undefined, + lng: undefined, + place_id: undefined, + radius: undefined }; - delete newState.place_id; - delete newState.lat; - delete newState.lng; - delete newState.radius; - return newState; } case EXPLORE_ACTION.USE_STORED_STATE: return { @@ -544,9 +567,12 @@ const ExploreProvider = ( { children }: ExploreProviderProps ) => { if ( !snapshot ) { return false; } - return Object.keys( snapshot ).some( key => snapshot[key] !== state[key] ); + return Object.keys( snapshot ).some( key => !isEqual( snapshot[key], state[key] ) ); }; - const differsFromSnapshot: boolean = checkSnapshot(); + const differsFromSnapshot: boolean = React.useMemo( + checkSnapshot, + [state, snapshot] + ); const discardChanges = () => { if ( !snapshot ) { diff --git a/tests/unit/components/SharedComponents/IconicTaxonChooser.test.js b/tests/unit/components/SharedComponents/IconicTaxonChooser.test.js index 0db2d7414..336ed3f3b 100644 --- a/tests/unit/components/SharedComponents/IconicTaxonChooser.test.js +++ b/tests/unit/components/SharedComponents/IconicTaxonChooser.test.js @@ -18,7 +18,7 @@ describe( "IconicTaxonChooser", () => { name: "Aves" } ); expect( - + ).toBeAccessible( ); } ); @@ -28,7 +28,7 @@ describe( "IconicTaxonChooser", () => { iconic_taxon_name: "Plantae" } ); - render( ); + render( ); const plantButton = await screen.findByTestId( `IconicTaxonButton.${mockTaxon.name.toLowerCase( )}` diff --git a/tests/unit/providers/ExploreContext.test.js b/tests/unit/providers/ExploreContext.test.js index d70208fe8..005e10389 100644 --- a/tests/unit/providers/ExploreContext.test.js +++ b/tests/unit/providers/ExploreContext.test.js @@ -40,5 +40,36 @@ describe( "ExploreContext", ( ) => { expect( reducedState.radius ).toBeUndefined( ); } ); } ); + describe( EXPLORE_ACTION.CHANGE_TAXON, ( ) => { + it( "should remove iconic_taxa", ( ) => { + const taxon = factory( "RemoteTaxon" ); + const initialState = { iconic_taxa: ["Animalia"] }; + const reducedState = exploreReducer( initialState, { + type: EXPLORE_ACTION.CHANGE_TAXON, + taxon, + taxonId: taxon.id + } ); + expect( reducedState.iconic_taxa ).toBeUndefined( ); + } ); + it( "should extract an id from a taxon", ( ) => { + const taxon = factory( "RemoteTaxon" ); + const initialState = { }; + const reducedState = exploreReducer( initialState, { + type: EXPLORE_ACTION.CHANGE_TAXON, + taxon + } ); + expect( reducedState.taxon_id ).toEqual( taxon.id ); + } ); + it( "should remove an id from a blank taxon", ( ) => { + const taxon = factory( "RemoteTaxon" ); + const initialState = { taxon, taxon_id: taxon.id }; + const reducedState = exploreReducer( initialState, { + type: EXPLORE_ACTION.CHANGE_TAXON, + taxon: null + } ); + // expect( reducedState ).not.toHaveProperty( "taxon_id" ); + expect( reducedState.taxon_id ).toBeUndefined( ); + } ); + } ); } ); } );