Fix: don't make API call for deleting local observations (#318)

* Check synced status before deleting observation remotely; closes #315

* Add tests for deleting observation from dialog

* Add jest settings for vscode; fix failing test from App.js

* Ignore vscode settings files

* Remove cached version of vscode settings file from git

* Update gitignore

* Add a test for canceling deletion
This commit is contained in:
Amanda Bullington
2022-12-30 08:34:32 -08:00
committed by GitHub
parent 2412b8dde0
commit d51685b488
6 changed files with 138 additions and 12 deletions

3
.gitignore vendored
View File

@@ -73,3 +73,6 @@ fastlane/Appfile
# Apple signing and auth files that fastlane might download
*.cer
*.mobileprovision
# VisualStudioCode #
.vscode

View File

@@ -1,7 +0,0 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.tabSize": 2,
"eslint.validate": ["javascript"]
}

View File

@@ -61,10 +61,11 @@ const App = ( { children }: Props ): Node => {
async function changeLanguageToLocale( locale ) {
await i18next.changeLanguage( locale );
}
if ( !currentUser ) { return; }
if ( currentUser?.locale ) {
changeLanguageToLocale( currentUser.locale );
}
}, [currentUser?.locale] );
}, [currentUser] );
// this children prop is here for the sake of testing with jest
// normally we would never do this in code

View File

@@ -27,13 +27,17 @@ const DeleteObservationDialog = ( {
const navigation = useNavigation( );
const { uuid } = currentObservation;
const handleLocalDeletion = ( ) => {
deleteLocalObservation( uuid );
hideDialog( );
navigation.navigate( "ObsList" );
};
const deleteObservationMutation = useAuthenticatedMutation(
( params, optsWithAuth ) => deleteObservation( params, optsWithAuth ),
{
onSuccess: ( ) => {
deleteLocalObservation( uuid );
hideDialog( );
navigation.navigate( "ObsList" );
handleLocalDeletion( );
}
}
);
@@ -47,7 +51,13 @@ const DeleteObservationDialog = ( {
<Dialog.Actions>
<Button onPress={hideDialog} text={t( "Cancel" )} level="primary" className="m-0.5" />
<Button
onPress={( ) => deleteObservationMutation.mutate( { uuid } )}
onPress={( ) => {
if ( !currentObservation._synced_at ) {
handleLocalDeletion( );
} else {
deleteObservationMutation.mutate( { uuid } );
}
}}
text={t( "Yes-delete-observation" )}
level="primary"
className="m-0.5"

View File

@@ -21,6 +21,7 @@ const KebabMenu = ( { children, visible, setVisible }: Props ): Node => {
onPress={openMenu}
icon="dots-horizontal"
textColor={colors.logInGray}
testID="KebabMenu.Button"
/>
);

View File

@@ -0,0 +1,118 @@
import { fireEvent, waitFor } from "@testing-library/react-native";
import DeleteObservationDialog from "components/ObsEdit/DeleteObservationDialog";
import inatjs from "inaturalistjs";
import { ObsEditContext } from "providers/contexts";
import ObsEditProvider from "providers/ObsEditProvider";
import React from "react";
import factory from "../../../factory";
import { renderComponent } from "../../../helpers/render";
jest.useFakeTimers( );
jest.mock( "providers/ObsEditProvider" );
jest.mock( "@react-navigation/native", ( ) => {
const actualNav = jest.requireActual( "@react-navigation/native" );
return {
...actualNav,
useNavigation: ( ) => ( {
navigate: jest.fn( )
} )
};
} );
// Mock ObservationProvider so it provides a specific array of observations
// without any current observation or ability to update or fetch
// observations
const mockObsEditProviderWithObs = obs => ObsEditProvider.mockImplementation( ( { children } ) => (
// eslint-disable-next-line react/jsx-no-constructed-context-values
<ObsEditContext.Provider value={{
currentObservation: obs[0],
deleteLocalObservation: ( ) => {
global.realm.write( ( ) => {
global.realm.delete( global.realm.objectForPrimaryKey( "Observation", obs[0].uuid ) );
} );
}
}}
>
{children}
</ObsEditContext.Provider>
) );
const renderDeleteDialog = ( ) => renderComponent(
<ObsEditProvider>
<DeleteObservationDialog deleteDialogVisible hideDialog={( ) => jest.fn( )} />
</ObsEditProvider>
);
const getLocalObservation = uuid => global.realm
.objectForPrimaryKey( "Observation", uuid );
describe( "delete observation", ( ) => {
describe( "delete an unsynced observation", ( ) => {
it( "should delete an observation from realm", async ( ) => {
const observations = [factory( "LocalObservation", {
_synced_at: null
} )];
global.realm.write( ( ) => {
global.realm.create( "Observation", observations[0] );
} );
const localObservation = getLocalObservation( observations[0].uuid );
expect( localObservation ).toBeTruthy( );
mockObsEditProviderWithObs( observations );
const { queryByText } = renderDeleteDialog( );
const deleteButton = queryByText( /Yes-delete-observation/ );
expect( deleteButton ).toBeTruthy( );
fireEvent.press( deleteButton );
expect( getLocalObservation( observations[0].uuid ) ).toBeFalsy( );
} );
it( "should not make a request to observations/delete", async ( ) => {
await waitFor( ( ) => {
expect( inatjs.observations.delete ).not.toHaveBeenCalled( );
} );
} );
} );
describe( "delete a previously synced observation", ( ) => {
it( "should make a request to observations/delete", async ( ) => {
const observations = [factory( "LocalObservation" )];
global.realm.write( ( ) => {
global.realm.create( "Observation", observations[0] );
} );
const localObservation = getLocalObservation( observations[0].uuid );
expect( localObservation ).toBeTruthy( );
mockObsEditProviderWithObs( observations );
const { queryByText } = renderDeleteDialog( );
// TODO: figure out why this needs English text and why the one above needs
// the generic text. Probably has to do with User object still being stored in global realm
// between tests
const deleteButton = queryByText( /delete/ );
expect( deleteButton ).toBeTruthy( );
fireEvent.press( deleteButton );
await waitFor( ( ) => {
expect( inatjs.observations.delete ).toHaveBeenCalledTimes( 1 );
expect( getLocalObservation( observations[0].uuid ) ).toBeFalsy( );
} );
} );
} );
describe( "cancel deletion", ( ) => {
it( "should not delete the observation from realm", ( ) => {
const observations = [factory( "LocalObservation" )];
global.realm.write( ( ) => {
global.realm.create( "Observation", observations[0] );
} );
const localObservation = getLocalObservation( observations[0].uuid );
expect( localObservation ).toBeTruthy( );
mockObsEditProviderWithObs( observations );
const { queryByText } = renderDeleteDialog( );
const cancelButton = queryByText( /Cancel/ );
expect( cancelButton ).toBeTruthy( );
fireEvent.press( cancelButton );
expect( getLocalObservation( observations[0].uuid ) ).toBeTruthy( );
} );
} );
} );