e2e mock for Geolocation.watchPosition to deflake tests (#1842)

This commit is contained in:
Ken-ichi
2024-07-24 20:15:03 -04:00
committed by GitHub
parent 27912676dc
commit 10bbea44c4
8 changed files with 125 additions and 19 deletions

View File

@@ -70,13 +70,16 @@ module.exports = {
],
"no-alert": 0,
"no-underscore-dangle": 0,
"no-unused-vars": [
// This gets around eslint problems when typing functions in TS
"no-unused-vars": 0,
"@typescript-eslint/no-unused-vars": [
"error",
{
vars: "all",
args: "after-used",
// Overriding airbnb to allow leading underscore to indicate unused var
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
ignoreRestSiblings: true
}
],
@@ -136,7 +139,6 @@ module.exports = {
// TODO: we should actually type these at some point ~amanda 041824
"@typescript-eslint/ban-types": 0,
"@typescript-eslint/no-unused-vars": 0,
"@typescript-eslint/no-var-requires": 0
},
// need this so jest doesn't show as undefined in jest.setup.js

View File

@@ -3,6 +3,8 @@ import {
} from "detox";
import Config from "react-native-config-node";
// This needs to be a relative path for the e2e-mock version to be used
import { CHUCKS_PAD } from "../src/appConstants/e2e";
import { iNatE2eBeforeAll, iNatE2eBeforeEach } from "./helpers";
describe( "Signed in user", () => {
@@ -24,6 +26,12 @@ describe( "Signed in user", () => {
await waitFor( element( by.id( "new-observation-text" ) ) )
.toBeVisible()
.withTimeout( 10000 );
// Ensure the location from the e2e-mock is being used so we don't end up
// with tests flaking out due to time zone issues
const pattern = new RegExp( `.*${CHUCKS_PAD.latitude.toFixed( 4 )}.*` );
await waitFor( element( by.text( pattern ) ) )
.toBeVisible()
.withTimeout( 10000 );
if ( options.upload ) {
// Press Upload now button
const uploadNowButton = element( by.id( "ObsEdit.uploadButton" ) );

20
src/appConstants/e2e.ts Normal file
View File

@@ -0,0 +1,20 @@
// e2e test data that needs to be referred to from e2e-mock files *and* the
// e2e tests themselves
// Darwin's house. Note that the e2e tests run in a UTC environment, so the
// observed_on_string will be set to a UTC time. If these coordinates fall
// within a time zone west of that (i.e. in the past), observation creation
// will fail during the period of the day when UTC time has crossed into the
// date after the date at these coordinates. The opposite (when local time is
// in a time zone behind the time zone of the coordinates) will not fail, but
// it might make an observation that's a day behind when you were expecting.
// eslint-disable-next-line import/prefer-default-export
export const CHUCKS_PAD = {
latitude: 51.3313127,
longitude: 0.0509862,
accuracy: 5,
altitude: null,
altitudeAccuracy: null,
heading: null,
speed: null
};

View File

@@ -36,20 +36,24 @@ const ObsEdit = ( ): Node => {
const isFocused = useIsFocused( );
const currentUser = useCurrentUser( );
const [shouldFetchLocation, setShouldFetchLocation] = useState( false );
const { hasPermissions, renderPermissionsGate, requestPermissions } = useLocationPermission( );
const {
hasPermissions: hasLocationPermission,
renderPermissionsGate: renderLocationPermissionGate,
requestPermissions: requestLocationPermission
} = useLocationPermission( );
const {
isFetchingLocation,
userLocation
} = useWatchPosition( {
shouldFetchLocation
} );
} = useWatchPosition( { shouldFetchLocation } );
// Note the intended functionality is *not* to request location permission
// until the user taps the missing location
useEffect( ( ) => {
if ( shouldFetchObservationLocation( currentObservation ) ) {
if ( hasLocationPermission && shouldFetchObservationLocation( currentObservation ) ) {
setShouldFetchLocation( true );
}
}, [currentObservation] );
}, [currentObservation, hasLocationPermission] );
useEffect( ( ) => {
if ( userLocation?.latitude ) {
@@ -73,11 +77,11 @@ const ObsEdit = ( ): Node => {
const onLocationPress = ( ) => {
// If we have location permissions, navigate to the location picker
if ( hasPermissions ) {
if ( hasLocationPermission ) {
navToLocationPicker();
} else {
// If we don't have location permissions, request them
requestPermissions( );
requestLocationPermission( );
}
};
@@ -145,13 +149,13 @@ const ObsEdit = ( ): Node => {
passesIdentificationTest={passesIdentificationTest}
setCurrentObservationIndex={setCurrentObservationIndex}
/>
{renderPermissionsGate( {
{renderLocationPermissionGate( {
// If the user does not give location permissions in any form,
// navigate to the location picker (if granted we just continue fetching the location)
onRequestDenied: navToLocationPicker,
onRequestBlocked: navToLocationPicker,
onModalHide: ( ) => {
if ( !hasPermissions ) navToLocationPicker();
if ( !hasLocationPermission ) navToLocationPicker();
}
} )}
</>

View File

@@ -55,8 +55,7 @@ const ScrollableWithStickyHeader = ( {
screenHeight,
screenWidth
} = useDeviceOrientation( );
// eslint-disable-next-line no-unused-vars
const [scrollPosition, setScrollPosition] = useState( 0 );
const [_scrollPosition, setScrollPosition] = useState( 0 );
const [stickyAt, setStickyAt] = useState( 0 );

View File

@@ -0,0 +1,40 @@
import Geolocation, {
GeolocationError,
GeolocationResponse
} from "@react-native-community/geolocation";
import { CHUCKS_PAD } from "appConstants/e2e.ts";
function watchPosition(
success: ( position: GeolocationResponse ) => void,
error?: ( error: GeolocationError ) => void,
options?: {
interval?: number;
fastestInterval?: number;
timeout?: number;
maximumAge?: number;
enableHighAccuracy?: boolean;
distanceFilter?: number;
useSignificantChanges?: boolean;
}
) {
console.log( "[DEBUG geolocationWrapper.e2e-mock] watchPosition" );
const watchID = Date.now();
setTimeout( ( ) => {
console.log( "[DEBUG geolocationWrapper.e2e-mock] watchPosition success" );
success( {
coords: CHUCKS_PAD,
timestamp: Date.now()
} );
}, 1000 );
return watchID;
}
function clearWatch( watchID: number ) {
console.log( "[DEBUG geolocationWrapper.e2e-mock] clearWatch, watchID: ", watchID );
}
export {
CHUCKS_PAD,
clearWatch,
watchPosition
};

View File

@@ -0,0 +1,29 @@
// This wraps the Geolocation methods we use so we can mock them for e2e tests
// that tend to have problems with locations and timezones
import Geolocation, {
GeolocationError,
GeolocationResponse
} from "@react-native-community/geolocation";
export function watchPosition(
success: ( position: GeolocationResponse ) => void,
error?: ( error: GeolocationError ) => void,
options?: {
interval?: number;
fastestInterval?: number;
timeout?: number;
maximumAge?: number;
enableHighAccuracy?: boolean;
distanceFilter?: number;
useSignificantChanges?: boolean;
}
) {
const watchID = Geolocation.watchPosition( success, error, options );
console.log( "[DEBUG geolocationWrapper.ts] watchPosition, watchID: ", watchID );
return watchID;
}
export function clearWatch( watchID: number ) {
Geolocation.clearWatch( watchID );
}

View File

@@ -1,10 +1,14 @@
import Geolocation, {
import {
GeolocationError,
GeolocationResponse
} from "@react-native-community/geolocation";
import { useNavigation } from "@react-navigation/native";
import { useCallback, useEffect, useState } from "react";
// Please don't change this to an aliased path or the e2e mock will not get
// used in our e2e tests on Github Actions
import { clearWatch, watchPosition } from "../sharedHelpers/geolocationWrapper";
export const TARGET_POSITIONAL_ACCURACY = 10;
interface UserLocation {
@@ -32,7 +36,7 @@ const useWatchPosition = ( options: {
shouldFetchLocation
);
const watchPosition = ( ) => {
const startWatch = ( ) => {
setIsFetchingLocation( true );
const success = ( position: GeolocationResponse ) => {
setCurrentPosition( position );
@@ -44,7 +48,7 @@ const useWatchPosition = ( options: {
};
try {
const watchID = Geolocation.watchPosition(
const watchID = watchPosition(
success,
failure,
geolocationOptions
@@ -56,7 +60,7 @@ const useWatchPosition = ( options: {
};
const stopWatch = useCallback( ( id: number ) => {
Geolocation.clearWatch( id );
clearWatch( id );
setSubscriptionId( null );
setCurrentPosition( null );
setIsFetchingLocation( false );
@@ -80,7 +84,7 @@ const useWatchPosition = ( options: {
useEffect( ( ) => {
if ( shouldFetchLocation ) {
watchPosition( );
startWatch( );
}
}, [shouldFetchLocation] );