From b57537089cb465ca150b25453082bfdb6738e331 Mon Sep 17 00:00:00 2001 From: Seth Peterson <10458078+sepeterson@users.noreply.github.com> Date: Fri, 11 Sep 2026 11:21:26 -0500 Subject: [PATCH] MOB-1525: exploreV2 search integration test --- tests/integration/ExploreV2Search.test.js | 167 ++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 tests/integration/ExploreV2Search.test.js diff --git a/tests/integration/ExploreV2Search.test.js b/tests/integration/ExploreV2Search.test.js new file mode 100644 index 000000000..241e86f0b --- /dev/null +++ b/tests/integration/ExploreV2Search.test.js @@ -0,0 +1,167 @@ +import { + fireEvent, + screen, + userEvent, + waitFor, + within, +} from "@testing-library/react-native"; +import initI18next from "i18n/initI18next"; +import inatjs from "inaturalistjs"; +import factory, { makeResponse } from "tests/factory"; +import { + lastObservationsSearchParams, + navigateToExplore, + openUniversalSearch, + resetExploreV2, + searchForTaxon, + submitUniversalSearch, + typeIntoSearchField, +} from "tests/helpers/exploreV2"; +import faker from "tests/helpers/faker"; +import { renderApp } from "tests/helpers/render"; +import setStoreStateLayout from "tests/helpers/setStoreStateLayout"; +import setupUniqueRealm from "tests/helpers/uniqueRealm"; +import { signIn, signOut } from "tests/helpers/user"; + +jest.unmock( "@react-navigation/native" ); + +const mockUser = factory( "LocalUser", { + login: faker.internet.username( ), + locale: "en", +} ); + +const mockTaxon = factory( "RemoteTaxon", { + name: "Eumyias thalassinus", + preferred_common_name: "Verditer Flycatcher", + rank: "species", + rank_level: 10, +} ); + +const mockPlace = factory( "RemotePlace", { + display_name: "Oakland, CA", +} ); + +const mockObservations = [factory( "RemoteObservation", { taxon: mockTaxon } )]; + +jest.mock( "sharedHelpers/fetchCoarseUserLocation", ( ) => ( { + __esModule: true, + default: ( ) => Promise.resolve( { latitude: 37, longitude: 34 } ), +} ) ); + +// UNIQUE REALM SETUP +const mockRealmIdentifier = __filename; +const { mockRealmModelsIndex, uniqueRealmBeforeAll, uniqueRealmAfterAll } = setupUniqueRealm( + mockRealmIdentifier, +); +jest.mock( "realmModels/index", ( ) => mockRealmModelsIndex ); +jest.mock( "providers/contexts", ( ) => { + const originalModule = jest.requireActual( "providers/contexts" ); + const { makeRealmHooks } = jest.requireActual( "tests/helpers/uniqueRealm" ); + return { + __esModule: true, + ...originalModule, + RealmContext: { + ...originalModule.RealmContext, + ...makeRealmHooks( __filename ), + }, + }; +} ); +beforeAll( uniqueRealmBeforeAll ); +afterAll( uniqueRealmAfterAll ); +// /UNIQUE REALM SETUP + +const actor = userEvent.setup( ); + +// The subject field and the location field both go through inatjs.search, told apart by source +const mockAutocomplete = ( ) => inatjs.search.mockImplementation( params => { + if ( params.sources === "places" ) { + return makeResponse( [{ type: "place", score: 1, place: mockPlace }] ); + } + return makeResponse( [{ type: "taxon", score: 1, taxon: mockTaxon }] ); +} ); + +beforeAll( async ( ) => { + await initI18next( ); + jest.useFakeTimers( ); + inatjs.observations.search.mockResolvedValue( makeResponse( mockObservations ) ); + inatjs.observations.speciesCounts.mockResolvedValue( makeResponse( [{ + count: 1, + taxon: mockTaxon, + }] ) ); +} ); + +beforeEach( async ( ) => { + setStoreStateLayout( { isDefaultMode: false, isAllAddObsOptionsMode: true } ); + resetExploreV2( ); + mockAutocomplete( ); + inatjs.observations.search.mockClear( ); + await signIn( mockUser, { realm: global.mockRealms[__filename] } ); +} ); + +afterEach( async ( ) => { + await signOut( { realm: global.mockRealms[__filename] } ); +} ); + +global.withAnimatedTimeTravelEnabled( { skipFakeTimers: true } ); + +describe( "searching from Universal Search", ( ) => { + it( "searches for species the signed-in user has not observed", async ( ) => { + renderApp( ); + await navigateToExplore( ); + await openUniversalSearch( ); + + await actor.press( await screen.findByTestId( "DefaultSearchOptions.unobserved" ) ); + await submitUniversalSearch( ); + + await waitFor( ( ) => { + expect( lastObservationsSearchParams( ) ).toMatchObject( { + unobserved_by_user_id: mockUser.id, + } ); + } ); + const header = await screen.findByTestId( "ExploreV2Header" ); + expect( within( header ).getByTestId( "ExploreV2Header.unobserved" ) ).toBeVisible( ); + } ); + + it( "searches the place the user picked and offers it again later", async ( ) => { + renderApp( ); + await navigateToExplore( ); + await openUniversalSearch( ); + + typeIntoSearchField( "UniversalSearch.locationInput", "oakland" ); + await actor.press( await screen.findByText( mockPlace.display_name ) ); + await submitUniversalSearch( ); + + await waitFor( ( ) => { + expect( lastObservationsSearchParams( ) ).toMatchObject( { place_id: mockPlace.id } ); + } ); + expect( lastObservationsSearchParams( ) ).not.toHaveProperty( "lat" ); + const header = await screen.findByTestId( "ExploreV2Header" ); + expect( within( header ).getByText( mockPlace.display_name ) ).toBeVisible( ); + + // The place is remembered for the next search + await openUniversalSearch( ); + fireEvent( screen.getByTestId( "UniversalSearch.locationInput" ), "focus" ); + expect( + within( await screen.findByTestId( "RecentLocations" ) ) + .getByText( mockPlace.display_name ), + ).toBeVisible( ); + } ); + + it( "still has the search when the user comes back from another tab", async ( ) => { + renderApp( ); + await navigateToExplore( ); + await openUniversalSearch( ); + await searchForTaxon( mockTaxon ); + await waitFor( ( ) => { + expect( lastObservationsSearchParams( ) ).toMatchObject( { taxon_id: mockTaxon.id } ); + } ); + + const tabBar = await screen.findByTestId( "CustomTabBar" ); + await actor.press( within( tabBar ).getByTestId( "NavButton.personIcon" ) ); + await actor.press( within( tabBar ).getByText( "Explore" ) ); + + await screen.findByTestId( "ExploreResults" ); + const header = await screen.findByTestId( "ExploreV2Header" ); + expect( within( header ).getByText( mockTaxon.name ) ).toBeVisible( ); + } ); +} );