WellNuo/services/ble/__tests__/BLEContext.cleanup.test.tsx
Sergei 9ceb20c4fa 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)
2026-02-01 11:18:34 -08:00

196 lines
5.7 KiB
TypeScript

/**
* BLE Context Cleanup Tests
* Tests for BLE scanning cleanup when screens lose focus
*/
import React from 'react';
import { renderHook, act, waitFor } from '@testing-library/react-native';
import { BLEProvider, useBLE } from '@/contexts/BLEContext';
import * as bleModule from '@/services/ble';
// Mock the BLE service
jest.mock('@/services/ble', () => ({
bleManager: {
scanDevices: jest.fn(),
stopScan: jest.fn(),
connectDevice: jest.fn(),
disconnectDevice: jest.fn(),
getWiFiList: jest.fn(),
setWiFi: jest.fn(),
getCurrentWiFi: jest.fn(),
rebootDevice: jest.fn(),
cleanup: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
},
isBLEAvailable: true,
}));
describe('BLEContext cleanup behavior', () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
<BLEProvider>{children}</BLEProvider>
);
beforeEach(() => {
jest.clearAllMocks();
});
it('should stop scan when cleanupBLE is called while scanning', async () => {
const mockScanDevices = jest.fn().mockResolvedValue([
{ id: 'device-1', name: 'WP_497_81a14c', mac: '142B2F81A14C', rssi: -55, wellId: 497 },
]);
const mockStopScan = jest.fn();
(bleModule.bleManager.scanDevices as jest.Mock) = mockScanDevices;
(bleModule.bleManager.stopScan as jest.Mock) = mockStopScan;
const { result } = renderHook(() => useBLE(), { wrapper });
// Start scanning
act(() => {
result.current.scanDevices();
});
expect(result.current.isScanning).toBe(true);
// Cleanup while scanning
await act(async () => {
await result.current.cleanupBLE();
});
expect(mockStopScan).toHaveBeenCalled();
expect(result.current.isScanning).toBe(false);
});
it('should clear found devices when cleanupBLE is called', async () => {
const mockScanDevices = jest.fn().mockResolvedValue([
{ id: 'device-1', name: 'WP_497_81a14c', mac: '142B2F81A14C', rssi: -55, wellId: 497 },
{ id: 'device-2', name: 'WP_498_82b25d', mac: '142B2F82B25D', rssi: -60, wellId: 498 },
]);
(bleModule.bleManager.scanDevices as jest.Mock) = mockScanDevices;
const { result } = renderHook(() => useBLE(), { wrapper });
// Scan and find devices
await act(async () => {
await result.current.scanDevices();
});
expect(result.current.foundDevices).toHaveLength(2);
// Cleanup
await act(async () => {
await result.current.cleanupBLE();
});
expect(result.current.foundDevices).toHaveLength(0);
});
it('should clear connected devices when cleanupBLE is called', async () => {
const mockConnectDevice = jest.fn().mockResolvedValue(true);
const mockCleanup = jest.fn().mockResolvedValue(undefined);
(bleModule.bleManager.connectDevice as jest.Mock) = mockConnectDevice;
(bleModule.bleManager.cleanup as jest.Mock) = mockCleanup;
const { result } = renderHook(() => useBLE(), { wrapper });
// Connect to a device
await act(async () => {
await result.current.connectDevice('device-1');
});
expect(result.current.connectedDevices.size).toBe(1);
// Cleanup
await act(async () => {
await result.current.cleanupBLE();
});
expect(mockCleanup).toHaveBeenCalled();
expect(result.current.connectedDevices.size).toBe(0);
});
it('should clear errors when cleanupBLE is called', async () => {
const mockScanDevices = jest.fn().mockRejectedValue(new Error('Scan failed'));
(bleModule.bleManager.scanDevices as jest.Mock) = mockScanDevices;
const { result } = renderHook(() => useBLE(), { wrapper });
// Trigger an error
await act(async () => {
try {
await result.current.scanDevices();
} catch {
// Expected
}
});
expect(result.current.error).toBeTruthy();
// Cleanup should clear errors
await act(async () => {
await result.current.cleanupBLE();
});
expect(result.current.error).toBeNull();
});
it('should not throw if cleanup fails', async () => {
const mockCleanup = jest.fn().mockRejectedValue(new Error('Cleanup failed'));
(bleModule.bleManager.cleanup as jest.Mock) = mockCleanup;
const { result } = renderHook(() => useBLE(), { wrapper });
// Cleanup should not throw even if bleManager.cleanup fails
await expect(
act(async () => {
await result.current.cleanupBLE();
})
).resolves.not.toThrow();
});
it('should stop scan before calling bleManager.cleanup', async () => {
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(() => {
callOrder.push('stopScan');
});
const mockCleanup = jest.fn().mockResolvedValue(undefined).mockImplementation(() => {
callOrder.push('cleanup');
});
(bleModule.bleManager.scanDevices as jest.Mock) = mockScanDevices;
(bleModule.bleManager.stopScan as jest.Mock) = mockStopScan;
(bleModule.bleManager.cleanup as jest.Mock) = mockCleanup;
const { result } = renderHook(() => useBLE(), { wrapper });
// Start scanning (don't await - let it run in background)
act(() => {
result.current.scanDevices();
});
// isScanning should be true now
expect(result.current.isScanning).toBe(true);
// Trigger cleanup while scanning
await act(async () => {
await result.current.cleanupBLE();
});
// stopScan should be called before cleanup
expect(callOrder).toEqual(['stopScan', 'cleanup']);
});
});