From ca8a4cfdc8bdcd3490cdf17c753083c0244ca03d Mon Sep 17 00:00:00 2001 From: sepeterson <10458078+sepeterson@users.noreply.github.com> Date: Thu, 16 Jul 2026 17:59:31 -0500 Subject: [PATCH] MOB-1341: ExploreResults tests --- .../ExploreV2/screens/ExploreResults.test.js | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 tests/unit/components/Explore/ExploreV2/screens/ExploreResults.test.js diff --git a/tests/unit/components/Explore/ExploreV2/screens/ExploreResults.test.js b/tests/unit/components/Explore/ExploreV2/screens/ExploreResults.test.js new file mode 100644 index 000000000..87c412579 --- /dev/null +++ b/tests/unit/components/Explore/ExploreV2/screens/ExploreResults.test.js @@ -0,0 +1,162 @@ +import { screen, waitFor } from "@testing-library/react-native"; +import ExploreResults from "components/Explore/ExploreV2/screens/ExploreResults"; +import initI18next from "i18n/initI18next"; +import { + EXPLORE_V2_ACTION, + EXPLORE_V2_PLACE_MODE, + initialExploreV2State, +} from "providers/ExploreV2Context"; +import React from "react"; +import { renderComponent } from "tests/helpers/render"; + +jest.mock( "@react-navigation/native", ( ) => { + const actualNav = jest.requireActual( "@react-navigation/native" ); + return { + ...actualNav, + useFocusEffect: cb => jest.requireActual( "react" ).useEffect( cb, [] ), + }; +} ); + +jest.mock( "providers/ExploreV2Context", ( ) => { + const actual = jest.requireActual( "providers/ExploreV2Context" ); + return { ...actual, useExploreV2: jest.fn( ) }; +} ); +const { useExploreV2 } = require( "providers/ExploreV2Context" ); + +let mockHasPermissions; +let mockHasBlockedPermissions; +const mockDispatch = jest.fn( ); +const mockRequestPermissions = jest.fn( ); +jest.mock( "sharedHooks/useLocationPermission", ( ) => ( { + __esModule: true, + default: ( ) => ( { + hasPermissions: mockHasPermissions, + hasBlockedPermissions: mockHasBlockedPermissions, + renderPermissionsGate: ( ) => null, + requestPermissions: mockRequestPermissions, + } ), +} ) ); + +jest.mock( "sharedHelpers/fetchCoarseUserLocation", ( ) => ( { + __esModule: true, + default: jest.fn( ), +} ) ); +const fetchCoarseUserLocation = require( "sharedHelpers/fetchCoarseUserLocation" ).default; + +const mockUseInfiniteExploreScroll = jest.fn( ); +jest.mock( "components/Explore/hooks/useInfiniteExploreScroll", ( ) => ( { + __esModule: true, + default: args => mockUseInfiniteExploreScroll( args ), +} ) ); + +jest.mock( "sharedHooks/useSpeciesCount", ( ) => ( { __esModule: true, default: ( ) => 0 } ) ); + +const mockState = location => ( { + ...initialExploreV2State, + location, +} ); + +const lastScrollArgs = ( ) => mockUseInfiniteExploreScroll.mock.calls.at( -1 )[0]; + +beforeAll( async ( ) => { + await initI18next( ); +} ); + +beforeEach( ( ) => { + mockHasPermissions = undefined; + mockHasBlockedPermissions = false; + mockDispatch.mockClear( ); + mockRequestPermissions.mockClear( ); + fetchCoarseUserLocation.mockReset( ); + mockUseInfiniteExploreScroll.mockReset( ); + mockUseInfiniteExploreScroll.mockReturnValue( { + fetchNextPage: jest.fn( ), + isFetchingNextPage: false, + handlePullToRefresh: jest.fn( ), + observations: [], + totalResults: 0, + } ); +} ); + +describe( "ExploreResults nearby resolution", ( ) => { + it( "includes fetched coordinates in the query when nearby with permission", async ( ) => { + mockHasPermissions = true; + fetchCoarseUserLocation.mockResolvedValueOnce( { latitude: 37.5, longitude: -122.1 } ); + useExploreV2.mockReturnValue( { + state: mockState( { placeMode: EXPLORE_V2_PLACE_MODE.NEARBY } ), + dispatch: mockDispatch, + } ); + + renderComponent( ); + + await waitFor( ( ) => { + const { params, enabled } = lastScrollArgs( ); + expect( params.lat ).toBe( 37.5 ); + expect( params.lng ).toBe( -122.1 ); + expect( params.radius ).toBe( 1 ); + expect( enabled ).toBe( true ); + } ); + } ); + + it( "shows the permission prompt and does not fetch when nearby, no permission", async ( ) => { + mockHasPermissions = false; + useExploreV2.mockReturnValue( { + state: mockState( { placeMode: EXPLORE_V2_PLACE_MODE.NEARBY } ), + dispatch: mockDispatch, + } ); + + renderComponent( ); + + expect( + await screen.findByText( /To view nearby organisms/ ), + ).toBeVisible( ); + expect( fetchCoarseUserLocation ).not.toHaveBeenCalled( ); + expect( lastScrollArgs( ).enabled ).toBe( false ); + } ); + + it( "dispatches worldwide without prompting when permission is blocked", async ( ) => { + mockHasPermissions = false; + mockHasBlockedPermissions = true; + useExploreV2.mockReturnValue( { + state: mockState( { placeMode: EXPLORE_V2_PLACE_MODE.NEARBY } ), + dispatch: mockDispatch, + } ); + + renderComponent( ); + + await waitFor( ( ) => expect( mockDispatch ).toHaveBeenCalledWith( { + type: EXPLORE_V2_ACTION.SET_LOCATION_WORLDWIDE, + } ) ); + expect( screen.queryByText( /To view nearby organisms/ ) ).toBeNull( ); + expect( fetchCoarseUserLocation ).not.toHaveBeenCalled( ); + } ); + + it( "dispatches worldwide when permission is granted but there is no fix", async ( ) => { + mockHasPermissions = true; + fetchCoarseUserLocation.mockResolvedValueOnce( null ); + useExploreV2.mockReturnValue( { + state: mockState( { placeMode: EXPLORE_V2_PLACE_MODE.NEARBY } ), + dispatch: mockDispatch, + } ); + + renderComponent( ); + + await waitFor( ( ) => expect( mockDispatch ).toHaveBeenCalledWith( { + type: EXPLORE_V2_ACTION.SET_LOCATION_WORLDWIDE, + } ) ); + } ); + + it( "fetches worldwide without coordinates when worldwide", async ( ) => { + mockHasPermissions = true; + useExploreV2.mockReturnValue( { + state: mockState( { placeMode: EXPLORE_V2_PLACE_MODE.WORLDWIDE } ), + dispatch: mockDispatch, + } ); + + renderComponent( ); + + await waitFor( ( ) => expect( lastScrollArgs( ).enabled ).toBe( true ) ); + expect( lastScrollArgs( ).params.lat ).toBeUndefined( ); + expect( fetchCoarseUserLocation ).not.toHaveBeenCalled( ); + } ); +} );