mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2025-12-23 22:18:36 -05:00
430 lines
13 KiB
JavaScript
430 lines
13 KiB
JavaScript
import "react-native-gesture-handler/jestSetup";
|
|
import "@shopify/flash-list/jestSetup";
|
|
|
|
import mockBottomSheet from "@gorhom/bottom-sheet/mock";
|
|
import mockClipboard from "@react-native-clipboard/clipboard/jest/clipboard-mock";
|
|
import mockRNCNetInfo from "@react-native-community/netinfo/jest/netinfo-mock";
|
|
// import { act } from "@testing-library/react";
|
|
import mockFs from "fs";
|
|
import inatjs from "inaturalistjs";
|
|
import fetchMock from "jest-fetch-mock";
|
|
import React from "react";
|
|
import mockRNDeviceInfo from "react-native-device-info/jest/react-native-device-info-mock";
|
|
// eslint-disable-next-line import/no-unresolved
|
|
import mockSafeAreaContext from "react-native-safe-area-context/jest/mock";
|
|
import mockFaker from "tests/helpers/faker";
|
|
import MockAudioRecorderPlayer from "tests/mocks/react-native-audio-recorder-player";
|
|
import * as mockRNLocalize from "tests/mocks/react-native-localize.ts";
|
|
import * as mockZustand from "tests/mocks/zustand.ts";
|
|
|
|
import factory, { makeResponse } from "./factory";
|
|
import {
|
|
mockCamera,
|
|
mockSortDevices,
|
|
mockUseCameraDevice,
|
|
mockUseCameraFormat
|
|
} from "./vision-camera/vision-camera";
|
|
|
|
// Mock the react-native-logs config because it has a dependency on AuthenticationService
|
|
// instead use console.logs for tests
|
|
jest.mock( "../react-native-logs.config", () => {
|
|
const log = {
|
|
extend: jest.fn( () => ( {
|
|
debug: msg => console.debug( msg ),
|
|
info: msg => console.info( msg ),
|
|
warn: msg => console.warn( msg ),
|
|
error: msg => console.error( msg )
|
|
} ) )
|
|
};
|
|
return {
|
|
log,
|
|
logFilePath: "inaturalist-rn-log.txt"
|
|
};
|
|
} );
|
|
|
|
jest.mock( "vision-camera-plugin-inatvision", () => ( {
|
|
getPredictionsForImage: jest.fn( () => Promise.resolve( { predictions: [] } ) ),
|
|
removeLogListener: jest.fn( ),
|
|
resetStoredResults: jest.fn( )
|
|
} ) );
|
|
|
|
jest.mock( "react-native-worklets-core", () => ( {
|
|
useSharedValue: jest.fn(),
|
|
Worklets: {
|
|
createRunOnJS: jest.fn(),
|
|
defaultContext: {
|
|
createRunAsync: jest.fn()
|
|
}
|
|
}
|
|
} ) );
|
|
|
|
jest.mock( "@sayem314/react-native-keep-awake" );
|
|
jest.mock( "react-native/Libraries/EventEmitter/NativeEventEmitter" );
|
|
|
|
jest.mock(
|
|
"@react-native-async-storage/async-storage",
|
|
() => require( "@react-native-async-storage/async-storage/jest/async-storage-mock" )
|
|
);
|
|
|
|
require( "react-native-reanimated" ).setUpTests();
|
|
|
|
jest.mock( "react-native-vision-camera", ( ) => ( {
|
|
Camera: mockCamera,
|
|
sortDevices: mockSortDevices,
|
|
useCameraDevice: mockUseCameraDevice,
|
|
useCameraFormat: mockUseCameraFormat,
|
|
VisionCameraProxy: {
|
|
initFrameProcessorPlugin: jest.fn( )
|
|
},
|
|
useFrameProcessor: jest.fn( )
|
|
} ) );
|
|
|
|
jest.mock( "react-native-localize", () => mockRNLocalize );
|
|
jest.mock( "react-native-safe-area-context", () => mockSafeAreaContext );
|
|
// Trivial mock b/c I assume we can't really test the native parts of this
|
|
// library ~~~kueda 20230516
|
|
jest.mock( "react-native-share-menu", ( ) => ( {
|
|
addNewShareListener: jest.fn( ),
|
|
getInitialShare: jest.fn( )
|
|
} ) );
|
|
|
|
// mock Portal with a Modal component inside of it (MediaViewer)
|
|
jest.mock( "react-native-paper", () => {
|
|
const actual = jest.requireActual( "react-native-paper" );
|
|
const MockedModule = {
|
|
...actual,
|
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
|
Portal: ( { children } ) => <>{children}</>
|
|
};
|
|
return MockedModule;
|
|
} );
|
|
|
|
jest.mock( "@react-navigation/native", ( ) => {
|
|
const actualNav = jest.requireActual( "@react-navigation/native" );
|
|
return {
|
|
...actualNav,
|
|
useIsFocused: jest.fn( ( ) => true ),
|
|
useFocusEffect: ( ) => jest.fn( ),
|
|
useRoute: jest.fn( ( ) => ( { params: {} } ) ),
|
|
useNavigation: ( ) => ( {
|
|
addListener: jest.fn(),
|
|
canGoBack: jest.fn( ( ) => true ),
|
|
goBack: jest.fn( ),
|
|
setOptions: jest.fn( )
|
|
} ),
|
|
useNavigationState: jest.fn( )
|
|
};
|
|
} );
|
|
|
|
jest.mock( "@react-navigation/drawer", ( ) => {
|
|
const actualNav = jest.requireActual( "@react-navigation/drawer" );
|
|
return {
|
|
...actualNav,
|
|
useDrawerStatus: jest.fn( ( ) => false )
|
|
};
|
|
} );
|
|
|
|
// this resolves error with importing file after Jest environment is torn down
|
|
// https://github.com/react-navigation/react-navigation/issues/9568#issuecomment-881943770
|
|
jest.mock( "@react-navigation/native/lib/commonjs/useLinking.native", ( ) => ( {
|
|
default: ( ) => ( { getInitialState: { then: jest.fn( ) } } ),
|
|
__esModule: true
|
|
} ) );
|
|
|
|
// https://github.com/callstack/react-native-testing-library/issues/658#issuecomment-766886514
|
|
jest.mock( "react-native/Libraries/LogBox/LogBox" );
|
|
|
|
jest.mock( "react-native-config", () => ( {
|
|
OAUTH_CLIENT_ID: process.env.OAUTH_CLIENT_ID,
|
|
OAUTH_CLIENT_SECRET: process.env.OAUTH_CLIENT_SECRET,
|
|
JWT_ANONYMOUS_API_SECRET: process.env.JWT_ANONYMOUS_API_SECRET,
|
|
API_URL: process.env.API_URL
|
|
} ) );
|
|
|
|
jest.mock( "react-native-device-info", () => mockRNDeviceInfo );
|
|
|
|
jest.mock( "react-native-sensitive-info", () => {
|
|
class RNSInfo {
|
|
static stores = new Map();
|
|
|
|
static getServiceName( o = {} ) {
|
|
return o.sharedPreferencesName
|
|
|| o.keychainService
|
|
|| "default";
|
|
}
|
|
|
|
static validateString( s ) {
|
|
if ( typeof s !== "string" ) { throw new Error( "Invalid string:", s ); }
|
|
}
|
|
|
|
static getItem = jest.fn( async ( k, o ) => {
|
|
RNSInfo.validateString( k );
|
|
|
|
const serviceName = RNSInfo.getServiceName( o );
|
|
const service = RNSInfo.stores.get( serviceName );
|
|
|
|
if ( service ) { return service.get( k ) || null; }
|
|
return null;
|
|
} );
|
|
|
|
static getAllItems = jest.fn( async o => {
|
|
const serviceName = RNSInfo.getServiceName( o );
|
|
const service = RNSInfo.stores.get( serviceName );
|
|
let mappedValues = [];
|
|
|
|
if ( service?.size ) {
|
|
// for ( const [k, v] of service.entries() ) {
|
|
// mappedValues.push( { key: k, value: v, service: serviceName } );
|
|
// }
|
|
mappedValues = service.entries( ).map(
|
|
( key, value ) => ( { key, value, service: serviceName } )
|
|
);
|
|
}
|
|
|
|
return mappedValues;
|
|
} );
|
|
|
|
static setItem = jest.fn( async ( k, v, o ) => {
|
|
RNSInfo.validateString( k );
|
|
RNSInfo.validateString( v );
|
|
|
|
const serviceName = RNSInfo.getServiceName( o );
|
|
let service = RNSInfo.stores.get( serviceName );
|
|
|
|
if ( !service ) {
|
|
RNSInfo.stores.set( serviceName, new Map() );
|
|
service = RNSInfo.stores.get( serviceName );
|
|
}
|
|
|
|
service.set( k, v );
|
|
|
|
return null;
|
|
} );
|
|
|
|
static deleteItem = jest.fn( async ( k, o ) => {
|
|
RNSInfo.validateString( k );
|
|
|
|
const serviceName = RNSInfo.getServiceName( o );
|
|
const service = RNSInfo.stores.get( serviceName );
|
|
|
|
if ( service ) { service.delete( k ); }
|
|
|
|
return null;
|
|
} );
|
|
|
|
static hasEnrolledFingerprints = jest.fn( async () => true );
|
|
|
|
static setInvalidatedByBiometricEnrollment = jest.fn();
|
|
|
|
// "Touch ID" | "Face ID" | false
|
|
static isSensorAvailable = jest.fn( async () => "Face ID" );
|
|
}
|
|
|
|
return RNSInfo;
|
|
} );
|
|
|
|
// Some test environments may need a little more time
|
|
jest.setTimeout( 50000 );
|
|
|
|
// https://github.com/zoontek/react-native-permissions
|
|
// eslint-disable-next-line global-require
|
|
jest.mock( "react-native-permissions", () => require( "react-native-permissions/mock" ) );
|
|
|
|
// mocking globally since this currently affects a handful of unit and integration tests
|
|
jest.mock( "@react-native-community/geolocation", ( ) => ( {
|
|
getCurrentPosition: jest.fn( )
|
|
} ) );
|
|
require( "react-native" ).NativeModules.RNCGeolocation = { };
|
|
|
|
jest.mock( "@react-native-community/netinfo", () => mockRNCNetInfo );
|
|
|
|
global.ReanimatedDataMock = {
|
|
now: () => 0
|
|
};
|
|
|
|
jest.mock( "react-native-fs", ( ) => {
|
|
const RNFS = {
|
|
appendFile: jest.fn( ),
|
|
CachesDirectoryPath: "caches/directory/path",
|
|
DocumentDirectoryPath: "document/directory/path",
|
|
exists: jest.fn( async ( ) => true ),
|
|
moveFile: async ( ) => "testdata",
|
|
copyFile: async ( ) => "testdata",
|
|
stat: jest.fn( ( ) => ( {
|
|
mtime: 123
|
|
} ) ),
|
|
readFile: jest.fn( ( ) => "testdata" ),
|
|
readDir: jest.fn( async ( ) => ( [
|
|
{
|
|
ctime: 123,
|
|
mtime: 123,
|
|
name: "testdata"
|
|
}
|
|
] ) ),
|
|
writeFile: jest.fn( async ( filePath, contents, _encoding ) => {
|
|
mockFs.writeFile( filePath, contents, jest.fn( ) );
|
|
} ),
|
|
mkdir: jest.fn( async ( filepath, _options ) => {
|
|
mockFs.mkdir( filepath, jest.fn( ) );
|
|
} ),
|
|
unlink: jest.fn( async ( path = "" ) => {
|
|
if ( !path ) return;
|
|
if ( typeof ( path ) !== "string" ) return;
|
|
mockFs.unlink( path, jest.fn( ) );
|
|
} )
|
|
};
|
|
|
|
return RNFS;
|
|
} );
|
|
|
|
require( "react-native" ).NativeModules.FileReaderModule = { };
|
|
|
|
// Mock native animation for all tests
|
|
jest.mock( "react-native/Libraries/Animated/NativeAnimatedHelper" );
|
|
|
|
jest.mock( "@gorhom/bottom-sheet", ( ) => ( {
|
|
...mockBottomSheet,
|
|
__esModule: true,
|
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
|
BottomSheetTextInput: ( ) => <></>
|
|
} ) );
|
|
|
|
jest.mock( "@react-native-camera-roll/camera-roll", ( ) => ( {
|
|
nativeInterface: jest.fn( ),
|
|
CameraRoll: {
|
|
getPhotos: jest.fn( ( ) => ( {
|
|
page_info: {
|
|
end_cursor: jest.fn( ),
|
|
has_next_page: false
|
|
},
|
|
edges: [
|
|
// This expexcts something like
|
|
// { node: photo }
|
|
]
|
|
} ) ),
|
|
getAlbums: jest.fn( ( ) => ( {
|
|
// Expecting album titles as keys and photo counts as values
|
|
// "My Amazing album": 12
|
|
} ) ),
|
|
save: jest.fn( ( _uri, _options = {} ) => mockFaker.system.filePath( ) )
|
|
}
|
|
} ) );
|
|
|
|
jest.mock( "react-native-exif-reader", ( ) => ( {
|
|
readExif: jest.fn( )
|
|
} ) );
|
|
|
|
// https://github.com/APSL/react-native-keyboard-aware-scroll-view/issues/493#issuecomment-861711442
|
|
jest.mock( "react-native-keyboard-aware-scroll-view", ( ) => ( {
|
|
KeyboardAwareScrollView: jest
|
|
.fn( )
|
|
.mockImplementation( ( { children } ) => children )
|
|
} ) );
|
|
|
|
// Mock inaturalistjs so we can make some fake responses
|
|
jest.mock( "inaturalistjs" );
|
|
inatjs.observations.search.mockResolvedValue( makeResponse( ) );
|
|
inatjs.observations.updates.mockResolvedValue( makeResponse( ) );
|
|
|
|
jest.mock( "react-native-orientation-locker", () => ( {
|
|
addDeviceOrientationListener: jest.fn( ),
|
|
addEventListener: jest.fn( ),
|
|
getDeviceOrientation: jest.fn( ),
|
|
getInitialOrientation: jest.fn( ),
|
|
getOrientation: jest.fn( ),
|
|
lockToPortrait: jest.fn( ),
|
|
removeEventListener: jest.fn( ),
|
|
removeOrientationListener: jest.fn( ),
|
|
unlockAllOrientations: jest.fn( )
|
|
} ) );
|
|
|
|
const mockErrorHandler = error => {
|
|
console.log( error );
|
|
};
|
|
jest.mock( "react-native-exception-handler", () => ( {
|
|
setJSExceptionHandler: jest
|
|
.fn()
|
|
.mockImplementation( () => mockErrorHandler() ),
|
|
setNativeExceptionHandler: jest
|
|
.fn()
|
|
.mockImplementation( () => mockErrorHandler() )
|
|
} ) );
|
|
|
|
jest.mock( "react-native-geocoder-reborn", ( ) => ( {
|
|
geocodePosition: jest.fn( coord => [
|
|
`Somewhere near ${coord.lat}, ${coord.lng}`,
|
|
"Somewhere",
|
|
"Somewheria",
|
|
"SW"
|
|
] )
|
|
} ) );
|
|
|
|
// Set up mocked fetch for testing (or disabling) fetch requests
|
|
fetchMock.enableMocks( );
|
|
fetchMock.dontMock( );
|
|
|
|
const mockIconicTaxon = factory( "RemoteTaxon", {
|
|
is_iconic: true,
|
|
name: "Mock iconic taxon"
|
|
} );
|
|
inatjs.taxa.search.mockResolvedValue( makeResponse( [mockIconicTaxon] ) );
|
|
|
|
jest.mock( "@bam.tech/react-native-image-resizer", ( ) => ( {
|
|
createResizedImage: jest.fn(
|
|
async (
|
|
path,
|
|
_maxWidth,
|
|
_maxHeight,
|
|
_compressFormat,
|
|
_quality,
|
|
_rotation,
|
|
_outputPath
|
|
) => ( { uri: path } )
|
|
)
|
|
} ) );
|
|
|
|
jest.mock( "jsrsasign" );
|
|
|
|
inatjs.announcements.search.mockResolvedValue( makeResponse( ) );
|
|
inatjs.observations.updates.mockResolvedValue( makeResponse( ) );
|
|
|
|
jest.mock( "react-native-audio-recorder-player", ( ) => MockAudioRecorderPlayer );
|
|
|
|
jest.mock( "@react-native-clipboard/clipboard", () => mockClipboard );
|
|
|
|
jest.mock( "react-native-webview", () => {
|
|
const MockWebView = jest.requireActual( "react-native" ).View;
|
|
|
|
return {
|
|
__esModule: true,
|
|
WebView: MockWebView,
|
|
default: MockWebView
|
|
};
|
|
} );
|
|
|
|
jest.mock( "react-native/Libraries/TurboModule/TurboModuleRegistry", () => {
|
|
const turboModuleRegistry = jest
|
|
.requireActual( "react-native/Libraries/TurboModule/TurboModuleRegistry" );
|
|
return {
|
|
...turboModuleRegistry,
|
|
getEnforcing: name => {
|
|
// List of TurboModules libraries to mock.
|
|
const modulesToMock = ["ReactNativeKCKeepAwake"];
|
|
if ( modulesToMock.includes( name ) ) {
|
|
return null;
|
|
}
|
|
return turboModuleRegistry.getEnforcing( name );
|
|
}
|
|
};
|
|
} );
|
|
|
|
// Mock zustand in a way that will allow us to reset its state between tests
|
|
jest.mock( "zustand", ( ) => mockZustand );
|
|
// afterEach( () => {
|
|
// act( () => {
|
|
// mockZustand.storeResetFns.forEach( resetFn => {
|
|
// resetFn();
|
|
// } );
|
|
// } );
|
|
// } );
|