1- import { describe , expect , it , vi , beforeEach , afterEach } from 'vitest' ;
1+ import { describe , expect , it , vi , beforeEach , afterEach , onTestFinished } from 'vitest' ;
22import {
33 PowerSyncDatabase ,
44 Schema ,
@@ -261,6 +261,7 @@ describe('attachment queue', () => {
261261 syncIntervalMs : INTERVAL_MILLISECONDS ,
262262 archivedCacheLimit : 0
263263 } ) ;
264+ onTestFinished ( ( ) => queue . stopSync ( ) ) ;
264265
265266 await queue . startSync ( ) ;
266267
@@ -280,8 +281,6 @@ describe('attachment queue', () => {
280281 const attachmentRecord = attachments . find ( ( r ) => r . id === id ) ;
281282 expect ( attachmentRecord ?. mediaType ) . toBe ( 'image/jpeg' ) ;
282283 expect ( mockDownloadFile ) . toHaveBeenCalledWith ( expect . objectContaining ( { mediaType : 'image/jpeg' } ) ) ;
283-
284- await queue . stopSync ( ) ;
285284 }
286285 ) ;
287286
@@ -538,6 +537,7 @@ describe('attachment queue', () => {
538537 syncIntervalMs : INTERVAL_MILLISECONDS ,
539538 errorHandler
540539 } ) ;
540+ onTestFinished ( ( ) => localeQueue . stopSync ( ) ) ;
541541
542542 const id = await localeQueue . generateAttachmentId ( ) ;
543543
@@ -578,6 +578,7 @@ describe('attachment queue', () => {
578578 syncIntervalMs : INTERVAL_MILLISECONDS ,
579579 archivedCacheLimit : 0
580580 } ) ;
581+ onTestFinished ( ( ) => slowQueue . stopSync ( ) ) ;
581582
582583 await slowQueue . startSync ( ) ;
583584
@@ -606,112 +607,103 @@ describe('attachment queue', () => {
606607 }
607608 ) ;
608609
609- it (
610- 'attachments queue should commit per-attachment, not at end of batch' ,
611- { timeout : 10000 } ,
612- async ( ) => {
613- const gates : Array < ReturnType < typeof deferred < ArrayBuffer > > > = [ deferred < ArrayBuffer > ( ) , deferred < ArrayBuffer > ( ) ] ;
614- const downloadIds : string [ ] = [ ] ;
615- const slowDownload = vi . fn ( ) . mockImplementation ( ( attachment : AttachmentRecord ) => {
616- const idx = downloadIds . length ;
617- downloadIds . push ( attachment . id ) ;
618- return gates [ idx ] ?. promise ?? Promise . resolve ( createMockJpegBuffer ( ) ) ;
619- } ) ;
610+ it ( 'attachments queue should commit per-attachment, not at end of batch' , { timeout : 10000 } , async ( ) => {
611+ const gates : Array < ReturnType < typeof deferred < ArrayBuffer > > > = [ deferred < ArrayBuffer > ( ) , deferred < ArrayBuffer > ( ) ] ;
612+ const downloadIds : string [ ] = [ ] ;
613+ const slowDownload = vi . fn ( ) . mockImplementation ( ( attachment : AttachmentRecord ) => {
614+ const idx = downloadIds . length ;
615+ downloadIds . push ( attachment . id ) ;
616+ return gates [ idx ] ?. promise ?? Promise . resolve ( createMockJpegBuffer ( ) ) ;
617+ } ) ;
620618
621- const slowQueue = new AttachmentQueue ( {
622- db,
623- watchAttachments,
624- remoteStorage : { downloadFile : slowDownload , uploadFile : mockUploadFile , deleteFile : mockDeleteFile } ,
625- localStorage : mockLocalStorage ,
626- syncIntervalMs : INTERVAL_MILLISECONDS ,
627- archivedCacheLimit : 0
628- } ) ;
619+ const slowQueue = new AttachmentQueue ( {
620+ db,
621+ watchAttachments,
622+ remoteStorage : { downloadFile : slowDownload , uploadFile : mockUploadFile , deleteFile : mockDeleteFile } ,
623+ localStorage : mockLocalStorage ,
624+ syncIntervalMs : INTERVAL_MILLISECONDS ,
625+ archivedCacheLimit : 0
626+ } ) ;
627+ onTestFinished ( ( ) => slowQueue . stopSync ( ) ) ;
629628
630- await slowQueue . startSync ( ) ;
629+ await slowQueue . startSync ( ) ;
631630
632- // Queue two downloads.
633- for ( let i = 0 ; i < 2 ; i ++ ) {
634- await db . execute ( 'INSERT INTO users (id, name, email, photo_id) VALUES (uuid(), ?, ?, uuid())' , [
635- `user${ i } ` ,
636- `user${ i } @example.com`
637- ] ) ;
638- }
631+ // Queue two downloads.
632+ for ( let i = 0 ; i < 2 ; i ++ ) {
633+ await db . execute ( 'INSERT INTO users (id, name, email, photo_id) VALUES (uuid(), ?, ?, uuid())' , [
634+ `user${ i } ` ,
635+ `user${ i } @example.com`
636+ ] ) ;
637+ }
639638
640- // Wait until the first download is in flight.
641- await waitForMatchCondition (
642- ( ) => watchAttachmentsTable ( ) ,
643- ( ) => slowDownload . mock . calls . length >= 1 ,
644- 5
645- ) ;
639+ // Wait until the first download is in flight.
640+ await waitForMatchCondition (
641+ ( ) => watchAttachmentsTable ( ) ,
642+ ( ) => slowDownload . mock . calls . length >= 1 ,
643+ 5
644+ ) ;
646645
647- // Resolve only the first download. The second is still pending.
648- gates [ 0 ] . resolve ( createMockJpegBuffer ( ) ) ;
646+ // Resolve only the first download. The second is still pending.
647+ gates [ 0 ] . resolve ( createMockJpegBuffer ( ) ) ;
649648
650- // The first attachment must transition to SYNCED while the second is still
651- // QUEUED_DOWNLOAD — i.e. the commit happened mid-batch, not at the end.
652- await waitForMatchCondition (
653- ( ) => watchAttachmentsTable ( ) ,
654- ( rows ) => {
655- const first = rows . find ( ( r ) => r . id === downloadIds [ 0 ] ) ;
656- const others = rows . filter ( ( r ) => r . id !== downloadIds [ 0 ] ) ;
657- return (
658- first ?. state === AttachmentState . SYNCED &&
659- others . some ( ( r ) => r . state === AttachmentState . QUEUED_DOWNLOAD )
660- ) ;
661- } ,
662- 5
663- ) ;
649+ // The first attachment must transition to SYNCED while the second is still
650+ // QUEUED_DOWNLOAD — i.e. the commit happened mid-batch, not at the end.
651+ await waitForMatchCondition (
652+ ( ) => watchAttachmentsTable ( ) ,
653+ ( rows ) => {
654+ const first = rows . find ( ( r ) => r . id === downloadIds [ 0 ] ) ;
655+ const others = rows . filter ( ( r ) => r . id !== downloadIds [ 0 ] ) ;
656+ return (
657+ first ?. state === AttachmentState . SYNCED && others . some ( ( r ) => r . state === AttachmentState . QUEUED_DOWNLOAD )
658+ ) ;
659+ } ,
660+ 5
661+ ) ;
664662
665- // Clean up: release the second so the worker drains and stopSync finishes.
666- gates [ 1 ] . resolve ( createMockJpegBuffer ( ) ) ;
667- await slowQueue . stopSync ( ) ;
668- }
669- ) ;
663+ // Clean up: release the second so the worker drains and stopSync finishes.
664+ gates [ 1 ] . resolve ( createMockJpegBuffer ( ) ) ;
665+ } ) ;
670666
671- it (
672- 'saveFile should not be blocked by an in-flight sync batch' ,
673- { timeout : 10000 } ,
674- async ( ) => {
675- const downloadStarted = deferred ( ) ;
676- const downloadGate = deferred < ArrayBuffer > ( ) ;
677- const slowDownload = vi . fn ( ) . mockImplementation ( ( ) => {
678- downloadStarted . resolve ( ) ;
679- return downloadGate . promise ;
680- } ) ;
667+ it ( 'saveFile should not be blocked by an in-flight sync batch' , { timeout : 10000 } , async ( ) => {
668+ const downloadStarted = deferred ( ) ;
669+ const downloadGate = deferred < ArrayBuffer > ( ) ;
670+ const slowDownload = vi . fn ( ) . mockImplementation ( ( ) => {
671+ downloadStarted . resolve ( ) ;
672+ return downloadGate . promise ;
673+ } ) ;
681674
682- const slowQueue = new AttachmentQueue ( {
683- db,
684- watchAttachments,
685- remoteStorage : { downloadFile : slowDownload , uploadFile : mockUploadFile , deleteFile : mockDeleteFile } ,
686- localStorage : mockLocalStorage ,
687- syncIntervalMs : INTERVAL_MILLISECONDS ,
688- archivedCacheLimit : 0
689- } ) ;
675+ const slowQueue = new AttachmentQueue ( {
676+ db,
677+ watchAttachments,
678+ remoteStorage : { downloadFile : slowDownload , uploadFile : mockUploadFile , deleteFile : mockDeleteFile } ,
679+ localStorage : mockLocalStorage ,
680+ syncIntervalMs : INTERVAL_MILLISECONDS ,
681+ archivedCacheLimit : 0
682+ } ) ;
683+ onTestFinished ( ( ) => slowQueue . stopSync ( ) ) ;
690684
691- await slowQueue . startSync ( ) ;
685+ await slowQueue . startSync ( ) ;
692686
693- // Queue a download that will hang.
694- await db . execute ( 'INSERT INTO users (id, name, email, photo_id) VALUES (uuid(), ?, ?, uuid())' , [
695- 'downloader' ,
696- 'downloader@example.com'
697- ] ) ;
687+ // Queue a download that will hang.
688+ await db . execute ( 'INSERT INTO users (id, name, email, photo_id) VALUES (uuid(), ?, ?, uuid())' , [
689+ 'downloader' ,
690+ 'downloader@example.com'
691+ ] ) ;
698692
699- await downloadStarted . promise ;
693+ await downloadStarted . promise ;
700694
701- // saveFile must complete promptly even while a download is in flight.
702- const saveStart = Date . now ( ) ;
703- const saved = await slowQueue . saveFile ( {
704- data : new Uint8Array ( 10 ) . fill ( 7 ) . buffer ,
705- fileExtension : 'jpg'
706- } ) ;
707- expect ( Date . now ( ) - saveStart ) . toBeLessThan ( 500 ) ;
708- expect ( saved . state ) . toBe ( AttachmentState . QUEUED_UPLOAD ) ;
695+ // saveFile must complete promptly even while a download is in flight.
696+ const saveStart = Date . now ( ) ;
697+ const saved = await slowQueue . saveFile ( {
698+ data : new Uint8Array ( 10 ) . fill ( 7 ) . buffer ,
699+ fileExtension : 'jpg'
700+ } ) ;
701+ expect ( Date . now ( ) - saveStart ) . toBeLessThan ( 500 ) ;
702+ expect ( saved . state ) . toBe ( AttachmentState . QUEUED_UPLOAD ) ;
709703
710- // Clean up.
711- downloadGate . resolve ( createMockJpegBuffer ( ) ) ;
712- await slowQueue . stopSync ( ) ;
713- }
714- ) ;
704+ // Clean up.
705+ downloadGate . resolve ( createMockJpegBuffer ( ) ) ;
706+ } ) ;
715707
716708 it (
717709 'stopSync should release a sync call queued behind a stalled batch instead of leaving it hanging' ,
@@ -733,6 +725,7 @@ describe('attachment queue', () => {
733725 syncIntervalMs : INTERVAL_MILLISECONDS ,
734726 archivedCacheLimit : 0
735727 } ) ;
728+ onTestFinished ( ( ) => slowQueue . stopSync ( ) ) ;
736729
737730 await slowQueue . startSync ( ) ;
738731
0 commit comments