diff --git a/src/app/core/shared/search/search-filter.service.spec.ts b/src/app/core/shared/search/search-filter.service.spec.ts index f18999bbe6..435e452549 100644 --- a/src/app/core/shared/search/search-filter.service.spec.ts +++ b/src/app/core/shared/search/search-filter.service.spec.ts @@ -1,3 +1,4 @@ +import { waitForAsync, TestBed } from '@angular/core/testing'; import { SearchFilterService } from './search-filter.service'; import { Store } from '@ngrx/store'; import { @@ -12,9 +13,13 @@ import { import { SearchFiltersState } from '../../../shared/search/search-filters/search-filter/search-filter.reducer'; import { SearchFilterConfig } from '../../../shared/search/models/search-filter-config.model'; import { FilterType } from '../../../shared/search/models/filter-type.model'; -import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub'; import { of as observableOf } from 'rxjs'; import { SortDirection, SortOptions } from '../../cache/models/sort-options.model'; +import { SearchServiceStub } from '../../../shared/testing/search-service.stub'; +import { SearchService } from './search.service'; +import { RouteService } from '../../services/route.service'; +import { routeServiceStub } from '../../../shared/testing/route-service.stub'; +import { SearchOptions } from '../../../shared/search/models/search-options.model'; describe('SearchFilterService', () => { let service: SearchFilterService; @@ -28,7 +33,8 @@ describe('SearchFilterService', () => { }); const value1 = 'random value'; - // const value2 = 'another value'; + + let searchService: SearchServiceStub; const store: Store = jasmine.createSpyObj('store', { /* eslint-disable no-empty,@typescript-eslint/no-empty-function */ dispatch: {}, @@ -36,36 +42,20 @@ describe('SearchFilterService', () => { select: observableOf(true) }); - const routeServiceStub: any = { - /* eslint-disable no-empty,@typescript-eslint/no-empty-function */ - hasQueryParamWithValue: (param: string, value: string) => { - }, - hasQueryParam: (param: string) => { - }, - removeQueryParameterValue: (param: string, value: string) => { - }, - addQueryParameterValue: (param: string, value: string) => { - }, - getQueryParameterValue: (param: string) => { - }, - getQueryParameterValues: (param: string) => { - return observableOf({}); - }, - getQueryParamsWithPrefix: (param: string) => { - return observableOf({}); - }, - getRouteParameterValue: (param: string) => { - } - /* eslint-enable no-empty, @typescript-eslint/no-empty-function */ - }; - const activatedRoute: any = new ActivatedRouteStub(); - const searchServiceStub: any = { - uiSearchRoute: '/search' - }; + beforeEach(waitForAsync(() => { + searchService = new SearchServiceStub(); - beforeEach(() => { - service = new SearchFilterService(store, routeServiceStub); - }); + TestBed.configureTestingModule({ + providers: [ + SearchFilterService, + { provide: SearchService, useValue: searchService }, + { provide: Store, useValue: store }, + { provide: RouteService, useValue: routeServiceStub }, + ], + }); + + service = TestBed.inject(SearchFilterService); + })); describe('when the initializeFilter method is triggered', () => { beforeEach(() => { @@ -269,4 +259,18 @@ describe('SearchFilterService', () => { }); }); + + describe('when findSuggestions is called with query \'test\'', () => { + const query = 'test'; + const searchOptions = new SearchOptions({}); + + beforeEach(() => { + spyOn(searchService, 'getFacetValuesFor').and.returnValue(observableOf()); + service.findSuggestions(mockFilterConfig, searchOptions, query); + }); + + it('should call getFacetValuesFor on the component\'s SearchService with the right query', () => { + expect(searchService.getFacetValuesFor).toHaveBeenCalledWith(mockFilterConfig, 1, searchOptions, query); + }); + }); }); diff --git a/src/app/core/shared/search/search-filter.service.ts b/src/app/core/shared/search/search-filter.service.ts index 2eafdf268a..14892bc78e 100644 --- a/src/app/core/shared/search/search-filter.service.ts +++ b/src/app/core/shared/search/search-filter.service.ts @@ -1,4 +1,4 @@ -import { BehaviorSubject, combineLatest as observableCombineLatest, Observable } from 'rxjs'; +import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; import { distinctUntilChanged, map } from 'rxjs/operators'; import { Injectable, InjectionToken, EventEmitter } from '@angular/core'; import { @@ -22,6 +22,15 @@ import { RouteService } from '../../services/route.service'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { Params } from '@angular/router'; import { AppliedFilter } from '../../../shared/search/models/applied-filter.model'; +import { SearchOptions } from '../../../shared/search/models/search-options.model'; +import { getFirstSucceededRemoteData } from '../operators'; +import { FacetValue } from '../../../shared/search/models/facet-value.model'; +import { PaginatedList } from '../../data/paginated-list.model'; +import { RemoteData } from '../../data/remote-data'; +import { stripOperatorFromFilterValue, getFacetValueForType } from '../../../shared/search/search.utils'; +import { EmphasizePipe } from '../../../shared/utils/emphasize.pipe'; +import { SearchService } from './search.service'; +import { InputSuggestion } from '../../../shared/input-suggestions/input-suggestions.model'; const filterStateSelector = (state: SearchFiltersState) => state.searchFilter; @@ -36,8 +45,11 @@ export const CHANGE_APPLIED_FILTERS: InjectionToken, - private routeService: RouteService) { + constructor( + protected searchService: SearchService, + protected store: Store, + protected routeService: RouteService, + ) { } /** @@ -146,6 +158,42 @@ export class SearchFilterService { ); } + /** + * Updates the found facet value suggestions for a given query + * Transforms the found values into display values + * + * @param searchFilterConfig The search filter config + * @param searchOptions The search options + * @param query The query for which is being searched + */ + findSuggestions(searchFilterConfig: SearchFilterConfig, searchOptions: SearchOptions, query: string): Observable { + if (isNotEmpty(query)) { + return this.searchService.getFacetValuesFor(searchFilterConfig, 1, searchOptions, query.toLowerCase()).pipe( + getFirstSucceededRemoteData(), + map((rd: RemoteData>) => rd.payload.page.map((facet) => { + return { + displayValue: this.getDisplayValue(facet, query), + query: getFacetValueForType(facet, searchFilterConfig), + value: stripOperatorFromFilterValue(getFacetValueForType(facet, searchFilterConfig)) + }; + })), + ); + } else { + return observableOf([]); + } + } + + /** + * Transforms the facet value string, so if the query matches part of the value, it's emphasized in the value + * + * @param facet The value of the facet as returned by the server + * @param query The query that was used to search facet values + * @returns {string} The facet value with the query part emphasized + */ + getDisplayValue(facet: FacetValue, query: string): string { + return `${new EmphasizePipe().transform(facet.value, query)} (${facet.count})`; + } + /** * Checks if the state of a given filter is currently collapsed or not * @param {string} filterName The filtername for which the collapsed state is checked diff --git a/src/app/shared/search/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html index e008226d19..698b8427d5 100644 --- a/src/app/shared/search/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html @@ -17,7 +17,7 @@ - { expect(comp.filter).toEqual(''); }); }); - - describe('when findSuggestions is called with query \'test\'', () => { - const query = 'test'; - beforeEach(() => { - comp.findSuggestions(query); - }); - - it('should call getFacetValuesFor on the component\'s SearchService with the right query', () => { - expect((comp as any).searchService.getFacetValuesFor).toHaveBeenCalledWith(comp.filterConfig, 1, {}, query); - }); - }); }); diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts index 6bf97538d2..c9115a9703 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts @@ -6,21 +6,18 @@ import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, import { debounceTime, distinctUntilChanged, filter, map, mergeMap, switchMap, take, tap } from 'rxjs/operators'; import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service'; -import { PaginatedList } from '../../../../../core/data/paginated-list.model'; -import { RemoteData } from '../../../../../core/data/remote-data'; import { hasNoValue, hasValue, isNotEmpty } from '../../../../empty.util'; -import { EmphasizePipe } from '../../../../utils/emphasize.pipe'; import { FacetValue } from '../../../models/facet-value.model'; import { SearchFilterConfig } from '../../../models/search-filter-config.model'; import { SearchService } from '../../../../../core/shared/search/search.service'; import { FILTER_CONFIG, IN_PLACE_SEARCH, REFRESH_FILTER, SearchFilterService, CHANGE_APPLIED_FILTERS } from '../../../../../core/shared/search/search-filter.service'; import { SearchConfigurationService } from '../../../../../core/shared/search/search-configuration.service'; -import { getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload } from '../../../../../core/shared/operators'; +import { getFirstSucceededRemoteDataPayload } from '../../../../../core/shared/operators'; import { InputSuggestion } from '../../../../input-suggestions/input-suggestions.model'; import { SearchOptions } from '../../../models/search-options.model'; import { SEARCH_CONFIG_SERVICE } from '../../../../../my-dspace-page/my-dspace-page.component'; import { currentPath } from '../../../../utils/route.utils'; -import { getFacetValueForType, stripOperatorFromFilterValue, addOperatorToFilterValue } from '../../../search.utils'; +import { stripOperatorFromFilterValue, addOperatorToFilterValue } from '../../../search.utils'; import { FacetValues } from '../../../models/facet-values.model'; import { AppliedFilter } from '../../../models/applied-filter.model'; @@ -61,7 +58,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { /** * Emits the result values for this filter found by the current filter query */ - filterSearchResults: Observable = observableOf([]); + filterSearchResults$: Observable = observableOf([]); /** * Emits the active values for this filter @@ -200,34 +197,8 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { .forEach((sub) => sub.unsubscribe()); } - /** - * Updates the found facet value suggestions for a given query - * Transforms the found values into display values - * @param data The query for which is being searched - */ - findSuggestions(data): void { - if (isNotEmpty(data)) { - this.searchOptions$.pipe(take(1)).subscribe( - (options) => { - this.filterSearchResults = this.searchService.getFacetValuesFor(this.filterConfig, 1, options, data.toLowerCase()) - .pipe( - getFirstSucceededRemoteData(), - map( - (rd: RemoteData>) => { - return rd.payload.page.map((facet) => { - return { - displayValue: this.getDisplayValue(facet, data), - query: this.getFacetValue(facet), - value: stripOperatorFromFilterValue(this.getFacetValue(facet)) - }; - }); - } - )); - } - ); - } else { - this.filterSearchResults = observableOf([]); - } + findSuggestions(query: string): void { + this.filterSearchResults$ = this.filterService.findSuggestions(this.filterConfig, this.searchOptions$.value, query); } /** @@ -250,18 +221,11 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { }); this.filter = ''; } - this.filterSearchResults = observableOf([]); + this.filterSearchResults$ = observableOf([]); }); } } - /** - * Retrieve facet value - */ - protected getFacetValue(facet: FacetValue): string { - return getFacetValueForType(facet, this.filterConfig); - } - protected retrieveFilterValues(useCachedVersionIfAvailable = true): Observable { return observableCombineLatest([this.searchOptions$, this.currentPage]).pipe( switchMap(([options, page]: [SearchOptions, number]) => this.searchService.getFacetValuesFor(this.filterConfig, page, options, null, useCachedVersionIfAvailable).pipe( @@ -309,16 +273,6 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { ); } - /** - * Transforms the facet value string, so if the query matches part of the value, it's emphasized in the value - * @param {FacetValue} facet The value of the facet as returned by the server - * @param {string} query The query that was used to search facet values - * @returns {string} The facet value with the query part emphasized - */ - getDisplayValue(facet: FacetValue, query: string): string { - return new EmphasizePipe().transform(facet.value, query) + ' (' + facet.count + ')'; - } - /** * Prevent unnecessary rerendering */ diff --git a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html index 1875cc8076..37ddeb081b 100644 --- a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html @@ -17,7 +17,7 @@ - -