33 * SPDX-License-Identifier: AGPL-3.0-or-later
44 */
55import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
6- import { REVEAL_INTERVAL , UnifiedSearchController } from '../../services/UnifiedSearchController.ts'
6+ import { REVEAL_INTERVAL_MS , UnifiedSearchController } from '../../services/UnifiedSearchController.ts'
77
88const service = vi . hoisted ( ( ) => ( {
99 search : vi . fn ( ) ,
@@ -215,6 +215,34 @@ describe('UnifiedSearchController', () => {
215215 loadMoreFailed : false ,
216216 } )
217217 } )
218+
219+ it ( 'ignores a stale response for a category the newer search dropped' , async ( ) => {
220+ const first = mockProviders ( [ 'files' , 'talk' ] )
221+
222+ const searchController = new UnifiedSearchController ( )
223+ searchController . search ( 'first' , [ 'files' , 'talk' ] )
224+
225+ // A newer search with a completely different category set supersedes it.
226+ const second = mockProviders ( [ 'deck' ] )
227+ searchController . search ( 'second' , [ 'deck' ] )
228+
229+ // The stale response is for 'files', which no longer exists in the
230+ // current search. Reconciling it must not throw on the missing category.
231+ first . files . resolve ( [ 'Stale files' ] )
232+ await vi . advanceTimersByTimeAsync ( 0 )
233+
234+ expect ( searchController . getSnapshot ( ) ) . toEqual ( {
235+ deck : loading ,
236+ } )
237+
238+ // The live search still resolves normally.
239+ second . deck . resolve ( [ 'Live deck' ] )
240+ await vi . advanceTimersByTimeAsync ( 0 )
241+
242+ expect ( searchController . getSnapshot ( ) ) . toEqual ( {
243+ deck : { status : 'loaded' , entries : [ 'Live deck' ] , cursor : undefined , hasMore : undefined , loadMoreFailed : false } ,
244+ } )
245+ } )
218246 } )
219247
220248 describe ( 'resetting between searches' , ( ) => {
@@ -416,7 +444,7 @@ describe('UnifiedSearchController', () => {
416444 deck : { status : 'blocked' , entries : [ 'Deck result' ] , cursor : undefined , hasMore : undefined , loadMoreFailed : false } ,
417445 } )
418446
419- await vi . advanceTimersByTimeAsync ( REVEAL_INTERVAL )
447+ await vi . advanceTimersByTimeAsync ( REVEAL_INTERVAL_MS )
420448
421449 expect ( searchController . getSnapshot ( ) ) . toEqual ( {
422450 files : loading ,
@@ -433,19 +461,19 @@ describe('UnifiedSearchController', () => {
433461
434462 // deck arrives out of order and is revealed by the first flush.
435463 providers . deck . resolve ( [ 'Deck result' ] )
436- await vi . advanceTimersByTimeAsync ( REVEAL_INTERVAL )
464+ await vi . advanceTimersByTimeAsync ( REVEAL_INTERVAL_MS )
437465 expect ( searchController . getSnapshot ( ) . deck . status ) . toBe ( 'loaded' )
438466
439467 // A later flush passes with nothing blocked while files/talk keep loading.
440- await vi . advanceTimersByTimeAsync ( REVEAL_INTERVAL )
468+ await vi . advanceTimersByTimeAsync ( REVEAL_INTERVAL_MS )
441469
442470 // talk now arrives out of order (files still loading) and is blocked.
443471 providers . talk . resolve ( [ 'Talk result' ] )
444472 await vi . advanceTimersByTimeAsync ( 0 )
445473 expect ( searchController . getSnapshot ( ) . talk . status ) . toBe ( 'blocked' )
446474
447475 // The timer must still be running to flush talk on a later cycle.
448- await vi . advanceTimersByTimeAsync ( REVEAL_INTERVAL )
476+ await vi . advanceTimersByTimeAsync ( REVEAL_INTERVAL_MS )
449477 expect ( searchController . getSnapshot ( ) . talk . status ) . toBe ( 'loaded' )
450478 } )
451479
@@ -460,7 +488,7 @@ describe('UnifiedSearchController', () => {
460488 await vi . advanceTimersByTimeAsync ( 0 )
461489
462490 // Nothing is loading or blocked, so the next flush should not re-arm.
463- await vi . advanceTimersByTimeAsync ( REVEAL_INTERVAL )
491+ await vi . advanceTimersByTimeAsync ( REVEAL_INTERVAL_MS )
464492 expect ( vi . getTimerCount ( ) ) . toBe ( 0 )
465493 } )
466494
@@ -472,7 +500,7 @@ describe('UnifiedSearchController', () => {
472500
473501 // First search: deck is blocked and its reveal timer is pending.
474502 first . deck . resolve ( [ 'First deck' ] )
475- await vi . advanceTimersByTimeAsync ( REVEAL_INTERVAL - 500 )
503+ await vi . advanceTimersByTimeAsync ( REVEAL_INTERVAL_MS - 500 )
476504 expect ( searchController . getSnapshot ( ) . deck . status ) . toBe ( 'blocked' )
477505
478506 // A new search starts before the first timer fires. It must clear that
@@ -483,7 +511,7 @@ describe('UnifiedSearchController', () => {
483511 second . deck . resolve ( [ 'Second deck' ] )
484512 // Advance past when the first search's timer would have fired (500ms from
485513 // now) but before the second search's timer is due.
486- await vi . advanceTimersByTimeAsync ( REVEAL_INTERVAL - 500 )
514+ await vi . advanceTimersByTimeAsync ( REVEAL_INTERVAL_MS - 500 )
487515
488516 expect ( searchController . getSnapshot ( ) . deck . status ) . toBe ( 'blocked' )
489517 } )
0 commit comments