mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2026-07-31 09:26:57 -04:00
* only return observations from useLocalObs
* remove unnecessary var
* export unsync'd filter
* remove obsoleted numUnuploadedObservations zustand state
* tests, typo
* fix tests
* add sync fields to keypaths & depp array
* consistent naming
* handle only ids downstream
* switch over to useIds
* restore ids return
* memoize realm mapping result
* extract realm hooks mocks into factory-available helper
* reapply to myobsresults
* reapply to myobsresults
* go back to wrapped ids for list parity
* fix test
* restrict mocked prop for more accurate MyObsSimple test
* comment cleanup
* fix fake timer flake w/ corrected setup
Claude:
1. withAnimatedTimeTravelEnabled({ skipFakeTimers: true }) does NOT call jest.useFakeTimers(), but still calls jest.setSystemTime(new Date(0)) in beforeEach — which requires fake timers.
2. Both SoundRecorder.test.js and PhotoDeletionExisting.test.js (and ObsEdit.test.js) call global.timeTravel(300) inside waitFor, which also requires fake timers.
3. Tests pass flakily when fake timers accidentally leak from another test file running in the same Jest worker. When they don't leak, timeTravel throws.
4. FadeInView is fully mocked to a plain View (instant) and Reanimated uses setUpTests(), so the 300ms timeTravel isn't actually advancing any real animation — it's just broken.
The fix: guard setSystemTime with the same check, and remove the timeTravel(300) calls from waitFor in tests that use skipFakeTimers: true.
* fix remaining time travel uses
* reset / restore timers to fix leaking fake timers
166 lines
5.5 KiB
JavaScript
166 lines
5.5 KiB
JavaScript
import {
|
|
screen,
|
|
userEvent,
|
|
} from "@testing-library/react-native";
|
|
import initI18next from "i18n/initI18next";
|
|
import * as ImagePicker from "react-native-image-picker";
|
|
import { SCREEN_AFTER_PHOTO_EVIDENCE } from "stores/createLayoutSlice";
|
|
import factory from "tests/factory";
|
|
import {
|
|
mockInteractionManagerRunAfterInteractions,
|
|
navigateToPhotoImporterFromMyObs,
|
|
saveObsEditObservation,
|
|
waitForMyObsGridItems,
|
|
} from "tests/helpers/addObsBottomSheet";
|
|
import faker from "tests/helpers/faker";
|
|
import { renderApp } from "tests/helpers/render";
|
|
import setStoreStateLayout from "tests/helpers/setStoreStateLayout";
|
|
import setupUniqueRealm from "tests/helpers/uniqueRealm";
|
|
|
|
// We're explicitly testing navigation here so we want react-navigation
|
|
// working normally
|
|
jest.unmock( "@react-navigation/native" );
|
|
|
|
const directory = faker.string.uuid( );
|
|
const mockFileName = `${faker.string.uuid( )}.jpg`;
|
|
const mockUri = `file:///var/mobile/Containers/Data/Application/${directory}/tmp/${mockFileName}`;
|
|
|
|
const mockImageLibraryResponse = {
|
|
assets: [
|
|
{
|
|
uri: mockUri,
|
|
fileName: mockFileName,
|
|
},
|
|
],
|
|
};
|
|
|
|
const mockImageLibraryResponseMultiplePhotos = {
|
|
assets: [
|
|
{
|
|
uri: "some_uri",
|
|
fileName: "some_file_name",
|
|
},
|
|
{
|
|
uri: mockUri,
|
|
fileName: mockFileName,
|
|
},
|
|
],
|
|
};
|
|
|
|
jest.mock( "react-native-image-picker", ( ) => ( {
|
|
launchImageLibrary: jest.fn( ( ) => mockImageLibraryResponse ),
|
|
} ) );
|
|
|
|
// 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 mockUser = factory( "LocalUser" );
|
|
// Mock useCurrentUser hook
|
|
jest.mock( "sharedHooks/useCurrentUser", () => ( {
|
|
__esModule: true,
|
|
default: jest.fn( () => mockUser ),
|
|
} ) );
|
|
|
|
jest.mock( "sharedHooks/useObservationCounts", () => {
|
|
const { UNSYNCED_FILTER } = jest.requireActual( "realmModels/Observation" );
|
|
return {
|
|
__esModule: true,
|
|
default: () => {
|
|
const realm = global.mockRealms[__filename];
|
|
if ( !realm ) return { numUnuploadedObservations: 0, numObsMissingBasics: 0 };
|
|
const unsynced = realm.objects( "Observation" ).filtered( UNSYNCED_FILTER );
|
|
return {
|
|
numUnuploadedObservations: unsynced.length,
|
|
numObsMissingBasics: unsynced
|
|
.filter( obs => obs.missingBasics( ) ).length,
|
|
};
|
|
},
|
|
};
|
|
} );
|
|
|
|
beforeAll( async () => {
|
|
await initI18next();
|
|
mockInteractionManagerRunAfterInteractions( );
|
|
} );
|
|
|
|
describe( "Photo Import", ( ) => {
|
|
global.withAnimatedTimeTravelEnabled( { skipFakeTimers: true } );
|
|
|
|
const actor = userEvent.setup( );
|
|
|
|
beforeEach( async () => {
|
|
setStoreStateLayout( {
|
|
isDefaultMode: false,
|
|
screenAfterPhotoEvidence: SCREEN_AFTER_PHOTO_EVIDENCE.OBS_EDIT,
|
|
isAllAddObsOptionsMode: true,
|
|
} );
|
|
} );
|
|
async function groupPhotosIntoObservation() {
|
|
const groupPhotosText = await screen.findByText( /Group Photos/ );
|
|
expect( groupPhotosText ).toBeVisible();
|
|
const path = "file://document/directory/path/galleryPhotos/";
|
|
const firstUri = `${path}${mockImageLibraryResponseMultiplePhotos.assets[0].fileName}`;
|
|
const secondUri = `${path}${mockImageLibraryResponseMultiplePhotos.assets[1].fileName}`;
|
|
const firstPhoto = await screen.findByTestId( `GroupPhotos.${firstUri}` );
|
|
await actor.press( firstPhoto );
|
|
const secondPhoto = await screen.findByTestId( `GroupPhotos.${secondUri}` );
|
|
await actor.press( secondPhoto );
|
|
const combineButton = await screen.findByLabelText( /Combine Photos/ );
|
|
await actor.press( combineButton );
|
|
const importButton = await screen.findByText( /IMPORT 1 OBSERVATION/ );
|
|
await actor.press( importButton );
|
|
}
|
|
|
|
async function saveObservationWithPhoto( saveOptions = {} ) {
|
|
// Make sure we're on ObsEdit
|
|
const evidenceTitle = await screen.findByText( "EVIDENCE" );
|
|
expect( evidenceTitle ).toBeVisible( );
|
|
|
|
const [photoEvidence] = await screen.findAllByLabelText( "Select or drag media" );
|
|
expect( photoEvidence ).toBeVisible();
|
|
await saveObsEditObservation( saveOptions );
|
|
if ( !saveOptions.skipMyObsWait ) {
|
|
const obsGridItems = await waitForMyObsGridItems();
|
|
expect( obsGridItems[0] ).toBeVisible();
|
|
// Wait until header shows that there's an obs to upload
|
|
await screen.findByText( /Upload \d observation/ );
|
|
}
|
|
}
|
|
|
|
it( "should create and save an observation with an imported photo", async ( ) => {
|
|
renderApp( );
|
|
await navigateToPhotoImporterFromMyObs();
|
|
await saveObservationWithPhoto();
|
|
} );
|
|
|
|
it( "should create and save an observation with multiple imported photos", async ( ) => {
|
|
jest.spyOn( ImagePicker, "launchImageLibrary" ).mockImplementation(
|
|
( ) => mockImageLibraryResponseMultiplePhotos,
|
|
);
|
|
renderApp( );
|
|
await navigateToPhotoImporterFromMyObs();
|
|
await groupPhotosIntoObservation();
|
|
await screen.findByTestId( "ObsEdit.saveButton", {}, { timeout: 10_000 } );
|
|
await saveObservationWithPhoto( { skipMyObsWait: true } );
|
|
} );
|
|
} );
|