WellNuo/jest.setup.js
Sergei e34ed5282a Add comprehensive BLE integration tests
Implemented integration tests for BLE functionality covering:
- Connection flow state machine (connecting, discovering, ready)
- Event system (listeners, state changes, connection events)
- WiFi operations (scan, configure, status, reboot)
- Error handling (timeouts, disconnections, concurrent connections)
- Batch operations (multiple device connections, cleanup)
- State machine edge cases (device name, timestamps)

Tests cover both RealBLEManager and MockBLEManager to ensure
consistent behavior across real devices and simulator.

Updated Jest configuration:
- Added expo winter runtime mocks
- Added structuredClone polyfill
- Added React Native module mocks (Platform, PermissionsAndroid, Linking, Alert)
- Updated transform ignore patterns for BLE modules

All 36 tests passing with comprehensive coverage of BLE integration.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-31 15:50:54 -08:00

182 lines
4.8 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(),
EventEmitter: class EventEmitter {},
UnavailabilityError: class UnavailabilityError extends Error {},
}));
// 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(),
};