Files
self/app/jest.setup.js
Justin Hernandez 202d0f8122 SELF-483: Enable backup recovery prompts (#834)
* Guard recovery prompts

* refactor(app): gate recovery prompts with allow list (#1251)

* fix typing

* fix header

* fix app loading

* fix tests

* Limit recovery prompts to home allowlist (#1460)

* fix test

* fix typing pipeline

* format and fix linting and tests

* tests pass

* fix tests

* split up testing

* save wip

* save button fix

* fix count

* fix modal width

* remove consologging

* remove depcrecated login count

* linting

* lint

* early return
2025-12-05 21:34:50 -08:00

1216 lines
36 KiB
JavaScript

// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
/* global jest */
/** @jest-environment jsdom */
// Set up Buffer globally for tests that need it
const { Buffer } = require('buffer');
global.Buffer = Buffer;
// Mock React Native PixelRatio globally before anything else loads
const mockPixelRatio = {
get: jest.fn(() => 2),
getFontScale: jest.fn(() => 1),
getPixelSizeForLayoutSize: jest.fn(layoutSize => layoutSize * 2),
roundToNearestPixel: jest.fn(layoutSize => Math.round(layoutSize * 2) / 2),
startDetecting: jest.fn(),
};
global.PixelRatio = mockPixelRatio;
// Define NativeModules early so it's available for react-native mock
// This will be assigned to global.NativeModules later, but we define it here
// so the react-native mock can reference it
const NativeModules = {
PassportReader: {
configure: jest.fn(),
scanPassport: jest.fn(),
trackEvent: jest.fn(),
flush: jest.fn(),
reset: jest.fn(),
},
ReactNativeBiometrics: {
isSensorAvailable: jest.fn().mockResolvedValue({
available: true,
biometryType: 'TouchID',
}),
createKeys: jest.fn().mockResolvedValue({ publicKey: 'mock-public-key' }),
deleteKeys: jest.fn().mockResolvedValue(true),
createSignature: jest
.fn()
.mockResolvedValue({ signature: 'mock-signature' }),
simplePrompt: jest.fn().mockResolvedValue({ success: true }),
},
NativeLoggerBridge: {
log: jest.fn(),
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
},
RNPassportReader: {
configure: jest.fn(),
scanPassport: jest.fn(),
trackEvent: jest.fn(),
flush: jest.fn(),
reset: jest.fn(),
},
};
// Assign to global so it's available everywhere
global.NativeModules = NativeModules;
// Mock react-native comprehensively - single source of truth for all tests
// Note: NativeModules will be defined later and assigned to global.NativeModules
// This mock accesses it at runtime via global.NativeModules
jest.mock('react-native', () => {
// Create AppState mock with listener tracking
// Expose listeners array globally so tests can access it
const appStateListeners = [];
global.mockAppStateListeners = appStateListeners;
const mockAppState = {
currentState: 'active',
addEventListener: jest.fn((eventType, handler) => {
appStateListeners.push(handler);
return {
remove: () => {
const index = appStateListeners.indexOf(handler);
if (index >= 0) {
appStateListeners.splice(index, 1);
}
},
};
}),
};
return {
__esModule: true,
AppState: mockAppState,
Platform: {
OS: 'ios',
select: jest.fn(obj => obj.ios || obj.default),
Version: 14,
},
// NativeModules is defined above and assigned to global.NativeModules
// Use getter to access it at runtime (jest.mock is hoisted)
get NativeModules() {
return global.NativeModules || {};
},
NativeEventEmitter: jest.fn().mockImplementation(nativeModule => {
return {
addListener: jest.fn(),
removeListener: jest.fn(),
removeAllListeners: jest.fn(),
emit: jest.fn(),
};
}),
PixelRatio: mockPixelRatio,
Dimensions: {
get: jest.fn(() => ({
window: { width: 375, height: 667, scale: 2 },
screen: { width: 375, height: 667, scale: 2 },
})),
},
Linking: {
getInitialURL: jest.fn().mockResolvedValue(null),
addEventListener: jest.fn(() => ({ remove: jest.fn() })),
removeEventListener: jest.fn(),
openURL: jest.fn().mockResolvedValue(undefined),
canOpenURL: jest.fn().mockResolvedValue(true),
},
StyleSheet: {
create: jest.fn(styles => styles),
flatten: jest.fn(style => style),
hairlineWidth: 1,
absoluteFillObject: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
},
},
View: 'View',
Text: 'Text',
ScrollView: 'ScrollView',
TouchableOpacity: 'TouchableOpacity',
TouchableHighlight: 'TouchableHighlight',
Image: 'Image',
ActivityIndicator: 'ActivityIndicator',
SafeAreaView: 'SafeAreaView',
requireNativeComponent: jest.fn(name => {
// Return a mock component function for any native component
const MockNativeComponent = jest.fn(props => props.children || null);
MockNativeComponent.displayName = `Mock(${name})`;
return MockNativeComponent;
}),
};
});
require('react-native-gesture-handler/jestSetup');
// Mock NativeAnimatedHelper - using virtual mock during RN 0.76.9 prep phase
jest.mock(
'react-native/src/private/animated/NativeAnimatedHelper',
() => ({}),
{ virtual: true },
);
// Mock React Native bridge config for mobile-sdk-alpha components
global.__fbBatchedBridgeConfig = {
messageQueue: {
SPY_MODE: false,
},
remoteModuleConfig: [],
};
// Set up global React Native test environment
global.__DEV__ = true;
// Set up global mock navigation ref for tests
global.mockNavigationRef = {
isReady: jest.fn(() => true),
getCurrentRoute: jest.fn(() => ({ name: 'Home' })),
navigate: jest.fn(),
goBack: jest.fn(),
canGoBack: jest.fn(() => true),
dispatch: jest.fn(),
getState: jest.fn(() => ({ routes: [{ name: 'Home' }], index: 0 })),
addListener: jest.fn(() => jest.fn()),
removeListener: jest.fn(),
};
// Load grouped mocks
require('./tests/__setup__/mocks/navigation');
require('./tests/__setup__/mocks/ui');
// Mock TurboModuleRegistry to provide required native modules for BOTH main app and mobile-sdk-alpha
jest.mock('react-native/Libraries/TurboModule/TurboModuleRegistry', () => ({
getEnforcing: jest.fn(name => {
if (name === 'PlatformConstants') {
return {
getConstants: () => ({
reactNativeVersion: { major: 0, minor: 76, patch: 9 },
forceTouchAvailable: false,
osVersion: '14.0',
systemName: 'iOS',
interfaceIdiom: 'phone',
Dimensions: {
window: { width: 375, height: 667, scale: 2 },
screen: { width: 375, height: 667, scale: 2 },
},
}),
};
}
if (name === 'SettingsManager') {
return {
getConstants: () => ({}),
};
}
if (name === 'DeviceInfo') {
return {
getConstants: () => ({
Dimensions: {
window: { width: 375, height: 667, scale: 2 },
screen: { width: 375, height: 667, scale: 2 },
},
}),
};
}
if (name === 'RNDeviceInfo') {
return {
getConstants: () => ({
Dimensions: {
window: { width: 375, height: 667, scale: 2 },
screen: { width: 375, height: 667, scale: 2 },
},
}),
};
}
return {
getConstants: () => ({}),
};
}),
get: jest.fn(() => null),
}));
// Mock main React Native PixelRatio module
jest.mock('react-native/Libraries/Utilities/PixelRatio', () => ({
get: jest.fn(() => 2),
getFontScale: jest.fn(() => 1),
getPixelSizeForLayoutSize: jest.fn(layoutSize => layoutSize * 2),
roundToNearestPixel: jest.fn(layoutSize => Math.round(layoutSize * 2) / 2),
startDetecting: jest.fn(),
}));
// Mock mobile-sdk-alpha to use the main React Native instance instead of its own
jest.mock(
'../packages/mobile-sdk-alpha/node_modules/react-native',
() => {
// Create the PixelRatio mock first
const PixelRatio = {
get: jest.fn(() => 2),
getFontScale: jest.fn(() => 1),
getPixelSizeForLayoutSize: jest.fn(layoutSize => layoutSize * 2),
roundToNearestPixel: jest.fn(
layoutSize => Math.round(layoutSize * 2) / 2,
),
startDetecting: jest.fn(),
};
// Return a simple object with all the mocks we need
// Avoid nested requireActual/requireMock to prevent OOM in CI
return {
__esModule: true,
PixelRatio,
Platform: {
OS: 'ios',
select: jest.fn(obj => obj.ios || obj.default),
Version: 14,
},
Dimensions: {
get: jest.fn(() => ({
window: { width: 375, height: 667, scale: 2 },
screen: { width: 375, height: 667, scale: 2 },
})),
},
StyleSheet: {
create: jest.fn(styles => styles),
flatten: jest.fn(style => style),
hairlineWidth: 1,
absoluteFillObject: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
},
},
View: 'View',
Text: 'Text',
ScrollView: 'ScrollView',
TouchableOpacity: 'TouchableOpacity',
requireNativeComponent: jest.fn(name => {
const MockNativeComponent = jest.fn(props => props.children || null);
MockNativeComponent.displayName = `Mock(${name})`;
return MockNativeComponent;
}),
};
},
{ virtual: true },
);
// Mock @turnkey/react-native-wallet-kit to prevent loading of problematic dependencies
jest.mock(
'@turnkey/react-native-wallet-kit',
() => ({
AuthState: {
Authenticated: 'Authenticated',
Unauthenticated: 'Unauthenticated',
},
useTurnkey: jest.fn(() => ({
handleGoogleOauth: jest.fn(),
fetchWallets: jest.fn().mockResolvedValue([]),
exportWallet: jest.fn(),
importWallet: jest.fn(),
authState: 'Unauthenticated',
logout: jest.fn(),
})),
TurnkeyProvider: ({ children }) => children,
}),
{ virtual: true },
);
// Mock the mobile-sdk-alpha's TurboModuleRegistry to prevent native module errors
jest.mock(
'../packages/mobile-sdk-alpha/node_modules/react-native/Libraries/TurboModule/TurboModuleRegistry',
() => ({
getEnforcing: jest.fn(name => {
if (name === 'PlatformConstants') {
return {
getConstants: () => ({
reactNativeVersion: { major: 0, minor: 76, patch: 9 },
forceTouchAvailable: false,
osVersion: '14.0',
systemName: 'iOS',
interfaceIdiom: 'phone',
Dimensions: {
window: { width: 375, height: 667, scale: 2 },
screen: { width: 375, height: 667, scale: 2 },
},
}),
};
}
return {
getConstants: () => ({}),
};
}),
get: jest.fn(() => null),
}),
{ virtual: true },
);
// Mock mobile-sdk-alpha's Dimensions module
jest.mock(
'../packages/mobile-sdk-alpha/node_modules/react-native/Libraries/Utilities/Dimensions',
() => ({
getConstants: jest.fn(() => ({
window: { width: 375, height: 667, scale: 2 },
screen: { width: 375, height: 667, scale: 2 },
})),
set: jest.fn(),
get: jest.fn(() => ({
window: { width: 375, height: 667, scale: 2 },
screen: { width: 375, height: 667, scale: 2 },
})),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
}),
{ virtual: true },
);
// Mock mobile-sdk-alpha's PixelRatio module directly since it's still needed by StyleSheet
jest.mock(
'../packages/mobile-sdk-alpha/node_modules/react-native/Libraries/Utilities/PixelRatio',
() => ({
get: jest.fn(() => 2),
getFontScale: jest.fn(() => 1),
getPixelSizeForLayoutSize: jest.fn(layoutSize => layoutSize * 2),
roundToNearestPixel: jest.fn(layoutSize => Math.round(layoutSize * 2) / 2),
startDetecting: jest.fn(),
}),
{ virtual: true },
);
// Mock mobile-sdk-alpha's StyleSheet module directly since it's still needed
jest.mock(
'../packages/mobile-sdk-alpha/node_modules/react-native/Libraries/StyleSheet/StyleSheet',
() => ({
create: jest.fn(styles => styles),
flatten: jest.fn(style => style),
hairlineWidth: 1,
absoluteFillObject: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
},
roundToNearestPixel: jest.fn(layoutSize => Math.round(layoutSize * 2) / 2),
}),
{ virtual: true },
);
// Mock main React Native StyleSheet module
jest.mock('react-native/Libraries/StyleSheet/StyleSheet', () => ({
create: jest.fn(styles => styles),
flatten: jest.fn(style => style),
hairlineWidth: 1,
absoluteFillObject: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
},
roundToNearestPixel: jest.fn(layoutSize => Math.round(layoutSize * 2) / 2),
}));
// Mock NativeDeviceInfo specs for both main app and mobile-sdk-alpha
jest.mock('react-native/src/private/specs/modules/NativeDeviceInfo', () => ({
getConstants: jest.fn(() => ({
Dimensions: {
window: { width: 375, height: 667, scale: 2 },
screen: { width: 375, height: 667, scale: 2 },
},
})),
}));
// Mock NativeStatusBarManagerIOS for react-native-edge-to-edge SystemBars
jest.mock(
'react-native/src/private/specs/modules/NativeStatusBarManagerIOS',
() => ({
setStyle: jest.fn(),
setHidden: jest.fn(),
setNetworkActivityIndicatorVisible: jest.fn(),
}),
);
// Mock react-native-gesture-handler to prevent getConstants errors
jest.mock('react-native-gesture-handler', () => {
// Avoid requiring React to prevent nested require memory issues
// Mock the components as simple pass-through functions
const MockScrollView = jest.fn(props => props.children || null);
const MockTouchableOpacity = jest.fn(props => props.children || null);
const MockTouchableHighlight = jest.fn(props => props.children || null);
const MockFlatList = jest.fn(props => null);
return {
// Provide gesture handler mock without requireActual to avoid OOM
GestureHandlerRootView: ({ children }) => children,
ScrollView: MockScrollView,
TouchableOpacity: MockTouchableOpacity,
TouchableHighlight: MockTouchableHighlight,
FlatList: MockFlatList,
Directions: {},
State: {},
Swipeable: jest.fn(() => null),
DrawerLayout: jest.fn(() => null),
PanGestureHandler: jest.fn(() => null),
TapGestureHandler: jest.fn(() => null),
LongPressGestureHandler: jest.fn(() => null),
};
});
// Mock react-native-safe-area-context
jest.mock('react-native-safe-area-context', () => {
// Avoid requiring React to prevent nested require memory issues
return {
__esModule: true,
SafeAreaProvider: jest.fn(({ children }) => children || null),
SafeAreaView: jest.fn(({ children }) => children || null),
useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
};
});
// Mock NativeEventEmitter to prevent null argument errors
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter', () => {
function MockNativeEventEmitter(nativeModule) {
// Accept any nativeModule argument (including null/undefined)
this.nativeModule = nativeModule;
this.addListener = jest.fn();
this.removeListener = jest.fn();
this.removeAllListeners = jest.fn();
this.emit = jest.fn();
}
// The mock needs to be the constructor itself, not wrapped
MockNativeEventEmitter.default = MockNativeEventEmitter;
return MockNativeEventEmitter;
});
// Mock react-native-device-info to prevent NativeEventEmitter errors
jest.mock('react-native-device-info', () => ({
getUniqueId: jest.fn().mockResolvedValue('mock-device-id'),
getReadableVersion: jest.fn().mockReturnValue('1.0.0'),
getVersion: jest.fn().mockReturnValue('1.0.0'),
getBuildNumber: jest.fn().mockReturnValue('1'),
getModel: jest.fn().mockReturnValue('mock-model'),
getBrand: jest.fn().mockReturnValue('mock-brand'),
isTablet: jest.fn().mockReturnValue(false),
isLandscape: jest.fn().mockResolvedValue(false),
getSystemVersion: jest.fn().mockReturnValue('14.0'),
getSystemName: jest.fn().mockReturnValue('iOS'),
default: {
getUniqueId: jest.fn().mockResolvedValue('mock-device-id'),
getReadableVersion: jest.fn().mockReturnValue('1.0.0'),
getVersion: jest.fn().mockReturnValue('1.0.0'),
getBuildNumber: jest.fn().mockReturnValue('1'),
getModel: jest.fn().mockReturnValue('mock-model'),
getBrand: jest.fn().mockReturnValue('mock-brand'),
isTablet: jest.fn().mockReturnValue(false),
isLandscape: jest.fn().mockResolvedValue(false),
getSystemVersion: jest.fn().mockReturnValue('14.0'),
getSystemName: jest.fn().mockReturnValue('iOS'),
},
}));
// Mock react-native-device-info nested in @turnkey/react-native-wallet-kit
jest.mock(
'node_modules/@turnkey/react-native-wallet-kit/node_modules/react-native-device-info',
() => ({
getUniqueId: jest.fn().mockResolvedValue('mock-device-id'),
getReadableVersion: jest.fn().mockReturnValue('1.0.0'),
getVersion: jest.fn().mockReturnValue('1.0.0'),
getBuildNumber: jest.fn().mockReturnValue('1'),
getModel: jest.fn().mockReturnValue('mock-model'),
getBrand: jest.fn().mockReturnValue('mock-brand'),
isTablet: jest.fn().mockReturnValue(false),
isLandscape: jest.fn().mockResolvedValue(false),
getSystemVersion: jest.fn().mockReturnValue('14.0'),
getSystemName: jest.fn().mockReturnValue('iOS'),
default: {
getUniqueId: jest.fn().mockResolvedValue('mock-device-id'),
getReadableVersion: jest.fn().mockReturnValue('1.0.0'),
getVersion: jest.fn().mockReturnValue('1.0.0'),
getBuildNumber: jest.fn().mockReturnValue('1'),
getModel: jest.fn().mockReturnValue('mock-model'),
getBrand: jest.fn().mockReturnValue('mock-brand'),
isTablet: jest.fn().mockReturnValue(false),
isLandscape: jest.fn().mockResolvedValue(false),
getSystemVersion: jest.fn().mockReturnValue('14.0'),
getSystemName: jest.fn().mockReturnValue('iOS'),
},
}),
{ virtual: true },
);
// Mock problematic mobile-sdk-alpha components that use React Native StyleSheet
jest.mock('@selfxyz/mobile-sdk-alpha', () => ({
NFCScannerScreen: jest.fn(() => null),
SelfClientProvider: jest.fn(({ children }) => children),
useSelfClient: jest.fn(() => {
// Create a consistent mock instance for memoization testing
if (!global.mockSelfClientInstance) {
global.mockSelfClientInstance = {
// Mock selfClient object with common methods
connect: jest.fn(),
disconnect: jest.fn(),
isConnected: false,
extractMRZInfo: jest.fn(mrzString => {
// Mock extractMRZInfo with realistic behavior
if (!mrzString || typeof mrzString !== 'string') {
throw new Error('Invalid MRZ string provided');
}
// Valid MRZ example from the test
if (mrzString.includes('L898902C3')) {
return {
documentNumber: 'L898902C3',
validation: {
overall: true,
},
// Add other expected MRZ fields
firstName: 'ANNA',
lastName: 'ERIKSSON',
nationality: 'UTO',
dateOfBirth: '740812',
sex: 'F',
expirationDate: '120415',
};
}
// For malformed/invalid MRZ strings, throw an error
throw new Error('Invalid MRZ format');
}),
trackEvent: jest.fn(),
};
}
return global.mockSelfClientInstance;
}),
createSelfClient: jest.fn(() => ({
// Mock createSelfClient return value
connect: jest.fn(),
disconnect: jest.fn(),
isConnected: false,
extractMRZInfo: jest.fn(mrzString => {
// Mock extractMRZInfo with realistic behavior
if (!mrzString || typeof mrzString !== 'string') {
throw new Error('Invalid MRZ string provided');
}
// Valid MRZ example from the test
if (mrzString.includes('L898902C3')) {
return {
documentNumber: 'L898902C3',
validation: {
overall: true,
},
// Add other expected MRZ fields
firstName: 'ANNA',
lastName: 'ERIKSSON',
nationality: 'UTO',
dateOfBirth: '740812',
sex: 'F',
expirationDate: '120415',
};
}
// For malformed/invalid MRZ strings, throw an error
throw new Error('Invalid MRZ format');
}),
trackEvent: jest.fn(),
})),
createListenersMap: jest.fn(() => ({
// Mock createListenersMap return value
map: new Map(),
addListener: jest.fn(),
removeListener: jest.fn(),
})),
isPassportDataValid: jest.fn((data, callbacks) => {
// Mock validation function with realistic behavior
if (!data || !data.passportMetadata) {
// Call appropriate callbacks for missing data
if (callbacks?.onPassportMetadataNull) {
callbacks.onPassportMetadataNull();
}
return false;
}
// Return true for valid data, false for invalid
return data.valid !== false;
}),
SdkEvents: {
// Mock SDK events object
PROVING_PASSPORT_DATA_NOT_FOUND: 'PROVING_PASSPORT_DATA_NOT_FOUND',
PROVING_STARTED: 'PROVING_STARTED',
PROVING_COMPLETED: 'PROVING_COMPLETED',
PROVING_FAILED: 'PROVING_FAILED',
// Add other events as needed
},
// Mock haptic functions
buttonTap: jest.fn(),
cancelTap: jest.fn(),
confirmTap: jest.fn(),
feedbackProgress: jest.fn(),
feedbackSuccess: jest.fn(),
feedbackUnsuccessful: jest.fn(),
impactLight: jest.fn(),
impactMedium: jest.fn(),
loadingScreenProgress: jest.fn(),
notificationError: jest.fn(),
notificationSuccess: jest.fn(),
notificationWarning: jest.fn(),
selectionChange: jest.fn(),
triggerFeedback: jest.fn(),
// Add other components and hooks as needed
}));
// Mock Sentry to prevent NativeModule.getConstants errors
jest.mock('@sentry/react-native', () => ({
addBreadcrumb: jest.fn(),
captureException: jest.fn(),
captureFeedback: jest.fn(),
captureMessage: jest.fn(),
setContext: jest.fn(),
setExtra: jest.fn(),
setTag: jest.fn(),
setUser: jest.fn(),
init: jest.fn(),
wrap: jest.fn(component => component),
withScope: jest.fn(callback => {
// Mock scope object
const scope = {
setLevel: jest.fn(),
setTag: jest.fn(),
setExtra: jest.fn(),
setContext: jest.fn(),
setUser: jest.fn(),
};
callback(scope);
}),
}));
jest.mock('@env', () => ({
ENABLE_DEBUG_LOGS: 'false',
MIXPANEL_NFC_PROJECT_TOKEN: 'test-token',
}));
global.FileReader = class {
constructor() {
this.onload = null;
}
readAsArrayBuffer() {
if (this.onload) {
this.onload({ target: { result: new ArrayBuffer(0) } });
}
}
};
jest.mock('@react-native-firebase/messaging', () => {
return () => ({
hasPermission: jest.fn(() => Promise.resolve(true)),
requestPermission: jest.fn(() => Promise.resolve(true)),
getToken: jest.fn(() => Promise.resolve('mock-token')),
onMessage: jest.fn(() => jest.fn()),
onNotificationOpenedApp: jest.fn(() => jest.fn()),
getInitialNotification: jest.fn(() => Promise.resolve(null)),
setBackgroundMessageHandler: jest.fn(),
registerDeviceForRemoteMessages: jest.fn(() => Promise.resolve()),
subscribeToTopic: jest.fn(),
unsubscribeFromTopic: jest.fn(),
});
});
jest.mock('@react-native-firebase/remote-config', () => {
const mockValue = { asBoolean: jest.fn(() => false) };
const mockConfig = {
setDefaults: jest.fn(),
setConfigSettings: jest.fn(),
fetchAndActivate: jest.fn(() => Promise.resolve(true)),
getValue: jest.fn(() => mockValue),
};
return () => mockConfig;
});
// Mock react-native-haptic-feedback
jest.mock('react-native-haptic-feedback', () => ({
trigger: jest.fn(),
}));
// Mock Segment Analytics
jest.mock('@segment/analytics-react-native', () => {
const mockClient = {
add: jest.fn(),
track: jest.fn(),
identify: jest.fn(),
screen: jest.fn(),
group: jest.fn(),
alias: jest.fn(),
reset: jest.fn(),
};
// Mock flush policy classes
const MockFlushPolicy = class {
constructor() {}
};
return {
createClient: jest.fn(() => mockClient),
EventPlugin: jest.fn(),
PluginType: {
ENRICHMENT: 'enrichment',
DESTINATION: 'destination',
BEFORE: 'before',
before: 'before',
},
StartupFlushPolicy: MockFlushPolicy,
BackgroundFlushPolicy: MockFlushPolicy,
};
});
// Note: @selfxyz/mobile-sdk-alpha is NOT mocked to allow testing real package methods
// This is intentional for the mobile-sdk-alpha migration testing
// Mock react-native-keychain
jest.mock('react-native-keychain', () => ({
SECURITY_LEVEL_ANY: 'MOCK_SECURITY_LEVEL_ANY',
SECURITY_LEVEL_SECURE_SOFTWARE: 'MOCK_SECURITY_LEVEL_SECURE_SOFTWARE',
SECURITY_LEVEL_SECURE_HARDWARE: 'MOCK_SECURITY_LEVEL_SECURE_HARDWARE',
setGenericPassword: jest.fn(),
getGenericPassword: jest.fn(),
resetGenericPassword: jest.fn(),
ACCESSIBLE: {
WHEN_UNLOCKED: 'AccessibleWhenUnlocked',
AFTER_FIRST_UNLOCK: 'AccessibleAfterFirstUnlock',
ALWAYS: 'AccessibleAlways',
WHEN_PASSCODE_SET_THIS_DEVICE_ONLY:
'AccessibleWhenPasscodeSetThisDeviceOnly',
WHEN_UNLOCKED_THIS_DEVICE_ONLY: 'AccessibleWhenUnlockedThisDeviceOnly',
AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY:
'AccessibleAfterFirstUnlockThisDeviceOnly',
ALWAYS_THIS_DEVICE_ONLY: 'AccessibleAlwaysThisDeviceOnly',
},
ACCESS_CONTROL: {
USER_PRESENCE: 'UserPresence',
BIOMETRY_ANY: 'BiometryAny',
BIOMETRY_CURRENT_SET: 'BiometryCurrentSet',
DEVICE_PASSCODE: 'DevicePasscode',
APPLICATION_PASSWORD: 'ApplicationPassword',
BIOMETRY_ANY_OR_DEVICE_PASSCODE: 'BiometryAnyOrDevicePasscode',
BIOMETRY_CURRENT_SET_OR_DEVICE_PASSCODE:
'BiometryCurrentSetOrDevicePasscode',
},
}));
// Mock AsyncStorage
jest.mock('@react-native-async-storage/async-storage', () => ({
setItem: jest.fn(),
getItem: jest.fn(),
removeItem: jest.fn(),
mergeItem: jest.fn(),
clear: jest.fn(),
getAllKeys: jest.fn(),
flushGetRequests: jest.fn(),
multiGet: jest.fn(),
multiSet: jest.fn(),
multiRemove: jest.fn(),
multiMerge: jest.fn(),
}));
// Mock react-native-check-version
jest.mock('react-native-check-version', () => ({
checkVersion: jest.fn().mockResolvedValue({
needsUpdate: false,
currentVersion: '1.0.0',
latestVersion: '1.0.0',
}),
}));
// Mock @react-native-community/netinfo
jest.mock('@react-native-community/netinfo', () => ({
addEventListener: jest.fn(() => jest.fn()),
useNetInfo: jest.fn().mockReturnValue({
type: 'wifi',
isConnected: true,
isInternetReachable: true,
details: {
isConnectionExpensive: false,
cellularGeneration: '4g',
},
}),
fetch: jest
.fn()
.mockResolvedValue({ isConnected: true, isInternetReachable: true }),
}));
// Mock react-native-nfc-manager
jest.mock('react-native-nfc-manager', () => ({
start: jest.fn(),
isSupported: jest.fn().mockResolvedValue(true),
isEnabled: jest.fn().mockResolvedValue(true),
registerTagEvent: jest.fn(),
unregisterTagEvent: jest.fn(),
requestTechnology: jest.fn(),
cancelTechnologyRequest: jest.fn(),
getTag: jest.fn(),
setAlertMessage: jest.fn(),
sendMifareCommand: jest.fn(),
sendCommandAPDU: jest.fn(),
transceive: jest.fn(),
getMaxTransceiveLength: jest.fn(),
setTimeout: jest.fn(),
connect: jest.fn(),
close: jest.fn(),
cleanUpTag: jest.fn(),
default: {
start: jest.fn(),
isSupported: jest.fn().mockResolvedValue(true),
isEnabled: jest.fn().mockResolvedValue(true),
registerTagEvent: jest.fn(),
unregisterTagEvent: jest.fn(),
requestTechnology: jest.fn(),
cancelTechnologyRequest: jest.fn(),
getTag: jest.fn(),
setAlertMessage: jest.fn(),
sendMifareCommand: jest.fn(),
sendCommandAPDU: jest.fn(),
transceive: jest.fn(),
getMaxTransceiveLength: jest.fn(),
setTimeout: jest.fn(),
connect: jest.fn(),
close: jest.fn(),
cleanUpTag: jest.fn(),
},
}));
// Mock react-native-passport-reader
jest.mock('react-native-passport-reader', () => {
const mockScanPassport = jest.fn();
// Mock the parameter count for scanPassport (iOS native method takes 9 parameters)
Object.defineProperty(mockScanPassport, 'length', { value: 9 });
const mockPassportReader = {
configure: jest.fn(),
scanPassport: mockScanPassport,
readPassport: jest.fn(),
cancelPassportRead: jest.fn(),
trackEvent: jest.fn(),
flush: jest.fn(),
reset: jest.fn(),
};
return {
PassportReader: mockPassportReader,
default: mockPassportReader,
reset: jest.fn(),
scan: jest.fn(),
};
});
// NativeModules is already defined at the top of the file and assigned to global.NativeModules
// No need to redefine it here
// Mock @/integrations/nfc/passportReader to properly expose the interface expected by tests
jest.mock('./src/integrations/nfc/passportReader', () => {
const mockScanPassport = jest.fn();
// Mock the parameter count for scanPassport (iOS native method takes 9 parameters)
Object.defineProperty(mockScanPassport, 'length', { value: 9 });
const mockPassportReader = {
configure: jest.fn(),
scanPassport: mockScanPassport,
trackEvent: jest.fn(),
flush: jest.fn(),
reset: jest.fn(),
};
return {
PassportReader: mockPassportReader,
reset: jest.fn(),
scan: jest.fn(),
default: mockPassportReader,
};
});
// Mock @stablelib packages
jest.mock('@stablelib/cbor', () => ({
encode: jest.fn(),
decode: jest.fn(),
}));
jest.mock('@stablelib/utf8', () => ({
encode: jest.fn(),
decode: jest.fn(),
}));
// Mock react-native-app-auth
jest.mock('react-native-app-auth', () => ({
authorize: jest.fn().mockResolvedValue({ accessToken: 'mock-access-token' }),
}));
// Mock @robinbobin/react-native-google-drive-api-wrapper
jest.mock('@robinbobin/react-native-google-drive-api-wrapper', () => {
class MockUploader {
setData() {
return this;
}
setDataMimeType() {
return this;
}
setRequestBody() {
return this;
}
execute = jest.fn();
}
class MockFiles {
newMultipartUploader() {
return new MockUploader();
}
list = jest.fn().mockResolvedValue({ files: [] });
delete = jest.fn();
getText = jest.fn().mockResolvedValue('');
}
class GDrive {
accessToken = '';
files = new MockFiles();
}
return {
__esModule: true,
GDrive,
MIME_TYPES: { application: { json: 'application/json' } },
APP_DATA_FOLDER_ID: 'appDataFolder',
};
});
// Mock react-native-cloud-storage
jest.mock('react-native-cloud-storage', () => {
const mockCloudStorage = {
setProviderOptions: jest.fn(),
isCloudAvailable: jest.fn().mockResolvedValue(true),
createFolder: jest.fn(),
deleteFolder: jest.fn(),
listFiles: jest.fn(),
readFile: jest.fn(),
writeFile: jest.fn(),
deleteFile: jest.fn(),
getFileInfo: jest.fn(),
getStorageInfo: jest.fn(),
getProvider: jest.fn(),
mkdir: jest.fn(),
exists: jest.fn(),
rmdir: jest.fn(),
};
return {
__esModule: true,
CloudStorage: mockCloudStorage,
CloudStorageScope: {
AppData: 'AppData',
Documents: 'Documents',
Full: 'Full',
},
CloudStorageProvider: {
GoogleDrive: 'GoogleDrive',
ICloud: 'ICloud',
},
};
});
// Mock @react-native-clipboard/clipboard
jest.mock('@react-native-clipboard/clipboard', () => ({
getString: jest.fn().mockResolvedValue(''),
setString: jest.fn(),
hasString: jest.fn().mockResolvedValue(false),
}));
// Mock react-native-localize
jest.mock('react-native-localize', () => ({
getLocales: jest.fn().mockReturnValue([
{
countryCode: 'US',
languageTag: 'en-US',
languageCode: 'en',
isRTL: false,
},
]),
getCountry: jest.fn().mockReturnValue('US'),
getTimeZone: jest.fn().mockReturnValue('America/New_York'),
getCurrencies: jest.fn().mockReturnValue(['USD']),
getTemperatureUnit: jest.fn().mockReturnValue('celsius'),
getFirstWeekDay: jest.fn().mockReturnValue(0),
uses24HourClock: jest.fn().mockReturnValue(false),
usesMetricSystem: jest.fn().mockReturnValue(false),
findBestAvailableLanguage: jest.fn().mockReturnValue({
languageTag: 'en-US',
isRTL: false,
}),
default: {
getLocales: jest.fn().mockReturnValue([
{
countryCode: 'US',
languageTag: 'en-US',
languageCode: 'en',
isRTL: false,
},
]),
getCountry: jest.fn().mockReturnValue('US'),
getTimeZone: jest.fn().mockReturnValue('America/New_York'),
getCurrencies: jest.fn().mockReturnValue(['USD']),
getTemperatureUnit: jest.fn().mockReturnValue('celsius'),
getFirstWeekDay: jest.fn().mockReturnValue(0),
uses24HourClock: jest.fn().mockReturnValue(false),
usesMetricSystem: jest.fn().mockReturnValue(false),
findBestAvailableLanguage: jest.fn().mockReturnValue({
languageTag: 'en-US',
isRTL: false,
}),
},
}));
// Ensure mobile-sdk-alpha's bundled react-native-localize dependency is mocked as well
jest.mock(
'../packages/mobile-sdk-alpha/node_modules/react-native-localize',
() => ({
getLocales: jest.fn().mockReturnValue([
{
countryCode: 'US',
languageTag: 'en-US',
languageCode: 'en',
isRTL: false,
},
]),
getCountry: jest.fn().mockReturnValue('US'),
getTimeZone: jest.fn().mockReturnValue('America/New_York'),
getCurrencies: jest.fn().mockReturnValue(['USD']),
getTemperatureUnit: jest.fn().mockReturnValue('celsius'),
getFirstWeekDay: jest.fn().mockReturnValue(0),
uses24HourClock: jest.fn().mockReturnValue(false),
usesMetricSystem: jest.fn().mockReturnValue(false),
findBestAvailableLanguage: jest.fn().mockReturnValue({
languageTag: 'en-US',
isRTL: false,
}),
default: {
getLocales: jest.fn().mockReturnValue([
{
countryCode: 'US',
languageTag: 'en-US',
languageCode: 'en',
isRTL: false,
},
]),
getCountry: jest.fn().mockReturnValue('US'),
getTimeZone: jest.fn().mockReturnValue('America/New_York'),
getCurrencies: jest.fn().mockReturnValue(['USD']),
getTemperatureUnit: jest.fn().mockReturnValue('celsius'),
getFirstWeekDay: jest.fn().mockReturnValue(0),
uses24HourClock: jest.fn().mockReturnValue(false),
usesMetricSystem: jest.fn().mockReturnValue(false),
findBestAvailableLanguage: jest.fn().mockReturnValue({
languageTag: 'en-US',
isRTL: false,
}),
},
}),
);
jest.mock('./src/services/notifications/notificationService', () =>
require('./tests/__setup__/notificationServiceMock.js'),
);
// Mock react-native-svg
jest.mock('react-native-svg', () => {
// Avoid requiring React to prevent nested require memory issues
// Mock SvgXml component that handles XML strings
const SvgXml = jest.fn(() => null);
SvgXml.displayName = 'SvgXml';
return {
__esModule: true,
default: SvgXml,
SvgXml,
Svg: jest.fn(() => null),
Circle: jest.fn(() => null),
Path: jest.fn(() => null),
G: jest.fn(() => null),
Rect: jest.fn(() => null),
Defs: jest.fn(() => null),
LinearGradient: jest.fn(() => null),
Stop: jest.fn(() => null),
ClipPath: jest.fn(() => null),
Polygon: jest.fn(() => null),
Polyline: jest.fn(() => null),
Line: jest.fn(() => null),
Text: jest.fn(() => null),
TSpan: jest.fn(() => null),
};
});
// Mock React Navigation
// Mock react-native-biometrics to prevent NativeModules errors
jest.mock('react-native-biometrics', () => {
class MockReactNativeBiometrics {
constructor(options) {
// Constructor accepts options but doesn't need to do anything
}
isSensorAvailable = jest.fn().mockResolvedValue({
available: true,
biometryType: 'TouchID',
});
createKeys = jest.fn().mockResolvedValue({ publicKey: 'mock-public-key' });
deleteKeys = jest.fn().mockResolvedValue(true);
createSignature = jest
.fn()
.mockResolvedValue({ signature: 'mock-signature' });
simplePrompt = jest.fn().mockResolvedValue({ success: true });
}
return {
__esModule: true,
default: MockReactNativeBiometrics,
};
});
// Mock NativeAppState native module to prevent getCurrentAppState errors
jest.mock('react-native/Libraries/AppState/NativeAppState', () => ({
__esModule: true,
default: {
getConstants: jest.fn(() => ({ initialAppState: 'active' })),
getCurrentAppState: jest.fn(() => Promise.resolve({ app_state: 'active' })),
addListener: jest.fn(),
removeListeners: jest.fn(),
},
}));
// Mock AppState to prevent getCurrentAppState errors
jest.mock('react-native/Libraries/AppState/AppState', () => {
// Use the global appStateListeners array so tests can access it
const appStateListeners = global.mockAppStateListeners || [];
return {
__esModule: true,
default: {
currentState: 'active',
addEventListener: jest.fn((eventType, handler) => {
appStateListeners.push(handler);
return {
remove: () => {
const index = appStateListeners.indexOf(handler);
if (index >= 0) {
appStateListeners.splice(index, 1);
}
},
};
}),
},
};
});