Skip to content

Commit

Permalink
feat(tooltip): The aria-expanded attribute of the anchor element will…
Browse files Browse the repository at this point in the history
… only be changed for anchor elements with interactive rich tooltips. Non-interactive rich tooltip anchor elements do not have the aria-haspopup and aria-expanded attributes.

PiperOrigin-RevId: 346614593
  • Loading branch information
Shi Shu authored and Copybara-Service committed Dec 9, 2020
1 parent 1085c3b commit c5dda80
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 20 deletions.
15 changes: 9 additions & 6 deletions packages/mdc-tooltip/component.ts
Expand Up @@ -33,8 +33,8 @@ export class MDCTooltip extends MDCComponent<MDCTooltipFoundation> {
return new MDCTooltip(root);
}

private anchorElem!: HTMLElement|null; // assigned in initialSyncWithDOM
private tooltipElem!: HTMLElement|null; // assigned in initialSyncWithDOM
private anchorElem!: HTMLElement; // assigned in initialize
private tooltipElem!: HTMLElement|null; // assigned in initialize
private isTooltipRich!: boolean; // assigned in initialSyncWithDOM
private isTooltipPersistent!: boolean; // assigned in initialSyncWithDOM

Expand All @@ -45,21 +45,24 @@ export class MDCTooltip extends MDCComponent<MDCTooltipFoundation> {
private handleTransitionEnd!: SpecificEventListener<'transitionend'>;
private handleClick!: SpecificEventListener<'click'>;

initialSyncWithDOM() {
initialize() {
const tooltipId = this.root.getAttribute('id');
if (!tooltipId) {
throw new Error('MDCTooltip: Tooltip component must have an id.');
}

this.anchorElem = document.querySelector<HTMLElement>(
`[aria-describedby="${tooltipId}"]`) ||
const anchorElem = document.querySelector<HTMLElement>(
`[aria-describedby="${tooltipId}"]`) ||
document.querySelector<HTMLElement>(`[data-tooltip-id="${tooltipId}"]`);
this.tooltipElem = document.querySelector<HTMLElement>(`#${tooltipId}`);
if (!this.anchorElem) {
if (!anchorElem) {
throw new Error(
'MDCTooltip: Tooltip component requires an anchor element annotated with [aria-describedby] or [data-tooltip-id] anchor element.');
}
this.anchorElem = anchorElem;
}

initialSyncWithDOM() {
this.isTooltipRich = this.foundation.getIsRich();
this.isTooltipPersistent = this.foundation.getIsPersistent();

Expand Down
2 changes: 2 additions & 0 deletions packages/mdc-tooltip/constants.ts
Expand Up @@ -44,6 +44,8 @@ const numbers = {
};

const attributes = {
ARIA_EXPANDED: 'aria-expanded',
ARIA_HASPOPUP: 'aria-haspopup',
PERSISTENT: 'data-mdc-tooltip-persistent',
};

Expand Down
16 changes: 12 additions & 4 deletions packages/mdc-tooltip/foundation.ts
Expand Up @@ -73,8 +73,9 @@ export class MDCTooltipFoundation extends MDCFoundation<MDCTooltipAdapter> {
};
}

private isRich!: boolean; // assigned in init()
private isPersistent!: boolean; // assigned in init()
private isInteractive!: boolean; // assigned in init()
private isRich!: boolean; // assigned in init()
private isPersistent!: boolean; // assigned in init()
private isShown = false;
private anchorGap = numbers.BOUNDED_ANCHOR_GAP;
private xTooltipPos = XPosition.DETECTED;
Expand Down Expand Up @@ -138,6 +139,9 @@ export class MDCTooltipFoundation extends MDCFoundation<MDCTooltipAdapter> {
this.isRich = this.adapter.hasClass(RICH);
this.isPersistent =
this.adapter.getAttribute(attributes.PERSISTENT) === 'true';
this.isInteractive =
!!this.adapter.getAnchorAttribute(attributes.ARIA_EXPANDED) &&
this.adapter.getAnchorAttribute(attributes.ARIA_HASPOPUP) === 'true';
}

getIsRich() {
Expand Down Expand Up @@ -274,7 +278,9 @@ export class MDCTooltipFoundation extends MDCFoundation<MDCTooltipAdapter> {
this.adapter.setAttribute('aria-hidden', 'false');
}
if (this.isRich) {
this.adapter.setAnchorAttribute('aria-expanded', 'true');
if (this.isInteractive) {
this.adapter.setAnchorAttribute('aria-expanded', 'true');
}
this.adapter.registerEventHandler(
'focusout', this.richTooltipFocusOutHandler);
if (!this.isPersistent) {
Expand Down Expand Up @@ -324,7 +330,9 @@ export class MDCTooltipFoundation extends MDCFoundation<MDCTooltipAdapter> {
this.adapter.deregisterEventHandler(
'focusout', this.richTooltipFocusOutHandler);
if (this.isRich) {
this.adapter.setAnchorAttribute('aria-expanded', 'false');
if (this.isInteractive) {
this.adapter.setAnchorAttribute('aria-expanded', 'false');
}
if (!this.isPersistent) {
this.adapter.deregisterEventHandler(
'mouseenter', this.richTooltipMouseEnterHandler);
Expand Down
2 changes: 1 addition & 1 deletion packages/mdc-tooltip/test/component.test.ts
Expand Up @@ -261,7 +261,7 @@ describe('MDCTooltip', () => {
});
});

describe('default rich tooltip tests', () => {
describe('default interactive rich tooltip tests', () => {
beforeEach(() => {
fixture = getFixture(`<div>
<button aria-describedby="tt0" aria-haspopup="true" aria-expanded="false">
Expand Down
39 changes: 30 additions & 9 deletions packages/mdc-tooltip/test/foundation.test.ts
Expand Up @@ -48,10 +48,12 @@ function expectShowToBeCalled(
expect(foundation['showTimeout']).toEqual(null);

if (foundation.getIsRich()) {
expect(mockAdapter.setAnchorAttribute)
.toHaveBeenCalledWith('aria-expanded', 'true');
expect(mockAdapter.registerEventHandler)
.toHaveBeenCalledWith('focusout', jasmine.any(Function));
if (foundation['isInteractive']) {
expect(mockAdapter.setAnchorAttribute)
.toHaveBeenCalledWith('aria-expanded', 'true');
}
if (!foundation.getIsPersistent()) {
expect(mockAdapter.registerEventHandler)
.toHaveBeenCalledWith('mouseenter', jasmine.any(Function));
Expand Down Expand Up @@ -87,10 +89,12 @@ function expectHideToBeCalled(
expect(foundation['showTimeout']).toEqual(null);

if (foundation.getIsRich()) {
expect(mockAdapter.setAnchorAttribute)
.toHaveBeenCalledWith('aria-expanded', 'false');
expect(mockAdapter.deregisterEventHandler)
.toHaveBeenCalledWith('focusout', jasmine.any(Function));
if (foundation['isInteractive']) {
expect(mockAdapter.setAnchorAttribute)
.toHaveBeenCalledWith('aria-expanded', 'false');
}
if (!foundation.getIsPersistent()) {
expect(mockAdapter.deregisterEventHandler)
.toHaveBeenCalledWith('mouseenter', jasmine.any(Function));
Expand Down Expand Up @@ -118,10 +122,12 @@ function expectHideNotToBeCalled(
foundation: MDCTooltipFoundation,
mockAdapter: jasmine.SpyObj<MDCTooltipAdapter>) {
if (foundation.getIsRich()) {
expect(mockAdapter.setAnchorAttribute)
.not.toHaveBeenCalledWith('aria-expanded', 'false');
expect(mockAdapter.deregisterEventHandler)
.not.toHaveBeenCalledWith('focusout', jasmine.any(Function));
if (foundation['isInteractive']) {
expect(mockAdapter.setAnchorAttribute)
.not.toHaveBeenCalledWith('aria-expanded', 'false');
}
if (!foundation.getIsPersistent()) {
expect(mockAdapter.deregisterEventHandler)
.not.toHaveBeenCalledWith('mouseenter', jasmine.any(Function));
Expand Down Expand Up @@ -149,12 +155,17 @@ function expectHideNotToBeCalled(

function setUpFoundationTestForRichTooltip(
tooltipFoundation: typeof MDCTooltipFoundation,
{isPersistent}: {isPersistent?: boolean} = {}) {
{isInteractive,
isPersistent}: {isInteractive?: boolean, isPersistent?: boolean} = {}) {
const {foundation, mockAdapter} = setUpFoundationTest(tooltipFoundation);

mockAdapter.hasClass.withArgs(CssClasses.RICH).and.returnValue(true);
mockAdapter.getAttribute.withArgs(attributes.PERSISTENT)
.and.returnValue(isPersistent ? 'true' : 'false');
mockAdapter.getAnchorAttribute.withArgs(attributes.ARIA_EXPANDED)
.and.returnValue(isInteractive ? 'false' : null);
mockAdapter.getAnchorAttribute.withArgs(attributes.ARIA_HASPOPUP)
.and.returnValue(isInteractive ? 'true' : 'false');
foundation.init();

return {foundation, mockAdapter};
Expand Down Expand Up @@ -227,14 +238,24 @@ describe('MDCTooltipFoundation', () => {
expect(mockAdapter.addClass).toHaveBeenCalledWith(CssClasses.SHOWING);
});

it('#show sets aria-expanded="true" on anchor element for rich tooltip',
it('#show does not set aria-expanded="true" on anchor element for non-interactive rich tooltip',
() => {
const {foundation, mockAdapter} =
setUpFoundationTestForRichTooltip(MDCTooltipFoundation);

foundation.show();

expect(mockAdapter.hasClass).toHaveBeenCalledWith(CssClasses.RICH);
expect(mockAdapter.setAnchorAttribute)
.not.toHaveBeenCalledWith('aria-expanded', 'true');
});

it('#show sets aria-expanded="true" on anchor element for interactive rich tooltip',
() => {
const {foundation, mockAdapter} = setUpFoundationTestForRichTooltip(
MDCTooltipFoundation, {isInteractive: true});

foundation.show();

expect(mockAdapter.setAnchorAttribute)
.toHaveBeenCalledWith('aria-expanded', 'true');
});
Expand Down

0 comments on commit c5dda80

Please sign in to comment.