[ACS-5742] Fix Search e2e's (#8902)

* [ACS-5742] added People filter to demo-shell

* [ACS-5742] fixed search e2e's

* [ACS-5742] linter

* [ACS-5742] build fix

* [ACS-5742] fixed build

* [ACS-5742] removed duplicated unit test
This commit is contained in:
Mykyta Maliarchuk
2023-09-12 11:34:44 +02:00
committed by GitHub
parent 3aa55996bf
commit 0d5e70ebf7
17 changed files with 490 additions and 304 deletions

View File

@@ -35,6 +35,7 @@
[disabled]="option | adfIsIncluded: selectedOptions : compareOption"
*ngFor="let option of filteredOptions" [value]="option" [matTooltipShowDelay]="tooltipShowDelay"
[matTooltipDisabled]="!option.fullPath" matTooltipPosition="right"
[attr.data-automation-id]="'option-' + (option.value)"
[matTooltip]="'SEARCH.RESULTS.WILL_CONTAIN' | translate:{searchTerm: option.fullPath || option.value}"
[ngClass]="(option | adfIsIncluded: selectedOptions : compareOption) && 'adf-autocomplete-added-option'">
{{ option.fullPath || option.value }}

View File

@@ -1,19 +1,19 @@
<mat-chip [attr.data-automation-id]="'search-filter-chip-tabbed-' + tabbedFacet.label"
disableRipple
class="adf-search-filter-chip-tabbed"
[class.adf-search-toggle-chip]="(displayValue$ | async) || menuTrigger.menuOpen"
[class.adf-search-toggle-chip]="displayValue || menuTrigger.menuOpen"
[disabled]="!isPopulated"
tabIndex="0"
[matMenuTriggerFor]="menu"
(onMenuOpen)="onMenuOpen()"
(keydown.enter)="onEnterKeydown()"
(keydown.escape)="onEscKeydown()"
[attr.title]="displayValue$ | async"
[attr.title]="displayValue"
#menuTrigger="matMenuTrigger">
<span class="adf-search-filter-placeholder">{{ tabbedFacet.label | translate }}:</span>
<span class="adf-search-filter-ellipsis adf-filter-value" *ngIf="displayValue$ | async as displayValue; else showAny">
<span class="adf-search-filter-ellipsis adf-filter-value" *ngIf="displayValue; else showAny">
&nbsp; {{ displayValue | translate }}
</span>
<ng-template #showAny><span class="adf-search-filter-ellipsis adf-filter-value">&nbsp;{{ 'SEARCH.FILTER.ANY' | translate }}</span></ng-template>
@@ -33,18 +33,9 @@
{{ tabbedFacet.label | translate }}
</ng-container>
<ng-container ngProjectAs="filter-content">
<adf-search-filter-tabbed>
<ng-container *ngFor="let field of tabbedFacet.fields">
<adf-search-chip-autocomplete-input
*adf-search-filter-tab="tabbedFacet.facets[field]?.label"
[autocompleteOptions]="autocompleteOptions[field]"
[onReset$]="reset$"
[allowOnlyPredefinedValues]="true"
[compareOption]="optionComparator"
(optionsChanged)="onOptionsChange($event, field)">
</adf-search-chip-autocomplete-input>
</ng-container>
</adf-search-filter-tabbed>
<adf-search-facet-tabbed-content [tabbedFacet]="tabbedFacet" (isPopulated)="isPopulated = $event"
[onReset$]="reset$" [onApply$]="apply$" (displayValue$)="displayValue = $event">
</adf-search-facet-tabbed-content>
</ng-container>
<ng-container ngProjectAs="filter-actions">
<button mat-button class="adf-search-action-button" (click)="onRemove()" id="cancel-filter-button">

View File

