@@ -10,7 +10,7 @@ import {BookService} from '../../service/book.service';
1010import { BookMetadataManageService } from '../../service/book-metadata-manage.service' ;
1111import { LibraryShelfMenuService } from '../../service/library-shelf-menu.service' ;
1212import { Book } from '../../model/book.model' ;
13- import { SortDirection } from '../../model/sort.model' ;
13+ import { SortDirection , SortOption } from '../../model/sort.model' ;
1414import { UserService } from '../../../settings/user-management/user.service' ;
1515import { SeriesCollapseFilter } from './filters/SeriesCollapseFilter' ;
1616import { CoverScalePreferenceService } from './cover-scale-preference.service' ;
@@ -112,6 +112,8 @@ interface BookBrowserHarness {
112112 updateFilters : ReturnType < typeof vi . fn > ;
113113 updateFilterMode : ReturnType < typeof vi . fn > ;
114114 updateMultiSort : ReturnType < typeof vi . fn > ;
115+ parseQueryParams : ReturnType < typeof vi . fn > ;
116+ syncQueryParams : ReturnType < typeof vi . fn > ;
115117 } ;
116118 routeSnapshot : {
117119 routeConfig : { path : string } ;
@@ -129,8 +131,8 @@ function createHarness(options?: {
129131} ) : BookBrowserHarness {
130132 const books = signal < Book [ ] > (
131133 options ?. books ?? [
132- makeBook ( 1 , 1 , 'Alpha' , '2024-01-01T00:00:00Z' ) ,
133134 makeBook ( 2 , 1 , 'Zulu' , '2024-02-01T00:00:00Z' ) ,
135+ makeBook ( 1 , 1 , 'Alpha' , '2024-01-01T00:00:00Z' ) ,
134136 makeBook ( 3 , 2 , 'Bravo' , '2024-03-01T00:00:00Z' ) ,
135137 ]
136138 ) ;
@@ -143,7 +145,17 @@ function createHarness(options?: {
143145 const paramMap$ = new BehaviorSubject ( convertToParamMap ( { libraryId : '1' } ) ) ;
144146 const queryParamMap$ = new BehaviorSubject ( convertToParamMap ( { } ) ) ;
145147 const url$ = new BehaviorSubject < unknown [ ] > ( [ ] ) ;
148+ const defaultSort : SortOption = { field : 'addedOn' , direction : SortDirection . DESCENDING , label : 'Added On' } ;
146149 const queryParamsService = {
150+ parseQueryParams : vi . fn ( ( _q , _u , _et , _ei , _sortOptions , defaultFilterMode ) => ( {
151+ viewMode : VIEW_MODES . GRID ,
152+ sortOption : defaultSort ,
153+ sortCriteria : [ defaultSort ] ,
154+ filters : { } ,
155+ filterMode : defaultFilterMode ,
156+ viewModeFromToggle : false ,
157+ } ) ) ,
158+ syncQueryParams : vi . fn ( ) ,
147159 shouldForceExpandSeries : vi . fn ( ( ) => false ) ,
148160 updateViewMode : vi . fn ( ) ,
149161 updateFilters : vi . fn ( ) ,
@@ -157,6 +169,7 @@ function createHarness(options?: {
157169 params : { libraryId : '1' } ,
158170 } ;
159171
172+ TestBed . resetTestingModule ( ) ;
160173 TestBed . configureTestingModule ( {
161174 providers : [
162175 SortService ,
@@ -206,6 +219,35 @@ function createHarness(options?: {
206219 setCollapsed : vi . fn ( ( value : boolean ) => seriesCollapsed . set ( value ) ) ,
207220 } ,
208221 } ,
222+ {
223+ provide : SortService ,
224+ useValue : {
225+ applySort : vi . fn ( ( b : Book [ ] ) => b ) ,
226+ } ,
227+ } ,
228+ {
229+ provide : BookSelectionService ,
230+ useValue : {
231+ selectedBooks : signal ( [ ] ) ,
232+ selectedCount : signal ( 0 ) ,
233+ deselectAll : vi . fn ( ) ,
234+ setCurrentBooks : vi . fn ( ) ,
235+ } ,
236+ } ,
237+ {
238+ provide : BookNavigationService ,
239+ useValue : {
240+ setAvailableBookIds : vi . fn ( ) ,
241+ } ,
242+ } ,
243+ {
244+ provide : BookBrowserScrollService ,
245+ useValue : {
246+ createKey : vi . fn ( ( ) => 'test-key' ) ,
247+ savePosition : vi . fn ( ) ,
248+ getPosition : vi . fn ( ( ) => 0 ) ,
249+ } ,
250+ } ,
209251 { provide : ConfirmationService , useValue : { confirm : vi . fn ( ) } } ,
210252 { provide : TaskHelperService , useValue : { } } ,
211253 {
@@ -255,12 +297,14 @@ function createHarness(options?: {
255297 if ( f . libraryId ) result = result . filter ( b => b . libraryId === f . libraryId ) ;
256298
257299 const s = _sort ( ) ;
258- const dir = s . dir === 'asc' ? 1 : - 1 ;
259300 result = [ ...result ] . sort ( ( a , b ) => {
260- if ( s . field === 'title' ) return dir * ( ( a . metadata ?. title ?? '' ) as string ) . localeCompare ( ( b . metadata ?. title ?? '' ) as string ) ;
261- if ( s . field === 'addedOn' ) return dir * ( a . addedOn ?? '' ) . localeCompare ( b . addedOn ?? '' ) ;
262- return 0 ;
301+ const aVal = s . field === 'title' ? ( a . metadata ?. title ?? '' ) : ( a . addedOn ?? '' ) ;
302+ const bVal = s . field === 'title' ? ( b . metadata ?. title ?? '' ) : ( b . addedOn ?? '' ) ;
303+ return String ( aVal ) . localeCompare ( String ( bVal ) ) ;
263304 } ) ;
305+ if ( s . dir === 'desc' ) {
306+ result . reverse ( ) ;
307+ }
264308 return result ;
265309 } ) ;
266310
@@ -384,7 +428,7 @@ describe('BookBrowserComponent', () => {
384428
385429 component . onViewModeChange ( VIEW_MODES . TABLE ) ;
386430
387- expect ( component . currentViewMode ( ) ) . toBe ( VIEW_MODES . TABLE ) ;
431+ expect ( ( component as unknown as { currentViewMode : ( ) => string } ) . currentViewMode ( ) ) . toBe ( VIEW_MODES . TABLE ) ;
388432 expect ( queryParamsService . updateViewMode ) . toHaveBeenCalledWith ( VIEW_MODES . TABLE ) ;
389433 } ) ;
390434
@@ -404,7 +448,7 @@ describe('BookBrowserComponent', () => {
404448 TestBed . flushEffects ( ) ;
405449
406450 expect ( component . books ( ) ?. map ( book => book . id ) ) . toEqual ( [ 1 , 2 ] ) ;
407- expect ( component . hasRenderedBooks ( ) ) . toBe ( true ) ;
451+ expect ( ( component as unknown as { hasRenderedBooks : ( ) => boolean } ) . hasRenderedBooks ( ) ) . toBe ( true ) ;
408452 expect ( component . isBooksRefreshing ( ) ) . toBe ( false ) ;
409453 } ) ;
410454
@@ -420,12 +464,12 @@ describe('BookBrowserComponent', () => {
420464 TestBed . flushEffects ( ) ;
421465
422466 expect ( component . books ( ) ) . toBeUndefined ( ) ;
423- expect ( component . hasRenderedBooks ( ) ) . toBe ( false ) ;
467+ expect ( ( component as unknown as { hasRenderedBooks : ( ) => boolean } ) . hasRenderedBooks ( ) ) . toBe ( false ) ;
424468
425469 vi . runOnlyPendingTimers ( ) ;
426470
427471 expect ( component . books ( ) ?. map ( book => book . id ) ) . toEqual ( [ 3 ] ) ;
428- expect ( component . hasRenderedBooks ( ) ) . toBe ( true ) ;
472+ expect ( ( component as unknown as { hasRenderedBooks : ( ) => boolean } ) . hasRenderedBooks ( ) ) . toBe ( true ) ;
429473 } ) ;
430474
431475 it ( 'hides loading placeholders when the books query is in an error state' , ( ) => {
@@ -434,9 +478,25 @@ describe('BookBrowserComponent', () => {
434478 isBooksLoading : true ,
435479 } ) ;
436480
437- expect ( component . showBooksLoadingPlaceholder ( ) ) . toBe ( false ) ;
438- expect ( component . showGridLoadingPlaceholder ( ) ) . toBe ( false ) ;
439- expect ( component . showTableLoadingPlaceholder ( ) ) . toBe ( false ) ;
481+ const comp = component as unknown as {
482+ showBooksLoadingPlaceholder : ( ) => boolean ;
483+ showGridLoadingPlaceholder : ( ) => boolean ;
484+ showTableLoadingPlaceholder : ( ) => boolean ;
485+ } ;
486+ expect ( comp . showBooksLoadingPlaceholder ( ) ) . toBe ( false ) ;
487+ expect ( comp . showGridLoadingPlaceholder ( ) ) . toBe ( false ) ;
488+ expect ( comp . showTableLoadingPlaceholder ( ) ) . toBe ( false ) ;
489+ } ) ;
490+
491+ it ( 'calls SeriesCollapseFilter.collapseBooks when computing pipelineInputs' , ( ) => {
492+ createHarness ( ) ;
493+ const filter = TestBed . inject ( SeriesCollapseFilter ) ;
494+ const collapseBooksSpy = vi . spyOn ( filter , 'collapseBooks' ) ;
495+
496+ vi . runOnlyPendingTimers ( ) ;
497+ TestBed . flushEffects ( ) ;
498+
499+ expect ( collapseBooksSpy ) . toHaveBeenCalled ( ) ;
440500 } ) ;
441501
442502 it ( 'triggers next page fetch when scrolled near the bottom of rendered content' , async ( ) => {
@@ -462,7 +522,6 @@ describe('BookBrowserComponent', () => {
462522 removeEventListener : vi . fn ( ) ,
463523 } as unknown as HTMLElement ;
464524
465- component . currentViewMode . set ( VIEW_MODES . GRID ) ;
466525 component . scrollContainerRef = { nativeElement : mockElement } as ElementRef < HTMLElement > ;
467526
468527 // Trigger initial check
0 commit comments