import { screen, userEvent, waitFor } from "@testing-library/react-native"; import inatjs from "inaturalistjs"; import { FeatureFlag } from "stores/createFeatureFlagSlice"; import useStore from "stores/useStore"; import factory, { makeResponse } from "tests/factory"; import { mockInteractionManagerRunAfterInteractions, saveObsEditObservation, } from "tests/helpers/addObsBottomSheet"; import faker from "tests/helpers/faker"; import { renderAppWithObservations } from "tests/helpers/render"; import setStoreStateFeatureFlags from "tests/helpers/setStoreStateFeatureFlags"; import setStoreStateLayout from "tests/helpers/setStoreStateLayout"; import setupUniqueRealm from "tests/helpers/uniqueRealm"; import { signIn, signOut } from "tests/helpers/user"; // We're explicitly using navigation here jest.unmock( "@react-navigation/native" ); // // 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 actor = userEvent.setup(); const realm = () => global.mockRealms[__filename]; const mockUser = factory( "LocalUser", { login: faker.internet.username(), iconUrl: faker.image.url(), locale: "en", } ); const observation = factory( "LocalObservation", { _created_at: faker.date.past(), taxon: factory( "LocalTaxon", { name: faker.person.firstName(), } ), } ); const mockObservations = [observation]; const mockProject = factory( "RemoteProject", { title: faker.lorem.sentence(), icon: faker.image.url(), header_image_url: faker.image.url(), description: faker.lorem.paragraph(), project_type: "", user_ids: [faker.number.int()], } ); async function selectProjectAndExpand( projectId ) { const projectRow = await waitFor( ( ) => { const row = screen.getByTestId( `AddToProjects.project.${projectId}` ); expect( row ).toBeVisible( ); return row; } ); await actor.press( projectRow ); await waitFor( ( ) => { expect( screen.getAllByLabelText( "Enter a response" ).length ).toBeGreaterThan( 0 ); } ); } async function fillRequiredTextField( value, obsField ) { await screen.findByText( obsField.name ); const textInput = screen.getAllByPlaceholderText( "Enter a response" ).find( node => typeof node.props.onChangeText === "function", ); expect( textInput ).toBeTruthy( ); jest.useRealTimers( ); try { await actor.type( textInput, value ); } finally { jest.useFakeTimers( ); } await waitFor( ( ) => { const { currentObservation } = useStore.getState( ); expect( currentObservation?.observationFieldValues ).toEqual( expect.arrayContaining( [ expect.objectContaining( { obsFieldId: obsField.id, value } ), ] ), ); } ); } beforeAll( async () => { jest.useFakeTimers(); mockInteractionManagerRunAfterInteractions(); inatjs.users.projects.mockResolvedValue( makeResponse( [mockProject] ) ); } ); beforeEach( () => { setStoreStateLayout( { isDefaultMode: false, } ); setStoreStateFeatureFlags( { [FeatureFlag.TraditionalProjectsEnabled]: true, } ); } ); describe( "AddToProjects", ( ) => { global.withAnimatedTimeTravelEnabled( { skipFakeTimers: true } ); beforeEach( async () => { await signIn( mockUser, { realm: global.mockRealms[__filename] } ); } ); afterEach( () => { signOut( { realm: global.mockRealms[__filename] } ); } ); async function navigateToAddToProjectsViaObsEdit( observations ) { // Nav to ObsEdit await renderAppWithObservations( observations, __filename ); const observationGridItem = await screen.findByTestId( `MyObservations.obsGridItem.${observations[0].uuid}`, ); await actor.press( observationGridItem ); // Nav to Add To Projects const addToProjectsRow = await screen.findByLabelText( /Add to Projects|Added to \d+ Project/, ); await actor.press( addToProjectsRow ); await screen.findByTestId( "add-to-projects" ); // Assert on Add To Projects screen expect( screen.getByTestId( "add-to-projects" ) ).toBeVisible(); expect( screen.getByText( "ADD TO PROJECTS" ) ).toBeVisible(); } it( "persists PO and OFV to Realm after chooser save and ObsEdit save", async () => { await navigateToAddToProjectsViaObsEdit( mockObservations ); await selectProjectAndExpand( mockProject.id ); const fieldValue = "shrubland"; const obsField = mockProject.project_observation_fields[0].observation_field; await fillRequiredTextField( fieldValue, obsField ); // Save const saveButton = screen.getByTestId( "AddToProjects.saveButton" ); await actor.press( saveButton ); // Assert we are on ObsEdit now is done in /navigation test await saveObsEditObservation( ); const savedObs = realm( ).objectForPrimaryKey( "Observation", observation.uuid ); expect( savedObs.projectObservations ).toHaveLength( 1 ); expect( savedObs.projectObservations[0].projectId ).toBe( mockProject.id ); expect( savedObs.projectObservations[0]._pending_deletion ).toBeFalsy( ); expect( savedObs.observationFieldValues ).toHaveLength( 1 ); expect( savedObs.observationFieldValues[0].obsFieldId ).toBe( obsField.id ); expect( savedObs.observationFieldValues[0].value ).toBe( fieldValue ); // No upload should happen expect( inatjs.project_observations.create ).not.toHaveBeenCalled( ); expect( inatjs.observation_field_values.create ).not.toHaveBeenCalled( ); } ); } );