mirror of
https://github.com/selfxyz/self.git
synced 2026-01-09 06:38:09 -05:00
SELF-1192: fix oom tests (#1429)
* fix oom tests? * update tests * try fixing tests again * fix: unblock mobile app jest runner * fix corrupt yarn lock * Reduce heavy React Native usage in tests (#1436) * Reduce heavy React Native usage in tests * Stabilize mobile tests * prettier * ignore podfile.lock * fix test and gitleaks * fix path * update * fix tests * address tamagui concern
This commit is contained in:
2
.github/workflows/gitleaks.yml
vendored
2
.github/workflows/gitleaks.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
- name: Install gitleaks
|
||||
uses: gitleaks/gitleaks-action@v2.3.9
|
||||
with:
|
||||
config-path: .gitleaks.toml
|
||||
config-path: gitleaks-override.toml
|
||||
fail: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
f506113a22e5b147132834e4659f5af308448389:app/tests/utils/deeplinks.test.ts:generic-api-key:183
|
||||
5a67b5cc50f291401d1da4e51706d0cfcf1c2316:app/tests/utils/deeplinks.test.ts:generic-api-key:182
|
||||
0e4555eee6589aa9cca68f451227b149277d8c90:app/tests/src/utils/points/api.test.ts:generic-api-key:34
|
||||
feb433e3553f8a7fa6c724b2de5a3e32ef079880:app/ios/Podfile.lock:generic-api-key:2594
|
||||
3d0e1b4589680df2451031913d067b1b91dafa60:app/ios/Podfile.lock:generic-api-key:2594
|
||||
3d0e1b4589680df2451031913d067b1b91dafa60:app/tests/utils/deeplinks.test.ts:generic-api-key:208
|
||||
circuits/circuits/gcp_jwt_verifier/example_jwt.txt:jwt:1
|
||||
3a3392417065169c48329fd2463b83e5a43b10db:app/ios/Podfile.lock:generic-api-key:2586
|
||||
|
||||
71
app/babel.config.test.cjs
Normal file
71
app/babel.config.test.cjs
Normal file
@@ -0,0 +1,71 @@
|
||||
// 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.
|
||||
|
||||
// Babel config for Jest tests that excludes hermes-parser to avoid WebAssembly issues
|
||||
// Based on React Native babel preset but with hermes parser plugin removed
|
||||
|
||||
module.exports = {
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
targets: {
|
||||
node: 'current',
|
||||
},
|
||||
},
|
||||
],
|
||||
'@babel/preset-typescript',
|
||||
[
|
||||
'@babel/preset-react',
|
||||
{
|
||||
runtime: 'automatic',
|
||||
},
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
// Module resolver for @ alias
|
||||
[
|
||||
'module-resolver',
|
||||
{
|
||||
root: ['./src'],
|
||||
alias: { '@': './src' },
|
||||
},
|
||||
],
|
||||
|
||||
// Core React Native transforms (minimal set needed for tests)
|
||||
['@babel/plugin-transform-class-properties', { loose: true }],
|
||||
['@babel/plugin-transform-classes', { loose: true }],
|
||||
['@babel/plugin-transform-private-methods', { loose: true }],
|
||||
['@babel/plugin-transform-private-property-in-object', { loose: true }],
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-syntax-export-default-from',
|
||||
'@babel/plugin-transform-export-namespace-from',
|
||||
'@babel/plugin-transform-unicode-regex',
|
||||
['@babel/plugin-transform-destructuring', { useBuiltIns: true }],
|
||||
'@babel/plugin-transform-spread',
|
||||
[
|
||||
'@babel/plugin-transform-object-rest-spread',
|
||||
{ loose: true, useBuiltIns: true },
|
||||
],
|
||||
['@babel/plugin-transform-optional-chaining', { loose: true }],
|
||||
['@babel/plugin-transform-nullish-coalescing-operator', { loose: true }],
|
||||
['@babel/plugin-transform-logical-assignment-operators', { loose: true }],
|
||||
// Flow type stripping to support React Native's Flow-based sources
|
||||
['@babel/plugin-syntax-flow'],
|
||||
['@babel/plugin-transform-flow-strip-types', { allowDeclareFields: true }],
|
||||
|
||||
// Environment variable support
|
||||
[
|
||||
'module:react-native-dotenv',
|
||||
{
|
||||
moduleName: '@env',
|
||||
path: '.env',
|
||||
blacklist: null,
|
||||
whitelist: null,
|
||||
safe: false,
|
||||
allowUndefined: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
@@ -1465,7 +1465,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- react-native-app-auth (8.0.3):
|
||||
- react-native-app-auth (8.1.0):
|
||||
- AppAuth (>= 1.7.6)
|
||||
- React-Core
|
||||
- react-native-biometrics (3.0.1):
|
||||
@@ -1560,7 +1560,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- react-native-safe-area-context (5.6.1):
|
||||
- react-native-safe-area-context (5.6.2):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -1573,8 +1573,8 @@ PODS:
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- react-native-safe-area-context/common (= 5.6.1)
|
||||
- react-native-safe-area-context/fabric (= 5.6.1)
|
||||
- react-native-safe-area-context/common (= 5.6.2)
|
||||
- react-native-safe-area-context/fabric (= 5.6.2)
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-rendererdebug
|
||||
@@ -1583,7 +1583,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- react-native-safe-area-context/common (5.6.1):
|
||||
- react-native-safe-area-context/common (5.6.2):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -1604,7 +1604,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- react-native-safe-area-context/fabric (5.6.1):
|
||||
- react-native-safe-area-context/fabric (5.6.2):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -2021,7 +2021,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- RNLocalize (3.5.3):
|
||||
- RNLocalize (3.6.0):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -2131,7 +2131,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- Sentry/HybridSDK (= 8.53.2)
|
||||
- Yoga
|
||||
- RNSVG (15.14.0):
|
||||
- RNSVG (15.15.0):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -2151,9 +2151,9 @@ PODS:
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- RNSVG/common (= 15.14.0)
|
||||
- RNSVG/common (= 15.15.0)
|
||||
- Yoga
|
||||
- RNSVG/common (15.14.0):
|
||||
- RNSVG/common (15.15.0):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -2583,7 +2583,7 @@ SPEC CHECKSUMS:
|
||||
React-logger: c4052eb941cca9a097ef01b59543a656dc088559
|
||||
React-Mapbuffer: 9343a5c14536d4463c80f09a960653d754daae21
|
||||
React-microtasksnativemodule: c7cafd8f4470cf8a4578ee605daa4c74d3278bf8
|
||||
react-native-app-auth: eb42594042a25455119a8c57194b4fd25b9352f4
|
||||
react-native-app-auth: e21c8ee920876b960e38c9381971bd189ebea06b
|
||||
react-native-biometrics: 43ed5b828646a7862dbc7945556446be00798e7d
|
||||
react-native-blur: 6334d934a9b5e67718b8f5725c44cc0a12946009
|
||||
react-native-cloud-storage: 8d89f2bc574cf11068dfd90933905974087fb9e9
|
||||
@@ -2592,7 +2592,7 @@ SPEC CHECKSUMS:
|
||||
react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187
|
||||
react-native-nfc-manager: 66a00e5ddab9704efebe19d605b1b8afb0bb1bd7
|
||||
react-native-passkey: 8853c3c635164864da68a6dbbcec7148506c3bcf
|
||||
react-native-safe-area-context: 90a89cb349c7f8168a707e6452288c2f665b9fd1
|
||||
react-native-safe-area-context: a7aad44fe544b55e2369a3086e16a01be60ce398
|
||||
react-native-sqlite-storage: 0c84826214baaa498796c7e46a5ccc9a82e114ed
|
||||
react-native-webview: 3f45e19f0ffc3701168768a6c37695e0f252410e
|
||||
React-nativeconfig: 415626a63057638759bcc75e0a96e2e07771a479
|
||||
@@ -2631,11 +2631,11 @@ SPEC CHECKSUMS:
|
||||
RNGestureHandler: a63b531307e5b2e6ea21d053a1a7ad4cf9695c57
|
||||
RNInAppBrowser: 6d3eb68d471b9834335c664704719b8be1bfdb20
|
||||
RNKeychain: 471ceef8c13f15a5534c3cd2674dbbd9d0680e52
|
||||
RNLocalize: 7683e450496a5aea9a2dab3745bfefa7341d3f5e
|
||||
RNLocalize: 4f5e4a46d2bccd04ccb96721e438dcb9de17c2e0
|
||||
RNReactNativeHapticFeedback: e526ac4a7ca9fb23c7843ea4fd7d823166054c73
|
||||
RNScreens: 806e1449a8ec63c2a4e4cf8a63cc80203ccda9b8
|
||||
RNSentry: 6ad982be2c8e32dab912afb4132b6a0d88484ea0
|
||||
RNSVG: e1cf5a9a5aa12c69f2ec47031defbd87ae7fb697
|
||||
RNSVG: 39476f26bbbe72ffe6194c6fc8f6acd588087957
|
||||
segment-analytics-react-native: a0c29c75ede1989118b50cac96b9495ea5c91a1d
|
||||
Sentry: 59993bffde4a1ac297ba6d268dc4bbce068d7c1b
|
||||
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
|
||||
|
||||
@@ -3,8 +3,18 @@
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
module.exports = {
|
||||
preset: 'react-native',
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'cjs', 'json', 'node'],
|
||||
moduleFileExtensions: [
|
||||
'ios.js',
|
||||
'android.js',
|
||||
'native.js',
|
||||
'ts',
|
||||
'tsx',
|
||||
'js',
|
||||
'jsx',
|
||||
'cjs',
|
||||
'json',
|
||||
'node',
|
||||
],
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(react-native|@react-native|@react-navigation|@react-native-community|@segment/analytics-react-native|@openpassport|react-native-keychain|react-native-check-version|react-native-nfc-manager|react-native-passport-reader|react-native-gesture-handler|uuid|@stablelib|@react-native-google-signin|react-native-cloud-storage|@react-native-clipboard|@react-native-firebase|@selfxyz|@sentry|@anon-aadhaar|react-native-svg|react-native-svg-circle-country-flags)/)',
|
||||
],
|
||||
@@ -16,6 +26,7 @@ module.exports = {
|
||||
testPathIgnorePatterns: [
|
||||
'/node_modules/',
|
||||
'/scripts/tests/', // Node.js native test runner tests
|
||||
'/babel\\.config\\.test\\.cjs',
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'^@env$': '<rootDir>/tests/__setup__/@env.js',
|
||||
@@ -50,7 +61,7 @@ module.exports = {
|
||||
'<rootDir>/../common/node_modules/@anon-aadhaar/core/dist/index.js',
|
||||
},
|
||||
transform: {
|
||||
'\\.[jt]sx?$': 'babel-jest',
|
||||
'\\.[jt]sx?$': ['babel-jest', { configFile: './babel.config.test.cjs' }],
|
||||
},
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
|
||||
@@ -282,17 +282,13 @@ jest.mock(
|
||||
|
||||
// Mock react-native-gesture-handler to prevent getConstants errors
|
||||
jest.mock('react-native-gesture-handler', () => {
|
||||
const React = require('react');
|
||||
// Avoid requiring React to prevent nested require memory issues
|
||||
|
||||
// Mock the components directly without requiring react-native
|
||||
// to avoid triggering hermes-parser WASM errors
|
||||
const MockScrollView = props =>
|
||||
React.createElement('ScrollView', props, props.children);
|
||||
const MockTouchableOpacity = props =>
|
||||
React.createElement('TouchableOpacity', props, props.children);
|
||||
const MockTouchableHighlight = props =>
|
||||
React.createElement('TouchableHighlight', props, props.children);
|
||||
const MockFlatList = props => React.createElement('FlatList', props);
|
||||
// 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 {
|
||||
...jest.requireActual('react-native-gesture-handler/jestSetup'),
|
||||
@@ -306,13 +302,11 @@ jest.mock('react-native-gesture-handler', () => {
|
||||
|
||||
// Mock react-native-safe-area-context
|
||||
jest.mock('react-native-safe-area-context', () => {
|
||||
const React = require('react');
|
||||
// Use React.createElement directly instead of requiring react-native to avoid memory issues
|
||||
// Avoid requiring React to prevent nested require memory issues
|
||||
return {
|
||||
__esModule: true,
|
||||
SafeAreaProvider: ({ children }) =>
|
||||
React.createElement('View', null, children),
|
||||
SafeAreaView: ({ children }) => React.createElement('View', null, children),
|
||||
SafeAreaProvider: jest.fn(({ children }) => children || null),
|
||||
SafeAreaView: jest.fn(({ children }) => children || null),
|
||||
useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
|
||||
};
|
||||
});
|
||||
@@ -903,53 +897,106 @@ jest.mock('react-native-localize', () => ({
|
||||
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/utils/notifications/notificationService', () =>
|
||||
require('./tests/__setup__/notificationServiceMock.js'),
|
||||
);
|
||||
|
||||
// Mock react-native-svg
|
||||
jest.mock('react-native-svg', () => {
|
||||
const React = require('react');
|
||||
// Avoid requiring React to prevent nested require memory issues
|
||||
|
||||
// Mock SvgXml component that handles XML strings
|
||||
const SvgXml = React.forwardRef(
|
||||
({ xml, width, height, style, ...props }, ref) => {
|
||||
return React.createElement('div', {
|
||||
ref,
|
||||
style: {
|
||||
width: width || 'auto',
|
||||
height: height || 'auto',
|
||||
display: 'inline-block',
|
||||
...style,
|
||||
},
|
||||
dangerouslySetInnerHTML: { __html: xml },
|
||||
...props,
|
||||
});
|
||||
},
|
||||
);
|
||||
const SvgXml = jest.fn(() => null);
|
||||
SvgXml.displayName = 'SvgXml';
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
default: SvgXml,
|
||||
SvgXml,
|
||||
Svg: props => React.createElement('Svg', props, props.children),
|
||||
Circle: props => React.createElement('Circle', props, props.children),
|
||||
Path: props => React.createElement('Path', props, props.children),
|
||||
G: props => React.createElement('G', props, props.children),
|
||||
Rect: props => React.createElement('Rect', props, props.children),
|
||||
Defs: props => React.createElement('Defs', props, props.children),
|
||||
LinearGradient: props =>
|
||||
React.createElement('LinearGradient', props, props.children),
|
||||
Stop: props => React.createElement('Stop', props, props.children),
|
||||
ClipPath: props => React.createElement('ClipPath', props, props.children),
|
||||
Polygon: props => React.createElement('Polygon', props, props.children),
|
||||
Polyline: props => React.createElement('Polyline', props, props.children),
|
||||
Line: props => React.createElement('Line', props, props.children),
|
||||
Text: props => React.createElement('Text', props, props.children),
|
||||
TSpan: props => React.createElement('TSpan', props, props.children),
|
||||
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),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -998,12 +1045,11 @@ jest.mock('@react-navigation/core', () => {
|
||||
});
|
||||
|
||||
// Mock react-native-webview globally to avoid ESM parsing and native behaviors
|
||||
// Note: Individual test files can override this with their own more specific mocks
|
||||
jest.mock('react-native-webview', () => {
|
||||
const React = require('react');
|
||||
// Use React.createElement directly instead of requiring react-native to avoid memory issues
|
||||
const MockWebView = React.forwardRef((props, ref) => {
|
||||
return React.createElement('View', { ref, testID: 'webview', ...props });
|
||||
});
|
||||
// Avoid requiring React to prevent nested require memory issues
|
||||
// Return a simple pass-through mock - tests can override with JSX mocks if needed
|
||||
const MockWebView = jest.fn(() => null);
|
||||
MockWebView.displayName = 'MockWebView';
|
||||
return {
|
||||
__esModule: true,
|
||||
@@ -1014,15 +1060,12 @@ jest.mock('react-native-webview', () => {
|
||||
|
||||
// Mock ExpandableBottomLayout to simple containers to avoid SDK internals in tests
|
||||
jest.mock('@/layouts/ExpandableBottomLayout', () => {
|
||||
const React = require('react');
|
||||
// Use React.createElement directly instead of requiring react-native to avoid memory issues
|
||||
const Layout = ({ children }) => React.createElement('View', null, children);
|
||||
const TopSection = ({ children }) =>
|
||||
React.createElement('View', null, children);
|
||||
const BottomSection = ({ children }) =>
|
||||
React.createElement('View', null, children);
|
||||
const FullSection = ({ children }) =>
|
||||
React.createElement('View', null, children);
|
||||
// Avoid requiring React to prevent nested require memory issues
|
||||
// These need to pass through children so WebView is rendered
|
||||
const Layout = ({ children, ...props }) => children;
|
||||
const TopSection = ({ children, ...props }) => children;
|
||||
const BottomSection = ({ children, ...props }) => children;
|
||||
const FullSection = ({ children, ...props }) => children;
|
||||
return {
|
||||
__esModule: true,
|
||||
ExpandableBottomLayout: { Layout, TopSection, BottomSection, FullSection },
|
||||
@@ -1031,21 +1074,26 @@ jest.mock('@/layouts/ExpandableBottomLayout', () => {
|
||||
|
||||
// Mock mobile-sdk-alpha components used by NavBar (Button, XStack)
|
||||
jest.mock('@selfxyz/mobile-sdk-alpha/components', () => {
|
||||
const React = require('react');
|
||||
// Use React.createElement directly instead of requiring react-native to avoid memory issues
|
||||
const Button = ({ children, onPress, icon, ...props }) =>
|
||||
React.createElement(
|
||||
'TouchableOpacity',
|
||||
{ onPress, ...props, testID: 'msdk-button' },
|
||||
icon
|
||||
? React.createElement('View', { testID: 'msdk-button-icon' }, icon)
|
||||
: null,
|
||||
children,
|
||||
// Avoid requiring React to prevent nested require memory issues
|
||||
// Create mock components that work with React testing library
|
||||
// Button needs to render a host element with onPress so tests can interact with it
|
||||
const Button = jest.fn(({ testID, icon, onPress, children, ...props }) => {
|
||||
// Render as a mock-touchable-opacity host element so fireEvent.press works
|
||||
// This allows tests to query by testID and press the button
|
||||
return (
|
||||
<mock-touchable-opacity testID={testID} onPress={onPress} {...props}>
|
||||
{icon || children || null}
|
||||
</mock-touchable-opacity>
|
||||
);
|
||||
const XStack = ({ children, ...props }) =>
|
||||
React.createElement('View', { ...props, testID: 'msdk-xstack' }, children);
|
||||
const Text = ({ children, ...props }) =>
|
||||
React.createElement('Text', { ...props }, children);
|
||||
});
|
||||
Button.displayName = 'MockButton';
|
||||
|
||||
const XStack = jest.fn(({ children, ...props }) => children || null);
|
||||
XStack.displayName = 'MockXStack';
|
||||
|
||||
const Text = jest.fn(({ children, ...props }) => children || null);
|
||||
Text.displayName = 'MockText';
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
Button,
|
||||
@@ -1055,18 +1103,110 @@ jest.mock('@selfxyz/mobile-sdk-alpha/components', () => {
|
||||
};
|
||||
});
|
||||
|
||||
// Mock Tamagui to avoid hermes-parser WASM memory issues during transformation
|
||||
jest.mock('tamagui', () => {
|
||||
// Avoid requiring React to prevent nested require memory issues
|
||||
// Create mock components that work with React testing library
|
||||
|
||||
// Helper to create a simple pass-through mock component
|
||||
const createMockComponent = displayName => {
|
||||
const Component = jest.fn(props => props.children || null);
|
||||
Component.displayName = displayName;
|
||||
return Component;
|
||||
};
|
||||
|
||||
// Mock styled function - simplified version that returns the component
|
||||
const styled = jest.fn(Component => Component);
|
||||
|
||||
// Create all Tamagui component mocks
|
||||
const Button = createMockComponent('MockButton');
|
||||
const XStack = createMockComponent('MockXStack');
|
||||
const YStack = createMockComponent('MockYStack');
|
||||
const ZStack = createMockComponent('MockZStack');
|
||||
const Text = createMockComponent('MockText');
|
||||
const View = createMockComponent('MockView');
|
||||
const ScrollView = createMockComponent('MockScrollView');
|
||||
const Spinner = createMockComponent('MockSpinner');
|
||||
const Image = createMockComponent('MockImage');
|
||||
const Card = createMockComponent('MockCard');
|
||||
const Separator = createMockComponent('MockSeparator');
|
||||
const TextArea = createMockComponent('MockTextArea');
|
||||
const Input = createMockComponent('MockInput');
|
||||
const Anchor = createMockComponent('MockAnchor');
|
||||
|
||||
// Mock Select component with nested components
|
||||
const Select = Object.assign(createMockComponent('MockSelect'), {
|
||||
Trigger: createMockComponent('MockSelectTrigger'),
|
||||
Value: createMockComponent('MockSelectValue'),
|
||||
Content: createMockComponent('MockSelectContent'),
|
||||
Item: createMockComponent('MockSelectItem'),
|
||||
Group: createMockComponent('MockSelectGroup'),
|
||||
Label: createMockComponent('MockSelectLabel'),
|
||||
Viewport: createMockComponent('MockSelectViewport'),
|
||||
ScrollUpButton: createMockComponent('MockSelectScrollUpButton'),
|
||||
ScrollDownButton: createMockComponent('MockSelectScrollDownButton'),
|
||||
});
|
||||
|
||||
// Mock Sheet component with nested components
|
||||
const Sheet = Object.assign(createMockComponent('MockSheet'), {
|
||||
Frame: createMockComponent('MockSheetFrame'),
|
||||
Overlay: createMockComponent('MockSheetOverlay'),
|
||||
Handle: createMockComponent('MockSheetHandle'),
|
||||
ScrollView: createMockComponent('MockSheetScrollView'),
|
||||
});
|
||||
|
||||
// Mock Adapt component
|
||||
const Adapt = createMockComponent('MockAdapt');
|
||||
|
||||
// Mock TamaguiProvider - simple pass-through that renders children
|
||||
const TamaguiProvider = jest.fn(({ children }) => children || null);
|
||||
TamaguiProvider.displayName = 'MockTamaguiProvider';
|
||||
|
||||
// Mock configuration factory functions
|
||||
const createFont = jest.fn(() => ({}));
|
||||
const createTamagui = jest.fn(() => ({}));
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
styled,
|
||||
Button,
|
||||
XStack,
|
||||
YStack,
|
||||
ZStack,
|
||||
Text,
|
||||
View,
|
||||
ScrollView,
|
||||
Spinner,
|
||||
Image,
|
||||
Card,
|
||||
Separator,
|
||||
TextArea,
|
||||
Input,
|
||||
Anchor,
|
||||
Select,
|
||||
Sheet,
|
||||
Adapt,
|
||||
TamaguiProvider,
|
||||
createFont,
|
||||
createTamagui,
|
||||
// Provide default exports for other common components
|
||||
default: jest.fn(() => null),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock Tamagui lucide icons to simple components to avoid theme context
|
||||
jest.mock('@tamagui/lucide-icons', () => {
|
||||
const React = require('react');
|
||||
// Use React.createElement directly instead of requiring react-native to avoid memory issues
|
||||
// Avoid requiring React to prevent nested require memory issues
|
||||
// Return mock components that can be queried by testID
|
||||
const makeIcon = name => {
|
||||
const Icon = ({ size, color, opacity }) =>
|
||||
React.createElement('View', {
|
||||
testID: `icon-${name}`,
|
||||
size,
|
||||
color,
|
||||
opacity,
|
||||
});
|
||||
// Use a mock element tag that React can render
|
||||
const Icon = props => ({
|
||||
$$typeof: Symbol.for('react.element'),
|
||||
type: `mock-icon-${name}`,
|
||||
props: { testID: `icon-${name}`, ...props },
|
||||
key: null,
|
||||
ref: null,
|
||||
});
|
||||
Icon.displayName = `MockIcon(${name})`;
|
||||
return Icon;
|
||||
};
|
||||
@@ -1074,14 +1214,13 @@ jest.mock('@tamagui/lucide-icons', () => {
|
||||
__esModule: true,
|
||||
ExternalLink: makeIcon('external-link'),
|
||||
X: makeIcon('x'),
|
||||
Clipboard: makeIcon('clipboard'),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock WebViewFooter to avoid SDK rendering complexity
|
||||
jest.mock('@/components/WebViewFooter', () => {
|
||||
const React = require('react');
|
||||
// Use React.createElement directly instead of requiring react-native to avoid memory issues
|
||||
const WebViewFooter = () =>
|
||||
React.createElement('View', { testID: 'webview-footer' });
|
||||
// Avoid requiring React to prevent nested require memory issues
|
||||
const WebViewFooter = jest.fn(() => null);
|
||||
return { __esModule: true, WebViewFooter };
|
||||
});
|
||||
|
||||
@@ -56,17 +56,18 @@
|
||||
"sync-versions": "bundle exec fastlane ios sync_version && bundle exec fastlane android sync_version",
|
||||
"tag:release": "node scripts/tag.cjs release",
|
||||
"tag:remove": "node scripts/tag.cjs remove",
|
||||
"test": "yarn build:deps && jest --passWithNoTests && node --test scripts/tests/*.cjs",
|
||||
"test": "yarn build:deps && yarn jest:run --passWithNoTests && node --test scripts/tests/*.cjs",
|
||||
"test:build": "yarn build:deps && yarn types && node ./scripts/bundle-analyze-ci.cjs ios && yarn test",
|
||||
"test:ci": "jest --passWithNoTests && node --test scripts/tests/*.cjs",
|
||||
"test:coverage": "jest --coverage --passWithNoTests",
|
||||
"test:coverage:ci": "jest --coverage --passWithNoTests --ci --coverageReporters=lcov --coverageReporters=text --coverageReporters=json",
|
||||
"test:ci": "yarn jest:run --passWithNoTests && node --test scripts/tests/*.cjs",
|
||||
"test:coverage": "yarn jest:run --coverage --passWithNoTests",
|
||||
"test:coverage:ci": "yarn jest:run --coverage --passWithNoTests --ci --coverageReporters=lcov --coverageReporters=text --coverageReporters=json",
|
||||
"test:e2e:android": "./scripts/mobile-ci-build-android.sh && maestro test tests/e2e/launch.android.flow.yaml",
|
||||
"test:e2e:ios": "xcodebuild -workspace ios/OpenPassport.xcworkspace -scheme OpenPassport -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build && maestro test tests/e2e/launch.ios.flow.yaml",
|
||||
"test:fastlane": "bundle exec ruby -Itest fastlane/test/helpers_test.rb",
|
||||
"test:tree-shaking": "node ./scripts/test-tree-shaking.cjs",
|
||||
"test:web-build": "jest tests/web-build-render.test.ts --testTimeout=180000",
|
||||
"test:web-build": "yarn jest:run tests/web-build-render.test.ts --testTimeout=180000",
|
||||
"types": "tsc --noEmit",
|
||||
"jest:run": "node ./node_modules/jest/bin/jest.js",
|
||||
"watch:sdk": "yarn workspace @selfxyz/mobile-sdk-alpha watch",
|
||||
"web": "vite",
|
||||
"web:build": "yarn build:deps && vite build",
|
||||
@@ -168,9 +169,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.28.3",
|
||||
"@babel/plugin-syntax-flow": "^7.27.1",
|
||||
"@babel/plugin-transform-classes": "^7.27.1",
|
||||
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
|
||||
"@babel/plugin-transform-flow-strip-types": "^7.27.1",
|
||||
"@babel/plugin-transform-private-methods": "^7.27.1",
|
||||
"@babel/preset-env": "^7.28.3",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@react-native-community/cli": "^16.0.3",
|
||||
"@react-native/babel-preset": "0.76.9",
|
||||
"@react-native/eslint-config": "0.76.9",
|
||||
|
||||
@@ -120,6 +120,11 @@ const notifyDocumentChange = (isMock: boolean) => {
|
||||
// Global flag to track if native modules are ready
|
||||
let nativeModulesReady = false;
|
||||
|
||||
// Test-only helper so unit tests can reset module-level state without re-importing
|
||||
export function __resetPassportProviderTestState() {
|
||||
nativeModulesReady = false;
|
||||
}
|
||||
|
||||
export const PassportContext = createContext<IPassportContext>({
|
||||
getData: () => Promise.resolve(null),
|
||||
getSelectedData: () => Promise.resolve(null),
|
||||
|
||||
@@ -266,9 +266,13 @@ const handleResponseAndroid = (response: AndroidScanResponse): PassportData => {
|
||||
|
||||
const dgHashesObj = JSON.parse(dataGroupHashes);
|
||||
const dg1HashString = dgHashesObj['1'];
|
||||
const dg1Hash = Array.from(Buffer.from(dg1HashString, 'hex'));
|
||||
const dg1Hash = dg1HashString
|
||||
? Array.from(Buffer.from(dg1HashString, 'hex'))
|
||||
: [];
|
||||
const dg2HashString = dgHashesObj['2'];
|
||||
const dg2Hash = Array.from(Buffer.from(dg2HashString, 'hex'));
|
||||
const dg2Hash = dg2HashString
|
||||
? Array.from(Buffer.from(dg2HashString, 'hex'))
|
||||
: [];
|
||||
const pem =
|
||||
'-----BEGIN CERTIFICATE-----' +
|
||||
documentSigningCertificate +
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import { Text } from 'react-native';
|
||||
import type { ReactNode } from 'react';
|
||||
import { render } from '@testing-library/react-native';
|
||||
|
||||
import ErrorBoundary from '@/components/ErrorBoundary';
|
||||
@@ -18,11 +18,19 @@ jest.mock('@/Sentry', () => ({
|
||||
captureException: jest.fn(),
|
||||
}));
|
||||
|
||||
const MockText = ({
|
||||
children,
|
||||
testID,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
testID?: string;
|
||||
}) => <mock-text testID={testID}>{children}</mock-text>;
|
||||
|
||||
const ProblemChild = () => {
|
||||
throw new Error('boom');
|
||||
};
|
||||
|
||||
const GoodChild = () => <Text>Good child</Text>;
|
||||
const GoodChild = () => <MockText testID="good-child">Good child</MockText>;
|
||||
|
||||
describe('ErrorBoundary', () => {
|
||||
beforeEach(() => {
|
||||
@@ -87,13 +95,13 @@ describe('ErrorBoundary', () => {
|
||||
});
|
||||
|
||||
it('renders children normally when no error occurs', () => {
|
||||
const { getByText } = render(
|
||||
const { getByTestId } = render(
|
||||
<ErrorBoundary>
|
||||
<GoodChild />
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
|
||||
expect(getByText('Good child')).toBeTruthy();
|
||||
expect(getByTestId('good-child')).toHaveTextContent('Good child');
|
||||
});
|
||||
|
||||
it('captures error details correctly', () => {
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { render, screen } from '@testing-library/react-native';
|
||||
|
||||
import { LoggerProvider, useLogger } from '@/providers/loggerProvider';
|
||||
import { AppLogger, NfcLogger } from '@/utils/logger';
|
||||
|
||||
// Mock the native logger bridge
|
||||
jest.mock('@/utils/logger/nativeLoggerBridge', () => ({
|
||||
@@ -97,6 +98,14 @@ jest.mock('@/utils/logger', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const MockText = ({
|
||||
children,
|
||||
testID,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
testID?: string;
|
||||
}) => <mock-text testID={testID}>{children}</mock-text>;
|
||||
|
||||
// Test component that uses the logger
|
||||
const TestComponent = () => {
|
||||
const loggers = useLogger();
|
||||
@@ -108,9 +117,9 @@ const TestComponent = () => {
|
||||
}, [loggers]);
|
||||
|
||||
return (
|
||||
<Text testID="test-component">
|
||||
<MockText testID="test-component">
|
||||
Test Component - AppLogger Level: {loggers.logLevels.info}
|
||||
</Text>
|
||||
</MockText>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -128,12 +137,11 @@ describe('LoggerProvider', () => {
|
||||
|
||||
// Verify the component renders without errors and shows context values
|
||||
expect(screen.getByTestId('test-component')).toBeTruthy();
|
||||
expect(
|
||||
screen.getByText('Test Component - AppLogger Level: 1'),
|
||||
).toBeTruthy();
|
||||
expect(screen.getByTestId('test-component')).toHaveTextContent(
|
||||
/Test Component - AppLogger Level:\s*1/,
|
||||
);
|
||||
|
||||
// Verify that logger methods were called with expected arguments
|
||||
const { AppLogger, NfcLogger } = require('@/utils/logger');
|
||||
expect(AppLogger.info).toHaveBeenCalledWith('Test message');
|
||||
expect(NfcLogger.debug).toHaveBeenCalledWith('NFC test');
|
||||
});
|
||||
@@ -141,11 +149,13 @@ describe('LoggerProvider', () => {
|
||||
it('should initialize and allow loggers to be called', () => {
|
||||
render(
|
||||
<LoggerProvider>
|
||||
<Text>Test</Text>
|
||||
<MockText testID="logger-provider-text">Test</MockText>
|
||||
</LoggerProvider>,
|
||||
);
|
||||
// The TestComponent is rendered in other tests; here we just assert provider renders without errors
|
||||
expect(screen.getByText('Test')).toBeTruthy();
|
||||
expect(screen.getByTestId('logger-provider-text')).toHaveTextContent(
|
||||
'Test',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when useLogger is used outside LoggerProvider', () => {
|
||||
@@ -165,12 +175,14 @@ describe('LoggerProvider', () => {
|
||||
// The nativeLoggerBridge import should be called when LoggerProvider is rendered
|
||||
render(
|
||||
<LoggerProvider>
|
||||
<Text>Test</Text>
|
||||
<MockText testID="logger-provider-text">Test</MockText>
|
||||
</LoggerProvider>,
|
||||
);
|
||||
|
||||
// Verify that the LoggerProvider renders without errors
|
||||
expect(screen.getByText('Test')).toBeTruthy();
|
||||
expect(screen.getByTestId('logger-provider-text')).toHaveTextContent(
|
||||
'Test',
|
||||
);
|
||||
});
|
||||
|
||||
it('should provide logLevels constant', () => {
|
||||
|
||||
@@ -2,30 +2,41 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { render, waitFor } from '@testing-library/react-native';
|
||||
|
||||
import { SelfClientProvider } from '@selfxyz/mobile-sdk-alpha';
|
||||
|
||||
// Import after mocking
|
||||
import {
|
||||
__resetPassportProviderTestState,
|
||||
initializeNativeModules,
|
||||
loadDocumentCatalogDirectlyFromKeychain,
|
||||
migrateFromLegacyStorage,
|
||||
PassportProvider,
|
||||
usePassport,
|
||||
} from '@/providers/passportDataProvider';
|
||||
|
||||
import { mockAdapters } from '../../utils/selfClientProvider';
|
||||
|
||||
// Mock react-native-keychain before importing the module
|
||||
const mockKeychain = {
|
||||
getGenericPassword: jest.fn(),
|
||||
setGenericPassword: jest.fn(),
|
||||
resetGenericPassword: jest.fn(),
|
||||
};
|
||||
|
||||
const listeners = new Map();
|
||||
|
||||
jest.mock('react-native-keychain', () => mockKeychain);
|
||||
jest.mock('react-native-keychain', () => {
|
||||
const mockKeychain = {
|
||||
getGenericPassword: jest.fn(),
|
||||
setGenericPassword: jest.fn(),
|
||||
resetGenericPassword: jest.fn(),
|
||||
};
|
||||
|
||||
return mockKeychain;
|
||||
});
|
||||
|
||||
const mockKeychain = jest.requireMock('react-native-keychain') as {
|
||||
getGenericPassword: jest.Mock;
|
||||
setGenericPassword: jest.Mock;
|
||||
resetGenericPassword: jest.Mock;
|
||||
};
|
||||
|
||||
// Mock the auth provider
|
||||
const mockAuthProvider = {
|
||||
@@ -36,6 +47,14 @@ jest.mock('@/providers/authProvider', () => ({
|
||||
useAuth: () => mockAuthProvider,
|
||||
}));
|
||||
|
||||
const MockText = ({
|
||||
children,
|
||||
testID,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
testID?: string;
|
||||
}) => <mock-text testID={testID}>{children}</mock-text>;
|
||||
|
||||
// Test component that uses the passport hook and extracts context values
|
||||
const TestComponent = () => {
|
||||
const passportContext = usePassport();
|
||||
@@ -53,15 +72,17 @@ const TestComponent = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text testID="context-functions-count">
|
||||
<MockText testID="context-functions-count">
|
||||
{contextValues.length} functions available
|
||||
</Text>
|
||||
<Text testID="context-functions-list">{contextValues.join(',')}</Text>
|
||||
<Text testID="getData-available">getData available</Text>
|
||||
<Text testID="setData-available">setData available</Text>
|
||||
<Text testID="loadDocumentCatalog-available">
|
||||
</MockText>
|
||||
<MockText testID="context-functions-list">
|
||||
{contextValues.join(',')}
|
||||
</MockText>
|
||||
<MockText testID="getData-available">getData available</MockText>
|
||||
<MockText testID="setData-available">setData available</MockText>
|
||||
<MockText testID="loadDocumentCatalog-available">
|
||||
loadDocumentCatalog available
|
||||
</Text>
|
||||
</MockText>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -73,20 +94,20 @@ const MultipleConsumersTest = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text testID="consumer1-functions">
|
||||
<MockText testID="consumer1-functions">
|
||||
{
|
||||
Object.keys(context1).filter(
|
||||
key => typeof context1[key as keyof typeof context1] === 'function',
|
||||
).length
|
||||
}
|
||||
</Text>
|
||||
<Text testID="consumer2-functions">
|
||||
</MockText>
|
||||
<MockText testID="consumer2-functions">
|
||||
{
|
||||
Object.keys(context2).filter(
|
||||
key => typeof context2[key as keyof typeof context2] === 'function',
|
||||
).length
|
||||
}
|
||||
</Text>
|
||||
</MockText>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -103,7 +124,9 @@ const ErrorBoundaryTest = () => {
|
||||
}
|
||||
};
|
||||
|
||||
return <Text testID="error-test-result">{testContextFunction()}</Text>;
|
||||
return (
|
||||
<MockText testID="error-test-result">{testContextFunction()}</MockText>
|
||||
);
|
||||
};
|
||||
|
||||
// Component to test context updates
|
||||
@@ -118,7 +141,7 @@ const ContextUpdateTest = () => {
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return <Text testID="update-count">{updateCount}</Text>;
|
||||
return <MockText testID="update-count">{updateCount}</MockText>;
|
||||
};
|
||||
|
||||
describe('PassportDataProvider', () => {
|
||||
@@ -126,6 +149,7 @@ describe('PassportDataProvider', () => {
|
||||
jest.clearAllMocks();
|
||||
console.log = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
__resetPassportProviderTestState();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -327,16 +351,9 @@ describe('PassportDataProvider', () => {
|
||||
});
|
||||
|
||||
describe('initializeNativeModules', () => {
|
||||
let initializeNativeModulesLocal: any;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
// Reset module state for each test by re-importing
|
||||
jest.resetModules();
|
||||
jest.doMock('react-native-keychain', () => mockKeychain);
|
||||
|
||||
const passportModule = require('@/providers/passportDataProvider');
|
||||
initializeNativeModulesLocal = passportModule.initializeNativeModules;
|
||||
__resetPassportProviderTestState();
|
||||
});
|
||||
|
||||
it('should return true immediately if native modules are already ready', async () => {
|
||||
@@ -346,7 +363,7 @@ describe('PassportDataProvider', () => {
|
||||
});
|
||||
|
||||
// First call should initialize
|
||||
const firstResult = await initializeNativeModulesLocal();
|
||||
const firstResult = await initializeNativeModules();
|
||||
expect(firstResult).toBe(true);
|
||||
expect(mockKeychain.getGenericPassword).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -354,7 +371,7 @@ describe('PassportDataProvider', () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Subsequent calls should return immediately without hitting keychain
|
||||
const secondResult = await initializeNativeModulesLocal();
|
||||
const secondResult = await initializeNativeModules();
|
||||
expect(secondResult).toBe(true);
|
||||
expect(mockKeychain.getGenericPassword).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -370,7 +387,7 @@ describe('PassportDataProvider', () => {
|
||||
.mockRejectedValueOnce(moduleError)
|
||||
.mockResolvedValue({ password: 'test' });
|
||||
|
||||
const result = await initializeNativeModulesLocal(3, 10); // 3 retries, 10ms delay
|
||||
const result = await initializeNativeModules(3, 10); // 3 retries, 10ms delay
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockKeychain.getGenericPassword).toHaveBeenCalledTimes(3);
|
||||
@@ -385,7 +402,7 @@ describe('PassportDataProvider', () => {
|
||||
.fn()
|
||||
.mockRejectedValue(moduleError);
|
||||
|
||||
const result = await initializeNativeModulesLocal(2, 10); // 2 retries, 10ms delay
|
||||
const result = await initializeNativeModules(2, 10); // 2 retries, 10ms delay
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(mockKeychain.getGenericPassword).toHaveBeenCalledTimes(2);
|
||||
@@ -396,7 +413,7 @@ describe('PassportDataProvider', () => {
|
||||
const otherError = new Error('Service not found');
|
||||
mockKeychain.getGenericPassword = jest.fn().mockRejectedValue(otherError);
|
||||
|
||||
const result = await initializeNativeModulesLocal();
|
||||
const result = await initializeNativeModules();
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockKeychain.getGenericPassword).toHaveBeenCalledTimes(1);
|
||||
@@ -404,20 +421,12 @@ describe('PassportDataProvider', () => {
|
||||
});
|
||||
|
||||
describe('migrateFromLegacyStorage', () => {
|
||||
let migrateFromLegacyStorageLocal: any;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.resetModules();
|
||||
jest.doMock('react-native-keychain', () => mockKeychain);
|
||||
|
||||
const passportModule = require('@/providers/passportDataProvider');
|
||||
migrateFromLegacyStorageLocal = passportModule.migrateFromLegacyStorage;
|
||||
__resetPassportProviderTestState();
|
||||
});
|
||||
|
||||
it('should skip migration if catalog already has documents', async () => {
|
||||
// First initialize native modules to set the flag
|
||||
const passportModule = require('@/providers/passportDataProvider');
|
||||
mockKeychain.getGenericPassword = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ password: 'test' }) // For initializeNativeModules
|
||||
@@ -426,11 +435,11 @@ describe('PassportDataProvider', () => {
|
||||
}); // For loadDocumentCatalog
|
||||
|
||||
// Initialize native modules first
|
||||
await passportModule.initializeNativeModules();
|
||||
await initializeNativeModules();
|
||||
|
||||
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
|
||||
await migrateFromLegacyStorageLocal();
|
||||
await migrateFromLegacyStorage();
|
||||
|
||||
// Should log that migration is already completed
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Migration already completed');
|
||||
@@ -439,8 +448,6 @@ describe('PassportDataProvider', () => {
|
||||
});
|
||||
|
||||
it('should migrate legacy documents when catalog is empty', async () => {
|
||||
// First initialize native modules to set the flag
|
||||
const passportModule = require('@/providers/passportDataProvider');
|
||||
mockKeychain.getGenericPassword = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ password: 'test' }) // For initializeNativeModules
|
||||
@@ -453,11 +460,11 @@ describe('PassportDataProvider', () => {
|
||||
.mockResolvedValue(false); // No more legacy documents
|
||||
|
||||
// Initialize native modules first
|
||||
await passportModule.initializeNativeModules();
|
||||
await initializeNativeModules();
|
||||
|
||||
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
|
||||
await migrateFromLegacyStorageLocal();
|
||||
await migrateFromLegacyStorage();
|
||||
|
||||
// Should log migration start and completion
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
@@ -469,8 +476,6 @@ describe('PassportDataProvider', () => {
|
||||
});
|
||||
|
||||
it('should handle errors during migration gracefully', async () => {
|
||||
// First initialize native modules to set the flag
|
||||
const passportModule = require('@/providers/passportDataProvider');
|
||||
mockKeychain.getGenericPassword = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ password: 'test' }) // For initializeNativeModules
|
||||
@@ -480,11 +485,11 @@ describe('PassportDataProvider', () => {
|
||||
.mockRejectedValue(new Error('Keychain error')); // Error on legacy service
|
||||
|
||||
// Initialize native modules first
|
||||
await passportModule.initializeNativeModules();
|
||||
await initializeNativeModules();
|
||||
|
||||
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
|
||||
await migrateFromLegacyStorageLocal();
|
||||
await migrateFromLegacyStorage();
|
||||
|
||||
// Should log error for each service that fails
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
@@ -497,30 +502,22 @@ describe('PassportDataProvider', () => {
|
||||
});
|
||||
|
||||
describe('loadDocumentCatalog', () => {
|
||||
let loadDocumentCatalogLocal: any;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.resetModules();
|
||||
jest.doMock('react-native-keychain', () => mockKeychain);
|
||||
|
||||
const passportModule = require('@/providers/passportDataProvider');
|
||||
loadDocumentCatalogLocal =
|
||||
passportModule.loadDocumentCatalogDirectlyFromKeychain;
|
||||
__resetPassportProviderTestState();
|
||||
});
|
||||
|
||||
it('should return empty catalog when Keychain is undefined', async () => {
|
||||
// Reset module registry to ensure mock takes effect
|
||||
jest.resetModules();
|
||||
// Mock that Keychain is undefined
|
||||
jest.doMock('react-native-keychain', () => undefined);
|
||||
|
||||
// Re-import the module after mocking to ensure mock is applied
|
||||
const passportModule = require('@/providers/passportDataProvider');
|
||||
const loadDocumentCatalogLocalUndefined =
|
||||
passportModule.loadDocumentCatalogDirectlyFromKeychain;
|
||||
|
||||
const result = await loadDocumentCatalogLocalUndefined();
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
jest.isolateModules(() => {
|
||||
jest.doMock('react-native-keychain', () => undefined);
|
||||
const passportModule = require('@/providers/passportDataProvider');
|
||||
passportModule
|
||||
.loadDocumentCatalogDirectlyFromKeychain()
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
});
|
||||
|
||||
expect(result).toEqual({ documents: [] });
|
||||
});
|
||||
@@ -528,7 +525,7 @@ describe('PassportDataProvider', () => {
|
||||
it('should return empty catalog when no catalog exists', async () => {
|
||||
mockKeychain.getGenericPassword = jest.fn().mockResolvedValue(false);
|
||||
|
||||
const result = await loadDocumentCatalogLocal();
|
||||
const result = await loadDocumentCatalogDirectlyFromKeychain();
|
||||
|
||||
expect(result).toEqual({ documents: [] });
|
||||
});
|
||||
@@ -540,7 +537,7 @@ describe('PassportDataProvider', () => {
|
||||
password: JSON.stringify({ documents: [{ id: 'test' }] }),
|
||||
});
|
||||
|
||||
const result = await loadDocumentCatalogLocal();
|
||||
const result = await loadDocumentCatalogDirectlyFromKeychain();
|
||||
|
||||
// The function should return empty catalog due to nativeModulesReady check
|
||||
expect(result).toEqual({ documents: [] });
|
||||
@@ -548,7 +545,6 @@ describe('PassportDataProvider', () => {
|
||||
|
||||
it('should return parsed catalog when it exists and native modules are ready', async () => {
|
||||
// First initialize native modules to set the flag
|
||||
const passportModule = require('@/providers/passportDataProvider');
|
||||
mockKeychain.getGenericPassword = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ password: 'test' }) // For initializeNativeModules
|
||||
@@ -557,17 +553,16 @@ describe('PassportDataProvider', () => {
|
||||
}); // For loadDocumentCatalog
|
||||
|
||||
// Initialize native modules first
|
||||
await passportModule.initializeNativeModules();
|
||||
await initializeNativeModules();
|
||||
|
||||
// Now test loadDocumentCatalog
|
||||
const result = await loadDocumentCatalogLocal();
|
||||
const result = await loadDocumentCatalogDirectlyFromKeychain();
|
||||
|
||||
expect(result).toEqual({ documents: [{ id: 'test' }] });
|
||||
});
|
||||
|
||||
it('should handle malformed JSON and return empty documents array', async () => {
|
||||
// First initialize native modules to set the flag
|
||||
const passportModule = require('@/providers/passportDataProvider');
|
||||
mockKeychain.getGenericPassword = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ password: 'test' }) // For initializeNativeModules
|
||||
@@ -576,11 +571,11 @@ describe('PassportDataProvider', () => {
|
||||
}); // For loadDocumentCatalog
|
||||
|
||||
// Initialize native modules first
|
||||
await passportModule.initializeNativeModules();
|
||||
await initializeNativeModules();
|
||||
|
||||
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
|
||||
const result = await loadDocumentCatalogLocal();
|
||||
const result = await loadDocumentCatalogDirectlyFromKeychain();
|
||||
|
||||
expect(result).toEqual({ documents: [] });
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
@@ -593,7 +588,6 @@ describe('PassportDataProvider', () => {
|
||||
|
||||
it('should handle invalid catalog structure and return the parsed structure', async () => {
|
||||
// First initialize native modules to set the flag
|
||||
const passportModule = require('@/providers/passportDataProvider');
|
||||
mockKeychain.getGenericPassword = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ password: 'test' }) // For initializeNativeModules
|
||||
@@ -602,9 +596,9 @@ describe('PassportDataProvider', () => {
|
||||
}); // For loadDocumentCatalog
|
||||
|
||||
// Initialize native modules first
|
||||
await passportModule.initializeNativeModules();
|
||||
await initializeNativeModules();
|
||||
|
||||
const result = await loadDocumentCatalogLocal();
|
||||
const result = await loadDocumentCatalogDirectlyFromKeychain();
|
||||
|
||||
// The function returns the parsed JSON as-is, even if it doesn't have the expected structure
|
||||
expect(result).toEqual({ invalidField: 'test' });
|
||||
@@ -612,7 +606,6 @@ describe('PassportDataProvider', () => {
|
||||
|
||||
it('should handle JSON parsing exceptions and return empty documents array', async () => {
|
||||
// First initialize native modules to set the flag
|
||||
const passportModule = require('@/providers/passportDataProvider');
|
||||
mockKeychain.getGenericPassword = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ password: 'test' }) // For initializeNativeModules
|
||||
@@ -621,11 +614,11 @@ describe('PassportDataProvider', () => {
|
||||
}); // For loadDocumentCatalog
|
||||
|
||||
// Initialize native modules first
|
||||
await passportModule.initializeNativeModules();
|
||||
await initializeNativeModules();
|
||||
|
||||
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
|
||||
const result = await loadDocumentCatalogLocal();
|
||||
const result = await loadDocumentCatalogDirectlyFromKeychain();
|
||||
|
||||
expect(result).toEqual({ documents: [] });
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
@@ -638,7 +631,6 @@ describe('PassportDataProvider', () => {
|
||||
|
||||
it('should handle null/undefined password and return empty documents array', async () => {
|
||||
// First initialize native modules to set the flag
|
||||
const passportModule = require('@/providers/passportDataProvider');
|
||||
mockKeychain.getGenericPassword = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ password: 'test' }) // For initializeNativeModules
|
||||
@@ -647,18 +639,11 @@ describe('PassportDataProvider', () => {
|
||||
}); // For loadDocumentCatalog
|
||||
|
||||
// Initialize native modules first
|
||||
await passportModule.initializeNativeModules();
|
||||
await initializeNativeModules();
|
||||
|
||||
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
|
||||
console.log('About to call loadDocumentCatalogLocal');
|
||||
const result = await loadDocumentCatalogLocal();
|
||||
console.log('Called loadDocumentCatalogLocal');
|
||||
|
||||
console.log('Actual result:', result);
|
||||
console.log('Result type:', typeof result);
|
||||
console.log('Is null?', result === null);
|
||||
console.log('Function name:', loadDocumentCatalogLocal.name);
|
||||
const result = await loadDocumentCatalogDirectlyFromKeychain();
|
||||
|
||||
// When password is null, JSON.parse(null) throws TypeError, which is caught
|
||||
// and the function returns empty catalog
|
||||
@@ -673,7 +658,6 @@ describe('PassportDataProvider', () => {
|
||||
|
||||
it('should handle empty string password and return empty documents array', async () => {
|
||||
// First initialize native modules to set the flag
|
||||
const passportModule = require('@/providers/passportDataProvider');
|
||||
mockKeychain.getGenericPassword = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ password: 'test' }) // For initializeNativeModules
|
||||
@@ -682,11 +666,11 @@ describe('PassportDataProvider', () => {
|
||||
}); // For loadDocumentCatalog
|
||||
|
||||
// Initialize native modules first
|
||||
await passportModule.initializeNativeModules();
|
||||
await initializeNativeModules();
|
||||
|
||||
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
|
||||
const result = await loadDocumentCatalogLocal();
|
||||
const result = await loadDocumentCatalogDirectlyFromKeychain();
|
||||
|
||||
expect(result).toEqual({ documents: [] });
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import { Text } from 'react-native';
|
||||
import type { ReactNode } from 'react';
|
||||
import { render, waitFor } from '@testing-library/react-native';
|
||||
|
||||
import {
|
||||
@@ -21,12 +21,22 @@ const mockInitRemoteConfig = initRemoteConfig as jest.MockedFunction<
|
||||
>;
|
||||
|
||||
// Test component that uses the hook
|
||||
const MockText = ({
|
||||
children,
|
||||
testID,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
testID: string;
|
||||
}) => <mock-text testID={testID}>{children}</mock-text>;
|
||||
|
||||
const TestComponent = () => {
|
||||
const { isInitialized, error } = useRemoteConfig();
|
||||
return (
|
||||
<>
|
||||
<Text testID="initialized">{isInitialized ? 'true' : 'false'}</Text>
|
||||
<Text testID="error">{error || 'none'}</Text>
|
||||
<MockText testID="initialized">
|
||||
{isInitialized ? 'true' : 'false'}
|
||||
</MockText>
|
||||
<MockText testID="error">{error || 'none'}</MockText>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,9 +6,41 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { renderHook } from '@testing-library/react-native';
|
||||
|
||||
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||
jest.mock('@/images/512w.png', () => 'mock-512w-image');
|
||||
jest.mock('@/images/nfc.png', () => 'mock-nfc-image');
|
||||
jest.mock('react-native-localize', () => {
|
||||
const getLocales = jest.fn(() => [
|
||||
{
|
||||
countryCode: 'US',
|
||||
languageTag: 'en-US',
|
||||
languageCode: 'en',
|
||||
isRTL: false,
|
||||
},
|
||||
]);
|
||||
const getCountry = jest.fn(() => 'US');
|
||||
|
||||
import { SelfClientProvider } from '@/providers/selfClientProvider';
|
||||
return {
|
||||
__esModule: true,
|
||||
default: {
|
||||
getLocales,
|
||||
getCountry,
|
||||
},
|
||||
getLocales,
|
||||
getCountry,
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('@/navigation', () => {
|
||||
const navigationRef = {
|
||||
isReady: jest.fn(() => false),
|
||||
navigate: jest.fn(),
|
||||
};
|
||||
return {
|
||||
__esModule: true,
|
||||
navigationRef,
|
||||
default: navigationRef,
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock(
|
||||
'@selfxyz/mobile-sdk-alpha/onboarding/confirm-identification',
|
||||
@@ -17,6 +49,54 @@ jest.mock(
|
||||
}),
|
||||
);
|
||||
|
||||
jest.mock('@selfxyz/mobile-sdk-alpha', () => {
|
||||
const mockClient = {
|
||||
getSelfAppState: jest.fn(() => ({})),
|
||||
getProtocolState: jest.fn(() => ({})),
|
||||
getDeepLinksState: jest.fn(() => ({})),
|
||||
};
|
||||
const mockSdkProvider = ({ children }: any) => (
|
||||
<mock-sdk-provider>{children}</mock-sdk-provider>
|
||||
);
|
||||
|
||||
const createListenersMap = () => {
|
||||
const map = new Map();
|
||||
return {
|
||||
map,
|
||||
addListener: (event: string, callback: (...args: unknown[]) => void) => {
|
||||
map.set(event, callback);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const SdkEvents = {
|
||||
PROVING_PASSPORT_DATA_NOT_FOUND: 'PROVING_PASSPORT_DATA_NOT_FOUND',
|
||||
PROVING_ACCOUNT_VERIFIED_SUCCESS: 'PROVING_ACCOUNT_VERIFIED_SUCCESS',
|
||||
PROVING_REGISTER_ERROR_OR_FAILURE: 'PROVING_REGISTER_ERROR_OR_FAILURE',
|
||||
PROVING_ACCOUNT_VERIFIED_PENDING: 'PROVING_ACCOUNT_VERIFIED_PENDING',
|
||||
PROVING_ACCOUNT_VERIFIED_FAILURE: 'PROVING_ACCOUNT_VERIFIED_FAILURE',
|
||||
};
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
useSelfClient: jest.fn(() => mockClient),
|
||||
SelfClientProvider: mockSdkProvider,
|
||||
createListenersMap,
|
||||
impactLight: jest.fn(),
|
||||
reactNativeScannerAdapter: {},
|
||||
SdkEvents,
|
||||
webNFCScannerShim: {},
|
||||
};
|
||||
});
|
||||
|
||||
let useSelfClient: () => unknown;
|
||||
let SelfClientProvider: ({ children }: { children: ReactNode }) => JSX.Element;
|
||||
|
||||
beforeAll(() => {
|
||||
({ useSelfClient } = require('@selfxyz/mobile-sdk-alpha'));
|
||||
({ SelfClientProvider } = require('@/providers/selfClientProvider'));
|
||||
});
|
||||
|
||||
describe('SelfClientProvider', () => {
|
||||
it('memoises the client instance', () => {
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
|
||||
@@ -7,6 +7,44 @@ import { render, waitFor } from '@testing-library/react-native';
|
||||
|
||||
import GratificationScreen from '@/screens/app/GratificationScreen';
|
||||
|
||||
jest.mock('react-native', () => {
|
||||
const MockView = ({ children, ...props }: any) => (
|
||||
<mock-view {...props}>{children}</mock-view>
|
||||
);
|
||||
const MockText = ({ children, ...props }: any) => (
|
||||
<mock-text {...props}>{children}</mock-text>
|
||||
);
|
||||
const mockDimensions = {
|
||||
get: jest.fn(() => ({ width: 320, height: 640 })),
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
};
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
Dimensions: mockDimensions,
|
||||
Pressable: ({ onPress, children }: any) => (
|
||||
<button onClick={onPress} type="button">
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
StyleSheet: {
|
||||
create: (styles: any) => styles,
|
||||
flatten: (style: any) => style,
|
||||
},
|
||||
Text: MockText,
|
||||
View: MockView,
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('react-native-edge-to-edge', () => ({
|
||||
SystemBars: () => null,
|
||||
}));
|
||||
|
||||
jest.mock('react-native-safe-area-context', () => ({
|
||||
useSafeAreaInsets: jest.fn(() => ({ top: 0, bottom: 0 })),
|
||||
}));
|
||||
|
||||
jest.mock('@react-navigation/native', () => ({
|
||||
useNavigation: jest.fn(),
|
||||
useRoute: jest.fn(),
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import { Linking } from 'react-native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import {
|
||||
fireEvent,
|
||||
@@ -13,11 +12,69 @@ import {
|
||||
|
||||
import { WebViewScreen } from '@/screens/shared/WebViewScreen';
|
||||
|
||||
jest.mock('react-native', () => {
|
||||
const mockLinking = {
|
||||
canOpenURL: jest.fn(),
|
||||
openURL: jest.fn(),
|
||||
};
|
||||
|
||||
const MockView = ({ children, ...props }: any) => (
|
||||
<mock-view {...props}>{children}</mock-view>
|
||||
);
|
||||
const mockBackHandler = {
|
||||
addEventListener: jest.fn(() => ({ remove: jest.fn() })),
|
||||
removeEventListener: jest.fn(),
|
||||
};
|
||||
|
||||
return {
|
||||
ActivityIndicator: (props: any) => <mock-activity-indicator {...props} />,
|
||||
BackHandler: mockBackHandler,
|
||||
Linking: mockLinking,
|
||||
StyleSheet: {
|
||||
create: (styles: unknown) => styles,
|
||||
flatten: (style: unknown) => style,
|
||||
},
|
||||
View: MockView,
|
||||
};
|
||||
});
|
||||
|
||||
const mockLinking = jest.requireMock('react-native').Linking as jest.Mocked<{
|
||||
canOpenURL: jest.Mock;
|
||||
openURL: jest.Mock;
|
||||
}>;
|
||||
|
||||
jest.mock('@react-navigation/native', () => ({
|
||||
useNavigation: jest.fn(),
|
||||
useFocusEffect: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@/components/NavBar/WebViewNavBar', () => ({
|
||||
WebViewNavBar: ({ children, onBackPress, ...props }: any) => (
|
||||
<mock-webview-navbar {...props}>
|
||||
<mock-pressable testID="icon-x" onPress={onBackPress} />
|
||||
{children}
|
||||
</mock-webview-navbar>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('@/components/WebViewFooter', () => ({
|
||||
WebViewFooter: () => <mock-webview-footer />,
|
||||
}));
|
||||
|
||||
jest.mock('@/layouts/ExpandableBottomLayout', () => ({
|
||||
ExpandableBottomLayout: {
|
||||
Layout: ({ children, ...props }: any) => (
|
||||
<mock-expandable-layout {...props}>{children}</mock-expandable-layout>
|
||||
),
|
||||
TopSection: ({ children, ...props }: any) => (
|
||||
<mock-expandable-top {...props}>{children}</mock-expandable-top>
|
||||
),
|
||||
BottomSection: ({ children, ...props }: any) => (
|
||||
<mock-expandable-bottom {...props}>{children}</mock-expandable-bottom>
|
||||
),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('react-native-webview', () => {
|
||||
// Lightweight host component so React can render while keeping props inspectable
|
||||
const MockWebView = ({ testID = 'webview', ...props }: any) => (
|
||||
@@ -56,6 +113,8 @@ describe('WebViewScreen URL sanitization and navigation interception', () => {
|
||||
canGoBack: () => true,
|
||||
});
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
mockLinking.canOpenURL.mockReset();
|
||||
mockLinking.openURL.mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -67,8 +126,7 @@ describe('WebViewScreen URL sanitization and navigation interception', () => {
|
||||
render(<WebViewScreen {...createProps('https://self.xyz')} />);
|
||||
// The Button component renders with msdk-button testID, find by icon
|
||||
const closeButtonIcon = screen.getByTestId('icon-x');
|
||||
const closeButton = closeButtonIcon.parent?.parent;
|
||||
fireEvent.press(closeButton!);
|
||||
fireEvent.press(closeButtonIcon);
|
||||
expect(mockGoBack).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -107,10 +165,8 @@ describe('WebViewScreen URL sanitization and navigation interception', () => {
|
||||
});
|
||||
|
||||
it('opens allowed external schemes externally and blocks in WebView (mailto, tel)', async () => {
|
||||
jest.spyOn(Linking, 'canOpenURL').mockResolvedValue(true as any);
|
||||
const openSpy = jest
|
||||
.spyOn(Linking, 'openURL')
|
||||
.mockResolvedValue(undefined as any);
|
||||
mockLinking.canOpenURL.mockResolvedValue(true as any);
|
||||
mockLinking.openURL.mockResolvedValue(undefined as any);
|
||||
render(<WebViewScreen {...createProps('https://self.xyz')} />);
|
||||
const webview = screen.getByTestId('webview');
|
||||
|
||||
@@ -119,19 +175,21 @@ describe('WebViewScreen URL sanitization and navigation interception', () => {
|
||||
});
|
||||
expect(resultMailto).toBe(false);
|
||||
await waitFor(() =>
|
||||
expect(openSpy).toHaveBeenCalledWith('mailto:test@example.com'),
|
||||
expect(mockLinking.openURL).toHaveBeenCalledWith(
|
||||
'mailto:test@example.com',
|
||||
),
|
||||
);
|
||||
|
||||
const resultTel = await webview.props.onShouldStartLoadWithRequest?.({
|
||||
url: 'tel:+123456789',
|
||||
});
|
||||
expect(resultTel).toBe(false);
|
||||
await waitFor(() => expect(openSpy).toHaveBeenCalledWith('tel:+123456789'));
|
||||
await waitFor(() =>
|
||||
expect(mockLinking.openURL).toHaveBeenCalledWith('tel:+123456789'),
|
||||
);
|
||||
});
|
||||
|
||||
it('blocks disallowed external schemes and does not attempt to open', async () => {
|
||||
const canOpenSpy = jest.spyOn(Linking, 'canOpenURL');
|
||||
const openSpy = jest.spyOn(Linking, 'openURL');
|
||||
render(<WebViewScreen {...createProps('https://self.xyz')} />);
|
||||
const webview = screen.getByTestId('webview');
|
||||
|
||||
@@ -139,13 +197,13 @@ describe('WebViewScreen URL sanitization and navigation interception', () => {
|
||||
url: 'ftp://example.com',
|
||||
});
|
||||
expect(result).toBe(false);
|
||||
expect(canOpenSpy).not.toHaveBeenCalled();
|
||||
expect(openSpy).not.toHaveBeenCalled();
|
||||
expect(mockLinking.canOpenURL).not.toHaveBeenCalled();
|
||||
expect(mockLinking.openURL).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('scrubs error log wording when external open fails', async () => {
|
||||
jest.spyOn(Linking, 'canOpenURL').mockResolvedValue(true as any);
|
||||
jest.spyOn(Linking, 'openURL').mockRejectedValue(new Error('boom'));
|
||||
mockLinking.canOpenURL.mockResolvedValue(true as any);
|
||||
mockLinking.openURL.mockRejectedValue(new Error('boom'));
|
||||
render(<WebViewScreen {...createProps('https://self.xyz')} />);
|
||||
const webview = screen.getByTestId('webview');
|
||||
|
||||
|
||||
@@ -13,14 +13,17 @@ import {
|
||||
} from '@/utils/proving/validateDocument';
|
||||
|
||||
// Mock the analytics module to avoid side effects in tests
|
||||
let mockTrackEvent: jest.Mock;
|
||||
jest.mock('@/utils/analytics', () => {
|
||||
mockTrackEvent = jest.fn();
|
||||
const mockTrackEvent = jest.fn();
|
||||
|
||||
return () => ({
|
||||
trackEvent: mockTrackEvent,
|
||||
});
|
||||
});
|
||||
|
||||
const mockTrackEvent = jest.requireMock('@/utils/analytics')()
|
||||
.trackEvent as jest.Mock;
|
||||
|
||||
// Mock the passport data provider to avoid database operations
|
||||
const mockGetAllDocumentsDirectlyFromKeychain = jest.fn();
|
||||
const mockLoadSelectedDocumentDirectlyFromKeychain = jest.fn();
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import { ethers } from 'ethers';
|
||||
import { Platform } from 'react-native';
|
||||
import { CloudStorage } from 'react-native-cloud-storage';
|
||||
// Import after mocks
|
||||
import { GDrive } from '@robinbobin/react-native-google-drive-api-wrapper';
|
||||
@@ -12,6 +11,24 @@ import { renderHook } from '@testing-library/react-native';
|
||||
import { useBackupMnemonic } from '@/utils/cloudBackup';
|
||||
import { createGDrive } from '@/utils/cloudBackup/google';
|
||||
|
||||
type SupportedPlatforms = 'ios' | 'android';
|
||||
|
||||
jest.mock('react-native', () => {
|
||||
const mockPlatform: { OS: SupportedPlatforms; select: jest.Mock } = {
|
||||
OS: 'ios',
|
||||
select: jest.fn(() => 'ios'),
|
||||
};
|
||||
|
||||
return {
|
||||
Platform: mockPlatform,
|
||||
};
|
||||
});
|
||||
|
||||
const mockPlatform = jest.requireMock('react-native').Platform as {
|
||||
OS: SupportedPlatforms;
|
||||
select: jest.Mock;
|
||||
};
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('react-native-cloud-storage', () => ({
|
||||
CloudStorage: {
|
||||
@@ -74,12 +91,12 @@ const mockMnemonic = {
|
||||
};
|
||||
|
||||
describe('cloudBackup', () => {
|
||||
let originalPlatform: any;
|
||||
let originalPlatform: SupportedPlatforms;
|
||||
let consoleSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
originalPlatform = Platform.OS;
|
||||
originalPlatform = mockPlatform.OS;
|
||||
// Suppress console.error during tests to avoid cluttering output
|
||||
consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
(GDrive as jest.Mock).mockImplementation(() => mockGDriveInstance);
|
||||
@@ -87,7 +104,7 @@ describe('cloudBackup', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Platform.OS = originalPlatform;
|
||||
mockPlatform.OS = originalPlatform;
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
@@ -106,7 +123,7 @@ describe('cloudBackup', () => {
|
||||
|
||||
describe('upload function - iOS', () => {
|
||||
beforeEach(() => {
|
||||
Platform.OS = 'ios';
|
||||
mockPlatform.OS = 'ios';
|
||||
});
|
||||
|
||||
it('should upload mnemonic to iCloud successfully', async () => {
|
||||
@@ -180,7 +197,7 @@ describe('cloudBackup', () => {
|
||||
|
||||
describe('upload function - Android', () => {
|
||||
beforeEach(() => {
|
||||
Platform.OS = 'android';
|
||||
mockPlatform.OS = 'android';
|
||||
});
|
||||
|
||||
it('should upload mnemonic to Google Drive successfully', async () => {
|
||||
@@ -217,7 +234,7 @@ describe('cloudBackup', () => {
|
||||
|
||||
describe('download function - iOS', () => {
|
||||
beforeEach(() => {
|
||||
Platform.OS = 'ios';
|
||||
mockPlatform.OS = 'ios';
|
||||
});
|
||||
|
||||
it('should download and parse mnemonic from iCloud successfully', async () => {
|
||||
@@ -295,7 +312,7 @@ describe('cloudBackup', () => {
|
||||
|
||||
describe('download function - Android', () => {
|
||||
beforeEach(() => {
|
||||
Platform.OS = 'android';
|
||||
mockPlatform.OS = 'android';
|
||||
});
|
||||
|
||||
it('should download and parse mnemonic from Google Drive successfully', async () => {
|
||||
@@ -397,7 +414,7 @@ describe('cloudBackup', () => {
|
||||
|
||||
describe('disableBackup function - iOS', () => {
|
||||
beforeEach(() => {
|
||||
Platform.OS = 'ios';
|
||||
mockPlatform.OS = 'ios';
|
||||
});
|
||||
|
||||
it('should remove backup folder from iCloud', async () => {
|
||||
@@ -414,7 +431,7 @@ describe('cloudBackup', () => {
|
||||
|
||||
describe('disableBackup function - Android', () => {
|
||||
beforeEach(() => {
|
||||
Platform.OS = 'android';
|
||||
mockPlatform.OS = 'android';
|
||||
});
|
||||
|
||||
it('should delete backup files from Google Drive', async () => {
|
||||
|
||||
@@ -2,10 +2,35 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import { Linking } from 'react-native';
|
||||
|
||||
import type { SelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||
|
||||
import {
|
||||
handleUrl,
|
||||
parseAndValidateUrlParams,
|
||||
setupUniversalLinkListenerInNavigation,
|
||||
} from '@/utils/deeplinks';
|
||||
|
||||
jest.mock('react-native', () => {
|
||||
const mockLinking = {
|
||||
addEventListener: jest.fn(),
|
||||
getInitialURL: jest.fn(),
|
||||
};
|
||||
|
||||
return {
|
||||
Linking: mockLinking,
|
||||
Platform: { OS: 'ios' },
|
||||
};
|
||||
});
|
||||
|
||||
const mockLinking = jest.requireMock('react-native').Linking as jest.Mocked<{
|
||||
addEventListener: jest.Mock;
|
||||
getInitialURL: jest.Mock;
|
||||
}>;
|
||||
|
||||
const mockPlatform = jest.requireMock('react-native').Platform as {
|
||||
OS: string;
|
||||
};
|
||||
|
||||
jest.mock('@/navigation', () => ({
|
||||
navigationRef: {
|
||||
navigate: jest.fn(),
|
||||
@@ -14,35 +39,33 @@ jest.mock('@/navigation', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const mockUserStore = { default: { getState: jest.fn() } };
|
||||
jest.mock('@/stores/userStore', () => ({
|
||||
__esModule: true,
|
||||
...mockUserStore,
|
||||
}));
|
||||
jest.mock('@/stores/userStore', () => {
|
||||
const mockUserStore = { default: { getState: jest.fn() } };
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
...mockUserStore,
|
||||
};
|
||||
});
|
||||
|
||||
const mockUserStore = jest.requireMock('@/stores/userStore') as {
|
||||
default: { getState: jest.Mock };
|
||||
};
|
||||
|
||||
let setDeepLinkUserDetails: jest.Mock;
|
||||
|
||||
let handleUrl: (selfClient: SelfClient, url: string) => void;
|
||||
let parseAndValidateUrlParams: (uri: string) => any;
|
||||
let setupUniversalLinkListenerInNavigation: () => () => void;
|
||||
|
||||
describe('deeplinks', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.resetModules();
|
||||
({
|
||||
handleUrl,
|
||||
parseAndValidateUrlParams,
|
||||
setupUniversalLinkListenerInNavigation,
|
||||
} = require('@/utils/deeplinks'));
|
||||
setDeepLinkUserDetails = jest.fn();
|
||||
jest.spyOn(Linking, 'getInitialURL').mockResolvedValue(null as any);
|
||||
jest
|
||||
.spyOn(Linking, 'addEventListener')
|
||||
.mockReturnValue({ remove: jest.fn() } as any);
|
||||
mockLinking.getInitialURL.mockReset();
|
||||
mockLinking.addEventListener.mockReset();
|
||||
mockLinking.getInitialURL.mockResolvedValue(null as any);
|
||||
mockLinking.addEventListener.mockReturnValue({ remove: jest.fn() } as any);
|
||||
mockUserStore.default.getState.mockReturnValue({
|
||||
setDeepLinkUserDetails,
|
||||
});
|
||||
mockPlatform.OS = 'ios';
|
||||
});
|
||||
|
||||
describe('handleUrl', () => {
|
||||
@@ -566,11 +589,11 @@ describe('deeplinks', () => {
|
||||
|
||||
it('setup listener registers and cleans up', () => {
|
||||
const remove = jest.fn();
|
||||
(Linking.getInitialURL as jest.Mock).mockResolvedValue(undefined);
|
||||
(Linking.addEventListener as jest.Mock).mockReturnValue({ remove });
|
||||
mockLinking.getInitialURL.mockResolvedValue(undefined as any);
|
||||
mockLinking.addEventListener.mockReturnValue({ remove });
|
||||
|
||||
const cleanup = setupUniversalLinkListenerInNavigation();
|
||||
expect(Linking.addEventListener).toHaveBeenCalled();
|
||||
expect(mockLinking.addEventListener).toHaveBeenCalled();
|
||||
cleanup();
|
||||
expect(remove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -2,22 +2,15 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
// Mock Platform without requiring react-native to avoid memory issues
|
||||
// Use a simple object that can be modified directly
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
import { parseScanResponse, scan } from '@/utils/nfcScanner';
|
||||
import { PassportReader } from '@/utils/passportReader';
|
||||
|
||||
// Mock Platform without requiring react-native to avoid memory issues
|
||||
// Use a closure to store the OS value, preventing test pollution
|
||||
let platformOS = 'ios'; // Default to iOS
|
||||
|
||||
const Platform = {
|
||||
get OS() {
|
||||
return platformOS;
|
||||
},
|
||||
set OS(value: string) {
|
||||
platformOS = value;
|
||||
},
|
||||
OS: 'ios', // Default to iOS
|
||||
Version: 14,
|
||||
};
|
||||
|
||||
@@ -25,6 +18,9 @@ jest.mock('react-native', () => ({
|
||||
Platform,
|
||||
}));
|
||||
|
||||
// Ensure the Node Buffer implementation is available to the module under test
|
||||
global.Buffer = Buffer;
|
||||
|
||||
describe('parseScanResponse', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
8
gitleaks-override.toml
Normal file
8
gitleaks-override.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[extend]
|
||||
path = ".gitleaks.toml"
|
||||
|
||||
[allowlist]
|
||||
description = "Project-specific overrides"
|
||||
paths = [
|
||||
'''(?:^|/)Podfile\.lock$'''
|
||||
]
|
||||
@@ -21,7 +21,7 @@
|
||||
"format": "SKIP_BUILD_DEPS=1 yarn format:root && yarn format:github && SKIP_BUILD_DEPS=1 yarn workspaces foreach --parallel -i --all --exclude self-workspace-root run format",
|
||||
"format:github": "yarn prettier --parser yaml --write .github/**/*.yml --single-quote false",
|
||||
"format:root": "echo 'format markdown' && yarn prettier --parser markdown --write *.md && echo 'format yaml' && yarn prettier --parser yaml --write .*.{yml,yaml} --single-quote false && yarn prettier --write scripts/**/*.{js,mjs,ts} && yarn prettier --parser json --write scripts/**/*.json",
|
||||
"gitleaks": "gitleaks protect --staged --redact --config=.gitleaks.toml",
|
||||
"gitleaks": "gitleaks protect --staged --redact --config=gitleaks-override.toml",
|
||||
"postinstall": "node scripts/run-patch-package.cjs",
|
||||
"lint": "yarn lint:headers && yarn workspaces foreach --parallel -i --all --exclude self-workspace-root run lint",
|
||||
"lint:headers": "node scripts/check-duplicate-headers.cjs . && node scripts/check-license-headers.mjs . --check",
|
||||
|
||||
Reference in New Issue
Block a user