mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
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:
@@ -24,6 +24,8 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils
|
|||||||
import { AppliedFilter } from '../../../models/applied-filter.model';
|
import { AppliedFilter } from '../../../models/applied-filter.model';
|
||||||
import { FacetValues } from '../../../models/facet-values.model';
|
import { FacetValues } from '../../../models/facet-values.model';
|
||||||
import { SearchFilterServiceStub } from '../../../../testing/search-filter-service.stub';
|
import { SearchFilterServiceStub } from '../../../../testing/search-filter-service.stub';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
||||||
|
|
||||||
describe('SearchFacetFilterComponent', () => {
|
describe('SearchFacetFilterComponent', () => {
|
||||||
let comp: SearchFacetFilterComponent;
|
let comp: SearchFacetFilterComponent;
|
||||||
@@ -32,6 +34,7 @@ describe('SearchFacetFilterComponent', () => {
|
|||||||
const value1 = 'testvalue1';
|
const value1 = 'testvalue1';
|
||||||
const value2 = 'test2';
|
const value2 = 'test2';
|
||||||
const value3 = 'another value3';
|
const value3 = 'another value3';
|
||||||
|
const value4 = '52d629dc-7d2f-47b9-aa2d-258b92e45ae1';
|
||||||
const mockFilterConfig: SearchFilterConfig = Object.assign(new SearchFilterConfig(), {
|
const mockFilterConfig: SearchFilterConfig = Object.assign(new SearchFilterConfig(), {
|
||||||
name: filterName1,
|
name: filterName1,
|
||||||
filterType: FilterType.text,
|
filterType: FilterType.text,
|
||||||
@@ -39,27 +42,39 @@ describe('SearchFacetFilterComponent', () => {
|
|||||||
isOpenByDefault: false,
|
isOpenByDefault: false,
|
||||||
pageSize: 2
|
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> = {
|
const values: Partial<FacetValues> = {
|
||||||
appliedFilters: [
|
appliedFilters: [
|
||||||
{
|
appliedFilter1,
|
||||||
filter: filterName1,
|
appliedFilter2,
|
||||||
operator: 'equals',
|
appliedFilter3,
|
||||||
label: value1,
|
],
|
||||||
value: value1,
|
pageInfo: Object.assign(new PageInfo(), {
|
||||||
},
|
currentPage: 0,
|
||||||
{
|
}),
|
||||||
filter: filterName1,
|
|
||||||
operator: 'equals',
|
|
||||||
label: value2,
|
|
||||||
value: value2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
filter: filterName1,
|
|
||||||
operator: 'equals',
|
|
||||||
label: value3,
|
|
||||||
value: value3,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchLink = '/search';
|
const searchLink = '/search';
|
||||||
@@ -205,4 +220,26 @@ describe('SearchFacetFilterComponent', () => {
|
|||||||
expect(comp.filter).toEqual('');
|
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],
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -20,6 +20,15 @@ import { currentPath } from '../../../../utils/route.utils';
|
|||||||
import { FacetValues } from '../../../models/facet-values.model';
|
import { FacetValues } from '../../../models/facet-values.model';
|
||||||
import { AppliedFilter } from '../../../models/applied-filter.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({
|
@Component({
|
||||||
selector: 'ds-search-facet-filter',
|
selector: 'ds-search-facet-filter',
|
||||||
template: ``,
|
template: ``,
|
||||||
@@ -104,7 +113,10 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
this.searchOptions$.subscribe(() => this.updateFilterValueList()),
|
this.searchOptions$.subscribe(() => this.updateFilterValueList()),
|
||||||
this.retrieveFilterValues().subscribe(),
|
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)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
import { ChangeDetectionStrategy, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { Observable, of as observableOf } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
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 { BrowserOnlyMockPipe } from '../../../testing/browser-only-mock.pipe';
|
||||||
import { SearchServiceStub } from '../../../testing/search-service.stub';
|
import { SearchServiceStub } from '../../../testing/search-service.stub';
|
||||||
import { SearchFilterServiceStub } from '../../../testing/search-filter-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', () => {
|
describe('SearchFilterComponent', () => {
|
||||||
let comp: SearchFilterComponent;
|
let comp: SearchFilterComponent;
|
||||||
let fixture: ComponentFixture<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 filterName1 = 'test name';
|
||||||
|
|
||||||
const mockFilterConfig: SearchFilterConfig = Object.assign(new SearchFilterConfig(), {
|
const mockFilterConfig: SearchFilterConfig = Object.assign(new SearchFilterConfig(), {
|
||||||
@@ -30,16 +42,21 @@ describe('SearchFilterComponent', () => {
|
|||||||
});
|
});
|
||||||
let searchFilterService: SearchFilterServiceStub;
|
let searchFilterService: SearchFilterServiceStub;
|
||||||
let sequenceService;
|
let sequenceService;
|
||||||
const mockResults = observableOf(['test', 'data']);
|
|
||||||
let searchService: SearchServiceStub;
|
let searchService: SearchServiceStub;
|
||||||
|
let searchConfigurationService: SearchConfigurationServiceStub;
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
searchFilterService = new SearchFilterServiceStub();
|
searchFilterService = new SearchFilterServiceStub();
|
||||||
searchService = new SearchServiceStub();
|
searchService = new SearchServiceStub();
|
||||||
|
searchConfigurationService = new SearchConfigurationServiceStub();
|
||||||
sequenceService = jasmine.createSpyObj('sequenceService', { next: 17 });
|
sequenceService = jasmine.createSpyObj('sequenceService', { next: 17 });
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NoopAnimationsModule],
|
imports: [
|
||||||
|
NoopAnimationsModule,
|
||||||
|
RouterModule.forRoot([]),
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
SearchFilterComponent,
|
SearchFilterComponent,
|
||||||
BrowserOnlyMockPipe,
|
BrowserOnlyMockPipe,
|
||||||
@@ -47,7 +64,7 @@ describe('SearchFilterComponent', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: SearchService, useValue: searchService },
|
{ provide: SearchService, useValue: searchService },
|
||||||
{ provide: SearchFilterService, useValue: searchFilterService },
|
{ provide: SearchFilterService, useValue: searchFilterService },
|
||||||
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
|
{ provide: SEARCH_CONFIG_SERVICE, useValue: searchConfigurationService },
|
||||||
{ provide: SequenceService, useValue: sequenceService },
|
{ provide: SequenceService, useValue: sequenceService },
|
||||||
],
|
],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
@@ -57,7 +74,6 @@ describe('SearchFilterComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(searchService, 'getFacetValuesFor').and.returnValue(mockResults);
|
|
||||||
fixture = TestBed.createComponent(SearchFilterComponent);
|
fixture = TestBed.createComponent(SearchFilterComponent);
|
||||||
comp = fixture.componentInstance; // SearchPageComponent test instance
|
comp = fixture.componentInstance; // SearchPageComponent test instance
|
||||||
comp.filter = mockFilterConfig;
|
comp.filter = mockFilterConfig;
|
||||||
@@ -121,4 +137,59 @@ describe('SearchFilterComponent', () => {
|
|||||||
sub.unsubscribe();
|
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,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -15,6 +15,7 @@ import { FacetValues } from '../../models/facet-values.model';
|
|||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { AppliedFilter } from '../../models/applied-filter.model';
|
import { AppliedFilter } from '../../models/applied-filter.model';
|
||||||
import { SearchOptions } from '../../models/search-options.model';
|
import { SearchOptions } from '../../models/search-options.model';
|
||||||
|
import { FACET_OPERATORS } from './search-facet-filter/search-facet-filter.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-filter',
|
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
|
* 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
|
* @returns {Observable<boolean>} Emits true whenever a given filter config should be shown
|
||||||
*/
|
*/
|
||||||
private isActive(): Observable<boolean> {
|
isActive(): Observable<boolean> {
|
||||||
return combineLatest([
|
return combineLatest([
|
||||||
this.appliedFilters$,
|
this.appliedFilters$,
|
||||||
this.searchConfigService.searchOptions,
|
this.searchConfigService.searchOptions,
|
||||||
]).pipe(
|
]).pipe(
|
||||||
switchMap(([selectedValues, options]: [AppliedFilter[], SearchOptions]) => {
|
switchMap(([selectedValues, options]: [AppliedFilter[], SearchOptions]) => {
|
||||||
if (isNotEmpty(selectedValues)) {
|
if (isNotEmpty(selectedValues.filter((appliedFilter: AppliedFilter) => FACET_OPERATORS.includes(appliedFilter.operator)))) {
|
||||||
return observableOf(true);
|
return observableOf(true);
|
||||||
} else {
|
} else {
|
||||||
return this.searchService.getFacetValuesFor(this.filter, 1, options).pipe(
|
return this.searchService.getFacetValuesFor(this.filter, 1, options).pipe(
|
||||||
|
@@ -4,6 +4,8 @@ import {
|
|||||||
FilterConfig,
|
FilterConfig,
|
||||||
SearchConfig,
|
SearchConfig,
|
||||||
} from '../../core/shared/search/search-filters/search-config.model';
|
} 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}
|
* Stub class of {@link SearchConfigurationService}
|
||||||
@@ -12,8 +14,8 @@ export class SearchConfigurationServiceStub {
|
|||||||
|
|
||||||
public paginationID = 'test-id';
|
public paginationID = 'test-id';
|
||||||
|
|
||||||
private searchOptions: BehaviorSubject<any> = new BehaviorSubject<any>({});
|
public searchOptions: BehaviorSubject<SearchOptions> = new BehaviorSubject(new SearchOptions({}));
|
||||||
private paginatedSearchOptions: BehaviorSubject<any> = new BehaviorSubject<any>({});
|
public paginatedSearchOptions: BehaviorSubject<PaginatedSearchOptions> = new BehaviorSubject(new PaginatedSearchOptions({}));
|
||||||
|
|
||||||
getCurrentFrontendFilters() {
|
getCurrentFrontendFilters() {
|
||||||
return observableOf([]);
|
return observableOf([]);
|
||||||
|
Reference in New Issue
Block a user