Skip to content

Commit c14a68b

Browse files
Mattia VianelliMattia Vianelli
authored andcommitted
DURACOM-441 Provided fallback logic for page search, we now fallback to the highest available totalPages if we reach an out of bound page
1 parent 993c89a commit c14a68b

File tree

3 files changed

+85
-1
lines changed

3 files changed

+85
-1
lines changed

src/app/search-page/configuration-search-page.component.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
AppConfig,
1515
} from '@dspace/config/app-config.interface';
1616
import { SearchManager } from '@dspace/core/browse/search-manager';
17+
import { PaginationService } from '@dspace/core/pagination/pagination.service';
1718
import { RouteService } from '@dspace/core/services/route.service';
1819
import { TranslateModule } from '@ngx-translate/core';
1920

@@ -69,7 +70,8 @@ export class ConfigurationSearchPageComponent extends SearchComponent {
6970
@Inject(APP_CONFIG) protected appConfig: AppConfig,
7071
@Inject(PLATFORM_ID) public platformId: string,
7172
protected searchManager: SearchManager,
73+
protected paginationService: PaginationService,
7274
) {
73-
super(service, sidebarService, windowService, searchConfigService, routeService, router, appConfig, platformId, searchManager);
75+
super(service, sidebarService, windowService, searchConfigService, routeService, router, appConfig, platformId, searchManager, paginationService);
7476
}
7577
}

src/app/shared/search/search.component.spec.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
import { CommunityDataService } from '@dspace/core/data/community-data.service';
2424
import { RemoteData } from '@dspace/core/data/remote-data';
2525
import { APP_DATA_SERVICES_MAP } from '@dspace/core/data-services-map-type';
26+
import { PaginationService } from '@dspace/core/pagination/pagination.service';
2627
import { PaginationComponentOptions } from '@dspace/core/pagination/pagination-component-options.model';
2728
import {
2829
getCollectionPageRoute,
@@ -31,6 +32,7 @@ import {
3132
import { RouteService } from '@dspace/core/services/route.service';
3233
import { DSpaceObject } from '@dspace/core/shared/dspace-object.model';
3334
import { Item } from '@dspace/core/shared/item.model';
35+
import { PageInfo } from '@dspace/core/shared/page-info.model';
3436
import { FilterType } from '@dspace/core/shared/search/models/filter-type.model';
3537
import { PaginatedSearchOptions } from '@dspace/core/shared/search/models/paginated-search-options.model';
3638
import { SearchFilterConfig } from '@dspace/core/shared/search/models/search-filter-config.model';
@@ -39,6 +41,7 @@ import {
3941
SearchConfig,
4042
SortConfig,
4143
} from '@dspace/core/shared/search/search-filters/search-config.model';
44+
import { PaginationServiceStub } from '@dspace/core/testing/pagination-service.stub';
4245
import { SidebarServiceStub } from '@dspace/core/testing/sidebar-service.stub';
4346
import {
4447
createSuccessfulRemoteDataObject,
@@ -253,6 +256,7 @@ export function configureSearchComponentTestingModule(compType, additionalDeclar
253256
{ provide: APP_DATA_SERVICES_MAP, useValue: {} },
254257
{ provide: APP_CONFIG, useValue: environment },
255258
{ provide: PLATFORM_ID, useValue: 'browser' },
259+
{ provide: PaginationService, useClass: PaginationServiceStub },
256260
],
257261
schemas: [NO_ERRORS_SCHEMA],
258262
}).overrideComponent(compType, {
@@ -452,4 +456,71 @@ describe('SearchComponent', () => {
452456
}));
453457
});
454458
});
459+
460+
describe('Fallback pagination logic', () => {
461+
let paginationServiceStub: PaginationServiceStub;
462+
463+
beforeEach(() => {
464+
paginationServiceStub = TestBed.inject(PaginationService) as any;
465+
});
466+
467+
afterEach(() => {
468+
searchManagerStub.search.and.returnValue(mockResultsRD$);
469+
paginationServiceStub.updateRoute.calls.reset();
470+
});
471+
472+
it('should navigate to last available page when requested page exceeds total pages', fakeAsync(() => {
473+
// Mock search to return empty results with totalPages = 3
474+
const emptySearchResults: SearchObjects<DSpaceObject> = Object.assign(new SearchObjects(), {
475+
page: [],
476+
pageInfo: Object.assign(new PageInfo(), { totalPages: 3, totalElements: 25, elementsPerPage: 10, currentPage: 5 }),
477+
});
478+
const emptyResultsRD = createSuccessfulRemoteDataObject(emptySearchResults);
479+
searchManagerStub.search.and.returnValue(of(emptyResultsRD));
480+
481+
fixture.detectChanges();
482+
tick(100);
483+
484+
// Reset updateRoute spy to only track calls after the page change
485+
paginationServiceStub.updateRoute.calls.reset();
486+
487+
// Emit new search options with currentPage = 5 (exceeding totalPages = 3)
488+
const paginationOptions = Object.assign(new PaginationComponentOptions(), {
489+
id: paginationId,
490+
currentPage: 5,
491+
pageSize: 10,
492+
});
493+
paginatedSearchOptions$.next(new PaginatedSearchOptions({ pagination: paginationOptions }));
494+
tick(100);
495+
496+
expect(paginationServiceStub.updateRoute).toHaveBeenCalledWith(paginationId, { page: 3 });
497+
}));
498+
499+
it('should NOT navigate when requested page is within total pages', fakeAsync(() => {
500+
// Mock search to return results on page 2
501+
const searchResultsPage2: SearchObjects<DSpaceObject> = Object.assign(new SearchObjects(), {
502+
page: [mockDso],
503+
pageInfo: Object.assign(new PageInfo(), { totalPages: 3, totalElements: 25, elementsPerPage: 10, currentPage: 2 }),
504+
});
505+
const resultsRD = createSuccessfulRemoteDataObject(searchResultsPage2);
506+
searchManagerStub.search.and.returnValue(of(resultsRD));
507+
508+
fixture.detectChanges();
509+
tick(100);
510+
511+
// Reset updateRoute spy
512+
paginationServiceStub.updateRoute.calls.reset();
513+
514+
// Emit new search options with currentPage = 2 (within totalPages = 3)
515+
const paginationOptions = Object.assign(new PaginationComponentOptions(), {
516+
id: paginationId,
517+
currentPage: 2,
518+
pageSize: 10,
519+
});
520+
paginatedSearchOptions$.next(new PaginatedSearchOptions({ pagination: paginationOptions }));
521+
tick(100);
522+
523+
expect(paginationServiceStub.updateRoute).not.toHaveBeenCalled();
524+
}));
525+
});
455526
});

