diff --git a/src/app/shared/search/search.component.spec.ts b/src/app/shared/search/search.component.spec.ts index 3b9451d16aa..3020c5a1656 100644 --- a/src/app/shared/search/search.component.spec.ts +++ b/src/app/shared/search/search.component.spec.ts @@ -41,6 +41,7 @@ import { } from '@dspace/core/shared/search/search-filters/search-config.model'; import { SidebarServiceStub } from '@dspace/core/testing/sidebar-service.stub'; import { + createFailedRemoteDataObject$, createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$, } from '@dspace/core/utilities/remote-data.utils'; @@ -200,6 +201,7 @@ export function configureSearchComponentTestingModule(compType, additionalDeclar getConfigurationSortOptions: sortOptionsList, getConfig: filtersConfigRD$, getConfigurationSearchConfig: of(searchConfig), + getSearchConfigurationFor: createSuccessfulRemoteDataObject$(searchConfig), getCurrentConfiguration: of('default'), getCurrentScope: of('test-id'), getCurrentSort: of(sortOptionsList[0]), @@ -450,6 +452,30 @@ describe('SearchComponent', () => { //Check that the last method from which the search depend upon is being called expect(searchManagerStub.search).toHaveBeenCalled(); })); + + describe('when the search configuration request fails', () => { + beforeEach(() => { + searchConfigurationServiceStub.getSearchConfigurationFor.and.returnValue( + createFailedRemoteDataObject$('Error fetching search configuration', 500), + ); + }); + + it('should still initialize, so the search results error can be rendered', fakeAsync(() => { + comp.ngOnInit(); + tick(100); + + let initialized: boolean; + comp.initialized$.subscribe((res) => initialized = res); + expect(initialized).toBeTrue(); + })); + + it('should still request results, whose failure is what the user is shown', fakeAsync(() => { + comp.ngOnInit(); + tick(100); + + expect(searchManagerStub.search).toHaveBeenCalled(); + })); + }); }); }); }); diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index 7d9a76d651c..1af38c23191 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -38,7 +38,10 @@ import { DSpaceObject } from '@dspace/core/shared/dspace-object.model'; import { followLink } from '@dspace/core/shared/follow-link-config.model'; import { Item } from '@dspace/core/shared/item.model'; import { ListableObject } from '@dspace/core/shared/object-collection/listable-object.model'; -import { getFirstCompletedRemoteData } from '@dspace/core/shared/operators'; +import { + getAllCompletedRemoteData, + getFirstCompletedRemoteData, +} from '@dspace/core/shared/operators'; import { PaginatedSearchOptions } from '@dspace/core/shared/search/models/paginated-search-options.model'; import { SearchFilterConfig } from '@dspace/core/shared/search/models/search-filter-config.model'; import { SearchObjects } from '@dspace/core/shared/search/models/search-objects.model'; @@ -401,9 +404,21 @@ export class SearchComponent implements OnDestroy, OnInit { // Determinate PaginatedSearchOptions and listen to any update on it const configuration$: Observable = this.searchConfigService .getCurrentConfiguration(this.configuration).pipe(distinctUntilChanged()); + // A failed search-configuration request must not stall the whole component. + // getConfigurationSearchConfig() pipes through + // getAllSucceededRemoteDataPayload(), so on failure it never emits: the + // combineLatest below never fires, initialized$ stays false, and the entire + // template is skipped — leaving a blank page in which not even + // ds-search-results (which renders the error) is created. Treat a failed + // configuration as "no sort options" so the component still initializes and + // the search results error can surface. const searchSortOptions$: Observable = combineLatest([configuration$, this.currentScope$]).pipe( - switchMap(([configuration, scope]: [string, string]) => this.searchConfigService.getConfigurationSearchConfig(configuration, scope)), - map((searchConfig: SearchConfig) => this.searchConfigService.getConfigurationSortOptions(searchConfig)), + switchMap(([configuration, scope]: [string, string]) => this.searchConfigService.getSearchConfigurationFor(scope, configuration).pipe( + getAllCompletedRemoteData(), + map((searchConfigRD: RemoteData) => searchConfigRD.hasSucceeded + ? this.searchConfigService.getConfigurationSortOptions(searchConfigRD.payload) + : []), + )), distinctUntilChanged(), ); const sortOption$: Observable = searchSortOptions$.pipe(