diff --git a/src/cdk/a11y/BUILD.bazel b/src/cdk/a11y/BUILD.bazel index 849cc4d2d2f4..81148f1486d2 100644 --- a/src/cdk/a11y/BUILD.bazel +++ b/src/cdk/a11y/BUILD.bazel @@ -49,6 +49,7 @@ ng_test_library( ":a11y", "//src/cdk/keycodes", "//src/cdk/observers", + "//src/cdk/overlay", "//src/cdk/platform", "//src/cdk/portal", "//src/cdk/testing/private", diff --git a/src/cdk/a11y/live-announcer/live-announcer.spec.ts b/src/cdk/a11y/live-announcer/live-announcer.spec.ts index 99605d10d3f9..03fa51d0c496 100644 --- a/src/cdk/a11y/live-announcer/live-announcer.spec.ts +++ b/src/cdk/a11y/live-announcer/live-announcer.spec.ts @@ -1,4 +1,6 @@ import {MutationObserverFactory} from '@angular/cdk/observers'; +import {Overlay} from '@angular/cdk/overlay'; +import {ComponentPortal} from '@angular/cdk/portal'; import {Component} from '@angular/core'; import {ComponentFixture, fakeAsync, flush, inject, TestBed, tick} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; @@ -12,6 +14,7 @@ import { describe('LiveAnnouncer', () => { let announcer: LiveAnnouncer; + let overlay: Overlay; let ariaLiveElement: Element; let fixture: ComponentFixture; @@ -19,17 +22,16 @@ describe('LiveAnnouncer', () => { beforeEach(() => TestBed.configureTestingModule({ imports: [A11yModule], - declarations: [TestApp], + declarations: [TestApp, TestModal], }), ); - beforeEach(fakeAsync( - inject([LiveAnnouncer], (la: LiveAnnouncer) => { - announcer = la; - ariaLiveElement = getLiveElement(); - fixture = TestBed.createComponent(TestApp); - }), - )); + beforeEach(fakeAsync(() => { + overlay = TestBed.inject(Overlay); + announcer = TestBed.inject(LiveAnnouncer); + ariaLiveElement = getLiveElement(); + fixture = TestBed.createComponent(TestApp); + })); it('should correctly update the announce text', fakeAsync(() => { let buttonElement = fixture.debugElement.query(By.css('button'))!.nativeElement; @@ -172,6 +174,49 @@ describe('LiveAnnouncer', () => { // Since we're testing whether the timeouts were flushed, we don't need any // assertions here. `fakeAsync` will fail the test if a timer was left over. })); + + it('should add aria-owns to open aria-modal elements', fakeAsync(() => { + const portal = new ComponentPortal(TestModal); + const overlayRef = overlay.create(); + const componentRef = overlayRef.attach(portal); + const modal = componentRef.location.nativeElement; + fixture.detectChanges(); + + expect(ariaLiveElement.id).toBeTruthy(); + expect(modal.hasAttribute('aria-owns')).toBe(false); + + announcer.announce('Hey Google', 'assertive'); + tick(100); + expect(modal.getAttribute('aria-owns')).toBe(ariaLiveElement.id); + + // Verify that the ID isn't duplicated. + announcer.announce('Hey Google again', 'assertive'); + tick(100); + expect(modal.getAttribute('aria-owns')).toBe(ariaLiveElement.id); + })); + + it('should expand aria-owns of open aria-modal elements', fakeAsync(() => { + const portal = new ComponentPortal(TestModal); + const overlayRef = overlay.create(); + const componentRef = overlayRef.attach(portal); + const modal = componentRef.location.nativeElement; + fixture.detectChanges(); + + componentRef.instance.ariaOwns = 'foo bar'; + componentRef.changeDetectorRef.detectChanges(); + + expect(ariaLiveElement.id).toBeTruthy(); + expect(modal.getAttribute('aria-owns')).toBe('foo bar'); + + announcer.announce('Hey Google', 'assertive'); + tick(100); + expect(modal.getAttribute('aria-owns')).toBe(`foo bar ${ariaLiveElement.id}`); + + // Verify that the ID isn't duplicated. + announcer.announce('Hey Google again', 'assertive'); + tick(100); + expect(modal.getAttribute('aria-owns')).toBe(`foo bar ${ariaLiveElement.id}`); + })); }); describe('with a custom element', () => { @@ -359,6 +404,11 @@ class TestApp { } } +@Component({template: '', host: {'[attr.aria-owns]': 'ariaOwns', 'aria-modal': 'true'}}) +class TestModal { + ariaOwns: string | null = null; +} + @Component({ template: `
.cdk-overlay-container [aria-modal="true"]', + ); + + for (let i = 0; i < modals.length; i++) { + const modal = modals[i]; + const ariaOwns = modal.getAttribute('aria-owns'); + + if (!ariaOwns) { + modal.setAttribute('aria-owns', id); + } else if (ariaOwns.indexOf(id) === -1) { + modal.setAttribute('aria-owns', ariaOwns + ' ' + id); + } + } + } } /**