mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
[ACS-10232][ACS-10326] Fix focus trap for search filters (#11447)
* [ACS-10232][ACS-10326] Fix focus trap for search filters * sonar issue fix
This commit is contained in:
+34
-10
@@ -26,15 +26,20 @@ import { HarnessLoader, TestKey } from '@angular/cdk/testing';
|
|||||||
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
||||||
import { MatChipHarness } from '@angular/material/chips/testing';
|
import { MatChipHarness } from '@angular/material/chips/testing';
|
||||||
import { MatIconHarness } from '@angular/material/icon/testing';
|
import { MatIconHarness } from '@angular/material/icon/testing';
|
||||||
|
import { ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';
|
||||||
|
|
||||||
describe('SearchFacetChipTabbedComponent', () => {
|
describe('SearchFacetChipTabbedComponent', () => {
|
||||||
let loader: HarnessLoader;
|
let loader: HarnessLoader;
|
||||||
let component: SearchFacetChipTabbedComponent;
|
let component: SearchFacetChipTabbedComponent;
|
||||||
let fixture: ComponentFixture<SearchFacetChipTabbedComponent>;
|
let fixture: ComponentFixture<SearchFacetChipTabbedComponent>;
|
||||||
|
|
||||||
|
const focusTrapFactory = jasmine.createSpyObj('ConfigurableFocusTrapFactory', ['create']);
|
||||||
|
const focusTrap = jasmine.createSpyObj('ConfigurableFocusTrap', ['focusInitialElement', 'destroy']);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [ContentTestingModule],
|
imports: [ContentTestingModule],
|
||||||
|
providers: [{ provide: ConfigurableFocusTrapFactory, useValue: focusTrapFactory }],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(SearchFacetChipTabbedComponent);
|
fixture = TestBed.createComponent(SearchFacetChipTabbedComponent);
|
||||||
@@ -53,6 +58,7 @@ describe('SearchFacetChipTabbedComponent', () => {
|
|||||||
};
|
};
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||||
|
focusTrapFactory.create.and.returnValue(focusTrap);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,6 +82,14 @@ describe('SearchFacetChipTabbedComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get chip harness and click it
|
||||||
|
*/
|
||||||
|
async function clickChip(): Promise<void> {
|
||||||
|
const chip = await loader.getHarness(MatChipHarness);
|
||||||
|
await (await chip.host()).click();
|
||||||
|
}
|
||||||
|
|
||||||
it('should display correct label for tabbed facet', () => {
|
it('should display correct label for tabbed facet', () => {
|
||||||
const label = fixture.debugElement.query(By.css('.adf-search-filter-placeholder')).nativeElement.innerText;
|
const label = fixture.debugElement.query(By.css('.adf-search-filter-placeholder')).nativeElement.innerText;
|
||||||
expect(label).toBe(component.tabbedFacet.label + ':');
|
expect(label).toBe(component.tabbedFacet.label + ':');
|
||||||
@@ -105,16 +119,14 @@ describe('SearchFacetChipTabbedComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should display correct title when facet is opened', async () => {
|
it('should display correct title when facet is opened', async () => {
|
||||||
const chip = await loader.getHarness(MatChipHarness);
|
await clickChip();
|
||||||
await (await chip.host()).click();
|
|
||||||
|
|
||||||
const title = fixture.debugElement.query(By.css('.adf-search-filter-title')).nativeElement.innerText.split('\n')[0];
|
const title = fixture.debugElement.query(By.css('.adf-search-filter-title')).nativeElement.innerText.split('\n')[0];
|
||||||
expect(title).toBe(component.tabbedFacet.label);
|
expect(title).toBe(component.tabbedFacet.label);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display adf-search-facet-tabbed-content component', async () => {
|
it('should display adf-search-facet-tabbed-content component', async () => {
|
||||||
const chip = await loader.getHarness(MatChipHarness);
|
await clickChip();
|
||||||
await (await chip.host()).click();
|
|
||||||
|
|
||||||
const activeTabLabel = fixture.debugElement.query(By.css('adf-search-facet-tabbed-content'));
|
const activeTabLabel = fixture.debugElement.query(By.css('adf-search-facet-tabbed-content'));
|
||||||
expect(activeTabLabel).toBeTruthy();
|
expect(activeTabLabel).toBeTruthy();
|
||||||
@@ -156,8 +168,7 @@ describe('SearchFacetChipTabbedComponent', () => {
|
|||||||
|
|
||||||
it('should update display value when new displayValue$ emitted', async () => {
|
it('should update display value when new displayValue$ emitted', async () => {
|
||||||
const displayValue = 'field_LABEL: test, test2';
|
const displayValue = 'field_LABEL: test, test2';
|
||||||
const chip = await loader.getHarness(MatChipHarness);
|
await clickChip();
|
||||||
await (await chip.host()).click();
|
|
||||||
|
|
||||||
emitChildEvent('displayValue$', displayValue);
|
emitChildEvent('displayValue$', displayValue);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -168,8 +179,7 @@ describe('SearchFacetChipTabbedComponent', () => {
|
|||||||
spyOn(component.menuTrigger, 'closeMenu').and.callThrough();
|
spyOn(component.menuTrigger, 'closeMenu').and.callThrough();
|
||||||
spyOn(component, 'onApply').and.callThrough();
|
spyOn(component, 'onApply').and.callThrough();
|
||||||
|
|
||||||
const chip = await loader.getHarness(MatChipHarness);
|
await clickChip();
|
||||||
await (await chip.host()).click();
|
|
||||||
|
|
||||||
const applyButton = fixture.debugElement.query(By.css('#apply-filter-button'));
|
const applyButton = fixture.debugElement.query(By.css('#apply-filter-button'));
|
||||||
applyButton.triggerEventHandler('click', {});
|
applyButton.triggerEventHandler('click', {});
|
||||||
@@ -181,12 +191,26 @@ describe('SearchFacetChipTabbedComponent', () => {
|
|||||||
spyOn(component.menuTrigger, 'closeMenu').and.callThrough();
|
spyOn(component.menuTrigger, 'closeMenu').and.callThrough();
|
||||||
spyOn(component, 'onRemove').and.callThrough();
|
spyOn(component, 'onRemove').and.callThrough();
|
||||||
|
|
||||||
const chip = await loader.getHarness(MatChipHarness);
|
await clickChip();
|
||||||
await (await chip.host()).click();
|
|
||||||
|
|
||||||
const applyButton = fixture.debugElement.query(By.css('#cancel-filter-button'));
|
const applyButton = fixture.debugElement.query(By.css('#cancel-filter-button'));
|
||||||
applyButton.triggerEventHandler('click', {});
|
applyButton.triggerEventHandler('click', {});
|
||||||
expect(component.menuTrigger.closeMenu).toHaveBeenCalled();
|
expect(component.menuTrigger.closeMenu).toHaveBeenCalled();
|
||||||
expect(component.onRemove).toHaveBeenCalled();
|
expect(component.onRemove).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create focus trap and focus initial element when menu opens', async () => {
|
||||||
|
await clickChip();
|
||||||
|
|
||||||
|
expect(focusTrapFactory.create).toHaveBeenCalledWith(component.menuContainer.nativeElement);
|
||||||
|
expect(focusTrap.focusInitialElement).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should destroy focus trap on menu closed', () => {
|
||||||
|
component.focusTrap = focusTrap;
|
||||||
|
component.onClosed();
|
||||||
|
|
||||||
|
expect(focusTrap.destroy).toHaveBeenCalled();
|
||||||
|
expect(component.focusTrap).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+11
-5
@@ -64,17 +64,23 @@ export class SearchFacetChipTabbedComponent {
|
|||||||
chipIcon = 'keyboard_arrow_down';
|
chipIcon = 'keyboard_arrow_down';
|
||||||
isPopulated = false;
|
isPopulated = false;
|
||||||
|
|
||||||
constructor(private focusTrapFactory: ConfigurableFocusTrapFactory, private changeDetectorRef: ChangeDetectorRef) {}
|
constructor(
|
||||||
|
private readonly focusTrapFactory: ConfigurableFocusTrapFactory,
|
||||||
|
private readonly changeDetectorRef: ChangeDetectorRef
|
||||||
|
) {}
|
||||||
|
|
||||||
onMenuOpen() {
|
onMenuOpen() {
|
||||||
if (this.menuContainer && !this.focusTrap) {
|
setTimeout(() => {
|
||||||
this.focusTrap = this.focusTrapFactory.create(this.menuContainer.nativeElement);
|
if (this.menuContainer && !this.focusTrap) {
|
||||||
}
|
this.focusTrap = this.focusTrapFactory.create(this.menuContainer.nativeElement);
|
||||||
|
this.focusTrap.focusInitialElement();
|
||||||
|
}
|
||||||
|
});
|
||||||
this.chipIcon = 'keyboard_arrow_up';
|
this.chipIcon = 'keyboard_arrow_up';
|
||||||
}
|
}
|
||||||
|
|
||||||
onClosed() {
|
onClosed() {
|
||||||
this.focusTrap.destroy();
|
this.focusTrap?.destroy();
|
||||||
this.focusTrap = null;
|
this.focusTrap = null;
|
||||||
this.chipIcon = 'keyboard_arrow_down';
|
this.chipIcon = 'keyboard_arrow_down';
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-1
@@ -26,6 +26,7 @@ import { HarnessLoader } from '@angular/cdk/testing';
|
|||||||
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
||||||
import { MatChipHarness } from '@angular/material/chips/testing';
|
import { MatChipHarness } from '@angular/material/chips/testing';
|
||||||
import { MatIconHarness } from '@angular/material/icon/testing';
|
import { MatIconHarness } from '@angular/material/icon/testing';
|
||||||
|
import { ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';
|
||||||
|
|
||||||
describe('SearchWidgetChipComponent', () => {
|
describe('SearchWidgetChipComponent', () => {
|
||||||
let loader: HarnessLoader;
|
let loader: HarnessLoader;
|
||||||
@@ -33,9 +34,13 @@ describe('SearchWidgetChipComponent', () => {
|
|||||||
let fixture: ComponentFixture<SearchWidgetChipComponent>;
|
let fixture: ComponentFixture<SearchWidgetChipComponent>;
|
||||||
let queryBuilder: SearchQueryBuilderService;
|
let queryBuilder: SearchQueryBuilderService;
|
||||||
|
|
||||||
|
const focusTrapFactory = jasmine.createSpyObj('ConfigurableFocusTrapFactory', ['create']);
|
||||||
|
const focusTrap = jasmine.createSpyObj('ConfigurableFocusTrap', ['focusInitialElement', 'destroy']);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [MatMenuModule, ContentTestingModule]
|
imports: [MatMenuModule, ContentTestingModule],
|
||||||
|
providers: [{ provide: ConfigurableFocusTrapFactory, useValue: focusTrapFactory }]
|
||||||
});
|
});
|
||||||
queryBuilder = TestBed.inject(SearchQueryBuilderService);
|
queryBuilder = TestBed.inject(SearchQueryBuilderService);
|
||||||
fixture = TestBed.createComponent(SearchWidgetChipComponent);
|
fixture = TestBed.createComponent(SearchWidgetChipComponent);
|
||||||
@@ -45,6 +50,7 @@ describe('SearchWidgetChipComponent', () => {
|
|||||||
component.category = simpleCategories[1];
|
component.category = simpleCategories[1];
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||||
|
focusTrapFactory.create.and.returnValue(focusTrap);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update search query on apply click', async () => {
|
it('should update search query on apply click', async () => {
|
||||||
@@ -80,4 +86,23 @@ describe('SearchWidgetChipComponent', () => {
|
|||||||
const icon = await chip.getHarness(MatIconHarness);
|
const icon = await chip.getHarness(MatIconHarness);
|
||||||
expect(await icon.getName()).toBe('keyboard_arrow_up');
|
expect(await icon.getName()).toBe('keyboard_arrow_up');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create focus trap and focus initial element when menu opens', async () => {
|
||||||
|
const chip = await loader.getHarness(MatChipHarness);
|
||||||
|
await (await chip.host()).click();
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
expect(focusTrapFactory.create).toHaveBeenCalledWith(component.menuContainer.nativeElement);
|
||||||
|
expect(focusTrap.focusInitialElement).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should destroy focus trap on main menu closed', () => {
|
||||||
|
component.focusTrap = focusTrap;
|
||||||
|
component.onClosed();
|
||||||
|
|
||||||
|
expect(focusTrap.destroy).toHaveBeenCalled();
|
||||||
|
expect(component.focusTrap).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+10
-4
@@ -66,7 +66,10 @@ export class SearchWidgetChipComponent implements AfterViewInit {
|
|||||||
focusTrap: ConfigurableFocusTrap;
|
focusTrap: ConfigurableFocusTrap;
|
||||||
chipIcon = 'keyboard_arrow_down';
|
chipIcon = 'keyboard_arrow_down';
|
||||||
|
|
||||||
constructor(private readonly cd: ChangeDetectorRef, private readonly focusTrapFactory: ConfigurableFocusTrapFactory) {}
|
constructor(
|
||||||
|
private readonly cd: ChangeDetectorRef,
|
||||||
|
private readonly focusTrapFactory: ConfigurableFocusTrapFactory
|
||||||
|
) {}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.widgetContainerComponent
|
this.widgetContainerComponent
|
||||||
@@ -78,9 +81,12 @@ export class SearchWidgetChipComponent implements AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMenuOpen() {
|
onMenuOpen() {
|
||||||
if (this.menuContainer && !this.focusTrap) {
|
setTimeout(() => {
|
||||||
this.focusTrap = this.focusTrapFactory.create(this.menuContainer.nativeElement);
|
if (this.menuContainer && !this.focusTrap) {
|
||||||
}
|
this.focusTrap = this.focusTrapFactory.create(this.menuContainer.nativeElement);
|
||||||
|
this.focusTrap.focusInitialElement();
|
||||||
|
}
|
||||||
|
});
|
||||||
this.chipIcon = 'keyboard_arrow_up';
|
this.chipIcon = 'keyboard_arrow_up';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user