@@ -165,6 +165,7 @@ describe("galleryRoms windowed fetch", () => {
165165 expect ( params . withCharIndex ) . toBeUndefined ( ) ;
166166 expect ( params . withFilterValues ) . toBeUndefined ( ) ;
167167 expect ( params . withRomIdIndex ) . toBeUndefined ( ) ;
168+ expect ( params . withTotal ) . toBeUndefined ( ) ;
168169 } ) ;
169170
170171 it ( "does not clobber the filter drawer when filter values are skipped" , async ( ) => {
@@ -225,6 +226,48 @@ describe("galleryRoms windowed fetch", () => {
225226 expect ( store . charIndex ) . toEqual ( { A : 0 , B : 10 } ) ;
226227 } ) ;
227228
229+ // Skipping the id index used to make the backend fall back to a full COUNT,
230+ // so every scroll batch re-counted the library for a total the page already
231+ // had (issue #4053).
232+ it ( "skips the total on a window the bootstrap already sized" , async ( ) => {
233+ getRoms . mockImplementation ( ( params : { limit ?: number } ) => {
234+ if ( params . limit === 1 ) {
235+ return Promise . resolve ( {
236+ data : { total : 500 , items : [ ] , char_index : { } , rom_id_index : [ ] } ,
237+ } ) ;
238+ }
239+ // The backend returns a null total when the count is skipped.
240+ return Promise . resolve ( {
241+ data : { total : null , items : [ ] , char_index : { } , rom_id_index : [ ] } ,
242+ } ) ;
243+ } ) ;
244+ const store = storeGalleryRoms ( ) ;
245+
246+ await store . fetchInitialMetadata ( ) ;
247+ expect ( store . total ) . toBe ( 500 ) ;
248+
249+ store . syncVisibleWindows ( [ 72 ] ) ;
250+ await flushPromises ( ) ;
251+
252+ const windowCall = getRoms . mock . calls . find ( ( c ) => c [ 0 ] . offset === 72 ) ;
253+ expect ( windowCall ?. [ 0 ] . withTotal ) . toBe ( false ) ;
254+ // The null total must not blank the size the bootstrap established.
255+ expect ( store . total ) . toBe ( 500 ) ;
256+ } ) ;
257+
258+ // The very first window doubles as the bootstrap when nothing has loaded
259+ // yet, so it still has to bring the total back with it.
260+ it ( "asks for the total on the first window when no bootstrap ran" , async ( ) => {
261+ getRoms . mockResolvedValue ( windowResponse ( 0 , 300 ) ) ;
262+ const store = storeGalleryRoms ( ) ;
263+
264+ store . syncVisibleWindows ( [ 0 ] ) ;
265+ await flushPromises ( ) ;
266+
267+ expect ( getRoms . mock . calls [ 0 ] [ 0 ] . withTotal ) . toBeUndefined ( ) ;
268+ expect ( store . total ) . toBe ( 300 ) ;
269+ } ) ;
270+
228271 it ( "does not mark a window loaded when the context is invalidated mid-apply" , async ( ) => {
229272 // Controllable frame yield so we can interleave a context switch between
230273 // the batched-apply's frames.
0 commit comments