diff --git a/src/api/error.js b/src/api/error.js index 80e1031cd..09b492ae7 100644 --- a/src/api/error.js +++ b/src/api/error.js @@ -43,10 +43,10 @@ async function handleError( e: Object, options: Object = {} ): Object { if ( typeof ( options.onApiError ) === "function" ) { options.onApiError( error ); } - // Default to throw errors. We almost never want handle supress an error at + // Default to throw errors. We almost never want supress an error at // this low level if ( options.throw === false ) { - return null; + return error; } throw error; } diff --git a/src/components/Camera/ARCamera/hooks/usePredictions.js b/src/components/Camera/ARCamera/hooks/usePredictions.js index 6b5708834..eea5fb53c 100644 --- a/src/components/Camera/ARCamera/hooks/usePredictions.js +++ b/src/components/Camera/ARCamera/hooks/usePredictions.js @@ -3,6 +3,7 @@ import { useState } from "react"; +import { useIconicTaxa } from "sharedHooks"; const usePredictions = ( ): Object => { const [result, setResult] = useState( null ); @@ -11,22 +12,27 @@ const usePredictions = ( ): Object => { const [fps, setFPS] = useState( 1 ); const [numStoredResults, setNumStoredResults] = useState( 4 ); const [cropRatio, setCropRatio] = useState( 1 ); + const iconicTaxa = useIconicTaxa( ); const handleTaxaDetected = cvResult => { - console.log( "[DEBUG usePredictions.js] cvResult: ", cvResult ); if ( cvResult && !modelLoaded ) { setModelLoaded( true ); } let prediction = null; - const { predictions } = cvResult; - predictions.sort( ( a, b ) => a.rank_level - b.rank_level ); - if ( predictions.length > 0 ) { - const finestPrediction = predictions[0]; + const { predictions: branch } = cvResult; + branch.sort( ( a, b ) => a.rank_level - b.rank_level ); + const branchIDs = branch.map( t => t.taxon_id ); + if ( branch.length > 0 ) { + const finestPrediction = branch[0]; + // Try to find a known iconic taxon in the model results so we can show + // an icon if we can't show a photo + const iconicTaxon = iconicTaxa?.find( t => branchIDs.indexOf( t.id ) >= 0 ); prediction = { taxon: { rank_level: finestPrediction.rank_level, id: finestPrediction.taxon_id, - name: finestPrediction.name + name: finestPrediction.name, + iconic_taxon_name: iconicTaxon?.name }, score: finestPrediction.score }; diff --git a/src/components/SharedComponents/DisplayTaxonName.js b/src/components/SharedComponents/DisplayTaxonName.js index 27d18482f..a16122811 100644 --- a/src/components/SharedComponents/DisplayTaxonName.js +++ b/src/components/SharedComponents/DisplayTaxonName.js @@ -137,7 +137,7 @@ const DisplayTaxonName = ( { return ( diff --git a/src/components/SharedComponents/ObservationsFlashList/ObsImage.js b/src/components/SharedComponents/ObservationsFlashList/ObsImage.js index f9f1d635d..725b788da 100644 --- a/src/components/SharedComponents/ObservationsFlashList/ObsImage.js +++ b/src/components/SharedComponents/ObservationsFlashList/ObsImage.js @@ -1,7 +1,7 @@ // @flow import classNames from "classnames"; import { IconicTaxonIcon } from "components/SharedComponents"; -import { Image } from "components/styledComponents"; +import { Image, View } from "components/styledComponents"; import type { Node } from "react"; import React from "react"; @@ -35,29 +35,36 @@ const ObsImage = ( { }: Props ): Node => { const noImg = !uri?.uri; + const iconicTaxon = ( + + ); + if ( noImg ) { - return ( - - ); + return iconicTaxon; } return ( - + + + {iconicTaxon} + + + ); }; diff --git a/src/components/SharedComponents/TaxonResult.js b/src/components/SharedComponents/TaxonResult.js index af36dafe8..67ec4e96b 100644 --- a/src/components/SharedComponents/TaxonResult.js +++ b/src/components/SharedComponents/TaxonResult.js @@ -2,7 +2,11 @@ import { useNavigation } from "@react-navigation/native"; import classnames from "classnames"; -import { DisplayTaxonName, INatIconButton } from "components/SharedComponents"; +import { + ActivityIndicator, + DisplayTaxonName, + INatIconButton +} from "components/SharedComponents"; import ObsImagePreview from "components/SharedComponents/ObservationsFlashList/ObsImagePreview"; import { Pressable, View } from "components/styledComponents"; import type { Node } from "react"; @@ -39,7 +43,7 @@ const TaxonResult = ( { handleCheckmarkPress, handlePress, showCheckmark = true, - taxon: taxonResult, + taxon: taxonProp, testID, white = false }: Props ): Node => { @@ -50,13 +54,13 @@ const TaxonResult = ( { // network requests for useTaxon instead of making individual API calls. // right now, this fetches a single taxon at a time on AR camera & // a short list of taxa from offline Suggestions - const localTaxon = useTaxon( taxonResult, fetchRemote ); - const taxon = fromLocal + const { taxon: localTaxon, isLoading } = useTaxon( taxonProp, fetchRemote ); + const usableTaxon = fromLocal ? localTaxon - : taxonResult; - const taxonImage = { uri: taxon?.default_photo?.url }; + : taxonProp; + const taxonImage = { uri: usableTaxon.default_photo?.url }; - const navToTaxonDetails = () => navigation.navigate( "TaxonDetails", { id: taxon.id } ); + const navToTaxonDetails = () => navigation.navigate( "TaxonDetails", { id: usableTaxon.id } ); return ( - - {( confidence && confidencePosition === "photo" ) && ( - - - - )} + + { + isLoading + ? ( + + ) + : ( + + ) + } + {( confidence && confidencePosition === "photo" ) && ( + + + + )} + {( confidence && confidencePosition === "text" ) && ( @@ -136,7 +149,7 @@ const TaxonResult = ( { ? theme.colors.onSecondary : theme.colors.secondary } - onPress={() => handleCheckmarkPress( taxon )} + onPress={() => handleCheckmarkPress( usableTaxon )} accessibilityLabel={t( "Checkmark" )} accessibilityHint={t( "Add-this-ID" )} testID={`${testID}.checkmark`} diff --git a/src/realmModels/Taxon.js b/src/realmModels/Taxon.js index c1f0f9462..ccca8bc26 100644 --- a/src/realmModels/Taxon.js +++ b/src/realmModels/Taxon.js @@ -1,5 +1,4 @@ import { Realm } from "@realm/react"; -import safeRealmWrite from "sharedHelpers/safeRealmWrite"; import Photo from "./Photo"; @@ -107,15 +106,6 @@ class Taxon extends Realm.Object { static uri = item => ( item && item.default_photo ) && { uri: item.default_photo.url }; - static saveRemoteTaxon = async ( remoteTaxon, realm ) => { - if ( remoteTaxon ) { - const localTaxon = Taxon.mapApiToRealm( remoteTaxon, realm ); - safeRealmWrite( realm, ( ) => { - realm.create( "Taxon", localTaxon, "modified" ); - }, "saving remote taxon in Taxon" ); - } - }; - static schema = { name: "Taxon", primaryKey: "id", @@ -136,7 +126,8 @@ class Taxon extends Realm.Object { rank: "string?", rank_level: "float?", isIconic: "bool?", - iconic_taxon_name: "string?" + iconic_taxon_name: "string?", + _synced_at: "date?" } }; } diff --git a/src/realmModels/index.js b/src/realmModels/index.js index 4534adca3..14d06f2d6 100644 --- a/src/realmModels/index.js +++ b/src/realmModels/index.js @@ -28,7 +28,7 @@ export default { User, Vote ], - schemaVersion: 46, + schemaVersion: 47, path: `${RNFS.DocumentDirectoryPath}/db.realm`, // https://github.com/realm/realm-js/pull/6076 embedded constraints migrationOptions: { diff --git a/src/sharedHooks/useIconicTaxa.js b/src/sharedHooks/useIconicTaxa.js index fbd6a28a2..b59284600 100644 --- a/src/sharedHooks/useIconicTaxa.js +++ b/src/sharedHooks/useIconicTaxa.js @@ -7,7 +7,8 @@ import { useAuthenticatedQuery, useIsConnected } from "sharedHooks"; const { useRealm } = RealmContext; -const useIconicTaxa = ( { reload }: Object ): Object => { +const useIconicTaxa = ( options: { reload: boolean } = { reload: false } ): Object => { + const { reload } = options; const realm = useRealm( ); const isConnected = useIsConnected( ); const [isUpdatingRealm, setIsUpdatingRealm] = useState( ); @@ -23,10 +24,11 @@ const useIconicTaxa = ( { reload }: Object ): Object => { if ( iconicTaxa?.length > 0 && !isUpdatingRealm ) { setIsUpdatingRealm( true ); safeRealmWrite( realm, ( ) => { - iconicTaxa.forEach( taxa => { + iconicTaxa.forEach( taxon => { realm.create( "Taxon", { - ...taxa, - isIconic: true + ...taxon, + isIconic: true, + _synced_at: new Date( ) }, "modified" ); } ); }, "modifying iconic taxa in useIconicTaxa" ); diff --git a/src/sharedHooks/useTaxon.js b/src/sharedHooks/useTaxon.js index f2098352a..937c27dac 100644 --- a/src/sharedHooks/useTaxon.js +++ b/src/sharedHooks/useTaxon.js @@ -3,30 +3,67 @@ import { fetchTaxon } from "api/taxa"; import { RealmContext } from "providers/contexts"; import Taxon from "realmModels/Taxon"; +import safeRealmWrite from "sharedHelpers/safeRealmWrite"; import { useAuthenticatedQuery } from "sharedHooks"; const { useRealm } = RealmContext; +const ONE_WEEK_MS = ( + 1000 // ms / s + * 60 // s / min + * 60 // min / hr + * 24 // hr / day + * 7 // day / wk +); + const useTaxon = ( taxon: Object, fetchRemote: boolean = true ): Object => { const realm = useRealm( ); - const existingTaxon = taxon.id && realm.objectForPrimaryKey( "Taxon", taxon?.id ); + const localTaxon = taxon.id && realm.objectForPrimaryKey( "Taxon", taxon.id ); + + const canFetchTaxon = !!taxon?.id; + const localTaxonNeedsSync = ( + // Definitely sync if there's no local copy + !localTaxon + || ( + // Sync if the local copy hasn't been synced in a week + localTaxon._synced_at && ( Date.now( ) - localTaxon._synced_at > ONE_WEEK_MS ) + ) + ); + const enabled = canFetchTaxon && fetchRemote && localTaxonNeedsSync; const { - data: remoteTaxon + data: remoteTaxon, + isLoading } = useAuthenticatedQuery( ["fetchTaxon", taxon?.id], optsWithAuth => fetchTaxon( taxon.id, { fields: Taxon.TAXON_FIELDS }, optsWithAuth ), { - enabled: !!( taxon?.id && fetchRemote ) + enabled } ); - if ( !existingTaxon && remoteTaxon ) { - Taxon.saveRemoteTaxon( remoteTaxon, realm ); + const mappedRemoteTaxon = remoteTaxon + ? Taxon.mapApiToRealm( remoteTaxon, realm ) + : null; + + if ( localTaxonNeedsSync && mappedRemoteTaxon ) { + safeRealmWrite( realm, ( ) => { + realm.create( + "Taxon", + { ...mappedRemoteTaxon, _synced_at: new Date( ) }, + "modified" + ); + }, "saving remote taxon in useTaxon" ); } - return existingTaxon || taxon; + // Local is best, local-ish version of remote will be available sooner, use + // whatever was passed in as a last resort + return { + taxon: localTaxon || mappedRemoteTaxon || taxon, + // Apparently useQuery isLoading is true if the query is disabled + isLoading: enabled && isLoading + }; }; export default useTaxon; diff --git a/tests/factories/LocalTaxon.js b/tests/factories/LocalTaxon.js index ef0f45345..8eeddbf4e 100644 --- a/tests/factories/LocalTaxon.js +++ b/tests/factories/LocalTaxon.js @@ -1,10 +1,10 @@ import { define } from "factoria"; export default define( "LocalTaxon", faker => ( { + _synced_at: faker.date.past( ), id: faker.number.int( ), name: faker.person.fullName( ), preferred_common_name: faker.person.fullName( ), rank: "species", - rank_level: 10, - taxon_photos: [] + rank_level: 10 } ) ); diff --git a/tests/helpers/render.js b/tests/helpers/render.js index b6ae217db..d5b84586f 100644 --- a/tests/helpers/render.js +++ b/tests/helpers/render.js @@ -23,7 +23,7 @@ const queryClient = new QueryClient( { } } ); -function renderComponent( component, update = null ) { +function renderComponent( component, update = null, renderOptions = {} ) { const renderMethod = update || render; return renderMethod( @@ -36,7 +36,8 @@ function renderComponent( component, update = null ) { - + , + renderOptions ); } @@ -82,9 +83,47 @@ async function renderAppWithObservations( await screen.findByTestId( `MyObservations.obsListItem.${observations[0].uuid}` ); } +/** + * Render a hook within a component + * + * Port of equivalent in react-testing-library + * (https://github.com/testing-library/react-testing-library/blob/edb6344d578a8c224daf0cd6e2984f36cc6e8d86/src/pure.js#L264C1-L290C2), + * but using our renderComponent + */ +function renderHook( renderCallback, options = {} ) { + const { initialProps, ...renderOptions } = options; + const result = React.createRef( ); + + const TestComponent = ( { renderCallbackProps } ) => { + // eslint-disable-next-line testing-library/render-result-naming-convention + const pendingResult = renderCallback( renderCallbackProps ); + + React.useEffect( ( ) => { + result.current = pendingResult; + } ); + + return null; + }; + + const { rerender: baseRerender, unmount } = renderComponent( + , + null, + renderOptions + ); + + function rerender( rerenderCallbackProps ) { + return baseRerender( + + ); + } + + return { result, rerender, unmount }; +} + export { renderApp, renderAppWithComponent, renderAppWithObservations, - renderComponent + renderComponent, + renderHook }; diff --git a/tests/integration/sharedHooks/useTaxon.test.js b/tests/integration/sharedHooks/useTaxon.test.js index 4215f7fbd..409652e28 100644 --- a/tests/integration/sharedHooks/useTaxon.test.js +++ b/tests/integration/sharedHooks/useTaxon.test.js @@ -1,8 +1,32 @@ -import { renderHook } from "@testing-library/react-native"; +import { waitFor } from "@testing-library/react-native"; +import inatjs from "inaturalistjs"; import safeRealmWrite from "sharedHelpers/safeRealmWrite"; import { useTaxon } from "sharedHooks"; -import factory from "tests/factory"; +import factory, { makeResponse } from "tests/factory"; import faker from "tests/helpers/faker"; +import { renderHook } from "tests/helpers/render"; +import setupUniqueRealm from "tests/helpers/uniqueRealm"; + +// 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] + } + }; +} ); +beforeAll( uniqueRealmBeforeAll ); +afterAll( uniqueRealmAfterAll ); +// /UNIQUE REALM SETUP const mockRemoteTaxon = factory( "RemoteTaxon", { default_photo: { @@ -10,25 +34,33 @@ const mockRemoteTaxon = factory( "RemoteTaxon", { } } ); -jest.mock( "sharedHooks/useAuthenticatedQuery", () => ( { - __esModule: true, - default: ( ) => ( { - data: mockRemoteTaxon - } ) -} ) ); - const mockTaxon = factory( "LocalTaxon", { + _syncedAt: faker.date.recent( { days: 1 } ), + default_photo: { + url: faker.image.url( ) + } +} ); +const mockOutdatedTaxon = factory( "LocalTaxon", { + _syncedAt: faker.date.recent( { days: 1, refDate: "2024-01-01" } ), default_photo: { url: faker.image.url( ) } } ); describe( "useTaxon", ( ) => { + beforeAll( async () => { + jest.useFakeTimers( ); + } ); + beforeEach( ( ) => { + jest.restoreAllMocks( ); + inatjs.taxa.fetch.mockResolvedValue( makeResponse( [mockRemoteTaxon] ) ); + } ); + describe( "with local taxon", ( ) => { - beforeEach( async ( ) => { + beforeEach( ( ) => { // Write mock taxon to realm - safeRealmWrite( global.realm, ( ) => { - global.realm.create( "Taxon", mockTaxon, "modified" ); + safeRealmWrite( global.mockRealms[mockRealmIdentifier], ( ) => { + global.mockRealms[mockRealmIdentifier].create( "Taxon", mockTaxon, "modified" ); }, "write mock taxon, useTaxon test" ); } ); @@ -40,23 +72,73 @@ describe( "useTaxon", ( ) => { describe( "when there is a local taxon with taxon id", ( ) => { it( "should return local taxon with default photo", ( ) => { const { result } = renderHook( ( ) => useTaxon( mockTaxon ) ); - expect( result.current ).toHaveProperty( "default_photo" ); - expect( result.current.default_photo.url ).toEqual( mockTaxon.default_photo.url ); + expect( inatjs.taxa.fetch ).not.toHaveBeenCalled( ); + const { taxon: resultTaxon } = result.current; + expect( resultTaxon ).toHaveProperty( "default_photo" ); + expect( resultTaxon.default_photo.url ).toEqual( mockTaxon.default_photo.url ); + } ); + + it( "should request a taxon from the API if the local copy is out of date", async ( ) => { + safeRealmWrite( global.mockRealms[mockRealmIdentifier], ( ) => { + global.mockRealms[mockRealmIdentifier].create( "Taxon", mockOutdatedTaxon, "modified" ); + }, "write mock outdated taxon, useTaxon test" ); + renderHook( ( ) => useTaxon( mockOutdatedTaxon ) ); + await waitFor( ( ) => expect( inatjs.taxa.fetch ).toHaveBeenCalled( ) ); } ); } ); } ); describe( "when there is no local taxon with taxon id", ( ) => { beforeEach( async ( ) => { - safeRealmWrite( global.realm, ( ) => { - global.realm.deleteAll( ); + safeRealmWrite( global.mockRealms[mockRealmIdentifier], ( ) => { + global.mockRealms[mockRealmIdentifier].deleteAll( ); }, "delete all realm, useTaxon test" ); } ); - it( "should make an API call and return passed in taxon when fetchRemote is enabled", ( ) => { - const taxonId = faker.number.int( ); - const { result } = renderHook( ( ) => useTaxon( { id: taxonId }, true ) ); - expect( result.current ).not.toHaveProperty( "default_photo" ); + describe( "with fetchRemote: true", ( ) => { + it( "should request the taxon from the API", async ( ) => { + expect( + global.mockRealms[mockRealmIdentifier].objectForPrimaryKey( "Taxon", mockTaxon.id ) + ).toBeNull( ); + renderHook( ( ) => useTaxon( mockTaxon ) ); + await waitFor( ( ) => expect( inatjs.taxa.fetch ).toHaveBeenCalled( ) ); + } ); + + it( "should return the argument taxon if request fails", ( ) => { + // I don't love this. While it kind of mocks at the edge of the code + // we need to integrate, it doesn't test out API error handling code. + // I tried mocking inatjs to make it throw, but that always seems to + // result in a failure in the test, even though useQuery should catch + // those errors. ~~~kueda20240305 + jest.mock( "@tanstack/react-query", () => ( { + useQuery: jest.fn( ( ) => ( { + error: { } + } ) ) + } ) ); + const partialTaxon = { id: faker.number.int( ), foo: "bar" }; + const { result } = renderHook( ( ) => useTaxon( partialTaxon ) ); + expect( result.current.taxon.foo ).toEqual( "bar" ); + jest.unmock( "@tanstack/react-query" ); + } ); + + it( "should return a taxon like a local taxon record if the request succeeds", async ( ) => { + const { result } = renderHook( ( ) => useTaxon( { id: mockTaxon.id } ) ); + await waitFor( ( ) => expect( inatjs.taxa.fetch ).toHaveBeenCalled( ) ); + expect( result.current.taxon ).toHaveProperty( "default_photo" ); + } ); + } ); + + describe( "with fetchRemote: false", ( ) => { + it( "should not call the API and return passed in taxon", ( ) => { + const taxonId = faker.number.int( ); + expect( + global.mockRealms[mockRealmIdentifier].objectForPrimaryKey( "Taxon", taxonId ) + ).toBeNull( ); + const { result } = renderHook( ( ) => useTaxon( { id: taxonId, foo: "bar" }, false ) ); + expect( inatjs.taxa.fetch ).not.toHaveBeenCalled( ); + expect( result.current.taxon ).not.toHaveProperty( "default_photo" ); + expect( result.current.taxon.foo ).toEqual( "bar" ); + } ); } ); } ); } ); diff --git a/tests/unit/components/Camera/ARCamera.test.js b/tests/unit/components/Camera/ARCamera.test.js index 4f03d5ffc..b4ae52701 100644 --- a/tests/unit/components/Camera/ARCamera.test.js +++ b/tests/unit/components/Camera/ARCamera.test.js @@ -35,7 +35,7 @@ jest.mock( "sharedHooks/useAuthenticatedQuery", () => ( { jest.mock( "sharedHooks/useTaxon", () => ( { __esModule: true, - default: () => mockLocalTaxon + default: () => ( { taxon: mockLocalTaxon } ) } ) ); const mockModelLoaded = { @@ -120,9 +120,11 @@ describe( "AR Camera", ( ) => { it( "displays iconic taxon icon if taxon does not exist in realm", ( ) => { jest.spyOn( useTaxon, "default" ).mockImplementation( () => ( { - ...mockLocalTaxon, - default_photo: { - url: null + taxon: { + ...mockLocalTaxon, + default_photo: { + url: null + } } } ) ); jest.spyOn( usePredictions, "default" ).mockImplementation( () => ( { diff --git a/tests/unit/components/SharedComponents/ObservationsFlashList/__snapshots__/ObsGridItem.test.js.snap b/tests/unit/components/SharedComponents/ObservationsFlashList/__snapshots__/ObsGridItem.test.js.snap index dc94ebc4e..62c14b85c 100644 --- a/tests/unit/components/SharedComponents/ObservationsFlashList/__snapshots__/ObsGridItem.test.js.snap +++ b/tests/unit/components/SharedComponents/ObservationsFlashList/__snapshots__/ObsGridItem.test.js.snap @@ -31,13 +31,7 @@ exports[`ObsGridItem for an observation with a photo should render 1`] = ` } testID="MyObservations.gridItem.00000000-0000-0000-0000-000000000000" > - + > + + + +  + + + + + ( { __esModule: true, - default: () => mockTaxon + default: () => ( { taxon: mockTaxon } ) } ) ); describe( "TaxonResult", () => { diff --git a/tests/unit/components/SharedComponents/__snapshots__/TaxonResult.test.js.snap b/tests/unit/components/SharedComponents/__snapshots__/TaxonResult.test.js.snap index 56555c761..8a2fcb9a1 100644 --- a/tests/unit/components/SharedComponents/__snapshots__/TaxonResult.test.js.snap +++ b/tests/unit/components/SharedComponents/__snapshots__/TaxonResult.test.js.snap @@ -77,10 +77,7 @@ exports[`TaxonResult should render correctly 1`] = ` "alignItems": "center", }, { - "width": 64, - }, - { - "flexGrow": 1, + "flexShrink": 1, }, ], ] @@ -91,49 +88,25 @@ exports[`TaxonResult should render correctly 1`] = ` [ [ { - "maxHeight": 210, - }, - { - "overflow": "hidden", - }, - { - "position": "relative", - }, - { - "borderBottomLeftRadius": 8, - "borderBottomRightRadius": 8, - "borderTopLeftRadius": 8, - "borderTopRightRadius": 8, + "width": 62, }, { "height": 62, }, { - "width": 62, + "justifyContent": "center", + }, + { + "position": "relative", }, - [ - { - "borderBottomLeftRadius": 12, - "borderBottomRightRadius": 12, - "borderTopLeftRadius": 12, - "borderTopRightRadius": 12, - }, - ], ], ] } - testID="undefined.photo" > - -  - - - +  + + + + [ + { + "position": "absolute", + }, + { + "right": 0, + }, + { + "paddingBottom": 4, + "paddingLeft": 4, + "paddingRight": 4, + "paddingTop": 4, + }, + { + "bottom": 0, + }, + ], + ] + } + /> + ( { __esModule: true, - default: () => mockTaxon + default: () => ( { taxon: mockTaxon } ) } ) ); const mockSuggestionsList = [{ diff --git a/tests/unit/components/Suggestions/TaxonSearch.test.js b/tests/unit/components/Suggestions/TaxonSearch.test.js index 3fa17143b..03aec49a8 100644 --- a/tests/unit/components/Suggestions/TaxonSearch.test.js +++ b/tests/unit/components/Suggestions/TaxonSearch.test.js @@ -33,7 +33,7 @@ jest.mock( "sharedHooks/useTaxonSearch", () => ( { jest.mock( "sharedHooks/useTaxon", () => ( { __esModule: true, - default: () => mockTaxaList[0] + default: () => ( { taxon: mockTaxaList[0] } ) } ) ); // react-native-paper's TextInput does a bunch of async stuff that's hard to