Files
iNaturalistReactNative/tests/integration/AddToProjects.test.js
T
2026-09-15 19:48:08 +02:00

171 lines
5.9 KiB
JavaScript

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( );
} );
} );