WellNuo/jest.setup.js
Sergei dad084c775 Add setup progress indicator to onboarding flow
Add SetupProgressIndicator component that shows users their current
position in the 4-step onboarding journey: Name → Beneficiary →
Equipment → Connect. The indicator displays:
- Visual progress bar with percentage fill
- Step circles with icons showing completed/current/pending status
- Current step label with "Step X of 4" text

Integrate the indicator into all four auth screens:
- enter-name.tsx (Step 1)
- add-loved-one.tsx (Step 2)
- purchase.tsx (Step 3)
- activate.tsx (Step 4)

Also add @expo/vector-icons mock to jest.setup.js for testing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-31 17:14:44 -08:00

204 lines
5.4 KiB
JavaScript

// Setup testing library
// Note: extend-expect is automatically loaded by @testing-library/react-native
// Mock expo modules
jest.mock('expo', () => ({
// Add any expo mocks here if needed
}));
// Mock expo modules core to prevent winter runtime errors
jest.mock('expo-modules-core', () => ({
NativeModulesProxy: {},
requireNativeViewManager: jest.fn(),
requireNativeModule: jest.fn(),
requireOptionalNativeModule: jest.fn(() => null),
EventEmitter: class EventEmitter {},
UnavailabilityError: class UnavailabilityError extends Error {},
}));
// Mock @expo/vector-icons
jest.mock('@expo/vector-icons', () => {
const React = require('react');
const MockIcon = (props) => React.createElement('Text', props, props.name);
return {
Ionicons: MockIcon,
MaterialIcons: MockIcon,
FontAwesome: MockIcon,
Feather: MockIcon,
AntDesign: MockIcon,
Entypo: MockIcon,
EvilIcons: MockIcon,
Foundation: MockIcon,
MaterialCommunityIcons: MockIcon,
Octicons: MockIcon,
SimpleLineIcons: MockIcon,
Zocial: MockIcon,
createIconSet: jest.fn(() => MockIcon),
};
});
// Mock expo winter runtime
global.__ExpoImportMetaRegistry = {
register: jest.fn(),
get: jest.fn(() => ({})),
};
// Mock structuredClone if not available
if (typeof global.structuredClone === 'undefined') {
global.structuredClone = (obj) => JSON.parse(JSON.stringify(obj));
}
// Mock Expo modules
jest.mock('expo-router', () => ({
router: {
push: jest.fn(),
replace: jest.fn(),
back: jest.fn(),
setParams: jest.fn(),
},
useLocalSearchParams: jest.fn(() => ({})),
useRouter: jest.fn(() => ({
push: jest.fn(),
replace: jest.fn(),
back: jest.fn(),
})),
useSegments: jest.fn(() => []),
usePathname: jest.fn(() => '/'),
}));
jest.mock('expo-secure-store', () => ({
getItemAsync: jest.fn(),
setItemAsync: jest.fn(),
deleteItemAsync: jest.fn(),
}));
jest.mock('expo-crypto', () => {
return {
getRandomBytesAsync: jest.fn((size) => {
const bytes = new Uint8Array(size);
for (let i = 0; i < size; i++) {
bytes[i] = (i * 7 + 13) % 256;
}
return Promise.resolve(bytes);
}),
digestStringAsync: jest.fn((algorithm, data) => {
const hash = Buffer.from(data).toString('base64').substring(0, 64).padEnd(64, '0');
return Promise.resolve(hash);
}),
CryptoDigestAlgorithm: {
SHA256: 'SHA256',
},
};
}, { virtual: true });
jest.mock('expo-image-picker', () => ({
requestMediaLibraryPermissionsAsync: jest.fn(() =>
Promise.resolve({ status: 'granted' })
),
launchImageLibraryAsync: jest.fn(() =>
Promise.resolve({
canceled: false,
assets: [{ uri: 'file://test-image.jpg' }],
})
),
}));
jest.mock('expo-image-manipulator', () => ({
manipulateAsync: jest.fn((uri, actions, options) =>
Promise.resolve({ uri: uri })
),
SaveFormat: {
JPEG: 'jpeg',
PNG: 'png',
},
}));
// Mock AsyncStorage
jest.mock('@react-native-async-storage/async-storage', () => ({
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
clear: jest.fn(),
getAllKeys: jest.fn(() => Promise.resolve([])),
multiGet: jest.fn(() => Promise.resolve([])),
multiSet: jest.fn(() => Promise.resolve()),
multiRemove: jest.fn(() => Promise.resolve()),
}));
// Mock native modules
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper', () => ({
default: {},
}), { virtual: true });
// Mock Platform
jest.mock('react-native/Libraries/Utilities/Platform', () => ({
OS: 'ios',
select: jest.fn((obj) => obj.ios),
Version: 14,
}));
// Mock PermissionsAndroid
jest.mock('react-native/Libraries/PermissionsAndroid/PermissionsAndroid', () => ({
PERMISSIONS: {
BLUETOOTH_SCAN: 'android.permission.BLUETOOTH_SCAN',
BLUETOOTH_CONNECT: 'android.permission.BLUETOOTH_CONNECT',
ACCESS_FINE_LOCATION: 'android.permission.ACCESS_FINE_LOCATION',
},
RESULTS: {
GRANTED: 'granted',
DENIED: 'denied',
NEVER_ASK_AGAIN: 'never_ask_again',
},
request: jest.fn(() => Promise.resolve('granted')),
requestMultiple: jest.fn(() => Promise.resolve({
'android.permission.BLUETOOTH_SCAN': 'granted',
'android.permission.BLUETOOTH_CONNECT': 'granted',
'android.permission.ACCESS_FINE_LOCATION': 'granted',
})),
}));
// Mock Linking
jest.mock('react-native/Libraries/Linking/Linking', () => ({
openURL: jest.fn(() => Promise.resolve()),
openSettings: jest.fn(() => Promise.resolve()),
sendIntent: jest.fn(() => Promise.resolve()),
}));
// Mock Alert
jest.mock('react-native/Libraries/Alert/Alert', () => ({
alert: jest.fn(),
}));
// Mock react-native-ble-plx
jest.mock('react-native-ble-plx', () => ({
BleManager: jest.fn().mockImplementation(() => ({
startDeviceScan: jest.fn(),
stopDeviceScan: jest.fn(),
connectToDevice: jest.fn(),
state: jest.fn(() => Promise.resolve('PoweredOn')),
})),
State: {
Unknown: 'Unknown',
Resetting: 'Resetting',
Unsupported: 'Unsupported',
Unauthorized: 'Unauthorized',
PoweredOff: 'PoweredOff',
PoweredOn: 'PoweredOn',
},
BleError: class BleError extends Error {},
BleErrorCode: {},
}));
// Mock react-native-base64
jest.mock('react-native-base64', () => ({
encode: jest.fn((str) => Buffer.from(str).toString('base64')),
decode: jest.fn((str) => Buffer.from(str, 'base64').toString()),
}));
// Silence console warnings in tests
global.console = {
...console,
warn: jest.fn(),
error: jest.fn(),
};