@@ -174,6 +174,77 @@ describe('IDBKeyValProvider', () => {
174174 } ) ;
175175 } ) ;
176176
177+ describe ( 'write-error normalization (aborted transactions)' , ( ) => {
178+ // A write transaction aborted by something other than its own request (connection close,
179+ // versionchange, a sibling transaction) leaves `transaction.error === null`, which idb-keyval
180+ // rejects with as-is. Every write path must instead reject with a real Error so the failure
181+ // can be classified and retried.
182+ function abortTransactionOnPut ( ) {
183+ const originalPut = IDBObjectStore . prototype . put ;
184+ jest . spyOn ( IDBObjectStore . prototype , 'put' ) . mockImplementation ( function put ( this : IDBObjectStore , ...args : Parameters < IDBObjectStore [ 'put' ] > ) {
185+ const request = originalPut . apply ( this , args ) ;
186+ this . transaction . abort ( ) ;
187+ return request ;
188+ } ) ;
189+ }
190+
191+ function abortTransactionOnDelete ( ) {
192+ const originalDelete = IDBObjectStore . prototype . delete ;
193+ jest . spyOn ( IDBObjectStore . prototype , 'delete' ) . mockImplementation ( function del ( this : IDBObjectStore , ...args : Parameters < IDBObjectStore [ 'delete' ] > ) {
194+ const request = originalDelete . apply ( this , args ) ;
195+ this . transaction . abort ( ) ;
196+ return request ;
197+ } ) ;
198+ }
199+
200+ function expectAbortError ( error : unknown ) {
201+ expect ( error ) . not . toBeNull ( ) ;
202+ expect ( error ) . toBeInstanceOf ( DOMException ) ;
203+ expect ( ( error as DOMException ) . name ) . toBe ( 'AbortError' ) ;
204+ expect ( ( error as DOMException ) . message . length ) . toBeGreaterThan ( 0 ) ;
205+ }
206+
207+ afterEach ( ( ) => {
208+ jest . restoreAllMocks ( ) ;
209+ } ) ;
210+
211+ it ( 'should reject setItem with a tagged AbortError, never null' , async ( ) => {
212+ abortTransactionOnPut ( ) ;
213+ const error = await IDBKeyValProvider . setItem ( ONYXKEYS . TEST_KEY , 'value' ) . catch ( ( e : unknown ) => e ) ;
214+ expectAbortError ( error ) ;
215+ } ) ;
216+
217+ it ( 'should reject multiSet with a tagged AbortError, never null' , async ( ) => {
218+ abortTransactionOnPut ( ) ;
219+ const error = await IDBKeyValProvider . multiSet ( [ [ ONYXKEYS . TEST_KEY , 'value' ] ] ) . catch ( ( e : unknown ) => e ) ;
220+ expectAbortError ( error ) ;
221+ } ) ;
222+
223+ it ( 'should reject multiMerge with a tagged AbortError, never null' , async ( ) => {
224+ abortTransactionOnPut ( ) ;
225+ const error = await IDBKeyValProvider . multiMerge ( [ [ ONYXKEYS . TEST_KEY , 'value' ] ] ) . catch ( ( e : unknown ) => e ) ;
226+ expectAbortError ( error ) ;
227+ } ) ;
228+
229+ it ( 'should reject setItem(null) with a tagged AbortError, never null' , async ( ) => {
230+ abortTransactionOnDelete ( ) ;
231+ const error = await IDBKeyValProvider . setItem ( ONYXKEYS . TEST_KEY , null ) . catch ( ( e : unknown ) => e ) ;
232+ expectAbortError ( error ) ;
233+ } ) ;
234+
235+ it ( 'should reject removeItem with a tagged AbortError, never null' , async ( ) => {
236+ abortTransactionOnDelete ( ) ;
237+ const error = await IDBKeyValProvider . removeItem ( ONYXKEYS . TEST_KEY ) . catch ( ( e : unknown ) => e ) ;
238+ expectAbortError ( error ) ;
239+ } ) ;
240+
241+ it ( 'should reject removeItems with a tagged AbortError, never null' , async ( ) => {
242+ abortTransactionOnDelete ( ) ;
243+ const error = await IDBKeyValProvider . removeItems ( [ ONYXKEYS . TEST_KEY ] ) . catch ( ( e : unknown ) => e ) ;
244+ expectAbortError ( error ) ;
245+ } ) ;
246+ } ) ;
247+
177248 describe ( 'mergeItem' , ( ) => {
178249 it ( 'should merge all the supported kinds of data correctly' , async ( ) => {
179250 await IDB . set ( ONYXKEYS . TEST_KEY , 'value' , IDBKeyValProvider . store ) ;
0 commit comments