diff --git a/src/cdk/menu/menu-aim.ts b/src/cdk/menu/menu-aim.ts index 7e04e120cda6..276a8b568990 100644 --- a/src/cdk/menu/menu-aim.ts +++ b/src/cdk/menu/menu-aim.ts @@ -130,6 +130,12 @@ export class TargetMenuAim implements MenuAim, OnDestroy { ngOnDestroy() { this._cleanupMousemove?.(); + + if (this._timeoutId !== null) { + clearTimeout(this._timeoutId); + this._timeoutId = null; + } + this._destroyed.next(); this._destroyed.complete(); } diff --git a/src/cdk/menu/menu.spec.ts b/src/cdk/menu/menu.spec.ts index 8bfd26e24c95..8e7cd6c8c787 100644 --- a/src/cdk/menu/menu.spec.ts +++ b/src/cdk/menu/menu.spec.ts @@ -80,12 +80,6 @@ describe('Menu', () => { }); describe('menu aim', () => { - // TODO(crisbeto): update the component to clear timeouts on destroy. - // Give some time for timeouts to be cleaned up. - afterEach(async () => { - await wait(350); - }); - /** A coordinate in the browser window */ type Point = {x: number; y: number}; @@ -276,6 +270,33 @@ describe('Menu', () => { expect(nativeMenus[1].id).toBe('share_menu'); }); + it('should not toggle a menu if the menu aim is destroyed while waiting', async () => { + openFileMenu(); + openMenuOnHover(nativeEditTrigger!); + const editPosition = nativeEditTrigger!.getBoundingClientRect(); + const sharePosition = nativeShareTrigger!.getBoundingClientRect(); + + // Move towards the edit submenu and stop on the share trigger so that + // the menu aim delays opening the share menu. + await hover( + { + x: editPosition.x + editPosition.width / 2, + y: editPosition.y + editPosition.height - 10, + }, + { + x: sharePosition.x + sharePosition.width - 10, + y: sharePosition.y + sharePosition.height - 10, + }, + nativeMenus[0], + 0, + ); + + fixture.destroy(); + await wait(350); + + expect(document.querySelector('#share_menu')).toBeNull(); + }); + it('should not close the edit submenu when hovering into its items in time', async () => { openFileMenu(); openMenuOnHover(nativeEditTrigger!);