@@ -537,6 +537,12 @@ describe('src/contexts/LedgerProvider.tsx', () => {
537537
538538 await waitFor ( ( ) => {
539539 expect ( getLedgerTransport ) . toHaveBeenCalled ( ) ;
540+ } ) ;
541+
542+ // Clear the AppAvalanche mock to ignore heartbeat calls
543+ ( AppAvalanche as unknown as jest . Mock ) . mockClear ( ) ;
544+
545+ await waitFor ( ( ) => {
540546 expect ( AppAvalanche ) . not . toHaveBeenCalled ( ) ;
541547 } ) ;
542548
@@ -1181,4 +1187,117 @@ describe('src/contexts/LedgerProvider.tsx', () => {
11811187 } ) ;
11821188 } ) ;
11831189 } ) ;
1190+
1191+ describe ( 'checkHeartbeat' , ( ) => {
1192+ beforeEach ( ( ) => {
1193+ // Clear any previous calls to refMock.send before each test
1194+ refMock . send . mockClear ( ) ;
1195+ } ) ;
1196+
1197+ afterEach ( ( ) => {
1198+ jest . restoreAllMocks ( ) ;
1199+ } ) ;
1200+
1201+ it ( 'should not run heartbeat when transport is not available' , async ( ) => {
1202+ // Mock transportRef.current to be null
1203+ jest . spyOn ( React , 'useRef' ) . mockReturnValue ( {
1204+ current : null ,
1205+ } ) ;
1206+
1207+ renderTestComponent ( ) ;
1208+
1209+ // Fast-forward time to trigger heartbeat
1210+ jest . advanceTimersByTime ( 3000 ) ;
1211+
1212+ // Should not call transport.send when no transport
1213+ expect ( refMock . send ) . not . toHaveBeenCalled ( ) ;
1214+ expect ( AppAvalanche ) . not . toHaveBeenCalled ( ) ;
1215+ } ) ;
1216+
1217+ it ( 'should run heartbeat when transport is available' , async ( ) => {
1218+ // Mock transportRef.current to be available
1219+ jest . spyOn ( React , 'useRef' ) . mockReturnValue ( {
1220+ current : refMock ,
1221+ } ) ;
1222+
1223+ // Mock app state to exist so heartbeat runs
1224+ const mockApp = new AppAvalanche ( refMock as any ) ;
1225+ jest . spyOn ( React , 'useState' ) . mockImplementation ( ( ( initialValue : any ) => {
1226+ if ( initialValue === undefined ) {
1227+ // This is the app state
1228+ return [ mockApp , jest . fn ( ) ] ;
1229+ }
1230+ return [ initialValue , jest . fn ( ) ] ;
1231+ } ) as any ) ;
1232+
1233+ renderTestComponent ( ) ;
1234+
1235+ // Fast-forward time to trigger heartbeat
1236+ jest . advanceTimersByTime ( 3000 ) ;
1237+
1238+ // Should call transport.send when transport is available
1239+ expect ( refMock . send ) . toHaveBeenCalled ( ) ;
1240+ } ) ;
1241+
1242+ it ( 'should run heartbeat when no app but transport is available' , async ( ) => {
1243+ // Mock transportRef.current to be available
1244+ jest . spyOn ( React , 'useRef' ) . mockReturnValue ( {
1245+ current : refMock ,
1246+ } ) ;
1247+
1248+ // Mock app state to be undefined (no app)
1249+ jest . spyOn ( React , 'useState' ) . mockImplementation ( ( ( initialValue : any ) => {
1250+ if ( initialValue === undefined ) {
1251+ // This is the app state - return undefined to simulate no app
1252+ return [ undefined , jest . fn ( ) ] ;
1253+ }
1254+ return [ initialValue , jest . fn ( ) ] ;
1255+ } ) as any ) ;
1256+
1257+ renderTestComponent ( ) ;
1258+
1259+ // Fast-forward time to trigger heartbeat
1260+ jest . advanceTimersByTime ( 3000 ) ;
1261+
1262+ // The heartbeat should run (it will attempt to reinitialize the app)
1263+ // We can verify this by checking that the heartbeat mechanism is active
1264+ // The actual reinitialization will happen through initLedgerApp
1265+ expect ( refMock . send ) . not . toHaveBeenCalled ( ) ; // No direct send call when no app
1266+ } ) ;
1267+
1268+ it ( 'should detect device lock error codes correctly' , ( ) => {
1269+ // Test the error detection logic directly
1270+ const testCases = [
1271+ { statusCode : 0x5515 , shouldBeLock : true } ,
1272+ { statusCode : 0x6700 , shouldBeLock : true } ,
1273+ { statusCode : 0x6b0c , shouldBeLock : true } ,
1274+ { statusCode : 0x9001 , shouldBeLock : false } ,
1275+ ] ;
1276+
1277+ testCases . forEach ( ( { statusCode, shouldBeLock } ) => {
1278+ const error = new Error ( 'Test error' ) as any ;
1279+ error . statusCode = statusCode ;
1280+ const isLockError =
1281+ error ?. statusCode === 0x5515 || // Device locked
1282+ error ?. statusCode === 0x6700 || // Incorrect length
1283+ error ?. statusCode === 0x6b0c ; // Something went wrong
1284+
1285+ expect ( isLockError ) . toBe ( shouldBeLock ) ;
1286+ } ) ;
1287+ } ) ;
1288+
1289+ it ( 'should clean up interval on unmount' , ( ) => {
1290+ const clearIntervalSpy = jest . spyOn ( global , 'clearInterval' ) ;
1291+
1292+ const { unmount } = renderTestComponent ( ) ;
1293+
1294+ // Fast-forward to set up interval
1295+ jest . advanceTimersByTime ( 3000 ) ;
1296+
1297+ // Unmount component
1298+ unmount ( ) ;
1299+
1300+ expect ( clearIntervalSpy ) . toHaveBeenCalled ( ) ;
1301+ } ) ;
1302+ } ) ;
11841303} ) ;
0 commit comments