44import { renderHook , act } from '@testing-library/react'
55import useBluetoothHRM from './useBluetoothHRM'
66import { mockBluetooth } from '@/tests/unit/mocks/webBluetooth'
7+ import * as cookieUtils from '@/utils/cookies'
78
89// Mock the WebSocket context
910jest . mock ( '@/context/WebSocketContext' , ( ) => ( {
@@ -13,6 +14,14 @@ jest.mock('@/context/WebSocketContext', () => ({
1314 } ) ,
1415} ) )
1516
17+ // Mock cookie utilities
18+ jest . mock ( '@/utils/cookies' , ( ) => ( {
19+ getCookie : jest . fn ( ) ,
20+ setCookie : jest . fn ( ) ,
21+ } ) )
22+
23+ const mockedCookieUtils = cookieUtils as jest . Mocked < typeof cookieUtils >
24+
1625describe ( 'useBluetoothHRM Race Conditions' , ( ) => {
1726 const originalNavigator = global . navigator
1827 let mockRequestDevice : jest . Mock
@@ -152,4 +161,43 @@ describe('useBluetoothHRM Race Conditions', () => {
152161 // The final status should be connected
153162 expect ( result . current . isConnected ) . toBe ( true )
154163 } )
164+
165+ it ( 'should only attempt to connect once when autoConnect is called multiple times concurrently' , async ( ) => {
166+ // Simulate that a device has been previously connected and its ID is saved
167+ mockedCookieUtils . getCookie . mockReturnValue ( 'test-device-id' )
168+
169+ // Simulate that the device is available to be re-connected to
170+ const mockSavedDevice = {
171+ id : 'test-device-id' ,
172+ name : 'Saved HRM' ,
173+ gatt : {
174+ connect : mockGattConnect ,
175+ } ,
176+ }
177+ Object . defineProperty ( global . navigator , 'bluetooth' , {
178+ value : {
179+ ...mockBluetooth ,
180+ getDevices : jest . fn ( ) . mockResolvedValue ( [ mockSavedDevice ] ) ,
181+ } ,
182+ writable : true ,
183+ } )
184+
185+ const { result } = renderHook ( ( ) => useBluetoothHRM ( ) )
186+
187+ // Act: Call autoConnect multiple times in parallel to simulate a race condition
188+ await act ( async ( ) => {
189+ const autoConnectPromises = [
190+ result . current . autoConnect ( ) ,
191+ result . current . autoConnect ( ) ,
192+ result . current . autoConnect ( ) ,
193+ ]
194+ // We don't care about the result of the promises, just that they complete
195+ await Promise . allSettled ( autoConnectPromises )
196+ } )
197+
198+ // Assert: Check that gatt.connect was only called once, proving the lock works
199+ expect ( mockGattConnect ) . toHaveBeenCalledTimes ( 1 )
200+ // The final status should be connected
201+ expect ( result . current . isConnected ) . toBe ( true )
202+ } )
155203} )
0 commit comments