WellNuo/services/ble/__tests__/BLEContext.cleanup.test.tsx
Sergei a30769387f Add BLE scanning cleanup on screen blur
Implement proper cleanup of BLE scanning operations when users navigate
away from the add-sensor screen to prevent resource waste and potential
issues.

Changes:
- Add useFocusEffect hook to stop BLE scan when screen loses focus
- Remove unused imports (Device, WPDevice, connectDevice, etc.)
- Add comprehensive tests for BLE cleanup behavior
- Add tests for screen unmount/blur scenarios

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-29 12:08:37 -08:00

183 lines
5.1 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(),
},
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: '81A14C', 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: '81A14C', rssi: -55, wellId: 497 },
{ id: 'device-2', name: 'WP_498_82b25d', mac: '82B25D', 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[] = [];
const mockStopScan = jest.fn(() => {
callOrder.push('stopScan');
});
const mockCleanup = jest.fn().mockResolvedValue(undefined).mockImplementation(() => {
callOrder.push('cleanup');
});
(bleModule.bleManager.stopScan as jest.Mock) = mockStopScan;
(bleModule.bleManager.cleanup as jest.Mock) = mockCleanup;
const { result } = renderHook(() => useBLE(), { wrapper });
// Start scanning
act(() => {
(result.current as any).setIsScanning?.(true);
});
// Trigger cleanup
await act(async () => {
await result.current.cleanupBLE();
});
// stopScan should be called before cleanup
expect(callOrder).toEqual(['stopScan', 'cleanup']);
});
});