From 71dd13faa80e60655d572966f832c6fcbd367ea2 Mon Sep 17 00:00:00 2001 From: RivaIvanova Date: Mon, 8 Dec 2025 16:56:12 +0200 Subject: [PATCH] fix(tooltip): show arrow for nested tooltips --- .../src/directives/tooltip/tooltip.common.ts | 2 +- .../tooltip/tooltip.directive.spec.ts | 68 ++++++++++++++++++- .../test-utils/tooltip-components.spec.ts | 39 +++++++++++ 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/projects/igniteui-angular/directives/src/directives/tooltip/tooltip.common.ts b/projects/igniteui-angular/directives/src/directives/tooltip/tooltip.common.ts index a37ed61f72e..654058bbd7f 100644 --- a/projects/igniteui-angular/directives/src/directives/tooltip/tooltip.common.ts +++ b/projects/igniteui-angular/directives/src/directives/tooltip/tooltip.common.ts @@ -185,7 +185,7 @@ export class TooltipPositionStrategy extends AutoPositionStrategy { return; } - const arrow = tooltip.querySelector('[data-arrow="true"]') as HTMLElement; + const arrow = Array.from(tooltip.children).find(el => el.matches('[data-arrow="true"]')) as HTMLElement; // If display is none -> tooltipTarget's hasArrow is false if (!arrow || arrow.style.display === 'none') { diff --git a/projects/igniteui-angular/directives/src/directives/tooltip/tooltip.directive.spec.ts b/projects/igniteui-angular/directives/src/directives/tooltip/tooltip.directive.spec.ts index 4ed7795d0b3..1846182d6de 100644 --- a/projects/igniteui-angular/directives/src/directives/tooltip/tooltip.directive.spec.ts +++ b/projects/igniteui-angular/directives/src/directives/tooltip/tooltip.directive.spec.ts @@ -2,7 +2,7 @@ import { DebugElement } from '@angular/core'; import { fakeAsync, TestBed, tick, flush, waitForAsync, ComponentFixture } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { IgxTooltipSingleTargetComponent, IgxTooltipMultipleTargetsComponent, IgxTooltipPlainStringComponent, IgxTooltipWithToggleActionComponent, IgxTooltipMultipleTooltipsComponent, IgxTooltipWithCloseButtonComponent, IgxTooltipWithNestedContentComponent } from '../../../../test-utils/tooltip-components.spec'; +import { IgxTooltipSingleTargetComponent, IgxTooltipMultipleTargetsComponent, IgxTooltipPlainStringComponent, IgxTooltipWithToggleActionComponent, IgxTooltipMultipleTooltipsComponent, IgxTooltipWithCloseButtonComponent, IgxTooltipWithNestedContentComponent, IgxTooltipNestedTooltipsComponent } from '../../../../test-utils/tooltip-components.spec'; import { UIInteractions } from '../../../../test-utils/ui-interactions.spec'; import { HorizontalAlignment, VerticalAlignment, AutoPositionStrategy } from '../../../../core/src/services/public_api'; import { IgxTooltipDirective } from './tooltip.directive'; @@ -29,7 +29,8 @@ describe('IgxTooltip', () => { IgxTooltipPlainStringComponent, IgxTooltipWithToggleActionComponent, IgxTooltipWithCloseButtonComponent, - IgxTooltipWithNestedContentComponent + IgxTooltipWithNestedContentComponent, + IgxTooltipNestedTooltipsComponent ] }).compileComponents(); UIInteractions.clearOverlay(); @@ -533,6 +534,69 @@ describe('IgxTooltip', () => { })); }); + describe('Nested tooltips', () => { + let tooltipTarget1: IgxTooltipTargetDirective; + let tooltipTarget2: IgxTooltipTargetDirective; + let tooltipTarget3: IgxTooltipTargetDirective; + + let tooltip1: IgxTooltipDirective; + let tooltip2: IgxTooltipDirective; + let tooltip3: IgxTooltipDirective; + + // Handles getting the left offset when tooltip placement is Bottom + const getArrowLeftOffset = (tooltip: IgxTooltipDirective) => { + const tooltipRect = tooltip.element.getBoundingClientRect(); + const arrowRect = tooltip.arrow.getBoundingClientRect(); + + const offset = tooltipRect.width / 2 - arrowRect.width / 2; + return Math.round(offset); + }; + + const verifyTooltipArrowAlignment = (tooltip: IgxTooltipDirective) => { + const arrowClassName = 'igx-tooltip--bottom'; + const arrowTopOffset = '-4px'; + const arrowLeftOffset = getArrowLeftOffset(tooltip) + 'px'; + + expect(tooltip.arrow.classList.contains(arrowClassName)).toBeTrue(); + expect(tooltip.arrow.style.top).toBe(arrowTopOffset); + expect(tooltip.arrow.style.left).toBe(arrowLeftOffset); + }; + + beforeEach(waitForAsync(() => { + fix = TestBed.createComponent(IgxTooltipNestedTooltipsComponent); + fix.detectChanges(); + + tooltipTarget1 = fix.componentInstance.targetLevel1; + tooltipTarget2 = fix.componentInstance.targetLevel2; + tooltipTarget3 = fix.componentInstance.targetLevel3; + + tooltip1 = fix.componentInstance.tooltipLevel1; + tooltip2 = fix.componentInstance.tooltipLevel2; + tooltip3 = fix.componentInstance.tooltipLevel3; + })); + + it('should show arrow for each tooltip', fakeAsync(() => { + hoverElement(tooltipTarget1); + flush(); + verifyTooltipVisibility(tooltip1.element, tooltipTarget1, true); + expect(tooltipTarget1.hasArrow).toBeTrue(); + verifyTooltipArrowAlignment(tooltip1); + + hoverElement(tooltipTarget2); + flush(); + verifyTooltipVisibility(tooltip2.element, tooltipTarget2, true); + expect(tooltipTarget2.hasArrow).toBeTrue(); + verifyTooltipArrowAlignment(tooltip2); + + hoverElement(tooltipTarget3); + flush(); + verifyTooltipVisibility(tooltip3.element, tooltipTarget3, true); + expect(tooltipTarget3.hasArrow).toBeTrue(); + verifyTooltipArrowAlignment(tooltip3); + + })); + }); + describe('Multiple targets with single tooltip', () => { let targetOne: IgxTooltipTargetDirective; let targetTwo: IgxTooltipTargetDirective; diff --git a/projects/igniteui-angular/test-utils/tooltip-components.spec.ts b/projects/igniteui-angular/test-utils/tooltip-components.spec.ts index c5cc8b9a719..01f45faf660 100644 --- a/projects/igniteui-angular/test-utils/tooltip-components.spec.ts +++ b/projects/igniteui-angular/test-utils/tooltip-components.spec.ts @@ -168,3 +168,42 @@ export class IgxTooltipWithNestedContentComponent { @ViewChild(IgxTooltipDirective, { static: true }) public tooltip!: IgxTooltipDirective; @ViewChild(IgxTooltipTargetDirective, { static: true }) public tooltipTarget!: IgxTooltipTargetDirective; } + +@Component({ + template: ` + + + +
+ Parent tooltip + + + +
+ Child tooltip + + + +
Grandchild tooltip
+ +
+
+ `, + imports: [IgxTooltipDirective, IgxTooltipTargetDirective], + standalone: true +}) +export class IgxTooltipNestedTooltipsComponent { + @ViewChild('targetLevel1', { read: IgxTooltipTargetDirective, static: true }) public targetLevel1: IgxTooltipTargetDirective; + @ViewChild('targetLevel2', { read: IgxTooltipTargetDirective, static: true }) public targetLevel2: IgxTooltipTargetDirective; + @ViewChild('targetLevel3', { read: IgxTooltipTargetDirective, static: true }) public targetLevel3: IgxTooltipTargetDirective; + + @ViewChild('tooltipLevel1', { read: IgxTooltipDirective, static: true }) public tooltipLevel1: IgxTooltipDirective; + @ViewChild('tooltipLevel2', { read: IgxTooltipDirective, static: true }) public tooltipLevel2: IgxTooltipDirective; + @ViewChild('tooltipLevel3', { read: IgxTooltipDirective, static: true }) public tooltipLevel3: IgxTooltipDirective; +}