From 1006cc2d0c7f32b8018eab07f1526f43f8a4464a Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 9 Sep 2019 16:17:19 +0200 Subject: [PATCH] fix(tooltip): avoid adding same aria description as trigger's aria-label (#16870) We automatically add an `aria-describedby` to the triggers of tooltips so that assistive technologies can read out the tooltip text. As a part of this process, the `AriaDescriber` has some functionality that checks whether the element doesn't have an `aria-label` with the same value already, and if it does, it avoids adding the description. The problem is that tooltips add this description very early in their lifecycle which means that if the element has a data-bound `aria-label`, it won't be detected by the `AriaDescriber`, as is the case for the buttons in the `mat-paginator`. These changes work around the issue by deferring the setting of the description. Fixes #16719. --- src/material/tooltip/tooltip.spec.ts | 30 ++++++++++++++++++++-------- src/material/tooltip/tooltip.ts | 10 +++++++++- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/material/tooltip/tooltip.spec.ts b/src/material/tooltip/tooltip.spec.ts index b5d3df5cd6ac..fabdcd6af7f0 100644 --- a/src/material/tooltip/tooltip.spec.ts +++ b/src/material/tooltip/tooltip.spec.ts @@ -63,6 +63,7 @@ describe('MatTooltip', () => { DynamicTooltipsDemo, TooltipOnTextFields, TooltipOnDraggableElement, + DataBoundAriaLabelTooltip, ], providers: [ {provide: Platform, useFactory: () => platform}, @@ -441,20 +442,31 @@ describe('MatTooltip', () => { expect(overlayContainerElement.textContent).toBe(''); })); - it('should have an aria-described element with the tooltip message', () => { + it('should have an aria-described element with the tooltip message', fakeAsync(() => { const dynamicTooltipsDemoFixture = TestBed.createComponent(DynamicTooltipsDemo); const dynamicTooltipsComponent = dynamicTooltipsDemoFixture.componentInstance; dynamicTooltipsComponent.tooltips = ['Tooltip One', 'Tooltip Two']; dynamicTooltipsDemoFixture.detectChanges(); + tick(); - const buttons = dynamicTooltipsComponent.getButtons(); + const buttons = dynamicTooltipsDemoFixture.nativeElement.querySelectorAll('button'); const firstButtonAria = buttons[0].getAttribute('aria-describedby'); expect(document.querySelector(`#${firstButtonAria}`)!.textContent).toBe('Tooltip One'); const secondButtonAria = buttons[1].getAttribute('aria-describedby'); expect(document.querySelector(`#${secondButtonAria}`)!.textContent).toBe('Tooltip Two'); - }); + })); + + it('should not add an ARIA description for elements that have the same text as a' + + 'data-bound aria-label', fakeAsync(() => { + const ariaLabelFixture = TestBed.createComponent(DataBoundAriaLabelTooltip); + ariaLabelFixture.detectChanges(); + tick(); + + const button = ariaLabelFixture.nativeElement.querySelector('button'); + expect(button.getAttribute('aria-describedby')).toBeFalsy(); + })); it('should not try to dispose the tooltip when destroyed and done hiding', fakeAsync(() => { tooltipDirective.show(); @@ -1011,14 +1023,16 @@ class OnPushTooltipDemo { }) class DynamicTooltipsDemo { tooltips: Array = []; +} - constructor(private _elementRef: ElementRef) {} - - getButtons() { - return this._elementRef.nativeElement.querySelectorAll('button'); - } +@Component({ + template: ``, +}) +class DataBoundAriaLabelTooltip { + message = 'Hello there'; } + @Component({ template: ` { + // The `AriaDescriber` has some functionality that avoids adding a description if it's the + // same as the `aria-label` of an element, however we can't know whether the tooltip trigger + // has a data-bound `aria-label` or when it'll be set for the first time. We can avoid the + // issue by deferring the description by a tick so Angular has time to set the `aria-label`. + Promise.resolve().then(() => { + this._ariaDescriber.describe(this._elementRef.nativeElement, this.message); + }); + }); } }