@@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
33import type { SafeLockReturn } from '../safeLock' ;
44
55describe ( 'SafeLock' , ( ) => {
6- let SafeLock : typeof import ( '../safeLock' ) . SafeLock ;
6+ let SafeLock : ( key : string ) => SafeLockReturn ;
77 let addEventListenerSpy : ReturnType < typeof vi . spyOn > ;
88
99 beforeEach ( async ( ) => {
@@ -37,8 +37,8 @@ describe('SafeLock', () => {
3737
3838 describe ( 'Web Locks API path' , ( ) => {
3939 it ( 'uses Web Locks API when available in secure context' , async ( ) => {
40- // Skip if Web Locks not available (like in jsdom without polyfill)
41- if ( ! ( 'locks' in navigator ) || ! navigator . locks ) {
40+ // Skip if Web Locks not available or not in secure context
41+ if ( ! ( 'locks' in navigator ) || ! navigator . locks || ! isSecureContext ) {
4242 return ;
4343 }
4444
@@ -117,6 +117,62 @@ describe('SafeLock', () => {
117117 } ) ;
118118 } ) ;
119119
120+ describe ( 'error handling' , ( ) => {
121+ it ( 'propagates callback errors without double-invocation' , async ( ) => {
122+ const originalLocks = navigator . locks ;
123+ const callbackError = new Error ( 'Callback failed' ) ;
124+ const callback = vi . fn ( ) . mockRejectedValue ( callbackError ) ;
125+
126+ const mockRequest = vi . fn ( ) . mockImplementation ( async ( _name , _options , cb ) => {
127+ return await cb ( ) ;
128+ } ) ;
129+
130+ Object . defineProperty ( navigator , 'locks' , {
131+ value : { request : mockRequest } ,
132+ configurable : true ,
133+ } ) ;
134+
135+ try {
136+ const lock = SafeLock ( 'test-error-propagation' ) ;
137+ await expect ( lock . acquireLockAndRun ( callback ) ) . rejects . toThrow ( 'Callback failed' ) ;
138+ expect ( callback ) . toHaveBeenCalledTimes ( 1 ) ;
139+ } finally {
140+ Object . defineProperty ( navigator , 'locks' , {
141+ value : originalLocks ,
142+ configurable : true ,
143+ } ) ;
144+ }
145+ } ) ;
146+
147+ it ( 'invokes callback in degraded mode on AbortError (timeout)' , async ( ) => {
148+ const originalLocks = navigator . locks ;
149+ const callback = vi . fn ( ) . mockResolvedValue ( 'success' ) ;
150+
151+ const abortError = new Error ( 'Lock request aborted' ) ;
152+ abortError . name = 'AbortError' ;
153+
154+ const mockRequest = vi . fn ( ) . mockRejectedValue ( abortError ) ;
155+
156+ Object . defineProperty ( navigator , 'locks' , {
157+ value : { request : mockRequest } ,
158+ configurable : true ,
159+ } ) ;
160+
161+ try {
162+ const lock = SafeLock ( 'test-abort-fallback' ) ;
163+ const result = await lock . acquireLockAndRun ( callback ) ;
164+
165+ expect ( callback ) . toHaveBeenCalledTimes ( 1 ) ;
166+ expect ( result ) . toBe ( 'success' ) ;
167+ } finally {
168+ Object . defineProperty ( navigator , 'locks' , {
169+ value : originalLocks ,
170+ configurable : true ,
171+ } ) ;
172+ }
173+ } ) ;
174+ } ) ;
175+
120176 describe ( 'beforeunload listener consolidation' , ( ) => {
121177 it ( 'registers only one beforeunload listener regardless of lock count' , ( ) => {
122178 const beforeUnloadCalls = addEventListenerSpy . mock . calls . filter ( call => call [ 0 ] === 'beforeunload' ) ;
0 commit comments