diff --git a/src/material/tooltip/testing/tooltip-harness.ts b/src/material/tooltip/testing/tooltip-harness.ts index 2232e80ef167..b7e4f8699ade 100644 --- a/src/material/tooltip/testing/tooltip-harness.ts +++ b/src/material/tooltip/testing/tooltip-harness.ts @@ -10,6 +10,7 @@ import { ComponentHarness, ComponentHarnessConstructor, HarnessPredicate, + TestElement, } from '@angular/cdk/testing'; import {TooltipHarnessFilters} from './tooltip-harness-filters'; @@ -17,7 +18,6 @@ import {TooltipHarnessFilters} from './tooltip-harness-filters'; export class MatTooltipHarness extends ComponentHarness { static hostSelector = '.mat-mdc-tooltip-trigger'; - private _optionalPanel = this.documentRootLocatorFactory().locatorForOptional('.mat-mdc-tooltip'); private _hiddenClass = 'mat-mdc-tooltip-hide'; private _disabledClass = 'mat-mdc-tooltip-disabled'; private _showAnimationName = 'mat-mdc-tooltip-show'; @@ -45,7 +45,7 @@ export class MatTooltipHarness extends ComponentHarness { // element has ripples. await host.dispatchEvent('touchstart', {changedTouches: []}); await host.hover(); - const panel = await this._optionalPanel(); + const panel = await this._getPanel(); await panel?.dispatchEvent('animationend', {animationName: this._showAnimationName}); } @@ -57,13 +57,13 @@ export class MatTooltipHarness extends ComponentHarness { // the tooltip binds different events depending on the device. await host.dispatchEvent('touchend'); await host.mouseAway(); - const panel = await this._optionalPanel(); + const panel = await this._getPanel(); await panel?.dispatchEvent('animationend', {animationName: this._hideAnimationName}); } /** Gets whether the tooltip is open. */ async isOpen(): Promise { - const panel = await this._optionalPanel(); + const panel = await this._getPanel(); return !!panel && !(await panel.hasClass(this._hiddenClass)); } @@ -75,7 +75,14 @@ export class MatTooltipHarness extends ComponentHarness { /** Gets a promise for the tooltip panel's text. */ async getTooltipText(): Promise { - const panel = await this._optionalPanel(); + const panel = await this._getPanel(); return panel ? panel.text() : ''; } + + /** Gets the tooltip panel associated with the trigger. */ + private async _getPanel(): Promise { + const host = await this.host(); + const locatorFactory = this.documentRootLocatorFactory(); + return locatorFactory.locatorForOptional(`#${await host.getAttribute('data-mat-tooltip')}`)(); + } } diff --git a/src/material/tooltip/tooltip.ts b/src/material/tooltip/tooltip.ts index 0d9edecfb2a6..9219a1a06e24 100644 --- a/src/material/tooltip/tooltip.ts +++ b/src/material/tooltip/tooltip.ts @@ -171,6 +171,7 @@ const MIN_VIEWPORT_TOOLTIP_THRESHOLD = 8; const UNBOUNDED_ANCHOR_GAP = 8; const MIN_HEIGHT = 24; const MAX_WIDTH = 200; +let uniqueId = 0; /** * Directive that attaches a material design tooltip to the host element. Animates the showing and @@ -184,6 +185,8 @@ const MAX_WIDTH = 200; host: { 'class': 'mat-mdc-tooltip-trigger', '[class.mat-mdc-tooltip-disabled]': 'disabled', + // Used by harnesses to match the trigger to its tooltip. + '[attr.data-mat-tooltip]': '_panelId', }, standalone: true, }) @@ -218,6 +221,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit { private _currentPosition: TooltipPosition; private readonly _cssClassPrefix: string = 'mat-mdc'; private _ariaDescriptionPending: boolean; + protected readonly _panelId = `mat-tooltip-panel-${uniqueId++}`; /** Allows the user to define the position of the tooltip relative to the parent element */ @Input('matTooltipPosition') @@ -467,6 +471,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit { const instance = (this._tooltipInstance = overlayRef.attach(this._portal).instance); instance._triggerElement = this._elementRef.nativeElement; instance._mouseLeaveHideDelay = this._hideDelay; + instance._id = this._panelId; instance .afterHidden() .pipe(takeUntil(this._destroyed)) @@ -946,6 +951,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit { host: { '(mouseleave)': '_handleMouseLeave($event)', 'aria-hidden': 'true', + '[id]': '_id', }, standalone: true, imports: [NgClass], @@ -975,6 +981,9 @@ export class TooltipComponent implements OnDestroy { /** Amount of milliseconds to delay the closing sequence. */ _mouseLeaveHideDelay: number; + /** Unique ID for the panel. Assigned by the trigger. */ + _id: string | undefined; + /** Whether animations are currently disabled. */ private _animationsDisabled: boolean; diff --git a/tools/public_api_guard/material/tooltip.md b/tools/public_api_guard/material/tooltip.md index 1c83fa4db149..00dee7a9f7b4 100644 --- a/tools/public_api_guard/material/tooltip.md +++ b/tools/public_api_guard/material/tooltip.md @@ -75,6 +75,8 @@ export class MatTooltip implements OnDestroy, AfterViewInit { ngOnDestroy(): void; // (undocumented) _overlayRef: OverlayRef | null; + // (undocumented) + protected readonly _panelId: string; get position(): TooltipPosition; set position(value: TooltipPosition); get positionAtOrigin(): boolean; @@ -150,6 +152,7 @@ export class TooltipComponent implements OnDestroy { // (undocumented) _handleMouseLeave({ relatedTarget }: MouseEvent): void; hide(delay: number): void; + _id: string | undefined; // (undocumented) _isMultiline: boolean; isVisible(): boolean;