111731: Hide the search facets when there are no facet suggestions & the applied filters of that facet don't have the operator equals, authority or range (because those should be displayed in the facets)

This commit is contained in:
Alexandre Vryghem
2024-05-07 10:32:55 +02:00
parent 28088bc8c8
commit 1f42f92344
5 changed files with 152 additions and 29 deletions

View File

@@ -24,6 +24,8 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils
import { AppliedFilter } from '../../../models/applied-filter.model';
import { FacetValues } from '../../../models/facet-values.model';
import { SearchFilterServiceStub } from '../../../../testing/search-filter-service.stub';
import { cold } from 'jasmine-marbles';
import { PageInfo } from '../../../../../core/shared/page-info.model';
describe('SearchFacetFilterComponent', () => {
let comp: SearchFacetFilterComponent;
@@ -32,6 +34,7 @@ describe('SearchFacetFilterComponent', () => {
const value1 = 'testvalue1';
const value2 = 'test2';
const value3 = 'another value3';
const value4 = '52d629dc-7d2f-47b9-aa2d-258b92e45ae1';
const mockFilterConfig: SearchFilterConfig = Object.assign(new SearchFilterConfig(), {
name: filterName1,
filterType: FilterType.text,
@@ -39,27 +42,39 @@ describe('SearchFacetFilterComponent', () => {
isOpenByDefault: false,
pageSize: 2
});
const appliedFilter1: AppliedFilter = Object.assign(new AppliedFilter(), {
filter: filterName1,
operator: 'equals',
label: value1,
value: value1,
});
const appliedFilter2: AppliedFilter = Object.assign(new AppliedFilter(), {
filter: filterName1,
operator: 'equals',
label: value2,
value: value2,
});
const appliedFilter3: AppliedFilter = Object.assign(new AppliedFilter(), {
filter: filterName1,
operator: 'equals',
label: value3,
value: value3,
});
const appliedFilter4: AppliedFilter = Object.assign(new AppliedFilter(), {
filter: filterName1,
operator: 'notauthority',
label: value4,
value: value4,
});
const values: Partial<FacetValues> = {
appliedFilters: [
{
filter: filterName1,
operator: 'equals',
label: value1,
value: value1,
},
{
filter: filterName1,
operator: 'equals',
label: value2,
value: value2,
},
{
filter: filterName1,
operator: 'equals',
label: value3,
value: value3,
}
]
appliedFilter1,
appliedFilter2,
appliedFilter3,
],
pageInfo: Object.assign(new PageInfo(), {
currentPage: 0,
}),
};
const searchLink = '/search';
@@ -205,4 +220,26 @@ describe('SearchFacetFilterComponent', () => {
expect(comp.filter).toEqual('');
});
});
describe('when new values are detected for a filter', () => {
let selectedValues$: BehaviorSubject<AppliedFilter[]>;
beforeEach(() => {
selectedValues$ = new BehaviorSubject([appliedFilter1, appliedFilter2, appliedFilter3]);
spyOn(searchService, 'getSelectedValuesForFilter').and.returnValue(selectedValues$);
comp.ngOnInit();
});
it('should updated the selectedAppliedFilters$ when they are AppliedFilters that should be displayed in the search facets', () => {
expect(comp.selectedAppliedFilters$).toBeObservable(cold('a', {
a: [appliedFilter1, appliedFilter2, appliedFilter3],
}));
selectedValues$.next([appliedFilter1, appliedFilter2, appliedFilter3, appliedFilter4]);
expect(comp.selectedAppliedFilters$).toBeObservable(cold('a', {
a: [appliedFilter1, appliedFilter2, appliedFilter3],
}));
});
});
});

View File

@@ -20,6 +20,15 @@ import { currentPath } from '../../../../utils/route.utils';
import { FacetValues } from '../../../models/facet-values.model';
import { AppliedFilter } from '../../../models/applied-filter.model';
/**
* The operators the {@link AppliedFilter} should have in order to be shown in the facets
*/
export const FACET_OPERATORS: string[] = [
'equals',
'authority',
'range',
];
@Component({
selector: 'ds-search-facet-filter',
template: ``,
@@ -104,7 +113,10 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
this.searchOptions$.subscribe(() => this.updateFilterValueList()),
this.retrieveFilterValues().subscribe(),
);
this.selectedAppliedFilters$ = this.searchService.getSelectedValuesForFilter(this.filterConfig.name);
this.selectedAppliedFilters$ = this.searchService.getSelectedValuesForFilter(this.filterConfig.name).pipe(
map((allAppliedFilters: AppliedFilter[]) => allAppliedFilters.filter((appliedFilter: AppliedFilter) => FACET_OPERATORS.includes(appliedFilter.operator))),
distinctUntilChanged((previous: AppliedFilter[], next: AppliedFilter[]) => JSON.stringify(previous) === JSON.stringify(next)),
);
}
/**

View File

@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { RouterModule } from '@angular/router';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';
import { Observable, of as observableOf } from 'rxjs';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@@ -16,10 +16,22 @@ import { SequenceService } from '../../../../core/shared/sequence.service';
import { BrowserOnlyMockPipe } from '../../../testing/browser-only-mock.pipe';
import { SearchServiceStub } from '../../../testing/search-service.stub';
import { SearchFilterServiceStub } from '../../../testing/search-filter-service.stub';
import { cold } from 'jasmine-marbles';
import { AppliedFilter } from '../../models/applied-filter.model';
import { FacetValues } from '../../models/facet-values.model';
import { createSuccessfulRemoteDataObject$ } from '../../../remote-data.utils';
describe('SearchFilterComponent', () => {
let comp: SearchFilterComponent;
let fixture: ComponentFixture<SearchFilterComponent>;
const appliedFilter1: AppliedFilter = Object.assign(new AppliedFilter(), {
operator: 'equals',
});
const appliedFilter2: AppliedFilter = Object.assign(new AppliedFilter(), {
operator: 'notauthority',
});
const filterName1 = 'test name';
const mockFilterConfig: SearchFilterConfig = Object.assign(new SearchFilterConfig(), {
@@ -30,16 +42,21 @@ describe('SearchFilterComponent', () => {
});
let searchFilterService: SearchFilterServiceStub;
let sequenceService;
const mockResults = observableOf(['test', 'data']);
let searchService: SearchServiceStub;
let searchConfigurationService: SearchConfigurationServiceStub;
beforeEach(waitForAsync(() => {
searchFilterService = new SearchFilterServiceStub();
searchService = new SearchServiceStub();
searchConfigurationService = new SearchConfigurationServiceStub();
sequenceService = jasmine.createSpyObj('sequenceService', { next: 17 });
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NoopAnimationsModule],
imports: [
NoopAnimationsModule,
RouterModule.forRoot([]),
TranslateModule.forRoot(),
],
declarations: [
SearchFilterComponent,
BrowserOnlyMockPipe,
@@ -47,7 +64,7 @@ describe('SearchFilterComponent', () => {
providers: [
{ provide: SearchService, useValue: searchService },
{ provide: SearchFilterService, useValue: searchFilterService },
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
{ provide: SEARCH_CONFIG_SERVICE, useValue: searchConfigurationService },
{ provide: SequenceService, useValue: sequenceService },
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -57,7 +74,6 @@ describe('SearchFilterComponent', () => {
}));
beforeEach(() => {
spyOn(searchService, 'getFacetValuesFor').and.returnValue(mockResults);
fixture = TestBed.createComponent(SearchFilterComponent);
comp = fixture.componentInstance; // SearchPageComponent test instance
comp.filter = mockFilterConfig;
@@ -121,4 +137,59 @@ describe('SearchFilterComponent', () => {
sub.unsubscribe();
});
});
describe('isActive', () => {
it('should return true when there are facet value suggestions & no valid applied values', () => {
spyOn(searchService, 'getFacetValuesFor').and.returnValue(createSuccessfulRemoteDataObject$(Object.assign(new FacetValues(), {
pageInfo: {
totalElements: 5,
},
} as FacetValues)));
comp.appliedFilters$ = observableOf([appliedFilter2]);
expect(comp.isActive()).toBeObservable(cold('(tt)', {
t: true,
}));
});
it('should return false when there are no facet value suggestions & no valid applied values', () => {
spyOn(searchService, 'getFacetValuesFor').and.returnValue(createSuccessfulRemoteDataObject$(Object.assign(new FacetValues(), {
pageInfo: {
totalElements: 0,
},
} as FacetValues)));
comp.appliedFilters$ = observableOf([appliedFilter2]);
expect(comp.isActive()).toBeObservable(cold('(tf)', {
t: true,
f: false,
}));
});
it('should return true when there are no facet value suggestions & but there are valid applied values', () => {
spyOn(searchService, 'getFacetValuesFor').and.returnValue(createSuccessfulRemoteDataObject$(Object.assign(new FacetValues(), {
pageInfo: {
totalElements: 0,
},
} as FacetValues)));
comp.appliedFilters$ = observableOf([appliedFilter1, appliedFilter2]);
expect(comp.isActive()).toBeObservable(cold('(tt)', {
t: true,
}));
});
it('should return true when there are facet value suggestions & there are valid applied values', () => {
spyOn(searchService, 'getFacetValuesFor').and.returnValue(createSuccessfulRemoteDataObject$(Object.assign(new FacetValues(), {
pageInfo: {
totalElements: 5,
},
} as FacetValues)));
comp.appliedFilters$ = observableOf([appliedFilter1, appliedFilter2]);
expect(comp.isActive()).toBeObservable(cold('(tt)', {
t: true,
}));
});
});
});

View File

@@ -15,6 +15,7 @@ import { FacetValues } from '../../models/facet-values.model';
import { RemoteData } from '../../../../core/data/remote-data';
import { AppliedFilter } from '../../models/applied-filter.model';
import { SearchOptions } from '../../models/search-options.model';
import { FACET_OPERATORS } from './search-facet-filter/search-facet-filter.component';
@Component({
selector: 'ds-search-filter',
@@ -166,13 +167,13 @@ export class SearchFilterComponent implements OnInit, OnDestroy {
* Check if a given filter is supposed to be shown or not
* @returns {Observable<boolean>} Emits true whenever a given filter config should be shown
*/
private isActive(): Observable<boolean> {
isActive(): Observable<boolean> {
return combineLatest([
this.appliedFilters$,
this.searchConfigService.searchOptions,
]).pipe(
switchMap(([selectedValues, options]: [AppliedFilter[], SearchOptions]) => {
if (isNotEmpty(selectedValues)) {
if (isNotEmpty(selectedValues.filter((appliedFilter: AppliedFilter) => FACET_OPERATORS.includes(appliedFilter.operator)))) {
return observableOf(true);
} else {
return this.searchService.getFacetValuesFor(this.filter, 1, options).pipe(

View File

@@ -4,6 +4,8 @@ import {
FilterConfig,
SearchConfig,
} from '../../core/shared/search/search-filters/search-config.model';
import { SearchOptions } from '../search/models/search-options.model';
import { PaginatedSearchOptions } from '../search/models/paginated-search-options.model';
/**
* Stub class of {@link SearchConfigurationService}
@@ -12,8 +14,8 @@ export class SearchConfigurationServiceStub {
public paginationID = 'test-id';
private searchOptions: BehaviorSubject<any> = new BehaviorSubject<any>({});
private paginatedSearchOptions: BehaviorSubject<any> = new BehaviorSubject<any>({});
public searchOptions: BehaviorSubject<SearchOptions> = new BehaviorSubject(new SearchOptions({}));
public paginatedSearchOptions: BehaviorSubject<PaginatedSearchOptions> = new BehaviorSubject(new PaginatedSearchOptions({}));
getCurrentFrontendFilters() {
return observableOf([]);