diff --git a/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.spec.ts b/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.spec.ts index 8d03abd239d9a8054e0efed06259e41bffb8d516..fe6160acf32b4e63623ac2996b32f6222e377188 100644 --- a/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.spec.ts +++ b/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.spec.ts @@ -1,5 +1,5 @@ import { getElementFromFixture } from '@alfa-client/test-utils'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { PopupComponent } from './popup.component'; @@ -40,6 +40,46 @@ describe('PopupComponent', () => { }); }); + describe('handleButtonClick', () => { + beforeEach(() => { + component.togglePopup = jest.fn(); + component.focusList = jest.fn(); + }); + + it('should toggle popup', () => { + component.handleButtonClick(); + + expect(component.togglePopup).toHaveBeenCalled(); + }); + + it('should focus list if popup is visible', () => { + component.isPopupOpen = true; + + component.handleButtonClick(); + + expect(component.focusList).toHaveBeenCalled(); + }); + + it('should not focus list if popup is hidden', () => { + component.handleButtonClick(); + + expect(component.focusList).not.toHaveBeenCalled(); + }); + }); + + describe('focusList', () => { + it('should focus popup list item', fakeAsync(() => { + component.isPopupOpen = true; + fixture.detectChanges(); + component.popupListRef.nativeElement.focus = jest.fn(); + + component.focusList(); + tick(); + + expect(component.popupListRef.nativeElement.focus).toHaveBeenCalled(); + })); + }); + describe('aria-expanded', () => { it('should be true if popup is open', () => { component.isPopupOpen = true; diff --git a/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.ts b/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.ts index 0a92ee71d3ecb72428af9fe37b74067121c95802..171397997f1bccc57c87c4a59b014dbd2aa6ed6a 100644 --- a/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.ts +++ b/alfa-client/libs/design-system/src/lib/popup/popup/popup.component.ts @@ -10,7 +10,7 @@ import { twMerge } from 'tailwind-merge'; template: `<div class="relative w-fit"> <button [ngClass]="[twMerge('w-fit outline-2 outline-offset-2 outline-focus', buttonClass)]" - (click)="togglePopup()" + (click)="handleButtonClick()" [attr.aria-expanded]="isPopupOpen" aria-haspopup="true" [attr.aria-label]="label" @@ -25,8 +25,9 @@ import { twMerge } from 'tailwind-merge'; [ngClass]="alignTo === 'left' ? 'right-0' : 'left-0'" role="dialog" aria-modal="true" + tabIndex="-1" cdkTrapFocus - [cdkTrapFocusAutoCapture]="true" + #popupList > <ng-content /> </ul> @@ -38,9 +39,10 @@ export class PopupComponent { @Input() buttonClass: string = ''; isPopupOpen: boolean = false; - twMerge = twMerge; + readonly twMerge = twMerge; @ViewChild('button') buttonRef: ElementRef<HTMLButtonElement>; + @ViewChild('popupList') popupListRef: ElementRef<HTMLUListElement>; @HostListener('document:keydown', ['$event']) onKeydownHandler(e: KeyboardEvent): void { @@ -56,6 +58,15 @@ export class PopupComponent { } } + handleButtonClick(): void { + this.togglePopup(); + if (this.isPopupOpen) this.focusList(); + } + + focusList(): void { + setTimeout(() => this.popupListRef.nativeElement.focus()); + } + togglePopup(): void { this.isPopupOpen = !this.isPopupOpen; }