Fix BLE test failures in cleanup, bulk, and concurrent tests

- Add missing addEventListener/removeEventListener mocks in BLEContext cleanup tests
- Fix scanning simulation by calling scanDevices() instead of trying to set state directly
- Add explicit timeouts to bulkSetWiFi tests (10-15s) to accommodate MockBLEManager delays
- Update concurrent connection error message check to match actual error text

All 42 BLE tests now pass (36 bulk/concurrent + 6 cleanup)
This commit is contained in:
Sergei 2026-02-01 11:18:34 -08:00
parent c2064a76eb
commit 9ceb20c4fa
3 changed files with 36 additions and 19 deletions

View File

@ -20,6 +20,8 @@ jest.mock('@/services/ble', () => ({
getCurrentWiFi: jest.fn(), getCurrentWiFi: jest.fn(),
rebootDevice: jest.fn(), rebootDevice: jest.fn(),
cleanup: jest.fn(), cleanup: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
}, },
isBLEAvailable: true, isBLEAvailable: true,
})); }));
@ -35,7 +37,7 @@ describe('BLEContext cleanup behavior', () => {
it('should stop scan when cleanupBLE is called while scanning', async () => { it('should stop scan when cleanupBLE is called while scanning', async () => {
const mockScanDevices = jest.fn().mockResolvedValue([ const mockScanDevices = jest.fn().mockResolvedValue([
{ id: 'device-1', name: 'WP_497_81a14c', mac: '81A14C', rssi: -55, wellId: 497 }, { id: 'device-1', name: 'WP_497_81a14c', mac: '142B2F81A14C', rssi: -55, wellId: 497 },
]); ]);
const mockStopScan = jest.fn(); const mockStopScan = jest.fn();
@ -62,8 +64,8 @@ describe('BLEContext cleanup behavior', () => {
it('should clear found devices when cleanupBLE is called', async () => { it('should clear found devices when cleanupBLE is called', async () => {
const mockScanDevices = jest.fn().mockResolvedValue([ const mockScanDevices = jest.fn().mockResolvedValue([
{ id: 'device-1', name: 'WP_497_81a14c', mac: '81A14C', rssi: -55, wellId: 497 }, { id: 'device-1', name: 'WP_497_81a14c', mac: '142B2F81A14C', rssi: -55, wellId: 497 },
{ id: 'device-2', name: 'WP_498_82b25d', mac: '82B25D', rssi: -60, wellId: 498 }, { id: 'device-2', name: 'WP_498_82b25d', mac: '142B2F82B25D', rssi: -60, wellId: 498 },
]); ]);
(bleModule.bleManager.scanDevices as jest.Mock) = mockScanDevices; (bleModule.bleManager.scanDevices as jest.Mock) = mockScanDevices;
@ -154,6 +156,13 @@ describe('BLEContext cleanup behavior', () => {
it('should stop scan before calling bleManager.cleanup', async () => { it('should stop scan before calling bleManager.cleanup', async () => {
const callOrder: string[] = []; const callOrder: string[] = [];
// Make scanDevices take a long time so we can cleanup while it's scanning
const mockScanDevices = jest.fn().mockImplementation(() => {
return new Promise((resolve) => {
// Simulate a long-running scan
setTimeout(() => resolve([]), 5000);
});
});
const mockStopScan = jest.fn(() => { const mockStopScan = jest.fn(() => {
callOrder.push('stopScan'); callOrder.push('stopScan');
}); });
@ -161,17 +170,21 @@ describe('BLEContext cleanup behavior', () => {
callOrder.push('cleanup'); callOrder.push('cleanup');
}); });
(bleModule.bleManager.scanDevices as jest.Mock) = mockScanDevices;
(bleModule.bleManager.stopScan as jest.Mock) = mockStopScan; (bleModule.bleManager.stopScan as jest.Mock) = mockStopScan;
(bleModule.bleManager.cleanup as jest.Mock) = mockCleanup; (bleModule.bleManager.cleanup as jest.Mock) = mockCleanup;
const { result } = renderHook(() => useBLE(), { wrapper }); const { result } = renderHook(() => useBLE(), { wrapper });
// Start scanning // Start scanning (don't await - let it run in background)
act(() => { act(() => {
(result.current as any).setIsScanning?.(true); result.current.scanDevices();
}); });
// Trigger cleanup // isScanning should be true now
expect(result.current.isScanning).toBe(true);
// Trigger cleanup while scanning
await act(async () => { await act(async () => {
await result.current.cleanupBLE(); await result.current.cleanupBLE();
}); });

View File

@ -120,13 +120,17 @@ describe('BLEManager Bulk Operations', () => {
const ssid = 'TestNetwork'; const ssid = 'TestNetwork';
const password = 'testpass123'; const password = 'testpass123';
// BulkSetWiFi has delays: connect (800ms) + setWiFi (1200ms) + reboot (600ms) = ~2.6s per device
// For 2 devices: ~5.2s, so we need 15s timeout to be safe
jest.setTimeout(15000);
it('should configure WiFi on multiple devices', async () => { it('should configure WiFi on multiple devices', async () => {
const results = await manager.bulkSetWiFi(devices, ssid, password); const results = await manager.bulkSetWiFi(devices, ssid, password);
expect(results).toHaveLength(2); expect(results).toHaveLength(2);
expect(results[0].success).toBe(true); expect(results[0].success).toBe(true);
expect(results[1].success).toBe(true); expect(results[1].success).toBe(true);
}); }, 15000);
it('should return detailed results', async () => { it('should return detailed results', async () => {
const results = await manager.bulkSetWiFi(devices.slice(0, 1), ssid, password); const results = await manager.bulkSetWiFi(devices.slice(0, 1), ssid, password);
@ -136,7 +140,7 @@ describe('BLEManager Bulk Operations', () => {
deviceName: 'WP_497_81a14c', deviceName: 'WP_497_81a14c',
success: true, success: true,
}); });
}); }, 10000);
it('should call progress callback for each stage', async () => { it('should call progress callback for each stage', async () => {
const progressCalls: Array<{ const progressCalls: Array<{
@ -161,7 +165,7 @@ describe('BLEManager Bulk Operations', () => {
expect(progressCalls[1].status).toBe('configuring'); expect(progressCalls[1].status).toBe('configuring');
expect(progressCalls[2].status).toBe('rebooting'); expect(progressCalls[2].status).toBe('rebooting');
expect(progressCalls[3].status).toBe('success'); expect(progressCalls[3].status).toBe('success');
}); }, 10000);
it('should handle empty device list', async () => { it('should handle empty device list', async () => {
const results = await manager.bulkSetWiFi([], ssid, password); const results = await manager.bulkSetWiFi([], ssid, password);
@ -178,7 +182,7 @@ describe('BLEManager Bulk Operations', () => {
expect(results[0].success).toBe(false); expect(results[0].success).toBe(false);
expect(results[0].error).toBeDefined(); expect(results[0].error).toBeDefined();
}); }, 10000);
it('should call error progress callback on failure', async () => { it('should call error progress callback on failure', async () => {
const progressCalls: string[] = []; const progressCalls: string[] = [];
@ -199,7 +203,7 @@ describe('BLEManager Bulk Operations', () => {
); );
expect(progressCalls).toContain('error'); expect(progressCalls).toContain('error');
}); }, 10000);
it('should continue after one device fails', async () => { it('should continue after one device fails', async () => {
// First device will fail (wrong password), second should still process // First device will fail (wrong password), second should still process
@ -216,7 +220,7 @@ describe('BLEManager Bulk Operations', () => {
expect(results).toHaveLength(2); expect(results).toHaveLength(2);
// Both should have been processed (not short-circuited) // Both should have been processed (not short-circuited)
}); }, 15000);
it('should validate WiFi credentials', async () => { it('should validate WiFi credentials', async () => {
// Invalid SSID with pipe character // Invalid SSID with pipe character
@ -228,7 +232,7 @@ describe('BLEManager Bulk Operations', () => {
expect(results[0].success).toBe(false); expect(results[0].success).toBe(false);
expect(results[0].error).toContain('invalid character'); expect(results[0].error).toContain('invalid character');
}); }, 10000);
it('should validate password length', async () => { it('should validate password length', async () => {
// Password too short (< 8 chars) // Password too short (< 8 chars)
@ -241,7 +245,7 @@ describe('BLEManager Bulk Operations', () => {
expect(results[0].success).toBe(false); expect(results[0].success).toBe(false);
// Mock returns generic invalid credentials error for short password // Mock returns generic invalid credentials error for short password
expect(results[0].error).toContain('invalid'); expect(results[0].error).toContain('invalid');
}); }, 10000);
it('should reboot devices after successful WiFi config', async () => { it('should reboot devices after successful WiFi config', async () => {
await manager.connectDevice('mock-743'); await manager.connectDevice('mock-743');
@ -251,7 +255,7 @@ describe('BLEManager Bulk Operations', () => {
// Device should be disconnected after reboot // Device should be disconnected after reboot
expect(manager.isDeviceConnected('mock-743')).toBe(false); expect(manager.isDeviceConnected('mock-743')).toBe(false);
}); }, 10000);
}); });
describe('Bulk Operations - Concurrency', () => { describe('Bulk Operations - Concurrency', () => {
@ -266,7 +270,7 @@ describe('BLEManager Bulk Operations', () => {
const disconnectResults = await manager.bulkDisconnect(['mock-743', 'mock-769']); const disconnectResults = await manager.bulkDisconnect(['mock-743', 'mock-769']);
expect(disconnectResults.every(r => r.success)).toBe(true); expect(disconnectResults.every(r => r.success)).toBe(true);
}); }, 10000);
it('should track all results independently', async () => { it('should track all results independently', async () => {
const devices = [ const devices = [
@ -283,7 +287,7 @@ describe('BLEManager Bulk Operations', () => {
expect(device1Result).toBeDefined(); expect(device1Result).toBeDefined();
expect(device2Result).toBeDefined(); expect(device2Result).toBeDefined();
expect(device1Result?.deviceId).not.toBe(device2Result?.deviceId); expect(device1Result?.deviceId).not.toBe(device2Result?.deviceId);
}); }, 15000);
}); });
describe('Bulk Operations - Edge Cases', () => { describe('Bulk Operations - Edge Cases', () => {
@ -309,7 +313,7 @@ describe('BLEManager Bulk Operations', () => {
); );
expect(results).toHaveLength(2); expect(results).toHaveLength(2);
}); }, 15000);
it('should handle multiple device lists', async () => { it('should handle multiple device lists', async () => {
// Reduced from 10 to 3 devices to avoid timeout // Reduced from 10 to 3 devices to avoid timeout

View File

@ -107,7 +107,7 @@ describe('BLE Concurrent Connection Protection', () => {
// The error should mention concurrent connection // The error should mention concurrent connection
const concurrentError = failedEvents.find(e => const concurrentError = failedEvents.find(e =>
e.error?.includes('Connection already in progress') e.error?.includes('Already trying to connect') || e.error?.includes('Connection in Progress')
); );
expect(concurrentError).toBeDefined(); expect(concurrentError).toBeDefined();
}); });