MOB-1339: respect scientific name preference

This commit is contained in:
sepeterson committed 2026-06-25 10:28:48 -05:00
1 parent 17509ba8f5
commit cf54517a0b
4 files changed
+139 -13

No files matched your search

@@ -46,7 +46,7 @@ export const resultToSubject = ( result: UniversalSearchResultItem ): ExploreV2S
export const subjectToText = (
subject: ExploreV2Subject,
prefersCommonNames: boolean,
commonNameIsPrimary: boolean,
): string => {
switch ( subject.type ) {
case "user":
@@ -55,7 +55,7 @@ export const subjectToText = (
return subject.project.title;
case "taxon":
default:
return ( prefersCommonNames && subject.taxon.preferred_common_name )
return ( commonNameIsPrimary && subject.taxon.preferred_common_name )
? generateTaxonPieces( subject.taxon ).commonName ?? subject.taxon.name
: subject.taxon.name;
}
@@ -55,7 +55,8 @@ const UniversalSearch = ( ) => {
const { t } = useTranslation( );
const { dispatch } = useExploreV2( );
const currentUser = useCurrentUser( );
const prefersCommonNames = currentUser?.prefers_common_names !== false;
const commonNameIsPrimary = currentUser?.prefers_common_names !== false
&& currentUser?.prefers_scientific_name_first !== true;
const [taxonText, setTaxonText] = useState( "" );
const [locationText, setLocationText] = useState( "" );
@@ -80,11 +81,11 @@ const UniversalSearch = ( ) => {
}, [debounceQuery] );
const handleSelect = useCallback( ( subject: ExploreV2Subject ) => {
setTaxonText( subjectToText( subject, prefersCommonNames ) );
setTaxonText( subjectToText( subject, commonNameIsPrimary ) );
setQueryImmediately( "" );
dispatch( { type: EXPLORE_V2_ACTION.SET_SUBJECT, subject } );
locationInputRef.current?.focus( );
}, [dispatch, prefersCommonNames, setQueryImmediately] );
}, [commonNameIsPrimary, dispatch, setQueryImmediately] );
const handleReset = useCallback( ( ) => {
setTaxonText( "" );
-1
View File
@@ -17,7 +17,6 @@ export type UniversalSearchResultItem =
const useUniversalSearch = ( query: string ) => {
const trimmedQuery = query.trim( );
// TODO look into some sort of throttle here
const shouldFetch = trimmedQuery.length > 0;
const {
@@ -1,4 +1,4 @@
import { fireEvent, screen } from "@testing-library/react-native";
import { act, fireEvent, screen } from "@testing-library/react-native";
import UniversalSearch from "components/Explore/ExploreV2/screens/UniversalSearch";
import initI18next from "i18n/initI18next";
import i18next from "i18next";
@@ -18,10 +18,92 @@ jest.mock( "@react-navigation/native", ( ) => {
};
} );
const mockDispatch = jest.fn( );
jest.mock( "providers/ExploreV2Context", ( ) => {
const actual = jest.requireActual( "providers/ExploreV2Context" );
return {
...actual,
useExploreV2: jest.fn( ),
};
} );
const { useExploreV2 } = require( "providers/ExploreV2Context" );
jest.mock( "sharedHooks/useUniversalSearch" );
const useUniversalSearch = require( "sharedHooks/useUniversalSearch" ).default;
jest.mock( "sharedHooks/useCurrentUser", ( ) => ( {
__esModule: true,
default: jest.fn( ),
} ) );
const useCurrentUser = require( "sharedHooks/useCurrentUser" ).default;
const CURRENT_USER = {
id: 99,
login: "tester",
icon_url: null,
observations_count: 42,
prefers_common_names: true,
prefers_scientific_name_first: false,
};
const MIXED_RESULTS = [
{
type: "user",
user: {
id: 7,
login: "carrieseltzer",
icon_url: "https://example.com/u.jpg",
observations_count: 5,
},
},
{
type: "taxon",
taxon: {
id: 12,
name: "Eumyias thalassinus",
preferred_common_name: "Verditer Flycatcher",
iconic_taxon_name: "Aves",
default_photo: { url: "https://example.com/t.jpg" },
},
},
{
type: "project",
project: {
id: 9,
title: "InverteFest",
project_type: "collection",
rule_preferences: [],
icon: "https://example.com/p.jpg",
},
},
];
const typeQuery = text => {
fireEvent.changeText( screen.getByTestId( "UniversalSearch.taxonInput" ), text );
// Only the timer advancement needs an act wrapper; fireEvent is already
// wrapped internally by Testing Library.
act( ( ) => {
jest.advanceTimersByTime( 400 );
} );
};
beforeAll( async ( ) => {
await initI18next( );
} );
beforeEach( ( ) => {
jest.useFakeTimers( );
mockNavigate.mockClear( );
mockDispatch.mockClear( );
useExploreV2.mockReturnValue( { dispatch: mockDispatch, state: {} } );
useCurrentUser.mockReturnValue( CURRENT_USER );
useUniversalSearch.mockReturnValue( { results: [], isLoading: false, refetch: jest.fn( ) } );
} );
afterEach( ( ) => {
jest.useRealTimers( );
} );
describe( "UniversalSearch screen", ( ) => {
it( "renders the core layout", ( ) => {
renderComponent( <UniversalSearch /> );
@@ -53,11 +135,55 @@ describe( "UniversalSearch screen", ( ) => {
expect( screen.queryByDisplayValue( "cup plant" ) ).toBeNull( );
expect( screen.queryByDisplayValue( "California" ) ).toBeNull( );
expect(
screen.getByPlaceholderText( i18next.t( "Search-for-species-user-or-project" ) ),
).toBeTruthy( );
expect(
screen.getByPlaceholderText( i18next.t( "Search-for-a-location" ) ),
).toBeTruthy( );
} );
it( "shows mixed autocomplete results while typing", ( ) => {
useUniversalSearch.mockReturnValue( {
results: MIXED_RESULTS,
isLoading: false,
refetch: jest.fn( ),
} );
renderComponent( <UniversalSearch /> );
typeQuery( "ver" );
expect( screen.getByText( "carrieseltzer" ) ).toBeTruthy( );
expect( screen.getByText( "InverteFest" ) ).toBeTruthy( );
expect( screen.getByTestId( "UniversalSearchResult.taxon.12" ) ).toBeTruthy( );
// each row has a (no-op) info icon
expect( screen.getByTestId( "UniversalSearchResult.info.7" ) ).toBeTruthy( );
} );
it( "fills the field and sets the subject when a result is tapped", ( ) => {
useUniversalSearch.mockReturnValue( {
results: MIXED_RESULTS,
isLoading: false,
refetch: jest.fn( ),
} );
renderComponent( <UniversalSearch /> );
typeQuery( "ver" );
fireEvent.press( screen.getByTestId( "UniversalSearchResult.user.7" ) );
expect( mockDispatch ).toHaveBeenCalledWith(
expect.objectContaining( {
type: "SET_SUBJECT",
subject: expect.objectContaining( {
type: "user",
user: expect.objectContaining( { id: 7, login: "carrieseltzer" } ),
} ),
} ),
);
// the search field is filled with the selected suggestion
expect( screen.getByDisplayValue( "carrieseltzer" ) ).toBeTruthy( );
} );
it( "navigates to Advanced Search", ( ) => {
renderComponent( <UniversalSearch /> );
fireEvent.press( screen.getByText( i18next.t( "Advanced-Search" ) ) );
expect( mockNavigate ).toHaveBeenCalledWith( "AdvancedSearch" );
} );
} );