Files
iNaturalistReactNative/tests/unit/components/MyObservations/hooks/useMyObservationsQuery.test.js
Abbey Campbell 0d024bd71e add tests
2026-07-21 15:12:35 -07:00

244 lines
9.0 KiB
JavaScript

import { renderHook } from "@testing-library/react-native";
import useMyObservationsQuery from "components/MyObservations/hooks/useMyObservationsQuery";
import useServerOrderedObservations
from "components/MyObservations/hooks/useServerOrderedObservations";
import { useMyObservations } from "providers/MyObservationsContext";
import { OBSERVATIONS_SORT } from "sharedHelpers/observationsSort";
import safeRealmWrite from "sharedHelpers/safeRealmWrite";
import useCurrentUser from "sharedHooks/useCurrentUser";
import factory from "tests/factory";
import setupUniqueRealm from "tests/helpers/uniqueRealm";
jest.mock( "components/MyObservations/hooks/useServerOrderedObservations", ( ) => ( {
__esModule: true,
default: jest.fn( ),
} ) );
jest.mock( "providers/MyObservationsContext", ( ) => ( {
__esModule: true,
useMyObservations: jest.fn( ),
} ) );
jest.mock( "sharedHooks/useCurrentUser", ( ) => ( {
__esModule: true,
default: jest.fn( ),
} ) );
// 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" );
return {
__esModule: true,
...originalModule,
RealmContext: {
...originalModule.RealmContext,
useRealm: ( ) => global.mockRealms[mockRealmIdentifier],
useQuery: ( { type, query } ) => {
const realm = global.mockRealms[mockRealmIdentifier];
const results = realm.objects( type );
return query
? query( results )
: results;
},
},
};
} );
beforeAll( uniqueRealmBeforeAll );
afterAll( uniqueRealmAfterAll );
// /UNIQUE REALM SETUP
const createObservation = observation => {
const realm = global.mockRealms[mockRealmIdentifier];
safeRealmWrite( realm, ( ) => {
realm.create( "Observation", observation, "modified" );
}, "create test observation for useMyObservationsQuery" );
};
const defaultServerResult = {
observationIds: [],
isLoading: false,
isFetchingNextPage: false,
error: null,
fetchNextPage: jest.fn( ),
refetch: jest.fn( ),
};
const mockUser = factory( "LocalUser" );
beforeEach( ( ) => {
// clear leftover state from previous test
const realm = global.mockRealms[mockRealmIdentifier];
safeRealmWrite( realm, ( ) => {
realm.deleteAll( );
}, "clear realm before each useMyObservationsQuery test" );
useServerOrderedObservations.mockReturnValue( defaultServerResult );
useCurrentUser.mockReturnValue( mockUser );
} );
afterEach( ( ) => {
jest.clearAllMocks( );
} );
describe( "useMyObservationsQuery", ( ) => {
it( "uses the local Realm manifest for the default sort, ignoring server results "
+ "and suppressing server loading/error/refetch since that query is disabled", ( ) => {
useMyObservations.mockReturnValue( {
state: { observationsSort: OBSERVATIONS_SORT.DATE_UPLOADED_NEWEST },
} );
const serverRefetch = jest.fn( );
const serverFetchNextPage = jest.fn( );
useServerOrderedObservations.mockReturnValue( {
observationIds: [{ uuid: "should-be-ignored-in-default-sort" }],
isLoading: true,
isFetchingNextPage: true,
error: new Error( "should be suppressed for default sort" ),
refetch: serverRefetch,
fetchNextPage: serverFetchNextPage,
} );
const localObs = factory( "LocalObservation", { needs_sync: false } );
createObservation( localObs );
const { result } = renderHook( ( ) => useMyObservationsQuery( ) );
expect( result.current.observationIds ).toEqual( [{ uuid: localObs.uuid }] );
expect( result.current.isServerAuthoritative ).toEqual( false );
expect( result.current.isLoading ).toEqual( false );
expect( result.current.isFetchingNextPage ).toEqual( false );
expect( result.current.error ).toBeNull( );
expect( result.current.refetch ).not.toBe( serverRefetch );
expect( result.current.fetchNextPage ).not.toBe( serverFetchNextPage );
expect( useServerOrderedObservations ).toHaveBeenCalledWith(
expect.objectContaining( { enabled: false } ),
);
} );
it( "prepends unsynced local observations ahead of server results for non-default sort", ( ) => {
useMyObservations.mockReturnValue( {
state: { observationsSort: OBSERVATIONS_SORT.DATE_OBSERVED_OLDEST },
} );
const serverObs = { uuid: factory( "LocalObservation" ).uuid };
const serverFetchNextPage = jest.fn( );
useServerOrderedObservations.mockReturnValue( {
...defaultServerResult,
observationIds: [serverObs],
isFetchingNextPage: true,
fetchNextPage: serverFetchNextPage,
} );
const unsyncedObs = factory( "LocalObservation", { needs_sync: true } );
createObservation( unsyncedObs );
const { result } = renderHook( ( ) => useMyObservationsQuery( ) );
expect( result.current.observationIds ).toEqual( [
{ uuid: unsyncedObs.uuid },
serverObs,
] );
expect( result.current.isServerAuthoritative ).toEqual( true );
expect( result.current.isFetchingNextPage ).toEqual( true );
expect( result.current.fetchNextPage ).toBe( serverFetchNextPage );
expect( useServerOrderedObservations ).toHaveBeenCalledWith(
expect.objectContaining( { enabled: true } ),
);
} );
it( "dedupes an observation that is both unsynced locally and present in server results", ( ) => {
useMyObservations.mockReturnValue( {
state: { observationsSort: OBSERVATIONS_SORT.DATE_OBSERVED_OLDEST },
} );
const unsyncedObs = factory( "LocalObservation", { needs_sync: true } );
const otherServerObs = { uuid: factory( "LocalObservation" ).uuid };
useServerOrderedObservations.mockReturnValue( {
...defaultServerResult,
observationIds: [{ uuid: unsyncedObs.uuid }, otherServerObs],
} );
createObservation( unsyncedObs );
const { result } = renderHook( ( ) => useMyObservationsQuery( ) );
expect( result.current.observationIds ).toEqual( [
{ uuid: unsyncedObs.uuid },
otherServerObs,
] );
} );
it( "is server authoritative for an active taxon search even under the default sort", ( ) => {
const searchedTaxon = { id: 121323, name: "Reptilia" };
useMyObservations.mockReturnValue( {
state: {
observationsSort: OBSERVATIONS_SORT.DATE_UPLOADED_NEWEST,
searchedTaxon,
},
} );
const serverObs = { uuid: factory( "LocalObservation" ).uuid };
useServerOrderedObservations.mockReturnValue( {
...defaultServerResult,
observationIds: [serverObs],
} );
const { result } = renderHook( ( ) => useMyObservationsQuery( ) );
expect( result.current.isServerAuthoritative ).toEqual( true );
expect( result.current.observationIds ).toEqual( [serverObs] );
expect( useServerOrderedObservations ).toHaveBeenCalledWith(
expect.objectContaining( { enabled: true, taxonId: searchedTaxon.id } ),
);
} );
it( "ignores an active taxon search when there is no current user", ( ) => {
useCurrentUser.mockReturnValue( null );
const searchedTaxon = { id: 121323, name: "Reptilia" };
useMyObservations.mockReturnValue( {
state: {
observationsSort: OBSERVATIONS_SORT.DATE_UPLOADED_NEWEST,
searchedTaxon,
},
} );
useServerOrderedObservations.mockReturnValue( {
...defaultServerResult,
observationIds: [{ uuid: "should-be-ignored-when-logged-out" }],
} );
const localObs = factory( "LocalObservation", { needs_sync: false } );
createObservation( localObs );
const { result } = renderHook( ( ) => useMyObservationsQuery( ) );
expect( result.current.isServerAuthoritative ).toEqual( false );
expect( result.current.observationIds ).toEqual( [{ uuid: localObs.uuid }] );
expect( useServerOrderedObservations ).toHaveBeenCalledWith(
expect.objectContaining( { enabled: false } ),
);
} );
it( "applies the selected sort to local observations when there is no current user", ( ) => {
useCurrentUser.mockReturnValue( null );
useMyObservations.mockReturnValue( {
state: { observationsSort: OBSERVATIONS_SORT.DATE_OBSERVED_OLDEST },
} );
useServerOrderedObservations.mockReturnValue( {
...defaultServerResult,
observationIds: [{ uuid: "should-be-ignored-when-logged-out" }],
} );
const olderObs = factory( "LocalObservation", { observed_on_string: "2020-01-01T00:00:00" } );
const newerObs = factory( "LocalObservation", { observed_on_string: "2022-06-15T00:00:00" } );
// create newer-first so a passing test can't be explained by insertion order
createObservation( newerObs );
createObservation( olderObs );
const { result } = renderHook( ( ) => useMyObservationsQuery( ) );
expect( result.current.observationIds ).toEqual( [
{ uuid: olderObs.uuid },
{ uuid: newerObs.uuid },
] );
expect( result.current.isServerAuthoritative ).toEqual( false );
expect( useServerOrderedObservations ).toHaveBeenCalledWith(
expect.objectContaining( { enabled: false } ),
);
} );
} );