src/app/shared/search/search.component.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { SearchManager } from '@dspace/core/browse/search-manager';
2626
import { SortOptions } from '@dspace/core/cache/models/sort-options.model';
2727
import { PaginatedList } from '@dspace/core/data/paginated-list.model';
2828
import { RemoteData } from '@dspace/core/data/remote-data';
29+
import { PaginationService } from '@dspace/core/pagination/pagination.service';
2930
import {
3031
COLLECTION_MODULE_PATH,
3132
COMMUNITY_MODULE_PATH,
@@ -356,6 +357,7 @@ export class SearchComponent implements OnDestroy, OnInit {
356357
@Inject(APP_CONFIG) protected appConfig: AppConfig,
357358
@Inject(PLATFORM_ID) public platformId: string,
358359
protected searchManager: SearchManager,
360+
protected paginationService: PaginationService,
359361
) {
360362
this.isXsOrSm$ = this.windowService.isXsOrSm();
361363
}
@@ -555,6 +557,15 @@ export class SearchComponent implements OnDestroy, OnInit {
555557
...followLinks,
556558
).pipe(getFirstCompletedRemoteData())
557559
.subscribe((results: RemoteData<SearchObjects<DSpaceObject>>) => {
560+
// Fallback logic: if no results and requested page > 1, navigate to the last available page
561+
if (results.hasSucceeded && results.payload?.page?.length === 0 && searchOptionsWithHidden.pagination.currentPage > 1) {
562+
const totalPages = results.payload?.pageInfo?.totalPages;
563+
if (totalPages && totalPages > 0 && searchOptionsWithHidden.pagination.currentPage > totalPages) {
564+
// Update the route to the last available page
565+
this.paginationService.updateRoute(this.paginationId, { page: totalPages });
566+
return;
567+
}
568+
}
558569
if (results.hasSucceeded) {
559570
if (this.trackStatistics) {
560571
this.service.trackSearch(searchOptionsWithHidden, results.payload);

0 commit comments

Comments
 (0)