import { screen, userEvent, waitFor, within, } from "@testing-library/react-native"; import initI18next from "i18n/initI18next"; import inatjs from "inaturalistjs"; import * as ImagePicker from "react-native-image-picker"; import { SCREEN_AFTER_PHOTO_EVIDENCE } from "stores/createLayoutSlice"; import factory, { makeResponse } from "tests/factory"; 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" ); return { __esModule: true, ...originalModule, RealmContext: { ...originalModule.RealmContext, useRealm: ( ) => global.mockRealms[mockRealmIdentifier], useQuery: ( ) => [], }, }; } ); beforeAll( uniqueRealmBeforeAll ); afterAll( uniqueRealmAfterAll ); // /UNIQUE REALM SETUP const mockUser = factory( "LocalUser" ); // Mock useCurrentUser hook jest.mock( "sharedHooks/useCurrentUser", () => ( { __esModule: true, default: jest.fn( () => mockUser ), } ) ); // Mock the response from inatjs.computervision.score_image const topSuggestion = { taxon: factory.states( "genus" )( "RemoteTaxon", { name: "Primum" } ), combined_score: 90, }; beforeAll( async () => { await initI18next(); jest.useFakeTimers( ); } ); beforeEach( ( ) => { setStoreStateLayout( { isDefaultMode: false, screenAfterPhotoEvidence: SCREEN_AFTER_PHOTO_EVIDENCE.SUGGESTIONS, isAllAddObsOptionsMode: true, } ); inatjs.computervision.score_image.mockResolvedValue( makeResponse( [topSuggestion] ) ); } ); describe( "Photo Import", ( ) => { const actor = userEvent.setup( ); async function importPhotoForNewObs() { const tabBar = await screen.findByTestId( "CustomTabBar" ); const addObsButton = await within( tabBar ).findByLabelText( "Add observations" ); await actor.press( addObsButton ); const photoImportButton = await within( tabBar ).findByLabelText( "Photo importer" ); await actor.press( photoImportButton ); } async function groupPhotosIntoObservation() { const groupPhotosText = await screen.findByText( /Group Photos/ ); // We used toBeVisible here but the update to RN0.77 broke this expectation expect( groupPhotosText ).toBeOnTheScreen( ); 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 viewSuggestionsAndAddId() { const topTaxonResultButton = await screen.findByTestId( `SuggestionsList.taxa.${topSuggestion.taxon.id}.checkmark`, ); await actor.press( topTaxonResultButton ); } async function saveObservationWithPhoto() { // Make sure we're on ObsEdit const evidenceTitle = await screen.findByText( "EVIDENCE" ); // We used toBeVisible here but the update to RN0.77 broke this expectation expect( evidenceTitle ).toBeOnTheScreen( ); const localFilePath = `file://document/directory/path/photoUploads/${mockFileName}`; const photoEvidence = await screen.findByTestId( `EvidenceList.${localFilePath}` ); // We used toBeVisible here but the update to RN0.77 broke this expectation expect( photoEvidence ).toBeOnTheScreen( ); const saveButton = await screen.findByText( "SAVE" ); await actor.press( saveButton ); const okButton = await screen.findByText( "OK" ); await actor.press( okButton ); await actor.press( saveButton ); // Wait until header shows that there's an obs to upload await screen.findByText( /Upload \d observation/ ); const obsGridItems = await screen.findAllByTestId( /MyObservations\.obsGridItem\..*/ ); await waitFor( () => { // We used toBeVisible here but the update to RN0.77 broke this expectation expect( obsGridItems[0] ).toBeOnTheScreen( ); }, { timeout: 3_000, interval: 500 } ); } it( "should create and save an observation with an imported photo", async ( ) => { renderApp( ); await importPhotoForNewObs( ); await viewSuggestionsAndAddId( ); await saveObservationWithPhoto( ); } ); it( "should create and save an observation with multiple imported photos", async ( ) => { jest.spyOn( ImagePicker, "launchImageLibrary" ).mockImplementation( ( ) => mockImageLibraryResponseMultiplePhotos, ); renderApp( ); await importPhotoForNewObs( ); await groupPhotosIntoObservation( ); await viewSuggestionsAndAddId( ); await saveObservationWithPhoto( ); } ); } );