From 47f89a6f9d319ef05d54c81041e448408042b71d Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 13 Feb 2024 11:36:26 +0100 Subject: [PATCH] 111731: Moved common remove/add queryParam logic for facet option/label to routeService --- src/app/core/services/route.service.spec.ts | 49 +++++- src/app/core/services/route.service.ts | 49 ++++++ .../search-configuration.service.spec.ts | 54 ++---- .../search/search-configuration.service.ts | 34 ++-- .../shared/search/search-filter.service.ts | 8 +- .../search-authority-filter.component.html | 4 +- .../search-boolean-filter.component.html | 4 +- .../search-facet-option.component.html | 2 +- .../search-facet-option.component.spec.ts | 123 ++++---------- .../search-facet-option.component.ts | 59 +++---- .../search-facet-range-option.component.ts | 8 +- ...earch-facet-selected-option.component.html | 2 +- ...ch-facet-selected-option.component.spec.ts | 158 ++++-------------- .../search-facet-selected-option.component.ts | 89 ++++------ .../search-hierarchy-filter.component.html | 4 +- .../search-text-filter.component.html | 4 +- .../search-label-range.component.html | 4 +- .../search-label-range.component.spec.ts | 31 +++- .../search-label-range.component.ts | 32 +++- .../search-label/search-label.component.html | 2 +- .../search-label.component.spec.ts | 27 ++- .../search-label/search-label.component.ts | 24 ++- .../search-configuration-service.stub.ts | 6 +- .../testing/search-filter-service.stub.ts | 75 +++++++++ 24 files changed, 436 insertions(+), 416 deletions(-) create mode 100644 src/app/shared/testing/search-filter-service.stub.ts diff --git a/src/app/core/services/route.service.spec.ts b/src/app/core/services/route.service.spec.ts index 18a916753a..802368f38d 100644 --- a/src/app/core/services/route.service.spec.ts +++ b/src/app/core/services/route.service.spec.ts @@ -9,6 +9,8 @@ import { RouteService } from './route.service'; import { RouterMock } from '../../shared/mocks/router.mock'; import { TestScheduler } from 'rxjs/testing'; import { AddUrlToHistoryAction } from '../history/history.actions'; +import { ActivatedRouteStub } from 'src/app/shared/testing/active-router.stub'; +import { take } from 'rxjs/operators'; describe('RouteService', () => { let scheduler: TestScheduler; @@ -29,6 +31,7 @@ describe('RouteService', () => { select: jasmine.createSpy('select') }); + let route: ActivatedRouteStub; const router = new RouterMock(); router.setParams(convertToParamMap(paramObject)); @@ -36,16 +39,11 @@ describe('RouteService', () => { paramObject[paramName2] = [paramValue2a, paramValue2b]; beforeEach(waitForAsync(() => { + route = new ActivatedRouteStub(paramObject); + return TestBed.configureTestingModule({ providers: [ - { - provide: ActivatedRoute, - useValue: { - queryParams: observableOf(paramObject), - params: observableOf(paramObject), - queryParamMap: observableOf(convertToParamMap(paramObject)) - }, - }, + { provide: ActivatedRoute, useValue: route }, { provide: Router, useValue: router }, { provide: Store, useValue: store }, ] @@ -181,4 +179,39 @@ describe('RouteService', () => { }); }); }); + + describe('getParamsWithoutAppliedFilter', () => { + beforeEach(() => { + route.testParams = { + 'query': '', + 'spc.page': '1', + 'f.author': '1282121b-5394-4689-ab93-78d537764052,authority', + 'f.has_content_in_original_bundle': 'true,equals', + }; + }); + + it('should remove the parameter completely if only one value is defined', (done: DoneFn) => { + service.getParamsExceptValue('f.author', '1282121b-5394-4689-ab93-78d537764052,authority').pipe(take(1)).subscribe((params: Params) => { + expect(params).toEqual({ + 'query': '', + 'spc.page': '1', + 'f.has_content_in_original_bundle': 'true,equals', + }); + done(); + }); + }); + + it('should return all params except the applied filter even when multiple filters of the same type are selected', (done: DoneFn) => { + route.testParams['f.author'] = ['1282121b-5394-4689-ab93-78d537764052,authority', '71b91a28-c280-4352-a199-bd7fc3312501,authority']; + service.getParamsExceptValue('f.author', '1282121b-5394-4689-ab93-78d537764052,authority').pipe(take(1)).subscribe((params: Params) => { + expect(params).toEqual({ + 'query': '', + 'spc.page': '1', + 'f.author': ['71b91a28-c280-4352-a199-bd7fc3312501,authority'], + 'f.has_content_in_original_bundle': 'true,equals', + }); + done(); + }); + }); + }); }); diff --git a/src/app/core/services/route.service.ts b/src/app/core/services/route.service.ts index b84bb40373..6c5b73f755 100644 --- a/src/app/core/services/route.service.ts +++ b/src/app/core/services/route.service.ts @@ -225,4 +225,53 @@ export class RouteService { } ); } + + /** + * Returns all the query parameters except for the one with the given name & value. + * + * @param name The name of the query param to exclude + * @param value The optional value that the query param needs to have to be excluded + */ + getParamsExceptValue(name: string, value?: string): Observable { + return this.route.queryParams.pipe( + map((params: Params) => { + const newParams: Params = Object.assign({}, params); + const queryParamValues: string | string[] = newParams[name]; + + if (queryParamValues === value || value === undefined) { + delete newParams[name]; + } else if (Array.isArray(queryParamValues) && queryParamValues.includes(value)) { + newParams[name] = (queryParamValues as string[]).filter((paramValue: string) => paramValue !== value); + } + return newParams; + }), + ); + } + + /** + * Returns all the existing query parameters and the new value pair with the given name & value. + * + * @param name The name of the query param for which you need to add the value + * @param value The optional value that the query param needs to have in addition to the current ones + */ + getParamsWithAdditionalValue(name: string, value: string): Observable { + return this.route.queryParams.pipe( + map((params: Params) => { + const newParams: Params = Object.assign({}, params); + const queryParamValues: string | string[] = newParams[name]; + + if (queryParamValues === undefined) { + newParams[name] = value; + } else { + if (Array.isArray(queryParamValues)) { + newParams[name] = [...queryParamValues, value]; + } else { + newParams[name] = [queryParamValues, value]; + } + } + return newParams; + }), + ); + } + } diff --git a/src/app/core/shared/search/search-configuration.service.spec.ts b/src/app/core/shared/search/search-configuration.service.spec.ts index 73447b63d8..21b09497e2 100644 --- a/src/app/core/shared/search/search-configuration.service.spec.ts +++ b/src/app/core/shared/search/search-configuration.service.spec.ts @@ -13,8 +13,6 @@ import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.u import { getMockRequestService } from '../../../shared/mocks/request.service.mock'; import { RequestEntry } from '../../data/request-entry.model'; import { SearchObjects } from '../../../shared/search/models/search-objects.model'; -import { Params } from '@angular/router'; -import { addOperatorToFilterValue } from '../../../shared/search/search.utils'; import { AppliedFilter } from '../../../shared/search/models/applied-filter.model'; describe('SearchConfigurationService', () => { @@ -41,7 +39,8 @@ describe('SearchConfigurationService', () => { const routeService = jasmine.createSpyObj('RouteService', { getQueryParameterValue: observableOf(value1), getQueryParamsWithPrefix: observableOf(prefixFilter), - getRouteParameterValue: observableOf('') + getRouteParameterValue: observableOf(''), + getParamsExceptValue: observableOf({}), }); const paginationService = new PaginationServiceStub(); @@ -283,7 +282,7 @@ describe('SearchConfigurationService', () => { }); }); - describe('getParamsWithoutAppliedFilter', () => { + describe('unselectAppliedFilterParams', () => { let appliedFilter: AppliedFilter; beforeEach(() => { @@ -293,51 +292,18 @@ describe('SearchConfigurationService', () => { value: '1282121b-5394-4689-ab93-78d537764052', label: 'Odinson, Thor', }); - activatedRoute.testParams = { - 'query': '', - 'spc.page': '1', - 'f.author': addOperatorToFilterValue(appliedFilter.value, appliedFilter.operator), - 'f.has_content_in_original_bundle': addOperatorToFilterValue('true', 'equals'), - 'f.dateIssued.max': '2000', - }; }); - it('should return all params except the applied filter', (done: DoneFn) => { - service.getParamsWithoutAppliedFilter(appliedFilter.filter, appliedFilter.value, appliedFilter.operator).pipe(take(1)).subscribe((params: Params) => { - expect(params).toEqual({ - 'query': '', - 'spc.page': '1', - 'f.has_content_in_original_bundle': addOperatorToFilterValue('true', 'equals'), - 'f.dateIssued.max': '2000', - }); - done(); - }); + it('should return all params except the applied filter', () => { + service.unselectAppliedFilterParams(appliedFilter.filter, appliedFilter.value, appliedFilter.operator); + + expect(routeService.getParamsExceptValue).toHaveBeenCalledWith('f.author', '1282121b-5394-4689-ab93-78d537764052,authority'); }); - it('should return all params except the applied filter even when multiple filters of the same type are selected', (done: DoneFn) => { - activatedRoute.testParams['f.author'] = [addOperatorToFilterValue(appliedFilter.value, appliedFilter.operator), addOperatorToFilterValue('71b91a28-c280-4352-a199-bd7fc3312501', 'authority')]; - service.getParamsWithoutAppliedFilter(appliedFilter.filter, appliedFilter.value, appliedFilter.operator).pipe(take(1)).subscribe((params: Params) => { - expect(params).toEqual({ - 'query': '', - 'spc.page': '1', - 'f.author': [addOperatorToFilterValue('71b91a28-c280-4352-a199-bd7fc3312501', 'authority')], - 'f.has_content_in_original_bundle': addOperatorToFilterValue('true', 'equals'), - 'f.dateIssued.max': '2000', - }); - done(); - }); - }); + it('should be able to remove AppliedFilter without operator', () => { + service.unselectAppliedFilterParams('dateIssued.max', '2000'); - it('should be able to remove AppliedFilter without operator', (done: DoneFn) => { - service.getParamsWithoutAppliedFilter('dateIssued.max', '2000').pipe(take(1)).subscribe((params: Params) => { - expect(params).toEqual({ - 'query': '', - 'spc.page': '1', - 'f.author': addOperatorToFilterValue(appliedFilter.value, appliedFilter.operator), - 'f.has_content_in_original_bundle': addOperatorToFilterValue('true', 'equals'), - }); - done(); - }); + expect(routeService.getParamsExceptValue).toHaveBeenCalledWith('f.dateIssued.max', '2000'); }); }); }); diff --git a/src/app/core/shared/search/search-configuration.service.ts b/src/app/core/shared/search/search-configuration.service.ts index eb53f5b964..eb88c9e734 100644 --- a/src/app/core/shared/search/search-configuration.service.ts +++ b/src/app/core/shared/search/search-configuration.service.ts @@ -526,22 +526,26 @@ export class SearchConfigurationService implements OnDestroy { ); } - getParamsWithoutAppliedFilter(filterName: string, value: string, operator?: string): Observable { - return this.route.queryParams.pipe( - map((params: Params) => { - const newParams: Params = Object.assign({}, params); - const queryParamValues: string | string[] = newParams[`f.${filterName}`]; - const excludeValue = hasValue(operator) ? addOperatorToFilterValue(value, operator) : value; + /** + * Calculates the {@link Params} of the search after removing a filter with a certain value + * + * @param filterName The {@link AppliedFilter}'s name + * @param value The {@link AppliedFilter}'s value + * @param operator The {@link AppliedFilter}'s optional operator + */ + unselectAppliedFilterParams(filterName: string, value: string, operator?: string): Observable { + return this.routeService.getParamsExceptValue(`f.${filterName}`, hasValue(operator) ? addOperatorToFilterValue(value, operator) : value); + } - if (queryParamValues === excludeValue) { - delete newParams[`f.${filterName}`]; - } else if (queryParamValues?.includes(excludeValue)) { - newParams[`f.${filterName}`] = (queryParamValues as string[]) - .filter((paramValue: string) => paramValue !== excludeValue); - } - return newParams; - }), - ); + /** + * Calculates the {@link Params} of the search after removing a filter with a certain value + * + * @param filterName The {@link AppliedFilter}'s name + * @param value The {@link AppliedFilter}'s value + * @param operator The {@link AppliedFilter}'s optional operator + */ + selectNewAppliedFilterParams(filterName: string, value: string, operator?: string): Observable { + return this.routeService.getParamsWithAdditionalValue(`f.${filterName}`, hasValue(operator) ? addOperatorToFilterValue(value, operator) : value); } /** diff --git a/src/app/core/shared/search/search-filter.service.ts b/src/app/core/shared/search/search-filter.service.ts index 614b077079..2eafdf268a 100644 --- a/src/app/core/shared/search/search-filter.service.ts +++ b/src/app/core/shared/search/search-filter.service.ts @@ -63,7 +63,7 @@ export class SearchFilterService { * Fetch the current active scope from the query parameters * @returns {Observable} */ - getCurrentScope() { + getCurrentScope(): Observable { return this.routeService.getQueryParameterValue('scope'); } @@ -71,7 +71,7 @@ export class SearchFilterService { * Fetch the current query from the query parameters * @returns {Observable} */ - getCurrentQuery() { + getCurrentQuery(): Observable { return this.routeService.getQueryParameterValue('query'); } @@ -113,7 +113,7 @@ export class SearchFilterService { * Fetch the current active filters from the query parameters * @returns {Observable} */ - getCurrentFilters() { + getCurrentFilters(): Observable { return this.routeService.getQueryParamsWithPrefix('f.'); } @@ -121,7 +121,7 @@ export class SearchFilterService { * Fetch the current view from the query parameters * @returns {Observable} */ - getCurrentView() { + getCurrentView(): Observable { return this.routeService.getQueryParameterValue('view'); } 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 471769e564..e008226d19 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 @@ -1,9 +1,9 @@
- +
- +
diff --git a/src/app/shared/search/search-filters/search-filter/search-boolean-filter/search-boolean-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-boolean-filter/search-boolean-filter.component.html index 663f1b33c6..07fa57b0cc 100644 --- a/src/app/shared/search/search-filters/search-filter/search-boolean-filter/search-boolean-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-boolean-filter/search-boolean-filter.component.html @@ -1,9 +1,9 @@
- +
- +
diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html index 3c15eff127..17bc6ced3a 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html @@ -1,7 +1,7 @@ + [queryParams]="addQueryParams$ | async"> + [queryParams]="removeQueryParams | async">
- +
- +
diff --git a/src/app/shared/search/search-filters/search-filter/search-text-filter/search-text-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-text-filter/search-text-filter.component.html index 471769e564..784592d0be 100644 --- a/src/app/shared/search/search-filters/search-filter/search-text-filter/search-text-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-text-filter/search-text-filter.component.html @@ -1,9 +1,9 @@
- +
- +
diff --git a/src/app/shared/search/search-labels/search-label-range/search-label-range.component.html b/src/app/shared/search/search-labels/search-label-range/search-label-range.component.html index 2fb0d39f27..7174da1e95 100644 --- a/src/app/shared/search/search-labels/search-label-range/search-label-range.component.html +++ b/src/app/shared/search/search-labels/search-label-range/search-label-range.component.html @@ -1,13 +1,13 @@ {{('search.filters.applied.f.' + appliedFilter.filter + '.min') | translate}}: {{ min }} × {{('search.filters.applied.f.' + appliedFilter.filter + '.max') | translate}}: {{ max }} × diff --git a/src/app/shared/search/search-labels/search-label-range/search-label-range.component.spec.ts b/src/app/shared/search/search-labels/search-label-range/search-label-range.component.spec.ts index 13b5952c86..658bf46e51 100644 --- a/src/app/shared/search/search-labels/search-label-range/search-label-range.component.spec.ts +++ b/src/app/shared/search/search-labels/search-label-range/search-label-range.component.spec.ts @@ -10,17 +10,24 @@ import { addOperatorToFilterValue } from '../../search.utils'; import { RouterTestingModule } from '@angular/router/testing'; import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service'; import { SearchConfigurationServiceStub } from '../../../testing/search-configuration-service.stub'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../testing/pagination-service.stub'; +import { take } from 'rxjs/operators'; +import { of as observableOf } from 'rxjs'; +import { PaginationComponentOptions } from '../../../pagination/pagination-component-options.model'; -describe('SearchLabelComponent', () => { +describe('SearchLabelRangeComponent', () => { let comp: SearchLabelRangeComponent; let fixture: ComponentFixture; let route: ActivatedRouteStub; let searchConfigurationService: SearchConfigurationServiceStub; + let paginationService: PaginationServiceStub; const searchLink = '/search'; let appliedFilter: AppliedFilter; let initialRouteParams: Params; + let pagination: PaginationComponentOptions; function init(): void { appliedFilter = Object.assign(new AppliedFilter(), { @@ -31,16 +38,22 @@ describe('SearchLabelComponent', () => { }); initialRouteParams = { 'query': '', - 'spc.page': '1', + 'page-id.page': '5', 'f.author': addOperatorToFilterValue(appliedFilter.value, appliedFilter.operator), 'f.has_content_in_original_bundle': addOperatorToFilterValue('true', 'equals'), }; + pagination = Object.assign(new PaginationComponentOptions(), { + id: 'page-id', + currentPage: 1, + pageSize: 20, + }); } beforeEach(waitForAsync(async () => { init(); route = new ActivatedRouteStub(initialRouteParams); searchConfigurationService = new SearchConfigurationServiceStub(); + paginationService = new PaginationServiceStub(pagination); await TestBed.configureTestingModule({ imports: [ @@ -51,6 +64,7 @@ describe('SearchLabelComponent', () => { SearchLabelRangeComponent, ], providers: [ + { provide: PaginationService, useValue: paginationService }, { provide: SearchConfigurationService, useValue: searchConfigurationService }, { provide: SearchService, useValue: new SearchServiceStub(searchLink) }, { provide: ActivatedRoute, useValue: route }, @@ -65,7 +79,16 @@ describe('SearchLabelComponent', () => { fixture.detectChanges(); }); - it('should create', () => { - expect(comp).toBeTruthy(); + describe('updateRemoveParams', () => { + it('should always reset the page to 1', (done: DoneFn) => { + spyOn(searchConfigurationService, 'unselectAppliedFilterParams').and.returnValue(observableOf(initialRouteParams)); + + comp.updateRemoveParams('f.dateIssued.max', '2000').pipe(take(1)).subscribe((params: Params) => { + expect(params).toEqual(Object.assign({}, initialRouteParams, { + 'page-id.page': 1, + })); + done(); + }); + }); }); }); diff --git a/src/app/shared/search/search-labels/search-label-range/search-label-range.component.ts b/src/app/shared/search/search-labels/search-label-range/search-label-range.component.ts index d2b279bd18..838a42cbfe 100644 --- a/src/app/shared/search/search-labels/search-label-range/search-label-range.component.ts +++ b/src/app/shared/search/search-labels/search-label-range/search-label-range.component.ts @@ -6,6 +6,8 @@ import { currentPath } from '../../../utils/route.utils'; import { AppliedFilter } from '../../models/applied-filter.model'; import { renderSearchLabelFor } from '../search-label-loader/search-label-loader.decorator'; import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service'; +import { map } from 'rxjs/operators'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; /** * Component that represents the label containing the currently active filters @@ -23,18 +25,19 @@ export class SearchLabelRangeComponent implements OnInit { searchLink: string; - removeParametersMin: Observable; + removeParametersMin$: Observable; - removeParametersMax: Observable; + removeParametersMax$: Observable; min: string; max: string; constructor( + protected paginationService: PaginationService, + protected router: Router, protected searchConfigurationService: SearchConfigurationService, protected searchService: SearchService, - protected router: Router, ) { } @@ -42,14 +45,31 @@ export class SearchLabelRangeComponent implements OnInit { this.searchLink = this.getSearchLink(); this.min = this.appliedFilter.value.substring(1, this.appliedFilter.value.indexOf('TO') - 1); this.max = this.appliedFilter.value.substring(this.appliedFilter.value.indexOf('TO') + 3, this.appliedFilter.value.length - 1); - this.removeParametersMin = this.searchConfigurationService.getParamsWithoutAppliedFilter(`${this.appliedFilter.filter}.min`, this.min); - this.removeParametersMax = this.searchConfigurationService.getParamsWithoutAppliedFilter(`${this.appliedFilter.filter}.max`, this.max); + this.removeParametersMin$ = this.updateRemoveParams(`${this.appliedFilter.filter}.min`, this.min); + this.removeParametersMax$ = this.updateRemoveParams(`${this.appliedFilter.filter}.max`, this.max); + } + + /** + * Calculates the parameters that should change if this {@link appliedFilter} would be removed from the active filters + * + * @param filterName The {@link AppliedFilter}'s name + * @param value The {@link AppliedFilter}'s value + * @param operator The {@link AppliedFilter}'s optional operator + */ + updateRemoveParams(filterName: string, value: string, operator?: string): Observable { + const page: string = this.paginationService.getPageParam(this.searchConfigurationService.paginationID); + return this.searchConfigurationService.unselectAppliedFilterParams(filterName, value, operator).pipe( + map((params: Params) => ({ + ...params, + [page]: 1, + })), + ); } /** * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true */ - private getSearchLink(): string { + getSearchLink(): string { if (this.inPlaceSearch) { return currentPath(this.router); } diff --git a/src/app/shared/search/search-labels/search-label/search-label.component.html b/src/app/shared/search/search-labels/search-label/search-label.component.html index 00435dcbe9..f092526f5e 100644 --- a/src/app/shared/search/search-labels/search-label/search-label.component.html +++ b/src/app/shared/search/search-labels/search-label/search-label.component.html @@ -1,6 +1,6 @@ + [queryParams]="(removeParameters$ | async)"> {{('search.filters.applied.f.' + appliedFilter.filter) | translate}}: {{'search.filters.' + appliedFilter.filter + '.' + appliedFilter.label | translate: {default: appliedFilter.label} }} × diff --git a/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts b/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts index 78d0b3d51a..96e4e9823b 100644 --- a/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts +++ b/src/app/shared/search/search-labels/search-label/search-label.component.spec.ts @@ -10,6 +10,11 @@ import { addOperatorToFilterValue } from '../../search.utils'; import { RouterTestingModule } from '@angular/router/testing'; import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service'; import { SearchConfigurationServiceStub } from '../../../testing/search-configuration-service.stub'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../testing/pagination-service.stub'; +import { PaginationComponentOptions } from '../../../pagination/pagination-component-options.model'; +import { of as observableOf } from 'rxjs'; +import { take } from 'rxjs/operators'; describe('SearchLabelComponent', () => { let comp: SearchLabelComponent; @@ -17,10 +22,12 @@ describe('SearchLabelComponent', () => { let route: ActivatedRouteStub; let searchConfigurationService: SearchConfigurationServiceStub; + let paginationService: PaginationServiceStub; const searchLink = '/search'; let appliedFilter: AppliedFilter; let initialRouteParams: Params; + let pagination: PaginationComponentOptions; function init(): void { appliedFilter = Object.assign(new AppliedFilter(), { @@ -35,12 +42,18 @@ describe('SearchLabelComponent', () => { 'f.author': addOperatorToFilterValue(appliedFilter.value, appliedFilter.operator), 'f.has_content_in_original_bundle': addOperatorToFilterValue('true', 'equals'), }; + pagination = Object.assign(new PaginationComponentOptions(), { + id: 'page-id', + currentPage: 1, + pageSize: 20, + }); } beforeEach(waitForAsync(async () => { init(); route = new ActivatedRouteStub(initialRouteParams); searchConfigurationService = new SearchConfigurationServiceStub(); + paginationService = new PaginationServiceStub(pagination); await TestBed.configureTestingModule({ imports: [ @@ -51,6 +64,7 @@ describe('SearchLabelComponent', () => { SearchLabelComponent, ], providers: [ + { provide: PaginationService, useValue: paginationService }, { provide: SearchConfigurationService, useValue: searchConfigurationService }, { provide: SearchService, useValue: new SearchServiceStub(searchLink) }, { provide: ActivatedRoute, useValue: route }, @@ -65,7 +79,16 @@ describe('SearchLabelComponent', () => { fixture.detectChanges(); }); - it('should create', () => { - expect(comp).toBeTruthy(); + describe('updateRemoveParams', () => { + it('should always reset the page to 1', (done: DoneFn) => { + spyOn(searchConfigurationService, 'unselectAppliedFilterParams').and.returnValue(observableOf(initialRouteParams)); + + comp.updateRemoveParams().pipe(take(1)).subscribe((params: Params) => { + expect(params).toEqual(Object.assign({}, initialRouteParams, { + 'page-id.page': 1, + })); + done(); + }); + }); }); }); diff --git a/src/app/shared/search/search-labels/search-label/search-label.component.ts b/src/app/shared/search/search-labels/search-label/search-label.component.ts index 0ee6863602..524296b1d5 100644 --- a/src/app/shared/search/search-labels/search-label/search-label.component.ts +++ b/src/app/shared/search/search-labels/search-label/search-label.component.ts @@ -6,6 +6,8 @@ import { currentPath } from '../../../utils/route.utils'; import { AppliedFilter } from '../../models/applied-filter.model'; import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service'; import { renderSearchLabelFor } from '../search-label-loader/search-label-loader.decorator'; +import { map } from 'rxjs/operators'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; /** * Component that represents the label containing the currently active filters @@ -25,27 +27,41 @@ export class SearchLabelComponent implements OnInit { @Input() inPlaceSearch: boolean; @Input() appliedFilter: AppliedFilter; searchLink: string; - removeParameters: Observable; + removeParameters$: Observable; /** * Initialize the instance variable */ constructor( + protected paginationService: PaginationService, + protected router: Router, protected searchConfigurationService: SearchConfigurationService, protected searchService: SearchService, - protected router: Router, ) { } ngOnInit(): void { this.searchLink = this.getSearchLink(); - this.removeParameters = this.searchConfigurationService.getParamsWithoutAppliedFilter(this.appliedFilter.filter, this.appliedFilter.value, this.appliedFilter.operator); + this.removeParameters$ = this.updateRemoveParams(); + } + + /** + * Calculates the parameters that should change if this {@link appliedFilter} would be removed from the active filters + */ + updateRemoveParams(): Observable { + const page: string = this.paginationService.getPageParam(this.searchConfigurationService.paginationID); + return this.searchConfigurationService.unselectAppliedFilterParams(this.appliedFilter.filter, this.appliedFilter.value, this.appliedFilter.operator).pipe( + map((params: Params) => ({ + ...params, + [page]: 1, + })), + ); } /** * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true */ - private getSearchLink(): string { + getSearchLink(): string { if (this.inPlaceSearch) { return currentPath(this.router); } diff --git a/src/app/shared/testing/search-configuration-service.stub.ts b/src/app/shared/testing/search-configuration-service.stub.ts index adfed62c1d..fb72310417 100644 --- a/src/app/shared/testing/search-configuration-service.stub.ts +++ b/src/app/shared/testing/search-configuration-service.stub.ts @@ -32,7 +32,11 @@ export class SearchConfigurationServiceStub { return observableOf([{value: 'test', label: 'test'}]); } - getParamsWithoutAppliedFilter(_filterName: string, _value: string, _operator?: string): Observable { + unselectAppliedFilterParams(_filterName: string, _value: string, _operator?: string): Observable { + return observableOf({}); + } + + selectNewAppliedFilterParams(_filterName: string, _value: string, _operator?: string): Observable { return observableOf({}); } diff --git a/src/app/shared/testing/search-filter-service.stub.ts b/src/app/shared/testing/search-filter-service.stub.ts new file mode 100644 index 0000000000..f732c98fb8 --- /dev/null +++ b/src/app/shared/testing/search-filter-service.stub.ts @@ -0,0 +1,75 @@ +import { Observable, of as observableOf } from 'rxjs'; +import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; +import { SortOptions, SortDirection } from '../../core/cache/models/sort-options.model'; +import { SearchFilterConfig } from '../search/models/search-filter-config.model'; +import { Params } from '@angular/router'; + +/* eslint-disable no-empty,@typescript-eslint/no-empty-function */ +export class SearchFilterServiceStub { + + isFilterActiveWithValue(_paramName: string, _filterValue: string): Observable { + return observableOf(true); + } + + isFilterActive(_paramName: string): Observable { + return observableOf(true); + } + + getCurrentScope(): Observable { + return observableOf(undefined); + } + + getCurrentQuery(): Observable { + return observableOf(undefined); + } + + getCurrentPagination(_pagination: any = {}): Observable { + return Object.assign(new PaginationComponentOptions()); + } + + getCurrentSort(_defaultSort: SortOptions): Observable { + return observableOf(new SortOptions('', SortDirection.ASC)); + } + + getCurrentFilters(): Observable { + return observableOf({}); + } + + getCurrentView(): Observable { + return observableOf(undefined); + } + + getSelectedValuesForFilter(_filterConfig: SearchFilterConfig): Observable { + return observableOf([]); + } + + isCollapsed(_filterName: string): Observable { + return observableOf(true); + } + + getPage(_filterName: string): Observable { + return observableOf(1); + } + + collapse(_filterName: string): void { + } + + expand(_filterName: string): void { + } + + toggle(_filterName: string): void { + } + + initializeFilter(_filter: SearchFilterConfig): void { + } + + decrementPage(_filterName: string): void { + } + + incrementPage(_filterName: string): void { + } + + resetPage(_filterName: string): void { + } + +}