Skip to content

Commit 73ca6f5

Browse files
committed
fix(material/tooltip): match panel to trigger in test harness
The tooltip harness was written the assumption that only tooltip can be shown on the page at a time, however in tests that might not be the case. These changes add a couple of attributes so a trigger can be matched to its specific panel. Fixes #26773.
1 parent 4adc372 commit 73ca6f5

File tree

3 files changed

+24
-5
lines changed

3 files changed

+24
-5
lines changed

src/material/tooltip/testing/tooltip-harness.ts

+12-5
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ import {
1010
ComponentHarness,
1111
ComponentHarnessConstructor,
1212
HarnessPredicate,
13+
TestElement,
1314
} from '@angular/cdk/testing';
1415
import {TooltipHarnessFilters} from './tooltip-harness-filters';
1516

1617
/** Harness for interacting with a mat-tooltip in tests. */
1718
export class MatTooltipHarness extends ComponentHarness {
1819
static hostSelector = '.mat-mdc-tooltip-trigger';
1920

20-
private _optionalPanel = this.documentRootLocatorFactory().locatorForOptional('.mat-mdc-tooltip');
2121
private _hiddenClass = 'mat-mdc-tooltip-hide';
2222
private _disabledClass = 'mat-mdc-tooltip-disabled';
2323
private _showAnimationName = 'mat-mdc-tooltip-show';
@@ -45,7 +45,7 @@ export class MatTooltipHarness extends ComponentHarness {
4545
// element has ripples.
4646
await host.dispatchEvent('touchstart', {changedTouches: []});
4747
await host.hover();
48-
const panel = await this._optionalPanel();
48+
const panel = await this._getPanel();
4949
await panel?.dispatchEvent('animationend', {animationName: this._showAnimationName});
5050
}
5151

@@ -57,13 +57,13 @@ export class MatTooltipHarness extends ComponentHarness {
5757
// the tooltip binds different events depending on the device.
5858
await host.dispatchEvent('touchend');
5959
await host.mouseAway();
60-
const panel = await this._optionalPanel();
60+
const panel = await this._getPanel();
6161
await panel?.dispatchEvent('animationend', {animationName: this._hideAnimationName});
6262
}
6363

6464
/** Gets whether the tooltip is open. */
6565
async isOpen(): Promise<boolean> {
66-
const panel = await this._optionalPanel();
66+
const panel = await this._getPanel();
6767
return !!panel && !(await panel.hasClass(this._hiddenClass));
6868
}
6969

@@ -75,7 +75,14 @@ export class MatTooltipHarness extends ComponentHarness {
7575

7676
/** Gets a promise for the tooltip panel's text. */
7777
async getTooltipText(): Promise<string> {
78-
const panel = await this._optionalPanel();
78+
const panel = await this._getPanel();
7979
return panel ? panel.text() : '';
8080
}
81+
82+
/** Gets the tooltip panel associated with the trigger. */
83+
private async _getPanel(): Promise<TestElement | null> {
84+
const host = await this.host();
85+
const locatorFactory = this.documentRootLocatorFactory();
86+
return locatorFactory.locatorForOptional(`#${await host.getAttribute('data-mat-tooltip')}`)();
87+
}
8188
}

src/material/tooltip/tooltip.ts

+9
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ const MIN_VIEWPORT_TOOLTIP_THRESHOLD = 8;
171171
const UNBOUNDED_ANCHOR_GAP = 8;
172172
const MIN_HEIGHT = 24;
173173
const MAX_WIDTH = 200;
174+
let uniqueId = 0;
174175

175176
/**
176177
* Directive that attaches a material design tooltip to the host element. Animates the showing and
@@ -184,6 +185,8 @@ const MAX_WIDTH = 200;
184185
host: {
185186
'class': 'mat-mdc-tooltip-trigger',
186187
'[class.mat-mdc-tooltip-disabled]': 'disabled',
188+
// Used by harnesses to match the trigger to its tooltip.
189+
'[attr.data-mat-tooltip]': '_panelId',
187190
},
188191
standalone: true,
189192
})
@@ -218,6 +221,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
218221
private _currentPosition: TooltipPosition;
219222
private readonly _cssClassPrefix: string = 'mat-mdc';
220223
private _ariaDescriptionPending: boolean;
224+
protected readonly _panelId = `mat-tooltip-panel-${uniqueId++}`;
221225

222226
/** Allows the user to define the position of the tooltip relative to the parent element */
223227
@Input('matTooltipPosition')
@@ -467,6 +471,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
467471
const instance = (this._tooltipInstance = overlayRef.attach(this._portal).instance);
468472
instance._triggerElement = this._elementRef.nativeElement;
469473
instance._mouseLeaveHideDelay = this._hideDelay;
474+
instance._id = this._panelId;
470475
instance
471476
.afterHidden()
472477
.pipe(takeUntil(this._destroyed))
@@ -946,6 +951,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
946951
host: {
947952
'(mouseleave)': '_handleMouseLeave($event)',
948953
'aria-hidden': 'true',
954+
'[id]': '_id',
949955
},
950956
standalone: true,
951957
imports: [NgClass],
@@ -975,6 +981,9 @@ export class TooltipComponent implements OnDestroy {
975981
/** Amount of milliseconds to delay the closing sequence. */
976982
_mouseLeaveHideDelay: number;
977983

984+
/** Unique ID for the panel. Assigned by the trigger. */
985+
_id: string | undefined;
986+
978987
/** Whether animations are currently disabled. */
979988
private _animationsDisabled: boolean;
980989

tools/public_api_guard/material/tooltip.md

+3
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ export class MatTooltip implements OnDestroy, AfterViewInit {
7575
ngOnDestroy(): void;
7676
// (undocumented)
7777
_overlayRef: OverlayRef | null;
78+
// (undocumented)
79+
protected readonly _panelId: string;
7880
get position(): TooltipPosition;
7981
set position(value: TooltipPosition);
8082
get positionAtOrigin(): boolean;
@@ -150,6 +152,7 @@ export class TooltipComponent implements OnDestroy {
150152
// (undocumented)
151153
_handleMouseLeave({ relatedTarget }: MouseEvent): void;
152154
hide(delay: number): void;
155+
_id: string | undefined;
153156
// (undocumented)
154157
_isMultiline: boolean;
155158
isVisible(): boolean;

0 commit comments

Comments
 (0)