/** * 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 }) => ( {children} ); 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']); }); });