mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2025-12-23 22:18:36 -05:00
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:
committed by
GitHub
parent
2412b8dde0
commit
d51685b488
3
.gitignore
vendored
3
.gitignore
vendored
@@ -73,3 +73,6 @@ fastlane/Appfile
|
||||
# Apple signing and auth files that fastlane might download
|
||||
*.cer
|
||||
*.mobileprovision
|
||||
|
||||
# VisualStudioCode #
|
||||
.vscode
|
||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"editor.tabSize": 2,
|
||||
"eslint.validate": ["javascript"]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -21,6 +21,7 @@ const KebabMenu = ( { children, visible, setVisible }: Props ): Node => {
|
||||
onPress={openMenu}
|
||||
icon="dots-horizontal"
|
||||
textColor={colors.logInGray}
|
||||
testID="KebabMenu.Button"
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
118
tests/unit/components/ObsEdit/DeleteObservationDialog.test.js
Normal file
118
tests/unit/components/ObsEdit/DeleteObservationDialog.test.js
Normal 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( );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
Reference in New Issue
Block a user