Show notifications obs photos for remote obs (#1519)

---------

Co-authored-by: Angie Ta <angie@inaturalist.org>
This commit is contained in:
Ken-ichi
2024-05-09 20:31:04 -07:00
committed by GitHub
parent 59e37c95e0
commit 0a1069fce9
6 changed files with 166 additions and 3 deletions

View File

@@ -65,6 +65,28 @@ const fetchRemoteObservation = async (
}
};
const fetchRemoteObservations = async (
uuids: Array<string>,
params: Object = {},
opts: Object = {}
): Promise<?number> => {
try {
const response = await inatjs.observations.fetch(
uuids,
params,
opts
);
if ( !response ) { return null; }
const { results } = response;
if ( results?.length > 0 ) {
return results.map( mapToLocalSchema );
}
return null;
} catch ( e ) {
return handleError( e );
}
};
const markAsReviewed = async ( params: Object = {}, opts: Object = {} ): Promise<?number> => {
try {
return await inatjs.observations.review( params, opts );
@@ -205,6 +227,7 @@ export {
fetchObservationUpdates,
fetchObservers,
fetchRemoteObservation,
fetchRemoteObservations,
fetchSpeciesCounts,
fetchUnviewedObservationUpdatesCount,
markAsReviewed,

View File

@@ -22,8 +22,9 @@ import {
useObservationsUpdates,
useTranslation
} from "sharedHooks";
import useRemoteObservation,
{ fetchRemoteObservationKey } from "sharedHooks/useRemoteObservation";
import useRemoteObservation, {
fetchRemoteObservationKey
} from "sharedHooks/useRemoteObservation";
import { ACTIVITY_TAB_ID, DETAILS_TAB_ID } from "stores/createLayoutSlice";
import useStore from "stores/useStore";

View File

@@ -1,12 +1,17 @@
// @flow
import { useInfiniteQuery } from "@tanstack/react-query";
import { fetchObservationUpdates } from "api/observations";
import { fetchObservationUpdates, fetchRemoteObservations } from "api/observations";
import { getJWT } from "components/LoginSignUp/AuthenticationService";
import { flatten } from "lodash";
import { RealmContext } from "providers/contexts";
import { useCallback } from "react";
import Observation from "realmModels/Observation";
import { reactQueryRetry } from "sharedHelpers/logging";
import { useCurrentUser } from "sharedHooks";
const { useRealm } = RealmContext;
const BASE_PARAMS = {
observations_by: "owner",
fields: "all",
@@ -17,6 +22,16 @@ const BASE_PARAMS = {
const useInfiniteNotificationsScroll = ( ): Object => {
const currentUser = useCurrentUser( );
const realm = useRealm( );
const fetchObsByUUIDs = useCallback( async ( uuids, authOptions ) => {
const observations = await fetchRemoteObservations(
uuids,
{ fields: Observation.FIELDS },
authOptions
);
Observation.upsertRemoteObservations( observations, realm );
}, [realm] );
const infQueryResult = useInfiniteQuery( {
queryKey: ["useInfiniteNotificationsScroll"],
@@ -35,6 +50,10 @@ const useInfiniteNotificationsScroll = ( ): Object => {
}
const response = await fetchObservationUpdates( params, options );
const obsUUIDs = response?.map( obsUpdate => obsUpdate.resource_uuid ) || [];
if ( obsUUIDs.length > 0 ) {
await fetchObsByUUIDs( obsUUIDs, options );
}
return response;
},

View File

@@ -1,5 +1,15 @@
import { define } from "factoria";
import userFactory from "./RemoteUser";
export default define( "RemoteComment", faker => ( {
body: faker.lorem.paragraph( ),
created_at: faker.date.past( ).toISOString( ),
id: faker.number.int( ),
parent_id: faker.number.int( ),
parent_type: "Observation",
updated_at: faker.date.past( ).toISOString( ),
user: userFactory( "RemoteUser" ),
user_id: faker.number.int( ),
uuid: faker.string.uuid( )
} ) );

View File

@@ -121,6 +121,7 @@ function renderHook( renderCallback, options = {} ) {
}
export {
queryClient,
renderApp,
renderAppWithComponent,
renderAppWithObservations,

View File

@@ -0,0 +1,109 @@
import { screen, waitFor } from "@testing-library/react-native";
import NotificationsContainer from "components/Notifications/NotificationsContainer";
import inatjs from "inaturalistjs";
import React from "react";
import factory, { makeResponse } from "tests/factory";
import { queryClient, renderAppWithComponent } from "tests/helpers/render";
import setupUniqueRealm from "tests/helpers/uniqueRealm";
import { signIn, signOut } from "tests/helpers/user";
// 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 mockUser = factory( "LocalUser" );
function makeMockObsUpdatesResponse( mockObs ) {
const mockObservation = mockObs || factory( "RemoteObservation", {
user: mockUser
} );
const mockComment = factory( "RemoteComment", { parent_id: mockObservation.id } );
const mockUpdate = {
id: 123,
created_at: mockComment.created_at,
comment: mockComment,
comment_id: mockComment.id,
notifier_id: mockComment.id,
notifier_type: "Comment",
notification: "activity",
resource_owner_id: mockUser.id,
resource_type: "Observation",
resource_id: mockObservation.id,
resource_uuid: mockObservation.uuid,
viewed: false
};
const obsUpdatesResponse = makeResponse( [mockUpdate] );
inatjs.observations.updates.mockResolvedValue( obsUpdatesResponse );
inatjs.observations.fetch.mockResolvedValue( makeResponse( [mockObservation] ) );
return obsUpdatesResponse;
}
describe( "Notifications", () => {
beforeEach( async () => {
jest.useFakeTimers( );
signIn( mockUser, { realm: global.mockRealms[__filename] } );
} );
afterEach( () => {
jest.clearAllMocks();
signOut( { realm: global.mockRealms[__filename] } );
queryClient.clear( );
} );
it( "should show a notification", async ( ) => {
makeMockObsUpdatesResponse( );
renderAppWithComponent( <NotificationsContainer /> );
await waitFor( ( ) => {
expect( inatjs.observations.updates ).toHaveBeenCalled( );
} );
expect(
await screen.findByText( /added a comment to an observation by you/ )
).toBeVisible( );
} );
it( "should show a photo for an observation not in the local database", async ( ) => {
const mockObservation = factory( "RemoteObservation", {
observation_photos: [
factory( "RemoteObservationPhoto" )
]
} );
const localObservation = global.mockRealms[__filename].objectForPrimaryKey(
"Observation",
mockObservation.uuid
);
expect( localObservation ).toBeFalsy( );
const photoUrl = mockObservation.observation_photos[0].photo.url;
expect( photoUrl ).toBeTruthy( );
const response = makeMockObsUpdatesResponse( mockObservation );
const { comment } = response.results[0];
expect( response.results[0].resource_uuid ).toEqual( mockObservation.uuid );
renderAppWithComponent( <NotificationsContainer /> );
expect( await screen.findByText( comment.user.login ) ).toBeVisible( );
const localObservationAfter = global.mockRealms[__filename].objectForPrimaryKey(
"Observation",
mockObservation.uuid
);
expect( localObservationAfter ).toBeTruthy( );
const image = await screen.findByTestId( "ObservationIcon.photo" );
await waitFor( () => {
expect( image.props.source ).toStrictEqual( { uri: photoUrl } );
} );
} );
} );