From 365c69360230540a67dd141f6bec999b2541a7e8 Mon Sep 17 00:00:00 2001 From: Shi Shu Date: Tue, 5 Jan 2021 09:39:23 -0800 Subject: [PATCH] fix(tooltip): Clear hideTimeout in handleAnchorMouseEnter so that the tooltip will not be hidden if the user rapidly moves the mouse in and out of the anchor element. PiperOrigin-RevId: 350158219 --- packages/mdc-tooltip/foundation.ts | 4 ++++ packages/mdc-tooltip/test/component.test.ts | 17 +++++++++++++++ packages/mdc-tooltip/test/foundation.test.ts | 22 ++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/packages/mdc-tooltip/foundation.ts b/packages/mdc-tooltip/foundation.ts index fd333d2e567..df61db43173 100644 --- a/packages/mdc-tooltip/foundation.ts +++ b/packages/mdc-tooltip/foundation.ts @@ -161,6 +161,10 @@ export class MDCTooltipFoundation extends MDCFoundation { // again. this.show(); } else { + // clearHideTimeout here since handleAnchorMouseLeave sets a hideTimeout + // and that can execute before the showTimeout executes, resulting in hide + // being called and the showTimeout set below to be cleared. + this.clearHideTimeout(); this.showTimeout = setTimeout(() => { this.show(); }, this.showDelayMs); diff --git a/packages/mdc-tooltip/test/component.test.ts b/packages/mdc-tooltip/test/component.test.ts index 09e274744c9..12d2c1a9ca9 100644 --- a/packages/mdc-tooltip/test/component.test.ts +++ b/packages/mdc-tooltip/test/component.test.ts @@ -204,6 +204,23 @@ describe('MDCTooltip', () => { expect(tooltipElem.getAttribute('aria-hidden')).toEqual('false'); }); + // This test accounts for rapid anchor mouseenter/leave. + it('sets aria-hidden to false when going from anchor leave to anchor enter', + () => { + const tooltipElem = fixture.querySelector('#tt0')!; + const anchorElem = + fixture.querySelector('[aria-describedby]')!; + MDCTooltip.attachTo(tooltipElem); + + emitEvent(anchorElem, 'mouseleave'); + jasmine.clock().tick(numbers.HIDE_DELAY_MS / 2); + emitEvent(anchorElem, 'mouseenter'); + jasmine.clock().tick(numbers.SHOW_DELAY_MS); + jasmine.clock().tick(numbers.HIDE_DELAY_MS / 2); + + expect(tooltipElem.getAttribute('aria-hidden')).toEqual('false'); + }); + it('leaves aria-hidden as true when showing tooltip on an anchor annotated with `data-tooltip-id`', () => { document.body.removeChild(fixture); diff --git a/packages/mdc-tooltip/test/foundation.test.ts b/packages/mdc-tooltip/test/foundation.test.ts index 7c927418d8f..faf7eac5c9b 100644 --- a/packages/mdc-tooltip/test/foundation.test.ts +++ b/packages/mdc-tooltip/test/foundation.test.ts @@ -654,6 +654,18 @@ describe('MDCTooltipFoundation', () => { expectTooltipToHaveBeenHidden(foundation, mockAdapter); }); + it('#handleAnchorMouseLeave does not clear showTimeout after #handleAnchorMouseEnter is called', + () => { + const {foundation} = setUpFoundationTest(MDCTooltipFoundation); + + foundation.handleAnchorMouseLeave(); + jasmine.clock().tick(numbers.HIDE_DELAY_MS / 2); + foundation.handleAnchorMouseEnter(); + jasmine.clock().tick(numbers.HIDE_DELAY_MS / 2); + + expect(foundation.showTimeout).not.toEqual(null); + }); + it(`#handleAnchorBlur hides the tooltip immediately for plain tooltips`, () => { const {foundation, mockAdapter} = @@ -761,6 +773,16 @@ describe('MDCTooltipFoundation', () => { expectTooltipToHaveBeenShown(foundation, mockAdapter); }); + it('#handleAnchorMouseEnter clears any pending hideTimeout', () => { + const {foundation} = setUpFoundationTest(MDCTooltipFoundation); + foundation.handleAnchorMouseLeave(); + expect(foundation.hideTimeout).not.toEqual(null); + + foundation.handleAnchorMouseEnter(); + + expect(foundation.hideTimeout).toEqual(null); + }); + it(`#handleAnchorFocus shows the tooltip after a ${ numbers.SHOW_DELAY_MS}ms delay if relatedTarget is not a HTMLElement`, () => {