diff --git a/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.spec.ts b/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.spec.ts index b04b036f00..506a44159f 100644 --- a/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.spec.ts +++ b/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.spec.ts @@ -26,15 +26,20 @@ import { HarnessLoader, TestKey } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { MatChipHarness } from '@angular/material/chips/testing'; import { MatIconHarness } from '@angular/material/icon/testing'; +import { ConfigurableFocusTrapFactory } from '@angular/cdk/a11y'; describe('SearchFacetChipTabbedComponent', () => { let loader: HarnessLoader; let component: SearchFacetChipTabbedComponent; let fixture: ComponentFixture; + const focusTrapFactory = jasmine.createSpyObj('ConfigurableFocusTrapFactory', ['create']); + const focusTrap = jasmine.createSpyObj('ConfigurableFocusTrap', ['focusInitialElement', 'destroy']); + beforeEach(() => { TestBed.configureTestingModule({ imports: [ContentTestingModule], + providers: [{ provide: ConfigurableFocusTrapFactory, useValue: focusTrapFactory }], schemas: [NO_ERRORS_SCHEMA] }); fixture = TestBed.createComponent(SearchFacetChipTabbedComponent); @@ -53,6 +58,7 @@ describe('SearchFacetChipTabbedComponent', () => { }; fixture.detectChanges(); loader = TestbedHarnessEnvironment.loader(fixture); + focusTrapFactory.create.and.returnValue(focusTrap); }); /** @@ -76,6 +82,14 @@ describe('SearchFacetChipTabbedComponent', () => { fixture.detectChanges(); } + /** + * Get chip harness and click it + */ + async function clickChip(): Promise { + const chip = await loader.getHarness(MatChipHarness); + await (await chip.host()).click(); + } + 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 + ':'); @@ -105,16 +119,14 @@ describe('SearchFacetChipTabbedComponent', () => { }); it('should display correct title when facet is opened', async () => { - const chip = await loader.getHarness(MatChipHarness); - await (await chip.host()).click(); + await clickChip(); const title = fixture.debugElement.query(By.css('.adf-search-filter-title')).nativeElement.innerText.split('\n')[0]; expect(title).toBe(component.tabbedFacet.label); }); it('should display adf-search-facet-tabbed-content component', async () => { - const chip = await loader.getHarness(MatChipHarness); - await (await chip.host()).click(); + await clickChip(); const activeTabLabel = fixture.debugElement.query(By.css('adf-search-facet-tabbed-content')); expect(activeTabLabel).toBeTruthy(); @@ -156,8 +168,7 @@ describe('SearchFacetChipTabbedComponent', () => { it('should update display value when new displayValue$ emitted', async () => { const displayValue = 'field_LABEL: test, test2'; - const chip = await loader.getHarness(MatChipHarness); - await (await chip.host()).click(); + await clickChip(); emitChildEvent('displayValue$', displayValue); fixture.detectChanges(); @@ -168,8 +179,7 @@ describe('SearchFacetChipTabbedComponent', () => { spyOn(component.menuTrigger, 'closeMenu').and.callThrough(); spyOn(component, 'onApply').and.callThrough(); - const chip = await loader.getHarness(MatChipHarness); - await (await chip.host()).click(); + await clickChip(); const applyButton = fixture.debugElement.query(By.css('#apply-filter-button')); applyButton.triggerEventHandler('click', {}); @@ -181,12 +191,26 @@ describe('SearchFacetChipTabbedComponent', () => { spyOn(component.menuTrigger, 'closeMenu').and.callThrough(); spyOn(component, 'onRemove').and.callThrough(); - const chip = await loader.getHarness(MatChipHarness); - await (await chip.host()).click(); + await clickChip(); const applyButton = fixture.debugElement.query(By.css('#cancel-filter-button')); applyButton.triggerEventHandler('click', {}); expect(component.menuTrigger.closeMenu).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(); + }); }); diff --git a/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.ts b/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.ts index 989c2318f2..836f325143 100644 --- a/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.ts +++ b/lib/content-services/src/lib/search/components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component.ts @@ -64,17 +64,23 @@ export class SearchFacetChipTabbedComponent { chipIcon = 'keyboard_arrow_down'; isPopulated = false; - constructor(private focusTrapFactory: ConfigurableFocusTrapFactory, private changeDetectorRef: ChangeDetectorRef) {} + constructor( + private readonly focusTrapFactory: ConfigurableFocusTrapFactory, + private readonly changeDetectorRef: ChangeDetectorRef + ) {} onMenuOpen() { - if (this.menuContainer && !this.focusTrap) { - this.focusTrap = this.focusTrapFactory.create(this.menuContainer.nativeElement); - } + setTimeout(() => { + if (this.menuContainer && !this.focusTrap) { + this.focusTrap = this.focusTrapFactory.create(this.menuContainer.nativeElement); + this.focusTrap.focusInitialElement(); + } + }); this.chipIcon = 'keyboard_arrow_up'; } onClosed() { - this.focusTrap.destroy(); + this.focusTrap?.destroy(); this.focusTrap = null; this.chipIcon = 'keyboard_arrow_down'; } diff --git a/lib/content-services/src/lib/search/components/search-filter-chips/search-widget-chip/search-widget-chip.component.spec.ts b/lib/content-services/src/lib/search/components/search-filter-chips/search-widget-chip/search-widget-chip.component.spec.ts index f319087ab1..dc8c8cb647 100644 --- a/lib/content-services/src/lib/search/components/search-filter-chips/search-widget-chip/search-widget-chip.component.spec.ts +++ b/lib/content-services/src/lib/search/components/search-filter-chips/search-widget-chip/search-widget-chip.component.spec.ts @@ -26,6 +26,7 @@ import { HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { MatChipHarness } from '@angular/material/chips/testing'; import { MatIconHarness } from '@angular/material/icon/testing'; +import { ConfigurableFocusTrapFactory } from '@angular/cdk/a11y'; describe('SearchWidgetChipComponent', () => { let loader: HarnessLoader; @@ -33,9 +34,13 @@ describe('SearchWidgetChipComponent', () => { let fixture: ComponentFixture; let queryBuilder: SearchQueryBuilderService; + const focusTrapFactory = jasmine.createSpyObj('ConfigurableFocusTrapFactory', ['create']); + const focusTrap = jasmine.createSpyObj('ConfigurableFocusTrap', ['focusInitialElement', 'destroy']); + beforeEach(() => { TestBed.configureTestingModule({ - imports: [MatMenuModule, ContentTestingModule] + imports: [MatMenuModule, ContentTestingModule], + providers: [{ provide: ConfigurableFocusTrapFactory, useValue: focusTrapFactory }] }); queryBuilder = TestBed.inject(SearchQueryBuilderService); fixture = TestBed.createComponent(SearchWidgetChipComponent); @@ -45,6 +50,7 @@ describe('SearchWidgetChipComponent', () => { component.category = simpleCategories[1]; fixture.detectChanges(); loader = TestbedHarnessEnvironment.loader(fixture); + focusTrapFactory.create.and.returnValue(focusTrap); }); it('should update search query on apply click', async () => { @@ -80,4 +86,23 @@ describe('SearchWidgetChipComponent', () => { const icon = await chip.getHarness(MatIconHarness); 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(); + }); }); diff --git a/lib/content-services/src/lib/search/components/search-filter-chips/search-widget-chip/search-widget-chip.component.ts b/lib/content-services/src/lib/search/components/search-filter-chips/search-widget-chip/search-widget-chip.component.ts index 061b515c6d..65b8717704 100644 --- a/lib/content-services/src/lib/search/components/search-filter-chips/search-widget-chip/search-widget-chip.component.ts +++ b/lib/content-services/src/lib/search/components/search-filter-chips/search-widget-chip/search-widget-chip.component.ts @@ -66,7 +66,10 @@ export class SearchWidgetChipComponent implements AfterViewInit { focusTrap: ConfigurableFocusTrap; chipIcon = 'keyboard_arrow_down'; - constructor(private readonly cd: ChangeDetectorRef, private readonly focusTrapFactory: ConfigurableFocusTrapFactory) {} + constructor( + private readonly cd: ChangeDetectorRef, + private readonly focusTrapFactory: ConfigurableFocusTrapFactory + ) {} ngAfterViewInit(): void { this.widgetContainerComponent @@ -78,9 +81,12 @@ export class SearchWidgetChipComponent implements AfterViewInit { } onMenuOpen() { - if (this.menuContainer && !this.focusTrap) { - this.focusTrap = this.focusTrapFactory.create(this.menuContainer.nativeElement); - } + setTimeout(() => { + if (this.menuContainer && !this.focusTrap) { + this.focusTrap = this.focusTrapFactory.create(this.menuContainer.nativeElement); + this.focusTrap.focusInitialElement(); + } + }); this.chipIcon = 'keyboard_arrow_up'; }