@@ -19,28 +19,22 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ContentTestingModule } from '../../../../testing/content.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { By } from '@angular/platform-browser';
import { SearchQueryBuilderService } from '../../../services/search-query-builder.service';
import { SearchFilterList } from '../../../models/search-filter-list.model';
import { SearchFacetChipTabbedComponent } from './search-facet-chip-tabbed.component';
import { FacetField } from '../../../models/facet-field.interface';
import { SearchFacetFiltersService } from '../../../services/search-facet-filters.service';
import { SimpleChange } from '@angular/core';
import { NO_ERRORS_SCHEMA } from '@angular/core';
describe('SearchFacetChipTabbedComponent', () => {
let component: SearchFacetChipTabbedComponent;
let fixture: ComponentFixture<SearchFacetChipTabbedComponent>;
let queryBuilder: SearchQueryBuilderService;
let searchFacetService: SearchFacetFiltersService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), ContentTestingModule]
imports: [TranslateModule.forRoot(), ContentTestingModule],
schemas: [NO_ERRORS_SCHEMA]
});
fixture = TestBed.createComponent(SearchFacetChipTabbedComponent);
component = fixture.componentInstance;
queryBuilder = TestBed.inject(SearchQueryBuilderService);
searchFacetService = TestBed.inject(SearchFacetFiltersService);
spyOn(queryBuilder, 'update').and.stub();
const facet1: FacetField = { type: 'field', label: 'field', field: 'field', buckets: new SearchFilterList() };
const facet2: FacetField = { type: 'field', label: 'field2', field: 'field2', buckets: new SearchFilterList() };
@@ -66,32 +60,12 @@ describe('SearchFacetChipTabbedComponent', () => {
return fixture.debugElement.query(By.css('.adf-search-filter-ellipsis.adf-filter-value')).nativeElement.innerText.trim();
}
function getTabs(): HTMLDivElement[] {
return fixture.debugElement.queryAll(By.css('.mat-tab-label-content')).map((element) => element.nativeElement);
}
function changeTab(tabIndex: number) {
getTabs()[tabIndex].click();
function emitChildEvent(eventName: string, event: any) {
const debugElem = fixture.debugElement.query(By.css('adf-search-facet-tabbed-content'));
debugElem.triggerEventHandler(eventName, event);
fixture.detectChanges();
}
function triggerComponentChanges() {
component.ngOnChanges({
tabbedFacet: new SimpleChange(null, component.tabbedFacet, false)
});
fixture.detectChanges();
}
function addBucketItem(field: string, displayValue: string) {
component.tabbedFacet.facets[field].buckets.items.push({
count: 1,
label: displayValue,
display: displayValue,
filterQuery: ''
});
triggerComponentChanges();
}
it('should display correct label for tabbed facet', () => {
const label = fixture.debugElement.query(By.css('.adf-search-filter-placeholder')).nativeElement.innerText;
expect(label).toBe(component.tabbedFacet.label + ':');
@@ -122,111 +96,54 @@ describe('SearchFacetChipTabbedComponent', () => {
expect(title).toBe(component.tabbedFacet.label);
});
it('should display 2 tabs with specific labels', () => {
it('should display adf-search-facet-tabbed-content component', () => {
openFacet();
const tabLabels = getTabs();
expect(tabLabels.length).toBe(2);
expect(tabLabels[0].innerText).toBe(component.tabbedFacet.facets['field'].label);
expect(tabLabels[1].innerText).toBe(component.tabbedFacet.facets['field2'].label);
});
it('should display creator tab as active initially and allow navigation', () => {
openFacet();
let activeTabLabel = fixture.debugElement.query(By.css('.mat-tab-label-active .mat-tab-label-content')).nativeElement.innerText;
expect(activeTabLabel).toBe(component.tabbedFacet.facets['field'].label);
changeTab(1);
activeTabLabel = fixture.debugElement.query(By.css('.mat-tab-label-active .mat-tab-label-content')).nativeElement.innerText;
expect(activeTabLabel).toBe(component.tabbedFacet.facets['field2'].label);
let activeTabLabel = fixture.debugElement.query(By.css('adf-search-facet-tabbed-content'));
expect(activeTabLabel).toBeTruthy();
});
it('should display arrow down icon and not disable the chip when items are loaded', () => {
addBucketItem('field', 'test');
component.isPopulated = true;
fixture.detectChanges();
const chip = fixture.debugElement.query(By.css('mat-chip'));
const icon = fixture.debugElement.query(By.css('mat-chip mat-icon')).nativeElement.innerText;
expect(chip.classes['mat-chip-disabled']).toBeUndefined();
expect(icon).toEqual('keyboard_arrow_down');
});
it('should display arrow up icon when menu is opened', () => {
addBucketItem('field', 'test');
it('should display arrow up icon when menu is opened', async () => {
openFacet();
emitChildEvent('isPopulated', true);
await fixture.whenStable();
const icon = fixture.debugElement.query(By.css('mat-chip mat-icon')).nativeElement.innerText;
expect(icon).toEqual('keyboard_arrow_up');
});
it('should create empty selected options for each tab initially', () => {
expect(component.selectedOptions['field']).toEqual([]);
expect(component.selectedOptions['field2']).toEqual([]);
});
it('should update autocomplete options when buckets change', () => {
addBucketItem('field', 'test');
addBucketItem('field2', 'test2');
expect(component.autocompleteOptions['field'].length).toBe(1);
expect(component.autocompleteOptions['field'][0]).toEqual({value: 'test'});
expect(component.autocompleteOptions['field2'].length).toBe(1);
expect(component.autocompleteOptions['field2'][0]).toEqual({value: 'test2'});
});
it('should add buckets when items are selected', () => {
spyOn(queryBuilder, 'addUserFacetBucket');
addBucketItem('field', 'test');
addBucketItem('field2', 'test2');
component.onOptionsChange([{ value: 'test' }], 'field');
expect(queryBuilder.addUserFacetBucket).toHaveBeenCalledWith('field',component.tabbedFacet.facets['field'].buckets.items[0]);
});
it('should remove buckets when items are unselected', () => {
spyOn(queryBuilder, 'removeUserFacetBucket');
addBucketItem('field', 'test');
addBucketItem('field2', 'test2');
component.onOptionsChange([], 'field');
expect(queryBuilder.removeUserFacetBucket).toHaveBeenCalledWith('field',component.tabbedFacet.facets['field'].buckets.items[0]);
});
it('should update display value when next elements are selected', () => {
const selectedOption1 = 'test';
const selectedOption2 = 'test2';
addBucketItem('field', selectedOption1);
addBucketItem('field', selectedOption2);
component.onOptionsChange([{ value: selectedOption1 }, { value: selectedOption2 }],'field');
it('should update display value when new displayValue$ emitted', () => {
const displayValue = 'field_LABEL: test, test2';
openFacet();
emitChildEvent('displayValue$', displayValue);
fixture.detectChanges();
expect(getDisplayValue()).toBe(`${component.tabbedFacet.facets['field'].label}_LABEL: ${selectedOption1}, ${selectedOption2}`);
expect(getDisplayValue()).toBe(displayValue);
});
it('should update display value when elements from both tabs are selected', () => {
const selectedOption1 = 'test';
const selectedOption2 = 'test2';
addBucketItem('field', selectedOption1);
addBucketItem('field2', selectedOption2);
component.onOptionsChange([{ value: selectedOption1 }], 'field');
component.onOptionsChange([{ value: selectedOption2 }], 'field2');
fixture.detectChanges();
expect(getDisplayValue()).toBe(`${component.tabbedFacet.facets['field'].label}_LABEL: ${selectedOption1} ${component.tabbedFacet.facets['field2'].label}_LABEL: ${selectedOption2}`);
});
it('should update search query and display value when apply btn is clicked', () => {
it('should call onApply and close modal when apply btn is clicked', () => {
spyOn(component.menuTrigger, 'closeMenu').and.callThrough();
spyOn(component, 'updateDisplayValue').and.callThrough();
spyOn(searchFacetService, 'updateSelectedBuckets').and.callThrough();
spyOn(component, 'onApply').and.callThrough();
openFacet();
const applyButton = fixture.debugElement.query(By.css('#apply-filter-button'));
applyButton.triggerEventHandler('click', {});
expect(queryBuilder.update).toHaveBeenCalled();
expect(component.menuTrigger.closeMenu).toHaveBeenCalled();
expect(component.updateDisplayValue).toHaveBeenCalled();
expect(searchFacetService.updateSelectedBuckets).toHaveBeenCalled();
expect(component.onApply).toHaveBeenCalled();
});
it('should update search query and display value when cancel btn is clicked', () => {
it('should call onRemove and close modal when cancel btn is clicked', () => {
spyOn(component.menuTrigger, 'closeMenu').and.callThrough();
spyOn(component, 'updateDisplayValue').and.callThrough();
spyOn(component, 'onRemove').and.callThrough();
openFacet();
const applyButton = fixture.debugElement.query(By.css('#cancel-filter-button'));
applyButton.triggerEventHandler('click', {});
expect(queryBuilder.update).toHaveBeenCalled();
expect(component.menuTrigger.closeMenu).toHaveBeenCalled();
expect(component.updateDisplayValue).toHaveBeenCalled();
expect(component.onRemove).toHaveBeenCalled();
});
});

View File

@@ -15,17 +15,11 @@
* limitations under the License.
*/
import { Component, ElementRef, Inject, Input, OnChanges, OnInit, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core';
import { Component, ElementRef, Input, ViewChild, ViewEncapsulation } from '@angular/core';
import { ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';
import { MatMenuTrigger } from '@angular/material/menu';
import { TabbedFacetField } from '../../../models/tabbed-facet-field.interface';
import { Subject } from 'rxjs';
import { SearchQueryBuilderService } from '../../../services/search-query-builder.service';
import { SEARCH_QUERY_SERVICE_TOKEN } from '../../../search-query-service.token';
import { FacetWidget } from '../../../models/facet-widget.interface';
import { TranslationService } from '@alfresco/adf-core';
import { SearchFacetFiltersService } from '../../../services/search-facet-filters.service';
import { AutocompleteOption } from '../../../models/autocomplete-option.interface';
@Component({
selector: 'adf-search-facet-chip-tabbed',
@@ -33,7 +27,7 @@ import { AutocompleteOption } from '../../../models/autocomplete-option.interfac
styleUrls: ['./search-facet-chip-tabbed.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class SearchFacetChipTabbedComponent implements OnInit, OnChanges, FacetWidget {
export class SearchFacetChipTabbedComponent {
@Input()
tabbedFacet: TabbedFacetField;
@@ -44,42 +38,16 @@ export class SearchFacetChipTabbedComponent implements OnInit, OnChanges, FacetW
menuTrigger: MatMenuTrigger;
private resetSubject$ = new Subject<void>();
private applySubject$ = new Subject<void>();
displayValue$ = new Subject<string>();
displayValue = '';
reset$ = this.resetSubject$.asObservable();
apply$ = this.applySubject$.asObservable();
focusTrap: ConfigurableFocusTrap;
chipIcon = 'keyboard_arrow_down';
autocompleteOptions = {};
selectedOptions = {};
isPopulated = false;
constructor(@Inject(SEARCH_QUERY_SERVICE_TOKEN) private queryBuilder: SearchQueryBuilderService,
private translationService: TranslationService,
private searchFacetFiltersService: SearchFacetFiltersService,
private focusTrapFactory: ConfigurableFocusTrapFactory) {
}
ngOnInit() {
this.tabbedFacet.fields.forEach((field) => {
Object.defineProperty(this.selectedOptions, field, {
value: [],
writable: true
});
});
}
ngOnChanges(changes: SimpleChanges) {
if (changes.tabbedFacet) {
this.isPopulated = this.tabbedFacet.fields.some((field) => this.tabbedFacet.facets[field]?.buckets.items.length > 0);
this.tabbedFacet.fields.forEach((field) => {
const options: AutocompleteOption[] = this.tabbedFacet.facets[field].buckets.items.map((item) => ({ value: item.display }));
Object.defineProperty(this.autocompleteOptions, field, {
value: options,
writable: true
});
});
}
}
constructor(private focusTrapFactory: ConfigurableFocusTrapFactory) {}
onMenuOpen() {
if (this.menuContainer && !this.focusTrap) {
@@ -95,12 +63,12 @@ export class SearchFacetChipTabbedComponent implements OnInit, OnChanges, FacetW
}
onRemove() {
this.reset();
this.resetSubject$.next();
this.menuTrigger.closeMenu();
}
onApply() {
this.submitValues();
this.applySubject$.next();
this.menuTrigger.closeMenu();
}
@@ -119,56 +87,4 @@ export class SearchFacetChipTabbedComponent implements OnInit, OnChanges, FacetW
this.menuTrigger.closeMenu();
}
}
onOptionsChange(selectedOptions: AutocompleteOption[], field: string) {
this.selectedOptions[field] = selectedOptions.map((selectedOption) => selectedOption.value);
this.isPopulated = this.tabbedFacet.fields.some((facetField) => this.selectedOptions[facetField].length > 0);
this.updateDisplayValue();
this.updateUserFacetBuckets();
this.queryBuilder.update();
}
updateDisplayValue() {
let displayValue = '';
this.tabbedFacet.fields.forEach((field) => {
if (this.selectedOptions[field].length > 0) {
const stackedOptions = this.selectedOptions[field].join(', ');
displayValue += `${this.translationService.instant(this.tabbedFacet.facets[field].label + '_LABEL')}: ${stackedOptions} `;
}
});
this.displayValue$.next(displayValue);
}
reset() {
this.resetSubject$.next();
this.updateUserFacetBuckets();
this.updateDisplayValue();
this.queryBuilder.update();
}
submitValues() {
this.updateUserFacetBuckets();
this.searchFacetFiltersService.updateSelectedBuckets();
this.updateDisplayValue();
this.queryBuilder.update();
}
optionComparator(option1: AutocompleteOption, option2: AutocompleteOption): boolean {
return option1.value.toUpperCase() === option2.value.toUpperCase();
}
private updateUserFacetBuckets() {
this.tabbedFacet.fields.forEach((field) => {
this.tabbedFacet.facets[field].buckets.items.forEach((item) => {
const matchedOption = this.selectedOptions[field].find((option) => option === item.display);
if (matchedOption) {
item.checked = true;
this.queryBuilder.addUserFacetBucket(field, item);
} else {
item.checked = false;
this.queryBuilder.removeUserFacetBucket(field, item);
}
});
});
}
}

View File

@@ -0,0 +1,12 @@
<adf-search-filter-tabbed>
<ng-container *ngFor="let field of tabbedFacet.fields">
<adf-search-chip-autocomplete-input
*adf-search-filter-tab="tabbedFacet.facets[field]?.label"
[autocompleteOptions]="autocompleteOptions[field]"
[onReset$]="reset$"
[allowOnlyPredefinedValues]="true"
[compareOption]="optionComparator"
(optionsChanged)="onOptionsChange($event, field)">
</adf-search-chip-autocomplete-input>
</ng-container>
</adf-search-filter-tabbed>

View File

@@ -0,0 +1,183 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ContentTestingModule } from '../../../../testing/content.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { By } from '@angular/platform-browser';
import { SearchQueryBuilderService } from '../../../services/search-query-builder.service';
import { SearchFilterList } from '../../../models/search-filter-list.model';
import { FacetField } from '../../../models/facet-field.interface';
import { SearchFacetFiltersService } from '../../../services/search-facet-filters.service';
import { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core';
import { SearchFacetTabbedContentComponent } from './search-facet-tabbed-content.component';
import { of } from 'rxjs';
describe('SearchFacetTabbedContentComponent', () => {
let component: SearchFacetTabbedContentComponent;
let fixture: ComponentFixture<SearchFacetTabbedContentComponent>;
let queryBuilder: SearchQueryBuilderService;
let searchFacetService: SearchFacetFiltersService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), ContentTestingModule],
schemas: [NO_ERRORS_SCHEMA]
});
fixture = TestBed.createComponent(SearchFacetTabbedContentComponent);
component = fixture.componentInstance;
queryBuilder = TestBed.inject(SearchQueryBuilderService);
searchFacetService = TestBed.inject(SearchFacetFiltersService);
spyOn(queryBuilder, 'update').and.stub();
const facet1: FacetField = { type: 'field', label: 'field', field: 'field', buckets: new SearchFilterList() };
const facet2: FacetField = { type: 'field', label: 'field2', field: 'field2', buckets: new SearchFilterList() };
component.tabbedFacet = {
fields: ['field', 'field2'],
label: 'LABEL',
facets: {
field: facet1,
field2: facet2
}
};
component.onReset$ = of(void 0);
component.onApply$ = of(void 0);
fixture.detectChanges();
});
function getTabs(): HTMLDivElement[] {
return fixture.debugElement.queryAll(By.css('.mat-tab-label-content')).map((element) => element.nativeElement);
}
function changeTab(tabIndex: number) {
getTabs()[tabIndex].click();
fixture.detectChanges();
}
function triggerComponentChanges() {
component.ngOnChanges({
tabbedFacet: new SimpleChange(null, component.tabbedFacet, false)
});
fixture.detectChanges();
}
function addBucketItem(field: string, displayValue: string) {
component.tabbedFacet.facets[field].buckets.items.push({
count: 1,
label: displayValue,
display: displayValue,
filterQuery: ''
});
triggerComponentChanges();
}
it('should display 2 tabs with specific labels', () => {
const tabLabels = getTabs();
expect(tabLabels.length).toBe(2);
expect(tabLabels[0].innerText).toBe(component.tabbedFacet.facets['field'].label);
expect(tabLabels[1].innerText).toBe(component.tabbedFacet.facets['field2'].label);
});
it('should display creator tab as active initially and allow navigation', () => {
let activeTabLabel = fixture.debugElement.query(By.css('.mat-tab-label-active .mat-tab-label-content')).nativeElement.innerText;
expect(activeTabLabel).toBe(component.tabbedFacet.facets['field'].label);
changeTab(1);
activeTabLabel = fixture.debugElement.query(By.css('.mat-tab-label-active .mat-tab-label-content')).nativeElement.innerText;
expect(activeTabLabel).toBe(component.tabbedFacet.facets['field2'].label);
});
it('should create empty selected options for each tab initially', () => {
expect(component.selectedOptions['field']).toEqual([]);
expect(component.selectedOptions['field2']).toEqual([]);
});
it('should update autocomplete options when buckets change', () => {
addBucketItem('field', 'test');
addBucketItem('field2', 'test2');
expect(component.autocompleteOptions['field'].length).toBe(1);
expect(component.autocompleteOptions['field'][0]).toEqual({value: 'test'});
expect(component.autocompleteOptions['field2'].length).toBe(1);
expect(component.autocompleteOptions['field2'][0]).toEqual({value: 'test2'});
});
it('should add buckets when items are selected', () => {
spyOn(queryBuilder, 'addUserFacetBucket');
addBucketItem('field', 'test');
addBucketItem('field2', 'test2');
component.onOptionsChange([{ value: 'test' }], 'field');
expect(queryBuilder.addUserFacetBucket).toHaveBeenCalledWith('field',component.tabbedFacet.facets['field'].buckets.items[0]);
});
it('should remove buckets when items are unselected', () => {
spyOn(queryBuilder, 'removeUserFacetBucket');
addBucketItem('field', 'test');
addBucketItem('field2', 'test2');
component.onOptionsChange([], 'field');
expect(queryBuilder.removeUserFacetBucket).toHaveBeenCalledWith('field',component.tabbedFacet.facets['field'].buckets.items[0]);
});
it('should update emit new display value when next elements are selected', () => {
const selectedOption1 = 'test';
const selectedOption2 = 'test2';
spyOn(component.displayValue$, 'emit');
addBucketItem('field', selectedOption1);
addBucketItem('field', selectedOption2);
component.onOptionsChange([{ value: selectedOption1 }, { value: selectedOption2 }],'field');
fixture.detectChanges();
expect(component.displayValue$.emit).toHaveBeenCalledWith(`${component.tabbedFacet.facets['field'].label}_LABEL: ${selectedOption1}, ${selectedOption2} `);
});
it('should update display value when elements from both tabs are selected', () => {
const selectedOption1 = 'test';
const selectedOption2 = 'test2';
const displayValueEmitterSpy = spyOn(component.displayValue$, 'emit');
addBucketItem('field', selectedOption1);
addBucketItem('field2', selectedOption2);
component.onOptionsChange([{value: selectedOption1}], 'field');
component.onOptionsChange([{value: selectedOption2}], 'field2');
fixture.detectChanges();
expect(displayValueEmitterSpy).toHaveBeenCalledTimes(2);
expect(displayValueEmitterSpy.calls.allArgs()).toEqual([
[`${component.tabbedFacet.facets['field'].label}_LABEL: ${selectedOption1} `],
[`${component.tabbedFacet.facets['field'].label}_LABEL: ${selectedOption1} ${component.tabbedFacet.facets['field2'].label}_LABEL: ${selectedOption2} `]
]);
});
it('should update search query and display value on submit',() => {
spyOn(component, 'updateDisplayValue').and.callThrough();
spyOn(component, 'submitValues').and.callThrough();
spyOn(searchFacetService, 'updateSelectedBuckets').and.callThrough();
component.submitValues();
expect(component.submitValues).toHaveBeenCalled();
expect(queryBuilder.update).toHaveBeenCalled();
expect(component.updateDisplayValue).toHaveBeenCalled();
expect(searchFacetService.updateSelectedBuckets).toHaveBeenCalled();
});
it('should update search query and display value on reset', () => {
spyOn(component, 'updateDisplayValue').and.callThrough();
component.reset();
expect(queryBuilder.update).toHaveBeenCalled();
expect(component.updateDisplayValue).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,144 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, EventEmitter, Inject, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { SearchQueryBuilderService } from '../../../services/search-query-builder.service';
import { SEARCH_QUERY_SERVICE_TOKEN } from '../../../search-query-service.token';
import { FacetWidget } from '../../../models/facet-widget.interface';
import { TranslationService } from '@alfresco/adf-core';
import { AutocompleteOption } from '../../../models/autocomplete-option.interface';
import { takeUntil } from 'rxjs/operators';
import { TabbedFacetField } from '../../../models/tabbed-facet-field.interface';
import { SearchFacetFiltersService } from '../../../services/search-facet-filters.service';
@Component({
selector: 'adf-search-facet-tabbed-content',
templateUrl: './search-facet-tabbed-content.component.html',
encapsulation: ViewEncapsulation.None
})
export class SearchFacetTabbedContentComponent implements OnInit, OnDestroy, OnChanges, FacetWidget {
@Input()
tabbedFacet: TabbedFacetField;
@Input()
onReset$: Observable<void>;
@Input()
onApply$: Observable<void>;
@Output()
isPopulated = new EventEmitter<boolean>();
@Output()
displayValue$ = new EventEmitter<string>();
private resetSubject$ = new Subject<void>();
private onDestroy$ = new Subject<void>();
reset$ = this.resetSubject$.asObservable();
chipIcon = 'keyboard_arrow_down';
autocompleteOptions = {};
selectedOptions = {};
constructor(@Inject(SEARCH_QUERY_SERVICE_TOKEN) private queryBuilder: SearchQueryBuilderService,
private translationService: TranslationService,
private searchFacetFiltersService: SearchFacetFiltersService) {
}
ngOnInit() {
this.tabbedFacet.fields.forEach((field) => {
Object.defineProperty(this.selectedOptions, field, {
value: [],
writable: true
});
});
this.onReset$?.pipe(takeUntil(this.onDestroy$)).subscribe(() => this.reset());
this.onApply$?.pipe(takeUntil(this.onDestroy$)).subscribe(() => this.submitValues());
}
ngOnDestroy() {
this.onDestroy$.next();
this.onDestroy$.complete();
}
ngOnChanges(changes: SimpleChanges) {
if (changes.tabbedFacet) {
this.isPopulated.emit(this.tabbedFacet.fields.some((field) => this.tabbedFacet.facets[field]?.buckets.items.length > 0));
this.tabbedFacet.fields.forEach((field) => {
const options: AutocompleteOption[] = this.tabbedFacet.facets[field].buckets.items.map((item) => ({ value: item.display }));
Object.defineProperty(this.autocompleteOptions, field, {
value: options,
writable: true
});
});
}
}
onOptionsChange(selectedOptions: AutocompleteOption[], field: string) {
this.selectedOptions[field] = selectedOptions.map((selectedOption) => selectedOption.value);
this.isPopulated.emit(this.tabbedFacet.fields.some((facetField) => this.selectedOptions[facetField].length > 0));
this.updateDisplayValue();
this.updateUserFacetBuckets();
this.queryBuilder.update();
}
updateDisplayValue() {
let displayValue = '';
this.tabbedFacet.fields.forEach((field) => {
if (this.selectedOptions[field].length > 0) {
const stackedOptions = this.selectedOptions[field].join(', ');
displayValue += `${this.translationService.instant(this.tabbedFacet.facets[field].label + '_LABEL')}: ${stackedOptions} `;
}
});
this.displayValue$.emit(displayValue);
}
reset() {
this.resetSubject$.next();
this.updateUserFacetBuckets();
this.updateDisplayValue();
this.queryBuilder.update();
}
submitValues() {
this.updateUserFacetBuckets();
this.searchFacetFiltersService.updateSelectedBuckets();
this.updateDisplayValue();
this.queryBuilder.update();
}
optionComparator(option1: AutocompleteOption, option2: AutocompleteOption): boolean {
return option1.value.toUpperCase() === option2.value.toUpperCase();
}
private updateUserFacetBuckets() {
this.tabbedFacet.fields.forEach((field) => {
this.tabbedFacet.facets[field].buckets.items.forEach((item) => {
const matchedOption = this.selectedOptions[field].find((option) => option === item.display);
if (matchedOption) {
item.checked = true;
this.queryBuilder.addUserFacetBucket(field, item);
} else {
item.checked = false;
this.queryBuilder.removeUserFacetBucket(field, item);
}
});
});
}
}

View File

@@ -24,6 +24,18 @@
</adf-search-widget-container>
</mat-expansion-panel>
<ng-container *ngIf="facetFiltersService.tabbedFacet && showContextFacets">
<mat-expansion-panel [attr.data-automation-id]="'expansion-panel-'+facetFiltersService.tabbedFacet.label" [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>{{ facetFiltersService.tabbedFacet.label | translate }}</mat-panel-title>
</mat-expansion-panel-header>
<adf-search-facet-tabbed-content
[tabbedFacet]="facetFiltersService.tabbedFacet"
[attr.data-automation-id]="'expansion-panel-'+facetFiltersService.tabbedFacet.label">
</adf-search-facet-tabbed-content>
</mat-expansion-panel>
</ng-container>
<ng-container *ngIf="facetFiltersService.responseFacets && showContextFacets">
<mat-expansion-panel [attr.data-automation-id]="'expansion-panel-'+field.label" *ngFor="let field of facetFiltersService.responseFacets"
[expanded]="shouldExpand(field)">

View File

@@ -69,5 +69,6 @@ export * from './components/reset-search.directive';
export * from './components/search-chip-autocomplete-input/search-chip-autocomplete-input.component';
export * from './components/search-filter-autocomplete-chips/search-filter-autocomplete-chips.component';
export * from './components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component';
export * from './components/search-filter-chips/search-facet-chip-tabbed/search-facet-tabbed-content.component';
export * from './search.module';

View File

@@ -57,6 +57,7 @@ import { SearchDateRangeAdvancedComponent } from './components/search-date-range
import { SearchDateRangeAdvancedTabbedComponent } from './components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component';
import { SearchFilterTabDirective } from './components/search-filter-tabbed/search-filter-tab.directive';
import { SearchFacetChipTabbedComponent } from './components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component';
import { SearchFacetTabbedContentComponent } from './components/search-filter-chips/search-facet-chip-tabbed/search-facet-tabbed-content.component';
@NgModule({
imports: [
@@ -100,7 +101,8 @@ import { SearchFacetChipTabbedComponent } from './components/search-filter-chips
SearchDateRangeAdvancedComponent,
SearchDateRangeAdvancedTabbedComponent,
SearchFilterTabDirective,
SearchFacetChipTabbedComponent
SearchFacetChipTabbedComponent,
SearchFacetTabbedContentComponent
],
exports: [
SearchComponent,
@@ -129,7 +131,8 @@ import { SearchFacetChipTabbedComponent } from './components/search-filter-chips
SearchFilterTabbedComponent,
SearchDateRangeAdvancedComponent,
ResetSearchDirective,
SearchFacetChipTabbedComponent
SearchFacetChipTabbedComponent,
SearchFacetTabbedContentComponent
],
providers: [
{ provide: SEARCH_QUERY_SERVICE_TOKEN, useExisting: SearchQueryBuilderService }