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>
204 lines
5.4 KiB
JavaScript
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(),
|
|
};
|